forked from cory/tildefriends
		
	Make databases.list, database.remove, and database.getLike all do their DB work off the main thread. That's the last thing I'm aware of.
This commit is contained in:
		@@ -51,7 +51,7 @@ void tf_database_register(JSContext* context)
 | 
				
			|||||||
	JS_SetPropertyStr(context, global, "Database", constructor);
 | 
						JS_SetPropertyStr(context, global, "Database", constructor);
 | 
				
			||||||
	JSValue databases = JS_NewObject(context);
 | 
						JSValue databases = JS_NewObject(context);
 | 
				
			||||||
	JS_SetPropertyStr(context, global, "databases", databases);
 | 
						JS_SetPropertyStr(context, global, "databases", databases);
 | 
				
			||||||
	JS_SetPropertyStr(context, databases, "list", JS_NewCFunctionData(context, _databases_list, 0, 0, 0, NULL));
 | 
						JS_SetPropertyStr(context, databases, "list", JS_NewCFunctionData(context, _databases_list, 1, 0, 0, NULL));
 | 
				
			||||||
	JS_FreeValue(context, global);
 | 
						JS_FreeValue(context, global);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -327,28 +327,66 @@ static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int
 | 
				
			|||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
 | 
					typedef struct _database_remove_t
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	database_t* database = JS_GetOpaque(this_val, _database_class_id);
 | 
						const char* id;
 | 
				
			||||||
	if (database)
 | 
						size_t key_length;
 | 
				
			||||||
 | 
						JSValue promise[2];
 | 
				
			||||||
 | 
						char key[];
 | 
				
			||||||
 | 
					} database_remove_t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void _database_remove_work(tf_ssb_t* ssb, void* user_data)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						database_remove_t* work = user_data;
 | 
				
			||||||
	sqlite3_stmt* statement;
 | 
						sqlite3_stmt* statement;
 | 
				
			||||||
		tf_ssb_t* ssb = tf_task_get_ssb(database->task);
 | 
					 | 
				
			||||||
	sqlite3* db = tf_ssb_acquire_db_writer(ssb);
 | 
						sqlite3* db = tf_ssb_acquire_db_writer(ssb);
 | 
				
			||||||
	if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
 | 
						if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
			size_t keyLength;
 | 
							if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
 | 
				
			||||||
			const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
 | 
					 | 
				
			||||||
			if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK &&
 | 
					 | 
				
			||||||
			sqlite3_step(statement) == SQLITE_OK)
 | 
								sqlite3_step(statement) == SQLITE_OK)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
			JS_FreeCString(context, keyString);
 | 
					 | 
				
			||||||
		sqlite3_finalize(statement);
 | 
							sqlite3_finalize(statement);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tf_ssb_release_db_writer(ssb, db);
 | 
						tf_ssb_release_db_writer(ssb, db);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
	return JS_UNDEFINED;
 | 
					
 | 
				
			||||||
 | 
					static void _database_remove_after_work(tf_ssb_t* ssb, int status, void* user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						database_remove_t* work = user_data;
 | 
				
			||||||
 | 
						JSContext* context = tf_ssb_get_context(ssb);
 | 
				
			||||||
 | 
						JSValue result = JS_UNDEFINED;
 | 
				
			||||||
 | 
						JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
 | 
				
			||||||
 | 
						tf_util_report_error(context, error);
 | 
				
			||||||
 | 
						JS_FreeValue(context, error);
 | 
				
			||||||
 | 
						JS_FreeValue(context, result);
 | 
				
			||||||
 | 
						JS_FreeValue(context, work->promise[0]);
 | 
				
			||||||
 | 
						JS_FreeValue(context, work->promise[1]);
 | 
				
			||||||
 | 
						tf_free((char*)work->id);
 | 
				
			||||||
 | 
						tf_free(work);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						JSValue result = JS_UNDEFINED;
 | 
				
			||||||
 | 
						database_t* database = JS_GetOpaque(this_val, _database_class_id);
 | 
				
			||||||
 | 
						if (database)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							size_t key_length = 0;
 | 
				
			||||||
 | 
							const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							database_remove_t* work = tf_malloc(sizeof(database_remove_t) + key_length + 1);
 | 
				
			||||||
 | 
							*work = (database_remove_t)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								.id = tf_strdup(database->id),
 | 
				
			||||||
 | 
								.key_length = key_length,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							memcpy(work->key, key, key_length + 1);
 | 
				
			||||||
 | 
							JS_FreeCString(context, key);
 | 
				
			||||||
 | 
							result = JS_NewPromiseCapability(context, work->promise);
 | 
				
			||||||
 | 
							tf_ssb_run_work(tf_task_get_ssb(database->task), _database_remove_work, _database_remove_after_work, work);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct _database_get_all_t
 | 
					typedef struct _database_get_all_t
 | 
				
			||||||
@@ -437,57 +475,156 @@ static JSValue _database_get_all(JSContext* context, JSValueConst this_val, int
 | 
				
			|||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct _key_value_t
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						char* key;
 | 
				
			||||||
 | 
						size_t key_length;
 | 
				
			||||||
 | 
						char* value;
 | 
				
			||||||
 | 
						size_t value_length;
 | 
				
			||||||
 | 
					} key_value_t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct _database_get_like_t
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const char* id;
 | 
				
			||||||
 | 
						const char* pattern;
 | 
				
			||||||
 | 
						key_value_t* results;
 | 
				
			||||||
 | 
						int results_length;
 | 
				
			||||||
 | 
						JSValue promise[2];
 | 
				
			||||||
 | 
					} database_get_like_t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void _database_get_like_work(tf_ssb_t* ssb, void* user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						database_get_like_t* work = user_data;
 | 
				
			||||||
 | 
						sqlite3_stmt* statement;
 | 
				
			||||||
 | 
						sqlite3* db = tf_ssb_acquire_db_reader(ssb);
 | 
				
			||||||
 | 
						if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK &&
 | 
				
			||||||
 | 
							sqlite3_bind_text(statement, 2, work->pattern, -1, NULL) == SQLITE_OK)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								while (sqlite3_step(statement) == SQLITE_ROW)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									work->results = tf_resize_vec(work->results, sizeof(key_value_t) * (work->results_length + 1));
 | 
				
			||||||
 | 
									key_value_t* out = &work->results[work->results_length];
 | 
				
			||||||
 | 
									*out = (key_value_t) {
 | 
				
			||||||
 | 
										.key_length = sqlite3_column_bytes(statement, 0),
 | 
				
			||||||
 | 
										.value_length = sqlite3_column_bytes(statement, 1),
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
									out->key = tf_malloc(out->key_length + 1);
 | 
				
			||||||
 | 
									memcpy(out->key, sqlite3_column_text(statement, 0), out->key_length + 1);
 | 
				
			||||||
 | 
									out->value = tf_malloc(out->value_length + 1);
 | 
				
			||||||
 | 
									memcpy(out->value, sqlite3_column_text(statement, 1), out->value_length + 1);
 | 
				
			||||||
 | 
									work->results_length++;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							sqlite3_finalize(statement);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tf_ssb_release_db_reader(ssb, db);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void _database_get_like_after_work(tf_ssb_t* ssb, int status, void* user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						database_get_like_t* work = user_data;
 | 
				
			||||||
 | 
						JSContext* context = tf_ssb_get_context(ssb);
 | 
				
			||||||
 | 
						JSValue result = JS_NewObject(context);
 | 
				
			||||||
 | 
						for (int i = 0; i < work->results_length; i++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							const key_value_t* row = &work->results[i];
 | 
				
			||||||
 | 
							JS_SetPropertyStr(context, result, row->key, JS_NewStringLen(context, row->value, row->value_length));
 | 
				
			||||||
 | 
							tf_free(row->key);
 | 
				
			||||||
 | 
							tf_free(row->value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						JS_FreeCString(context, work->pattern);
 | 
				
			||||||
 | 
						JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
 | 
				
			||||||
 | 
						tf_util_report_error(context, error);
 | 
				
			||||||
 | 
						JS_FreeValue(context, error);
 | 
				
			||||||
 | 
						JS_FreeValue(context, result);
 | 
				
			||||||
 | 
						JS_FreeValue(context, work->promise[0]);
 | 
				
			||||||
 | 
						JS_FreeValue(context, work->promise[1]);
 | 
				
			||||||
 | 
						tf_free((void*)work->id);
 | 
				
			||||||
 | 
						tf_free(work->results);
 | 
				
			||||||
 | 
						tf_free(work);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
 | 
					static JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	JSValue result = JS_UNDEFINED;
 | 
						JSValue result = JS_UNDEFINED;
 | 
				
			||||||
	database_t* database = JS_GetOpaque(this_val, _database_class_id);
 | 
						database_t* database = JS_GetOpaque(this_val, _database_class_id);
 | 
				
			||||||
	if (database)
 | 
						if (database)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		sqlite3_stmt* statement;
 | 
					 | 
				
			||||||
		tf_ssb_t* ssb = tf_task_get_ssb(database->task);
 | 
							tf_ssb_t* ssb = tf_task_get_ssb(database->task);
 | 
				
			||||||
 | 
							database_get_like_t* work = tf_malloc(sizeof(database_get_like_t));
 | 
				
			||||||
 | 
							*work = (database_get_like_t)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								.id = tf_strdup(database->id),
 | 
				
			||||||
 | 
								.pattern = JS_ToCString(context, argv[0]),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							result = JS_NewPromiseCapability(context, work->promise);
 | 
				
			||||||
 | 
							tf_ssb_run_work(ssb, _database_get_like_work, _database_get_like_after_work, work);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct _databases_list_t
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const char* pattern;
 | 
				
			||||||
 | 
						char** names;
 | 
				
			||||||
 | 
						int names_length;
 | 
				
			||||||
 | 
						JSValue promise[2];
 | 
				
			||||||
 | 
					} databases_list_t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void _databases_list_work(tf_ssb_t* ssb, void* user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						databases_list_t* work = user_data;
 | 
				
			||||||
	sqlite3* db = tf_ssb_acquire_db_reader(ssb);
 | 
						sqlite3* db = tf_ssb_acquire_db_reader(ssb);
 | 
				
			||||||
		if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
 | 
						sqlite3_stmt* statement;
 | 
				
			||||||
 | 
						if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
			const char* pattern = JS_ToCString(context, argv[0]);
 | 
							if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK)
 | 
				
			||||||
			if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, pattern, -1, NULL) == SQLITE_OK)
 | 
					 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
				result = JS_NewObject(context);
 | 
					 | 
				
			||||||
			while (sqlite3_step(statement) == SQLITE_ROW)
 | 
								while (sqlite3_step(statement) == SQLITE_ROW)
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
					JS_SetPropertyStr(context, result, (const char*)sqlite3_column_text(statement, 0),
 | 
									work->names = tf_resize_vec(work->names, sizeof(char*) * (work->names_length + 1));
 | 
				
			||||||
						JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 1), sqlite3_column_bytes(statement, 1)));
 | 
									work->names[work->names_length] = tf_strdup((const char*)sqlite3_column_text(statement, 0));
 | 
				
			||||||
 | 
									work->names_length++;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
			JS_FreeCString(context, pattern);
 | 
					 | 
				
			||||||
		sqlite3_finalize(statement);
 | 
							sqlite3_finalize(statement);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tf_ssb_release_db_reader(ssb, db);
 | 
						tf_ssb_release_db_reader(ssb, db);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
	return result;
 | 
					
 | 
				
			||||||
 | 
					static void _databases_list_after_work(tf_ssb_t* ssb, int status, void* user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						databases_list_t* work = user_data;
 | 
				
			||||||
 | 
						JSContext* context = tf_ssb_get_context(ssb);
 | 
				
			||||||
 | 
						JSValue result = JS_NewArray(context);
 | 
				
			||||||
 | 
						for (int i = 0; i < work->names_length; i++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							JS_SetPropertyUint32(context, result, i, JS_NewString(context, work->names[i]));
 | 
				
			||||||
 | 
							tf_free(work->names[i]);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						JS_FreeCString(context, work->pattern);
 | 
				
			||||||
 | 
						JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
 | 
				
			||||||
 | 
						tf_util_report_error(context, error);
 | 
				
			||||||
 | 
						JS_FreeValue(context, error);
 | 
				
			||||||
 | 
						JS_FreeValue(context, result);
 | 
				
			||||||
 | 
						JS_FreeValue(context, work->promise[0]);
 | 
				
			||||||
 | 
						JS_FreeValue(context, work->promise[1]);
 | 
				
			||||||
 | 
						tf_free(work->names);
 | 
				
			||||||
 | 
						tf_free(work);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static JSValue _databases_list(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
 | 
					static JSValue _databases_list(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	tf_task_t* task = tf_task_get(context);
 | 
						tf_task_t* task = tf_task_get(context);
 | 
				
			||||||
	tf_ssb_t* ssb = tf_task_get_ssb(task);
 | 
						tf_ssb_t* ssb = tf_task_get_ssb(task);
 | 
				
			||||||
	sqlite3* db = tf_ssb_acquire_db_reader(ssb);
 | 
						databases_list_t* work = tf_malloc(sizeof(databases_list_t));
 | 
				
			||||||
	JSValue array = JS_UNDEFINED;
 | 
						*work = (databases_list_t)
 | 
				
			||||||
	sqlite3_stmt* statement;
 | 
					 | 
				
			||||||
	if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		const char* pattern = JS_ToCString(context, argv[0]);
 | 
							.pattern = JS_ToCString(context, argv[0]),
 | 
				
			||||||
		if (sqlite3_bind_text(statement, 1, pattern, -1, NULL) == SQLITE_OK)
 | 
						};
 | 
				
			||||||
		{
 | 
						JSValue result = JS_NewPromiseCapability(context, work->promise);
 | 
				
			||||||
			array = JS_NewArray(context);
 | 
						tf_ssb_run_work(ssb, _databases_list_work, _databases_list_after_work, work);
 | 
				
			||||||
			uint32_t index = 0;
 | 
						return result;
 | 
				
			||||||
			while (sqlite3_step(statement) == SQLITE_ROW)
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				JS_SetPropertyUint32(context, array, index++, JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0)));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		JS_FreeCString(context, pattern);
 | 
					 | 
				
			||||||
		sqlite3_finalize(statement);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	tf_ssb_release_db_reader(ssb, db);
 | 
					 | 
				
			||||||
	return array;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -281,6 +281,11 @@ static void _test_database(const tf_test_options_t* options)
 | 
				
			|||||||
		"		exit(5);\n"
 | 
							"		exit(5);\n"
 | 
				
			||||||
		"	}\n"
 | 
							"	}\n"
 | 
				
			||||||
		"	await db.set('c', 3);\n"
 | 
							"	await db.set('c', 3);\n"
 | 
				
			||||||
 | 
							"	await db.set('d', 3);\n"
 | 
				
			||||||
 | 
							"	await db.remove('d', 3);\n"
 | 
				
			||||||
 | 
							"	if (JSON.stringify(await db.getLike('b%')) != '{\"b\":\"2\"}') {\n"
 | 
				
			||||||
 | 
							"		exit(6);\n"
 | 
				
			||||||
 | 
							"	}\n"
 | 
				
			||||||
		"\n"
 | 
							"\n"
 | 
				
			||||||
		"	var expected = ['a', 'b', 'c'];\n"
 | 
							"	var expected = ['a', 'b', 'c'];\n"
 | 
				
			||||||
		"	var have = await db.getAll();\n"
 | 
							"	var have = await db.getAll();\n"
 | 
				
			||||||
@@ -297,6 +302,9 @@ static void _test_database(const tf_test_options_t* options)
 | 
				
			|||||||
		"		print('Expected but did not find: ' + JSON.stringify(expected));\n"
 | 
							"		print('Expected but did not find: ' + JSON.stringify(expected));\n"
 | 
				
			||||||
		"		exit(4);\n"
 | 
							"		exit(4);\n"
 | 
				
			||||||
		"	}\n"
 | 
							"	}\n"
 | 
				
			||||||
 | 
							"	if (JSON.stringify(await databases.list('%')) != '[\"testdb\"]') {\n"
 | 
				
			||||||
 | 
							"		exit(7);\n"
 | 
				
			||||||
 | 
							"	}\n"
 | 
				
			||||||
		"}\n"
 | 
							"}\n"
 | 
				
			||||||
		"main();");
 | 
							"main();");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user