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:
parent
b6a937c954
commit
2c03496373
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
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);
|
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
||||||
if (database)
|
if (database)
|
||||||
{
|
{
|
||||||
sqlite3_stmt* statement;
|
size_t key_length = 0;
|
||||||
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
|
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
|
||||||
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)
|
database_remove_t* work = tf_malloc(sizeof(database_remove_t) + key_length + 1);
|
||||||
|
*work = (database_remove_t)
|
||||||
{
|
{
|
||||||
size_t keyLength;
|
.id = tf_strdup(database->id),
|
||||||
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
|
.key_length = key_length,
|
||||||
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)
|
memcpy(work->key, key, key_length + 1);
|
||||||
{
|
JS_FreeCString(context, key);
|
||||||
}
|
result = JS_NewPromiseCapability(context, work->promise);
|
||||||
JS_FreeCString(context, keyString);
|
tf_ssb_run_work(tf_task_get_ssb(database->task), _database_remove_work, _database_remove_after_work, work);
|
||||||
sqlite3_finalize(statement);
|
|
||||||
}
|
|
||||||
tf_ssb_release_db_writer(ssb, db);
|
|
||||||
}
|
}
|
||||||
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);
|
||||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
database_get_like_t* work = tf_malloc(sizeof(database_get_like_t));
|
||||||
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
|
*work = (database_get_like_t)
|
||||||
{
|
{
|
||||||
const char* pattern = JS_ToCString(context, argv[0]);
|
.id = tf_strdup(database->id),
|
||||||
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, pattern, -1, NULL) == SQLITE_OK)
|
.pattern = JS_ToCString(context, argv[0]),
|
||||||
{
|
};
|
||||||
result = JS_NewObject(context);
|
result = JS_NewPromiseCapability(context, work->promise);
|
||||||
while (sqlite3_step(statement) == SQLITE_ROW)
|
tf_ssb_run_work(ssb, _database_get_like_work, _database_get_like_after_work, work);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
return result;
|
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)
|
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();");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user