/* 2022-05-22 The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: * May you do good and not evil. * May you find forgiveness for yourself and forgive others. * May you share freely, never taking more than you give. *********************************************************************** This file is intended to be loaded after loading sqlite3-module.wasm. It sets one of any number of potential bindings using that API, this one as closely matching the C-native API as is feasible. Note that this file is not named sqlite3.js because that file gets generated by emscripten as the JS-glue counterpart of sqlite3.wasm. The API gets installed as self.sqlite3, where self is expected to be either the global window or Worker object. Because using this API properly requires some degree of WASM-related magic, it is not recommended that this API be used as-is in client-level code, but instead is intended to be used as a basis for APIs more appropriate for high-level client code. This file installs namespace.sqlite3, where namespace is `self`, meaning either the global window or worker, depending on where this is loaded from. */ (function(namespace){ /* For reference: sql.js does essentially everything we want and it solves much of the wasm-related voodoo, but we'll need a different structure because we want the db connection to run in a worker thread and feed data back into the main thread. Regardless of those differences, it makes a great point of reference: https://github.com/sql-js/sql.js Some of the specific design goals here: - Bind a low-level sqlite3 API which is close to the native one in terms of usage. - Create a higher-level one, more akin to sql.js and node.js-style implementations. This one would speak directly to the low-level API. This API could be used by clients who import the low-level API directly into their main thread (which we don't want to recommend but also don't want to outright forbid). - Create a second higher-level one which speaks to the low-level API via worker messages. This one would be intended for use in the main thread, talking to the low-level UI via worker messages. Because workers have only a single message channel, some acrobatics will be needed here to feed async work results back into client-side callbacks (as those callbacks cannot simply be passed to the worker). Exactly what those acrobatics should look like is not yet entirely clear and much experimentation is pending. */ /** Set up the main sqlite3 binding API here, mimicking the C API as closely as we can. Attribution: though not a direct copy/paste, much of what follows is strongly influenced by the sql.js implementation. */ const api = { /* It is important that the following integer values match those from the C code. Ideally we could fetch them from the C API, e.g., in the form of a JSON object, but getting that JSON string constructed within our current confised is currently not worth the effort. */ /* Minimum subset of sqlite result codes we'll need. */ SQLITE_OK: 0, SQLITE_ROW: 100, SQLITE_DONE: 101, /* sqlite data types */ SQLITE_INTEGER: 1, SQLITE_FLOAT: 2, SQLITE_TEXT: 3, SQLITE_BLOB: 4, /* sqlite encodings, used for creating UDFs, noting that we will only support UTF8. */ SQLITE_UTF8: 1 }; const cwrap = Module.cwrap; [/* C-side functions to bind. Each entry is an array with 3 or 4 elements: ["c-side name", "result type" (cwrap() syntax), [arg types in cwrap() syntax] ] If it has 4 elements, the first one is an alternate name to use for the JS-side binding. That's required when overloading a binding for two different uses. */ ["sqlite3_open", "number", ["string", "number"]], ["sqlite3_close_v2", "number", ["number"]], ["sqlite3_exec", "number", ["number", "string", "number", "number", "number"]], ["sqlite3_changes", "number", ["number"]], ["sqlite3_prepare_v2", "number", ["number", "string", "number", "number", "number"]], ["sqlite3_prepare_v2_sqlptr", /* Impl which requires that the 2nd argument be a pointer to the SQL, instead of a string. This is used for cases where we require a non-NULL value for the final argument. We may or may not need this, depending on how our higher-level API shapes up, but this code's spiritual guide (sql.js) uses it we we'll include it. */ "sqlite3_prepare_v2", "number", ["number", "number", "number", "number", "number"]], ["sqlite3_bind_text","number",["number", "number", "number", "number", "number"]], ["sqlite3_bind_blob","number",["number", "number", "number", "number", "number"]], ["sqlite3_bind_double","number",["number", "number", "number"]], ["sqlite3_bind_int","number",["number", "number", "number"]], ["sqlite3_bind_parameter_index","number",["number", "string"]], ["sqlite3_step", "number", ["number"]], ["sqlite3_errmsg", "string", ["number"]], ["sqlite3_column_count","number",["number"]], ["sqlite3_data_count", "number", ["number"]], ["sqlite3_column_count", "number", ["number"]], ["sqlite3_column_double","number",["number", "number"]], ["sqlite3_column_text","string",["number", "number"]], ["sqlite3_column_blob","number", ["number", "number"]], ["sqlite3_column_bytes","number",["number", "number"]], ["sqlite3_column_type","number",["number", "number"]], ["sqlite3_column_name","string",["number", "number"]], ["sqlite3_reset", "number", ["number"]], ["sqlite3_clear_bindings","number",["number"]], ["sqlite3_finalize", "number", ["number"]], ["sqlite3_create_function_v2", "number", ["number", "string", "number", "number", "number", "number", "number", "number", "number"]], ["sqlite3_value_type", "number", ["number"]], ["sqlite3_value_bytes","number",["number"]], ["sqlite3_value_text", "string", ["number"]], ["sqlite3_value_blob", "number", ["number"]], ["sqlite3_value_double","number",["number"]], ["sqlite3_result_double",null,["number", "number"]], ["sqlite3_result_null",null,["number"]], ["sqlite3_result_text",null,["number", "string", "number", "number"]], ["sqlite3_result_blob",null,["number", "number", "number", "number"]], ["sqlite3_result_int",null,["number", "number"]], ["sqlite3_result_error",null,["number", "string", "number"]], ["sqlite3_libversion", "string", []], ["sqlite3_sourceid", "string", []] //["sqlite3_sql", "string", ["number"]], //["sqlite3_normalized_sql", "string", ["number"]] ].forEach(function(e){ const a = Array.prototype.slice.call(e); const k = (4==a.length) ? a.shift() : a[0]; api[k] = cwrap.apply(this, a); }); //console.debug("libversion =",api.sqlite3_libversion()); namespace.sqlite3 = api; })(self/*worker or window*/);