From b6a937c954666ebd45642ebc81992e6a7c786a88 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 16 Jun 2024 10:16:39 -0400 Subject: [PATCH] Move db.exchange DB work off of the main thread. --- src/database.js.c | 113 +++++++++++++++++++++++++++++----------------- src/tests.c | 6 ++- 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/src/database.js.c b/src/database.js.c index a3405123..9edfb0f8 100644 --- a/src/database.js.c +++ b/src/database.js.c @@ -245,55 +245,86 @@ static JSValue _database_set(JSContext* context, JSValueConst this_val, int argc return result; } +typedef struct _database_exchange_t +{ + const char* id; + const char* key; + size_t key_length; + const char* expected; + size_t expected_length; + const char* value; + size_t value_length; + bool result; + JSValue promise[2]; +} database_exchange_t; + +static void _database_exchange_work(tf_ssb_t* ssb, void* user_data) +{ + database_exchange_t* work = user_data; + sqlite3* db = tf_ssb_acquire_db_writer(ssb); + sqlite3_stmt* statement; + if (!work->expected) + { + if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -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_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE) + { + work->result = sqlite3_changes(db) != 0; + } + sqlite3_finalize(statement); + } + } + else if (sqlite3_prepare(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->id, -1, NULL) == SQLITE_OK && + sqlite3_bind_text(statement, 3, work->key, work->key_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 4, work->expected, work->expected_length, NULL) == SQLITE_OK && + sqlite3_step(statement) == SQLITE_DONE) + { + work->result = sqlite3_changes(db) != 0; + } + sqlite3_finalize(statement); + } + tf_ssb_release_db_writer(ssb, db); +} + +static void _database_exchange_after_work(tf_ssb_t* ssb, int status, void* user_data) +{ + database_exchange_t* work = user_data; + JSContext* context = tf_ssb_get_context(ssb); + JSValue result = work->result ? JS_TRUE : 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]); + JS_FreeCString(context, work->key); + JS_FreeCString(context, work->expected); + JS_FreeCString(context, work->value); + tf_free((char*)work->id); + tf_free(work); +} + static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { - JSValue exchanged = JS_UNDEFINED; + 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 (JS_IsNull(argv[1]) || JS_IsUndefined(argv[1])) + database_exchange_t* work = tf_malloc(sizeof(database_exchange_t)); + *work = (database_exchange_t) { - if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) - { - size_t key_length; - size_t set_length; - const char* key = JS_ToCStringLen(context, &key_length, argv[0]); - const char* set = JS_ToCStringLen(context, &set_length, argv[2]); - if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, key_length, NULL) == SQLITE_OK && - sqlite3_bind_text(statement, 3, set, set_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE) - { - exchanged = sqlite3_changes(db) != 0 ? JS_TRUE : JS_FALSE; - } - JS_FreeCString(context, key); - JS_FreeCString(context, set); - sqlite3_finalize(statement); - } - } - else if (sqlite3_prepare(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK) - { - size_t key_length; - size_t expected_length; - size_t set_length; - const char* key = JS_ToCStringLen(context, &key_length, argv[0]); - const char* expected = JS_ToCStringLen(context, &expected_length, argv[1]); - const char* set = JS_ToCStringLen(context, &set_length, argv[2]); - if (sqlite3_bind_text(statement, 1, set, set_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, database->id, -1, NULL) == SQLITE_OK && - sqlite3_bind_text(statement, 3, key, key_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 4, expected, expected_length, NULL) == SQLITE_OK && - sqlite3_step(statement) == SQLITE_DONE) - { - exchanged = sqlite3_changes(db) != 0 ? JS_TRUE : JS_FALSE; - } - JS_FreeCString(context, key); - JS_FreeCString(context, expected); - JS_FreeCString(context, set); - sqlite3_finalize(statement); - } - tf_ssb_release_db_writer(ssb, db); + .id = tf_strdup(database->id), + }; + work->key = JS_ToCStringLen(context, &work->key_length, argv[0]); + work->expected = (JS_IsNull(argv[1]) || JS_IsUndefined(argv[1])) ? NULL : JS_ToCStringLen(context, &work->expected_length, argv[1]); + work->value = JS_ToCStringLen(context, &work->value_length, argv[2]); + result = JS_NewPromiseCapability(context, work->promise); + tf_ssb_run_work(ssb, _database_exchange_work, _database_exchange_after_work, work); } - return exchanged; + return result; } static JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) diff --git a/src/tests.c b/src/tests.c index 61238652..bc9f12d9 100644 --- a/src/tests.c +++ b/src/tests.c @@ -275,7 +275,11 @@ static void _test_database(const tf_test_options_t* options) " if (await db.get('a') != 1) {\n" " exit(2);\n" " }\n" - " await db.set('b', 2);\n" + " await db.exchange('b', null, 1);\n" + " await db.exchange('b', 1, 2);\n" + " if (await db.get('b') != 2) {\n" + " exit(5);\n" + " }\n" " await db.set('c', 3);\n" "\n" " var expected = ['a', 'b', 'c'];\n"