summaryrefslogtreecommitdiff
path: root/src/napi-sqlite.c
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2023-11-16 15:48:12 -0300
committerEuAndreh <eu@euandre.org>2023-11-16 15:51:57 -0300
commitb2b5ba67c25705ddabb7f59baf725604eaf67c04 (patch)
tree96ffbd2b9455965a814cc55ec47b50fbbd213b68 /src/napi-sqlite.c
parentMakefile, mkdeps.sh: Enforce JS->native dependency (diff)
downloadpapod-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.c807
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);
}