forked from cory/tildefriends
631 lines
21 KiB
C
631 lines
21 KiB
C
#include "database.js.h"
|
|
|
|
#include "log.h"
|
|
#include "mem.h"
|
|
#include "ssb.h"
|
|
#include "task.h"
|
|
#include "util.js.h"
|
|
|
|
#include "sqlite3.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
static JSClassID _database_class_id;
|
|
static int _database_count;
|
|
|
|
typedef struct _database_t
|
|
{
|
|
JSContext* context;
|
|
JSValue object;
|
|
void* task;
|
|
const char* id;
|
|
} database_t;
|
|
|
|
static JSValue _database_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data);
|
|
static void _database_finalizer(JSRuntime* runtime, JSValue value);
|
|
|
|
static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _databases_list(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data);
|
|
|
|
void tf_database_register(JSContext* context)
|
|
{
|
|
JS_NewClassID(&_database_class_id);
|
|
JSClassDef def = {
|
|
.class_name = "Database",
|
|
.finalizer = &_database_finalizer,
|
|
};
|
|
if (JS_NewClass(JS_GetRuntime(context), _database_class_id, &def) != 0)
|
|
{
|
|
tf_printf("Failed to register database.\n");
|
|
}
|
|
|
|
JSValue global = JS_GetGlobalObject(context);
|
|
JSValue constructor = JS_NewCFunctionData(context, _database_create, 0, 0, 0, NULL);
|
|
JS_SetConstructorBit(context, constructor, true);
|
|
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, 1, 0, 0, NULL));
|
|
JS_FreeValue(context, global);
|
|
}
|
|
|
|
static JSValue _database_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
|
{
|
|
++_database_count;
|
|
JSValue object = JS_NewObjectClass(context, _database_class_id);
|
|
|
|
database_t* database = tf_malloc(sizeof(database_t));
|
|
*database = (database_t) {
|
|
.task = JS_GetContextOpaque(context),
|
|
.context = context,
|
|
.object = object,
|
|
};
|
|
const char* id = JS_ToCString(context, argv[0]);
|
|
database->id = tf_strdup(id);
|
|
JS_FreeCString(context, id);
|
|
JS_SetOpaque(object, database);
|
|
|
|
JS_SetPropertyStr(context, object, "get", JS_NewCFunction(context, _database_get, "get", 1));
|
|
JS_SetPropertyStr(context, object, "set", JS_NewCFunction(context, _database_set, "set", 2));
|
|
JS_SetPropertyStr(context, object, "exchange", JS_NewCFunction(context, _database_exchange, "exchange", 2));
|
|
JS_SetPropertyStr(context, object, "remove", JS_NewCFunction(context, _database_remove, "remove", 1));
|
|
JS_SetPropertyStr(context, object, "getAll", JS_NewCFunction(context, _database_get_all, "getAll", 0));
|
|
JS_SetPropertyStr(context, object, "getLike", JS_NewCFunction(context, _database_get_like, "getLike", 1));
|
|
|
|
return object;
|
|
}
|
|
|
|
static void _database_finalizer(JSRuntime* runtime, JSValue value)
|
|
{
|
|
database_t* database = JS_GetOpaque(value, _database_class_id);
|
|
if (database)
|
|
{
|
|
tf_free((void*)database->id);
|
|
tf_free(database);
|
|
}
|
|
--_database_count;
|
|
}
|
|
|
|
typedef struct _database_get_t
|
|
{
|
|
const char* id;
|
|
const char* key;
|
|
size_t key_length;
|
|
char* out_value;
|
|
size_t out_length;
|
|
JSValue promise[2];
|
|
} database_get_t;
|
|
|
|
static void _database_get_work(tf_ssb_t* ssb, void* user_data)
|
|
{
|
|
database_get_t* work = user_data;
|
|
sqlite3_stmt* statement;
|
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
|
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -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_ROW)
|
|
{
|
|
size_t length = sqlite3_column_bytes(statement, 0);
|
|
char* data = tf_malloc(length + 1);
|
|
memcpy(data, sqlite3_column_text(statement, 0), length);
|
|
data[length] = '\0';
|
|
work->out_value = data;
|
|
work->out_length = length;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
tf_ssb_release_db_reader(ssb, db);
|
|
}
|
|
|
|
static void _database_get_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
|
{
|
|
database_get_t* work = user_data;
|
|
JSContext* context = tf_ssb_get_context(ssb);
|
|
JSValue result = JS_UNDEFINED;
|
|
if (work->out_value)
|
|
{
|
|
result = JS_NewStringLen(context, work->out_value, work->out_length);
|
|
}
|
|
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->out_value);
|
|
tf_free(work);
|
|
}
|
|
|
|
static JSValue _database_get(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)
|
|
{
|
|
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
|
|
|
|
size_t length;
|
|
const char* key = JS_ToCStringLen(context, &length, argv[0]);
|
|
database_get_t* work = tf_malloc(sizeof(database_get_t) + strlen(database->id) + 1 + length + 1);
|
|
*work = (database_get_t) {
|
|
.id = (const char*)(work + 1),
|
|
.key = (const char*)(work + 1) + strlen(database->id) + 1,
|
|
.key_length = length,
|
|
};
|
|
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
|
|
memcpy((char*)work->key, key, length + 1);
|
|
JS_FreeCString(context, key);
|
|
|
|
tf_ssb_run_work(ssb, _database_get_work, _database_get_after_work, work);
|
|
result = JS_NewPromiseCapability(context, work->promise);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
typedef struct _database_set_t
|
|
{
|
|
const char* id;
|
|
const char* key;
|
|
size_t key_length;
|
|
const char* value;
|
|
size_t value_length;
|
|
bool result;
|
|
JSValue promise[2];
|
|
} database_set_t;
|
|
|
|
static void _database_set_work(tf_ssb_t* ssb, void* user_data)
|
|
{
|
|
database_set_t* work = user_data;
|
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
|
sqlite3_stmt* statement;
|
|
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -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_OK)
|
|
{
|
|
work->result = true;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
tf_ssb_release_db_writer(ssb, db);
|
|
}
|
|
|
|
static void _database_set_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
|
{
|
|
database_set_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]);
|
|
tf_free(work);
|
|
}
|
|
|
|
static JSValue _database_set(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)
|
|
{
|
|
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
|
|
|
|
size_t key_length = 0;
|
|
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
|
|
size_t value_length = 0;
|
|
const char* value = JS_ToCStringLen(context, &value_length, argv[1]);
|
|
|
|
database_set_t* work = tf_malloc(sizeof(database_set_t) + strlen(database->id) + 1 + key_length + 1 + value_length + 1);
|
|
*work = (database_set_t) {
|
|
.id = (const char*)(work + 1),
|
|
.key = (const char*)(work + 1) + strlen(database->id) + 1,
|
|
.value = (const char*)(work + 1) + strlen(database->id) + 1 + key_length + 1,
|
|
.key_length = key_length,
|
|
.value_length = value_length,
|
|
};
|
|
|
|
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
|
|
memcpy((char*)work->key, key, key_length + 1);
|
|
memcpy((char*)work->value, value, value_length + 1);
|
|
|
|
result = JS_NewPromiseCapability(context, work->promise);
|
|
tf_ssb_run_work(ssb, _database_set_work, _database_set_after_work, work);
|
|
JS_FreeCString(context, key);
|
|
JS_FreeCString(context, value);
|
|
}
|
|
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 result = JS_UNDEFINED;
|
|
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
|
if (database)
|
|
{
|
|
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
|
|
database_exchange_t* work = tf_malloc(sizeof(database_exchange_t));
|
|
*work = (database_exchange_t)
|
|
{
|
|
.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 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)
|
|
{
|
|
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
|
|
{
|
|
const char* id;
|
|
const char* key;
|
|
size_t key_length;
|
|
char** out_values;
|
|
size_t* out_lengths;
|
|
int out_values_length;
|
|
JSValue promise[2];
|
|
} database_get_all_t;
|
|
|
|
static void _database_get_all_work(tf_ssb_t* ssb, void* user_data)
|
|
{
|
|
database_get_all_t* work = user_data;
|
|
sqlite3_stmt* statement;
|
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
|
if (sqlite3_prepare(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
|
|
{
|
|
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK)
|
|
{
|
|
while (sqlite3_step(statement) == SQLITE_ROW)
|
|
{
|
|
work->out_values = tf_resize_vec(work->out_values, sizeof(char*) * (work->out_values_length + 1));
|
|
work->out_lengths = tf_resize_vec(work->out_lengths, sizeof(size_t) * (work->out_values_length + 1));
|
|
size_t length = sqlite3_column_bytes(statement, 0);
|
|
char* data = tf_malloc(length + 1);
|
|
memcpy(data, sqlite3_column_text(statement, 0), length);
|
|
data[length] = '\0';
|
|
work->out_values[work->out_values_length] = data;
|
|
work->out_lengths[work->out_values_length] = length;
|
|
work->out_values_length++;
|
|
}
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
tf_ssb_release_db_reader(ssb, db);
|
|
}
|
|
|
|
static void _database_get_all_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
|
{
|
|
database_get_all_t* work = user_data;
|
|
JSContext* context = tf_ssb_get_context(ssb);
|
|
JSValue result = JS_NewArray(context);
|
|
;
|
|
for (int i = 0; i < work->out_values_length; i++)
|
|
{
|
|
JS_SetPropertyUint32(context, result, i, JS_NewStringLen(context, work->out_values[i], work->out_lengths[i]));
|
|
tf_free((void*)work->out_values[i]);
|
|
}
|
|
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->out_values);
|
|
tf_free(work->out_lengths);
|
|
tf_free(work);
|
|
}
|
|
|
|
static JSValue _database_get_all(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)
|
|
{
|
|
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
|
|
|
|
size_t length;
|
|
const char* key = JS_ToCStringLen(context, &length, argv[0]);
|
|
database_get_all_t* work = tf_malloc(sizeof(database_get_all_t) + strlen(database->id) + 1 + length + 1);
|
|
*work = (database_get_all_t) {
|
|
.id = (const char*)(work + 1),
|
|
.key = (const char*)(work + 1) + strlen(database->id) + 1,
|
|
.key_length = length,
|
|
};
|
|
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
|
|
memcpy((char*)work->key, key, length + 1);
|
|
JS_FreeCString(context, key);
|
|
|
|
tf_ssb_run_work(ssb, _database_get_all_work, _database_get_all_after_work, work);
|
|
result = JS_NewPromiseCapability(context, work->promise);
|
|
}
|
|
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)
|
|
{
|
|
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_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);
|
|
databases_list_t* work = tf_malloc(sizeof(databases_list_t));
|
|
*work = (databases_list_t)
|
|
{
|
|
.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;
|
|
}
|