diff options
author | EuAndreh <eu@euandre.org> | 2023-11-16 15:48:12 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2023-11-16 15:51:57 -0300 |
commit | b2b5ba67c25705ddabb7f59baf725604eaf67c04 (patch) | |
tree | 96ffbd2b9455965a814cc55ec47b50fbbd213b68 /src/napi-sqlite.c | |
parent | Makefile, mkdeps.sh: Enforce JS->native dependency (diff) | |
download | papod-b2b5ba67c25705ddabb7f59baf725604eaf67c04.tar.gz papod-b2b5ba67c25705ddabb7f59baf725604eaf67c04.tar.xz |
Add WIP non-async functions to napi-sqlite.c
Diffstat (limited to 'src/napi-sqlite.c')
-rw-r--r-- | src/napi-sqlite.c | 807 |
1 files changed, 767 insertions, 40 deletions
diff --git a/src/napi-sqlite.c b/src/napi-sqlite.c index df3b042..74ab340 100644 --- a/src/napi-sqlite.c +++ b/src/napi-sqlite.c @@ -1,74 +1,801 @@ +#include <assert.h> #include <stdio.h> +#include <stdlib.h> #include <node/node_api.h> +#include <sqlite3.h> + + +static napi_value ffi_open(napi_env env, napi_callback_info info); +static napi_value ffi_exec(napi_env env, napi_callback_info info); +static napi_value ffi_all (napi_env env, napi_callback_info info); +static napi_value ffi_run (napi_env env, napi_callback_info info); + + +struct NAPIContext { + napi_env env; + napi_value value; + uint32_t index; +}; + +struct Fn { + const char *const label; + napi_value(*const handle)(napi_env env, napi_callback_info info); +}; + +static const struct Fn fns[] = { + { .label = "open", .handle = ffi_open, }, + { NULL, NULL }, +}; + +struct Fn methods[] = { + { .label = "exec", .handle = ffi_exec, }, + { .label = "all", .handle = ffi_all, }, + { .label = "run", .handle = ffi_run, }, + { NULL, NULL }, +}; -/* -FIXME static const napi_type_tag SQLITE_DB_TYPE_TAG = { 0x0e9614d459f746cc, 0x88b814a5dc5c4cf7 }; -*/ + +static const int +SQLITE_OPEN_FLAGS = + /* + From https://www.sqlite.org/c3ref/open.html: + + > The database is opened for reading and writing, and is created if + > it does not already exist. This is the behavior that is always + > used for sqlite3_open() and sqlite3_open16(). + */ + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | + + /* + From https://www.sqlite.org/c3ref/open.html: + + > The new database connection will use the "serialized" threading + > mode. This means the multiple threads can safely attempt to use + > the same database connection at the same time. (Mutexes will block + > any actual concurrency, but in this mode there is no harm in + > trying.) + */ + SQLITE_OPEN_FULLMUTEX | + + /* + From https://www.sqlite.org/c3ref/open.html: + + > The database connection comes up in "extended result code mode". + > In other words, the database behaves has if + > sqlite3_extended_result_codes(db,1) where called on the database + > connection as soon as the connection is created. In addition to + > setting the extended result code mode, this flag also causes + > sqlite3_open_v2() to return an extended result code. + + From https://www.sqlite.org/c3ref/extended_result_codes.html: + + > The sqlite3_extended_result_codes() routine enables or disables the + > extended result codes feature of SQLite. The extended result codes + > are disabled by default for historical compatibility. + */ + SQLITE_OPEN_EXRESCODE; + + +static const char *const +FIX_SQLITE_PRAGMAS = + /* + From https://research.cs.wisc.edu/adsl/Publications/alice-osdi14.pdf: + + > Similarly, SQLite does not provide durability under the default + > journal-mode (we became aware of this only after interacting with + > developers), but its documentation seems misleading. + > + > (...) + > + > The developers suggest the SQLite vulnerability is actually not a + > behavior guaranteed by SQLite (specifically, that durability cannot + > be achieved under rollback journaling); we believe the + > documentation is misleading. + > + > (...) + > + > Unclear documentation of application guarantees contributes to the + > confusion about crash vulnerabilities. During discussions with + > developers about durability vulnerabilities, we found that SQLite, + > which proclaims itself as fully ACID-complaint, does not provide + > durability (even optionally) with the default storage engine, + > though the documentation suggests it does. + */ + "PRAGMA journal_mode = WAL;\n" + "PRAGMA synchronous = EXTRA;\n" + + /* + From https://www.sqlite.org/foreignkeys.html: + + > Foreign key constraints are disabled by default (for backwards + > compatibility), so must be enabled separately for each database + > connection. + */ + "PRAGMA foreign_keys = ON;\n" + ; + + +// FIXME: make this async +static napi_value +ffi_exec(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1]; + int sqlite_rc; + napi_status napi_rc; + char *sql = NULL; + size_t sql_size1; + size_t sql_size2; + sqlite3 *db_handle = NULL; + char *error_msg = NULL; + + napi_rc = napi_get_cb_info( + env, + info, + &argc, + argv, + NULL, + (void *)&db_handle + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "111TODO ERRCODE", + "Failed to parse arguments TODO i18n" + ); + goto out; + } + + napi_rc = napi_get_value_string_utf8(env, argv[0], NULL, 0, &sql_size1); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "222TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); + goto out; + } + + if (sql_size1 == SIZE_MAX) { + napi_throw_error( + env, + "SQLITE_EOVERFLOW", + "TODO " + ); + goto out; + } + sql_size1++; // include the NULL-terminator in size measurement + + sql = malloc(sql_size1); + if (!sql) { + napi_throw_error( + env, + "SQLITE_ENOMEM", + "TODO i18n" + ); + goto out; + } + + napi_rc = napi_get_value_string_utf8( + env, + argv[0], + sql, + sql_size1, + &sql_size2 + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); + goto out; + } + assert(sql_size1 == sql_size2 + 1 && + "Unstable behaviour from Node-API"); + assert(sql); + + sqlite_rc = sqlite3_exec( + db_handle, + sql, + NULL, + NULL, + &error_msg + ); + if (sqlite_rc != SQLITE_OK) { + napi_throw_error( + env, + "3iii33TODO ERRCODE", + sqlite3_errstr(sqlite_rc) + ); + goto out; + } + +out: + if (error_msg) { + sqlite3_free(error_msg); + } + if (sql) { + free(sql); + } + return NULL; +} + + +static int +accumulate_all_results( + void *ctxptr, + int argc, + char **argv, + char **column_names +) { + int rc = 0; + + napi_status napi_rc; + napi_value row = NULL; + + struct NAPIContext *ctx = ctxptr; + napi_env env = ctx->env; + napi_value results = ctx->value; + uint32_t index = ctx->index; + + napi_rc = napi_create_object(env, &row); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "TODO i18n" + ); + rc = -1; + goto out; + } + + for (int i = 0; i < argc; i++) { + const char *const column_name = column_names[i]; + const char *const column_value = argv[i]; + + // printf("column_name: %s\n", column_name); + // printf("column_value: %s\n", column_value); + + napi_value str; // FIXME: dispatch on the type + napi_rc = napi_create_string_utf8( + env, + column_value, + NAPI_AUTO_LENGTH, + &str + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "SQLITE_MiiiETHOD_CREATE", + "TODO i18n" + ); + rc = -1; + goto out; + } + + napi_rc = napi_set_named_property( + env, + row, + column_name, + str + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "iSQLITE_MiETHOD_CREATE", + "TODO i18n" + ); + rc = -1; + goto out; + } + } + + napi_rc = napi_set_element( + env, + results, + index, + row + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "TODO i18n" + ); + rc = -1; + goto out; + } + + ctx->index++; + +out: + return rc; +} static napi_value -myfn(napi_env env, napi_callback_info info) { +ffi_all(napi_env env, napi_callback_info info) { napi_value ret = NULL; - napi_status status; size_t argc = 1; - int number; napi_value argv[1]; - napi_value my_number; + int sqlite_rc; + napi_status napi_rc; + char *sql = NULL; + size_t sql_size1; + size_t sql_size2; + sqlite3 *db_handle = NULL; + char *error_msg = NULL; + napi_value results_array = NULL; + + napi_rc = napi_get_cb_info( + env, + info, + &argc, + argv, + NULL, + (void *)&db_handle + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "111TODO ERRCODE", + "Failed to parse arguments TODO i18n" + ); + goto out; + } + + napi_rc = napi_get_value_string_utf8(env, argv[0], NULL, 0, &sql_size1); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "222TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); + goto out; + } - status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to parse arguments FIXME i18n"); + if (sql_size1 == SIZE_MAX) { + napi_throw_error( + env, + "SQLITE_EOVERFLOW", + "TODO " + ); goto out; } + sql_size1++; // include the NULL-terminator in size measurement - status = napi_get_value_int32(env, argv[0], &number); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Invalid number was passed as argument FIXME i18n"); + sql = malloc(sql_size1); + if (!sql) { + napi_throw_error( + env, + "SQLITE_ENOMEM", + "TODO i18n" + ); goto out; } - number = number * 2; - status = napi_create_int32(env, number, &my_number); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Unable to create return value FIXME i18n"); + napi_rc = napi_get_value_string_utf8( + env, + argv[0], + sql, + sql_size1, + &sql_size2 + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); goto out; } - ret = my_number; + assert(sql_size1 == sql_size2 + 1 && + "Unstable behaviour from Node-API"); + assert(sql); + + napi_rc = napi_create_array(env, &results_array); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); + goto out; + } + + struct NAPIContext ctx = { + .env = env, + .value = results_array, + .index = 0, + }; + sqlite_rc = sqlite3_exec( + db_handle, + sql, + accumulate_all_results, + &ctx, + &error_msg + ); + if (sqlite_rc != SQLITE_OK) { + napi_throw_error( + env, + "i3iiii33TODO ERRCODE", + sqlite3_errstr(sqlite_rc) + ); + goto out; + } + + ret = results_array; out: + if (error_msg) { + sqlite3_free(error_msg); + } + if (sql) { + free(sql); + } return ret; } static napi_value -open(napi_env env, napi_callback_info info) { - (void)env; - (void)info; - return NULL; +ffi_run(napi_env env, napi_callback_info info) { + napi_value ret = NULL; + + size_t argc = 1; + napi_value argv[1]; + int sqlite_rc; + napi_status napi_rc; + char *sql = NULL; + size_t sql_size1; + size_t sql_size2; + sqlite3 *db_handle = NULL; + char *error_msg = NULL; + napi_value results_array = NULL; + + napi_rc = napi_get_cb_info( + env, + info, + &argc, + argv, + NULL, + (void *)&db_handle + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "111TODO ERRCODE", + "Failed to parse arguments TODO i18n" + ); + goto out; + } + + napi_rc = napi_get_value_string_utf8(env, argv[0], NULL, 0, &sql_size1); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "222TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); + goto out; + } + + if (sql_size1 == SIZE_MAX) { + napi_throw_error( + env, + "SQLITE_EOVERFLOW", + "TODO " + ); + goto out; + } + sql_size1++; // include the NULL-terminator in size measurement + + sql = malloc(sql_size1); + if (!sql) { + napi_throw_error( + env, + "SQLITE_ENOMEM", + "TODO i18n" + ); + goto out; + } + + napi_rc = napi_get_value_string_utf8( + env, + argv[0], + sql, + sql_size1, + &sql_size2 + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); + goto out; + } + assert(sql_size1 == sql_size2 + 1 && + "Unstable behaviour from Node-API"); + assert(sql); + + napi_rc = napi_create_array(env, &results_array); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); + goto out; + } + + struct NAPIContext ctx = { + .env = env, + .value = results_array, + .index = 0, + }; + sqlite_rc = sqlite3_exec( + db_handle, + sql, + accumulate_all_results, + &ctx, + &error_msg + ); + if (sqlite_rc != SQLITE_OK) { + napi_throw_error( + env, + "i3iiii33TODO ERRCODE", + sqlite3_errstr(sqlite_rc) + ); + goto out; + } + + ret = results_array; + +out: + if (error_msg) { + sqlite3_free(error_msg); + } + if (sql) { + free(sql); + } + return ret; +} + +static void +finalize_db_handle(napi_env env, void *finalize_data, void *finalize_hint) { + (void)finalize_hint; + sqlite3 *db_handle = finalize_data; + int sqlite_rc; + + sqlite_rc = sqlite3_close(db_handle); + if (sqlite_rc != SQLITE_OK) { + napi_throw_error( + env, + "TODO ERRCODE", + sqlite3_errstr(sqlite_rc) + ); + goto out; + } + +out: + return; } static napi_value -close(napi_env env, napi_callback_info info) { - (void)env; - (void)info; - return NULL; +ffi_open(napi_env env, napi_callback_info info) { + napi_value ret = NULL; + + size_t argc = 1; + napi_value argv[1]; + int sqlite_rc; + napi_status napi_rc; + char *filename = NULL; + size_t filename_size1; + size_t filename_size2; + sqlite3 *db_handle = NULL; + napi_value wrapped_db_handle = NULL; + char *error_msg = NULL; + + napi_rc = napi_get_cb_info(env, info, &argc, argv, NULL, NULL); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "Failed to parse arguments TODO i18n" + ); + goto out; + } + + napi_rc = napi_get_value_string_utf8( + env, + argv[0], + NULL, // FIXME: what is this? + 0, // FIXME: what is this? + &filename_size1 + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); + goto out; + } + + if (filename_size1 == SIZE_MAX) { + napi_throw_error( + env, + "SQLITE_EOVERFLOW", + "TODO " + ); + goto out; + } + filename_size1++; // include the NULL-terminator in size measurement + + filename = malloc(filename_size1); + if (!filename) { + napi_throw_error( + env, + "SQLITE_ENOMEM", + "TODO i18n" + ); + goto out; + } + + napi_rc = napi_get_value_string_utf8( + env, + argv[0], + filename, + filename_size1, + &filename_size2 + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "Invalid number was passed as argument TODO i18n" + ); + goto out; + } + assert(filename_size1 == filename_size2 + 1 && + "Unstable behaviour from Node-API"); + assert(filename); + + sqlite_rc = sqlite3_open_v2( + filename, + &db_handle, + SQLITE_OPEN_FLAGS, + NULL + ); + if (sqlite_rc != SQLITE_OK) { + napi_throw_error( + env, + "3i33TODO ERRCODE", + sqlite3_errstr(sqlite_rc) + ); + goto out; + } + + sqlite_rc = sqlite3_exec( + db_handle, + FIX_SQLITE_PRAGMAS, + NULL, + NULL, + &error_msg + ); + if (sqlite_rc != SQLITE_OK) { + napi_throw_error( + env, + "3ii33TODO ERRCODE", + sqlite3_errstr(sqlite_rc) + ); + goto out; + } + + + // FIXME: setup the global error log: + // https://www.sqlite.org/errlog.html + + napi_rc = napi_create_object(env, &wrapped_db_handle); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "TODO i18n" + ); + goto out; + } + + napi_rc = napi_type_tag_object( + env, + wrapped_db_handle, + &SQLITE_DB_TYPE_TAG + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "TODO i18n" + ); + goto out; + } + + napi_rc = napi_wrap( + env, + wrapped_db_handle, + db_handle, + finalize_db_handle, + NULL, + NULL + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "TODO ERRCODE", + "TODO i18n" + ); + goto out; + } + + for (size_t i = 0; methods[i].label && methods[i].handle; i++) { + napi_value fn; + napi_rc = napi_create_function( + env, + methods[i].label, + NAPI_AUTO_LENGTH, + methods[i].handle, + db_handle, + &fn + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "SQLITE_MiiETHOD_CREATE", + "TODO i18n" + ); + goto out; + } + + napi_rc = napi_set_named_property( + env, + wrapped_db_handle, + methods[i].label, + fn + ); + if (napi_rc != napi_ok) { + napi_throw_error( + env, + "SQLITE_METHOD_SETNAME", + "TODO i18n" + ); + goto out; + } + } + ret = wrapped_db_handle; + +out: + if (error_msg) { + sqlite3_free(error_msg); + } + if (filename) { + free(filename); + } + if (!ret) { + if (db_handle) { + if (sqlite3_close(db_handle)) { + // logerr(); + } + } + } + return ret; } -static const struct { - const char *label; - napi_value(*const handle)(napi_env env, napi_callback_info info); -} fns[] = { - { .label = "myfn", .handle = myfn, }, - { .label = "open", .handle = open, }, - { .label = "close", .handle = close, }, - { NULL, NULL }, -}; static napi_value -init(napi_env env, napi_value exports) { +ffi_init(napi_env env, napi_value exports) { napi_value ret = exports; napi_status status; @@ -80,7 +807,7 @@ init(napi_env env, napi_value exports) { fns[i].label, NAPI_AUTO_LENGTH, fns[i].handle, - "xucrutes", + NULL, &fn ); if (status != napi_ok) { @@ -88,7 +815,7 @@ init(napi_env env, napi_value exports) { napi_throw_error( env, "SQLITE_FN_CREATE", - "Unable to wrap native function FIXME i18n" + "Unable to wrap native function TODO i18n" ); goto out; } @@ -104,7 +831,7 @@ init(napi_env env, napi_value exports) { napi_throw_error( env, "SQLITE_FN_SETNAME", - "Unable to populate exports FIXME i18n" + "Unable to populate exports TODO i18n" ); goto out; } @@ -116,5 +843,5 @@ out: NAPI_MODULE_INIT() { - return init(env, exports); + return ffi_init(env, exports); } |