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:
Cory McWilliams 2024-06-16 12:17:51 -04:00
parent b6a937c954
commit 2c03496373
2 changed files with 197 additions and 52 deletions

View File

@ -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);
}
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 JS_UNDEFINED; 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);
}
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]);
} }
return result; 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;
} }

View File

@ -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();");