From 2c03496373a014f2f582bcf099ce82f6785f4c84 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 16 Jun 2024 12:17:51 -0400 Subject: [PATCH] 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. --- src/database.js.c | 241 ++++++++++++++++++++++++++++++++++++---------- src/tests.c | 8 ++ 2 files changed, 197 insertions(+), 52 deletions(-) diff --git a/src/database.js.c b/src/database.js.c index 9edfb0f8..b580b224 100644 --- a/src/database.js.c +++ b/src/database.js.c @@ -51,7 +51,7 @@ void tf_database_register(JSContext* context) JS_SetPropertyStr(context, global, "Database", constructor); JSValue databases = JS_NewObject(context); 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); } @@ -327,28 +327,66 @@ static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int return result; } +typedef struct _database_remove_t +{ + const char* id; + 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* 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_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && + sqlite3_step(statement) == SQLITE_OK) + { + } + sqlite3_finalize(statement); + } + tf_ssb_release_db_writer(ssb, db); +} + +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) { - sqlite3_stmt* statement; - tf_ssb_t* ssb = tf_task_get_ssb(database->task); - 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) + 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) { - size_t keyLength; - 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) - { - } - JS_FreeCString(context, keyString); - sqlite3_finalize(statement); - } - tf_ssb_release_db_writer(ssb, db); + .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 JS_UNDEFINED; + return result; } typedef struct _database_get_all_t @@ -437,57 +475,156 @@ static JSValue _database_get_all(JSContext* context, JSValueConst this_val, int 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) { JSValue result = JS_UNDEFINED; database_t* database = JS_GetOpaque(this_val, _database_class_id); if (database) { - sqlite3_stmt* statement; tf_ssb_t* ssb = tf_task_get_ssb(database->task); - 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) + database_get_like_t* work = tf_malloc(sizeof(database_get_like_t)); + *work = (database_get_like_t) { - const char* pattern = JS_ToCString(context, argv[0]); - 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) - { - JS_SetPropertyStr(context, result, (const char*)sqlite3_column_text(statement, 0), - JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 1), sqlite3_column_bytes(statement, 1))); - } - } - JS_FreeCString(context, pattern); - sqlite3_finalize(statement); - } - tf_ssb_release_db_reader(ssb, db); + .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_stmt* statement; + if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK) + { + while (sqlite3_step(statement) == SQLITE_ROW) + { + work->names = tf_resize_vec(work->names, sizeof(char*) * (work->names_length + 1)); + work->names[work->names_length] = tf_strdup((const char*)sqlite3_column_text(statement, 0)); + work->names_length++; + } + } + sqlite3_finalize(statement); + } + tf_ssb_release_db_reader(ssb, db); +} + +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) { tf_task_t* task = tf_task_get(context); tf_ssb_t* ssb = tf_task_get_ssb(task); - sqlite3* db = tf_ssb_acquire_db_reader(ssb); - JSValue array = JS_UNDEFINED; - sqlite3_stmt* statement; - if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK) + databases_list_t* work = tf_malloc(sizeof(databases_list_t)); + *work = (databases_list_t) { - const char* pattern = JS_ToCString(context, argv[0]); - if (sqlite3_bind_text(statement, 1, pattern, -1, NULL) == SQLITE_OK) - { - array = JS_NewArray(context); - uint32_t index = 0; - 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; + .pattern = JS_ToCString(context, argv[0]), + }; + JSValue result = JS_NewPromiseCapability(context, work->promise); + tf_ssb_run_work(ssb, _databases_list_work, _databases_list_after_work, work); + return result; } diff --git a/src/tests.c b/src/tests.c index bc9f12d9..b457b814 100644 --- a/src/tests.c +++ b/src/tests.c @@ -281,6 +281,11 @@ static void _test_database(const tf_test_options_t* options) " exit(5);\n" " }\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" " var expected = ['a', 'b', 'c'];\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" " exit(4);\n" " }\n" + " if (JSON.stringify(await databases.list('%')) != '[\"testdb\"]') {\n" + " exit(7);\n" + " }\n" "}\n" "main();");