summaryrefslogtreecommitdiffstats
path: root/ext/wasm/api
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/api')
-rw-r--r--ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api204
-rw-r--r--ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see5
-rw-r--r--ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api3
-rw-r--r--ext/wasm/api/README.md164
-rw-r--r--ext/wasm/api/extern-post-js.c-pp.js126
-rw-r--r--ext/wasm/api/extern-pre-js.js7
-rw-r--r--ext/wasm/api/post-js-footer.js4
-rw-r--r--ext/wasm/api/post-js-header.js26
-rw-r--r--ext/wasm/api/pre-js.c-pp.js110
-rw-r--r--ext/wasm/api/sqlite3-api-cleanup.js63
-rw-r--r--ext/wasm/api/sqlite3-api-glue.js1671
-rw-r--r--ext/wasm/api/sqlite3-api-oo1.js1946
-rw-r--r--ext/wasm/api/sqlite3-api-prologue.js2154
-rw-r--r--ext/wasm/api/sqlite3-api-worker1.js695
-rw-r--r--ext/wasm/api/sqlite3-license-version-header.js25
-rw-r--r--ext/wasm/api/sqlite3-opfs-async-proxy.js915
-rw-r--r--ext/wasm/api/sqlite3-v-helper.js718
-rw-r--r--ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js1287
-rw-r--r--ext/wasm/api/sqlite3-vfs-opfs.c-pp.js1469
-rw-r--r--ext/wasm/api/sqlite3-wasm.c1928
-rw-r--r--ext/wasm/api/sqlite3-worker1-promiser.c-pp.js282
-rw-r--r--ext/wasm/api/sqlite3-worker1.c-pp.js54
22 files changed, 13856 insertions, 0 deletions
diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
new file mode 100644
index 0000000..57cd61e
--- /dev/null
+++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
@@ -0,0 +1,204 @@
+_malloc
+_free
+_realloc
+_sqlite3_aggregate_context
+_sqlite3_auto_extension
+_sqlite3_bind_blob
+_sqlite3_bind_double
+_sqlite3_bind_int
+_sqlite3_bind_int64
+_sqlite3_bind_null
+_sqlite3_bind_parameter_count
+_sqlite3_bind_parameter_index
+_sqlite3_bind_pointer
+_sqlite3_bind_text
+_sqlite3_busy_handler
+_sqlite3_busy_timeout
+_sqlite3_cancel_auto_extension
+_sqlite3_changes
+_sqlite3_changes64
+_sqlite3_clear_bindings
+_sqlite3_close_v2
+_sqlite3_collation_needed
+_sqlite3_column_blob
+_sqlite3_column_bytes
+_sqlite3_column_count
+_sqlite3_column_count
+_sqlite3_column_double
+_sqlite3_column_int
+_sqlite3_column_int64
+_sqlite3_column_name
+_sqlite3_column_text
+_sqlite3_column_type
+_sqlite3_column_value
+_sqlite3_commit_hook
+_sqlite3_compileoption_get
+_sqlite3_compileoption_used
+_sqlite3_complete
+_sqlite3_context_db_handle
+_sqlite3_create_collation
+_sqlite3_create_collation_v2
+_sqlite3_create_function
+_sqlite3_create_function_v2
+_sqlite3_create_module
+_sqlite3_create_module_v2
+_sqlite3_create_window_function
+_sqlite3_data_count
+_sqlite3_db_filename
+_sqlite3_db_handle
+_sqlite3_db_name
+_sqlite3_db_status
+_sqlite3_declare_vtab
+_sqlite3_deserialize
+_sqlite3_drop_modules
+_sqlite3_errcode
+_sqlite3_errmsg
+_sqlite3_error_offset
+_sqlite3_errstr
+_sqlite3_exec
+_sqlite3_expanded_sql
+_sqlite3_extended_errcode
+_sqlite3_extended_result_codes
+_sqlite3_file_control
+_sqlite3_finalize
+_sqlite3_free
+_sqlite3_get_auxdata
+_sqlite3_get_autocommit
+_sqlite3_initialize
+_sqlite3_keyword_count
+_sqlite3_keyword_name
+_sqlite3_keyword_check
+_sqlite3_last_insert_rowid
+_sqlite3_libversion
+_sqlite3_libversion_number
+_sqlite3_limit
+_sqlite3_malloc
+_sqlite3_malloc64
+_sqlite3_msize
+_sqlite3_open
+_sqlite3_open_v2
+_sqlite3_overload_function
+_sqlite3_prepare_v2
+_sqlite3_prepare_v3
+_sqlite3_preupdate_blobwrite
+_sqlite3_preupdate_count
+_sqlite3_preupdate_depth
+_sqlite3_preupdate_hook
+_sqlite3_preupdate_new
+_sqlite3_preupdate_old
+_sqlite3_progress_handler
+_sqlite3_randomness
+_sqlite3_realloc
+_sqlite3_realloc64
+_sqlite3_reset
+_sqlite3_reset_auto_extension
+_sqlite3_result_blob
+_sqlite3_result_double
+_sqlite3_result_error
+_sqlite3_result_error_code
+_sqlite3_result_error_nomem
+_sqlite3_result_error_toobig
+_sqlite3_result_int
+_sqlite3_result_int64
+_sqlite3_result_null
+_sqlite3_result_pointer
+_sqlite3_result_subtype
+_sqlite3_result_text
+_sqlite3_result_zeroblob
+_sqlite3_result_zeroblob64
+_sqlite3_rollback_hook
+_sqlite3_serialize
+_sqlite3_set_authorizer
+_sqlite3_set_auxdata
+_sqlite3_set_last_insert_rowid
+_sqlite3_shutdown
+_sqlite3_sourceid
+_sqlite3_sql
+_sqlite3_status
+_sqlite3_status64
+_sqlite3_step
+_sqlite3_stmt_isexplain
+_sqlite3_stmt_readonly
+_sqlite3_stmt_status
+_sqlite3_strglob
+_sqlite3_stricmp
+_sqlite3_strlike
+_sqlite3_strnicmp
+_sqlite3_table_column_metadata
+_sqlite3_total_changes
+_sqlite3_total_changes64
+_sqlite3_trace_v2
+_sqlite3_txn_state
+_sqlite3_update_hook
+_sqlite3_uri_boolean
+_sqlite3_uri_int64
+_sqlite3_uri_key
+_sqlite3_uri_parameter
+_sqlite3_user_data
+_sqlite3_value_blob
+_sqlite3_value_bytes
+_sqlite3_value_double
+_sqlite3_value_dup
+_sqlite3_value_free
+_sqlite3_value_frombind
+_sqlite3_value_int
+_sqlite3_value_int64
+_sqlite3_value_nochange
+_sqlite3_value_numeric_type
+_sqlite3_value_pointer
+_sqlite3_value_subtype
+_sqlite3_value_text
+_sqlite3_value_type
+_sqlite3_vfs_find
+_sqlite3_vfs_register
+_sqlite3_vfs_unregister
+_sqlite3_vtab_collation
+_sqlite3_vtab_distinct
+_sqlite3_vtab_in
+_sqlite3_vtab_in_first
+_sqlite3_vtab_in_next
+_sqlite3_vtab_nochange
+_sqlite3_vtab_on_conflict
+_sqlite3_vtab_rhs_value
+_sqlite3changegroup_add
+_sqlite3changegroup_add_strm
+_sqlite3changegroup_delete
+_sqlite3changegroup_new
+_sqlite3changegroup_output
+_sqlite3changegroup_output_strm
+_sqlite3changeset_apply
+_sqlite3changeset_apply_strm
+_sqlite3changeset_apply_v2
+_sqlite3changeset_apply_v2_strm
+_sqlite3changeset_concat
+_sqlite3changeset_concat_strm
+_sqlite3changeset_conflict
+_sqlite3changeset_finalize
+_sqlite3changeset_fk_conflicts
+_sqlite3changeset_invert
+_sqlite3changeset_invert_strm
+_sqlite3changeset_new
+_sqlite3changeset_next
+_sqlite3changeset_old
+_sqlite3changeset_op
+_sqlite3changeset_pk
+_sqlite3changeset_start
+_sqlite3changeset_start_strm
+_sqlite3changeset_start_v2
+_sqlite3changeset_start_v2_strm
+_sqlite3session_attach
+_sqlite3session_changeset
+_sqlite3session_changeset_size
+_sqlite3session_changeset_strm
+_sqlite3session_config
+_sqlite3session_create
+_sqlite3session_delete
+_sqlite3session_diff
+_sqlite3session_enable
+_sqlite3session_indirect
+_sqlite3session_isempty
+_sqlite3session_memory_used
+_sqlite3session_object_config
+_sqlite3session_patchset
+_sqlite3session_patchset_strm
+_sqlite3session_table_filter
diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see
new file mode 100644
index 0000000..83f3a97
--- /dev/null
+++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see
@@ -0,0 +1,5 @@
+_sqlite3_key
+_sqlite3_key_v2
+_sqlite3_rekey
+_sqlite3_rekey_v2
+_sqlite3_activate_see
diff --git a/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api b/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api
new file mode 100644
index 0000000..aab1d8b
--- /dev/null
+++ b/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api
@@ -0,0 +1,3 @@
+FS
+wasmMemory
+
diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md
new file mode 100644
index 0000000..eb0f073
--- /dev/null
+++ b/ext/wasm/api/README.md
@@ -0,0 +1,164 @@
+# sqlite3-api.js And Friends
+
+This is the README for the files `sqlite3-*.js` and
+`sqlite3-wasm.c`. This collection of files is used to build a
+single-file distribution of the sqlite3 WASM API. It is broken into
+multiple JS files because:
+
+1. To facilitate including or excluding certain components for
+ specific use cases. e.g. by removing `sqlite3-api-oo1.js` if the
+ OO#1 API is not needed.
+
+2. To facilitate modularizing the pieces for use in different WASM
+ build environments. e.g. the files `post-js-*.js` are for use with
+ Emscripten's `--post-js` feature, and nowhere else.
+
+3. Certain components must be in their own standalone files in order
+ to be loaded as JS Workers.
+
+Note that the structure described here is the current state of things,
+as of this writing, but is not set in stone forever and may change
+at any time.
+
+The overall idea is that the following files get concatenated
+together, in the listed order, the resulting file is loaded by a
+browser client:
+
+- **`sqlite3-api-prologue.js`**\
+ Contains the initial bootstrap setup of the sqlite3 API
+ objects. This is exposed as a function, rather than objects, so that
+ the next step can pass in a config object which abstracts away parts
+ of the WASM environment, to facilitate plugging it in to arbitrary
+ WASM toolchains.
+- **`../common/whwasmutil.js`**\
+ A semi-third-party collection of JS/WASM utility code intended to
+ replace much of the Emscripten glue. The sqlite3 APIs internally use
+ these APIs instead of their Emscripten counterparts, in order to be
+ more portable to arbitrary WASM toolchains. This API is
+ configurable, in principle, for use with arbitrary WASM
+ toolchains. It is "semi-third-party" in that it was created in order
+ to support this tree but is standalone and maintained together
+ with...
+- **`../jaccwabyt/jaccwabyt.js`**\
+ Another semi-third-party API which creates bindings between JS
+ and C structs, such that changes to the struct state from either JS
+ or C are visible to the other end of the connection. This is also an
+ independent spinoff project, conceived for the sqlite3 project but
+ maintained separately.
+- **`sqlite3-api-glue.js`**\
+ Invokes functionality exposed by the previous two files to flesh out
+ low-level parts of `sqlite3-api-prologue.js`. Most of these pieces
+ related to populating the `sqlite3.capi.wasm` object. This file
+ also deletes most global-scope symbols the above files create,
+ effectively moving them into the scope being used for initializing
+ the API.
+- **`<build>/sqlite3-api-build-version.js`**\
+ Gets created by the build process and populates the
+ `sqlite3.version` object. This part is not critical, but records the
+ version of the library against which this module was built.
+- **`sqlite3-api-oo1.js`**\
+ Provides a high-level object-oriented wrapper to the lower-level C
+ API, colloquially known as OO API #1. Its API is similar to other
+ high-level sqlite3 JS wrappers and should feel relatively familiar
+ to anyone familiar with such APIs. That said, it is not a "required
+ component" and can be elided from builds which do not want it.
+- **`sqlite3-api-worker1.js`**\
+ A Worker-thread-based API which uses OO API #1 to provide an
+ interface to a database which can be driven from the main Window
+ thread via the Worker message-passing interface. Like OO API #1,
+ this is an optional component, offering one of any number of
+ potential implementations for such an API.
+ - **`sqlite3-worker1.js`**\
+ Is not part of the amalgamated sources and is intended to be
+ loaded by a client Worker thread. It loads the sqlite3 module
+ and runs the Worker #1 API which is implemented in
+ `sqlite3-api-worker1.js`.
+ - **`sqlite3-worker1-promiser.js`**\
+ Is likewise not part of the amalgamated sources and provides
+ a Promise-based interface into the Worker #1 API. This is
+ a far user-friendlier way to interface with databases running
+ in a Worker thread.
+- **`sqlite3-v-helper.js`**\
+ Installs `sqlite3.vfs` and `sqlite3.vtab`, namespaces which contain
+ helpers for use by downstream code which creates `sqlite3_vfs`
+ and `sqlite3_module` implementations.
+- **`sqlite3-vfs-opfs.c-pp.js`**\
+ is an sqlite3 VFS implementation which supports the Origin-Private
+ FileSystem (OPFS) as a storage layer to provide persistent storage
+ for database files in a browser. It requires...
+ - **`sqlite3-opfs-async-proxy.js`**\
+ is the asynchronous backend part of the OPFS proxy. It speaks
+ directly to the (async) OPFS API and channels those results back
+ to its synchronous counterpart. This file, because it must be
+ started in its own Worker, is not part of the amalgamation.
+- **`sqlite3-vfs-opfs-sahpool.c-pp.js`**\
+ is another sqlite3 VFS supporting the OPFS, but uses a completely
+ different approach that the above-listed one.
+- **`sqlite3-api-cleanup.js`**\
+ The previous files do not immediately extend the library. Instead
+ they add callback functions to be called during its
+ bootstrapping. Some also temporarily create global objects in order
+ to communicate their state to the files which follow them. This file
+ cleans up any dangling globals and runs the API bootstrapping
+ process, which is what finally executes the initialization code
+ installed by the previous files. As of this writing, this code
+ ensures that the previous files leave no more than a single global
+ symbol installed. When adapting the API for non-Emscripten
+ toolchains, this "should" be the only file where changes are needed.
+
+
+**Files with the extension `.c-pp.js`** are intended [to be processed
+with `c-pp`](#c-pp), noting that such preprocessing may be applied
+after all of the relevant files are concatenated. That extension is
+used primarily to keep the code maintainers cognisant of the fact that
+those files contain constructs which may not run as-is in any given
+JavaScript environment.
+
+The build process glues those files together, resulting in
+`sqlite3-api.js`, which is everything except for the
+`pre/post-js-*.js` files, and `sqlite3.js`, which is the
+Emscripten-generated amalgamated output and includes the
+`pre/post-js-*.js` parts, as well as the Emscripten-provided module
+loading pieces.
+
+The non-JS outlier file is `sqlite3-wasm.c`: it is a proxy for
+`sqlite3.c` which `#include`'s that file and adds a couple more
+WASM-specific helper functions, at least one of which requires access
+to private/static `sqlite3.c` internals. `sqlite3.wasm` is compiled
+from this file rather than `sqlite3.c`.
+
+The following files are part of the build process but are injected
+into the build-generated `sqlite3.js` along with `sqlite3-api.js`.
+
+- `extern-pre-js.js`\
+ Emscripten-specific header for Emscripten's `--extern-pre-js`
+ flag. As of this writing, that file is only used for experimentation
+ purposes and holds no code relevant to the production deliverables.
+- `pre-js.c-pp.js`\
+ Emscripten-specific header for Emscripten's `--pre-js` flag. This
+ file is intended as a place to override certain Emscripten behavior
+ before it starts up, but corner-case Emscripten bugs keep that from
+ being a reality.
+- `post-js-header.js`\
+ Emscripten-specific header for the `--post-js` input. It opens up
+ a lexical scope by starting a post-run handler for Emscripten.
+- `post-js-footer.js`\
+ Emscripten-specific footer for the `--post-js` input. This closes
+ off the lexical scope opened by `post-js-header.js`.
+- `extern-post-js.c-pp.js`\
+ Emscripten-specific header for Emscripten's `--extern-post-js`
+ flag. This file overwrites the Emscripten-installed
+ `sqlite3InitModule()` function with one which, after the module is
+ loaded, also initializes the asynchronous parts of the sqlite3
+ module. For example, the OPFS VFS support.
+
+<a id='c-pp'></a>
+Preprocessing of Source Files
+------------------------------------------------------------------------
+
+Certain files in the build require preprocessing to filter in/out
+parts which differ between vanilla JS, ES6 Modules, and node.js
+builds. The preprocessor application itself is in
+[`c-pp.c`](/file/ext/wasm/c-pp.c) and the complete technical details
+of such preprocessing are maintained in
+[`GNUMakefile`](/file/ext/wasm/GNUmakefile).
diff --git a/ext/wasm/api/extern-post-js.c-pp.js b/ext/wasm/api/extern-post-js.c-pp.js
new file mode 100644
index 0000000..63e5505
--- /dev/null
+++ b/ext/wasm/api/extern-post-js.c-pp.js
@@ -0,0 +1,126 @@
+
+/* ^^^^ ACHTUNG: blank line at the start is necessary because
+ Emscripten will not add a newline in some cases and we need
+ a blank line for a sed-based kludge for the ES6 build. */
+/* extern-post-js.js must be appended to the resulting sqlite3.js
+ file. It gets its name from being used as the value for the
+ --extern-post-js=... Emscripten flag. Note that this code, unlike
+ most of the associated JS code, runs outside of the
+ Emscripten-generated module init scope, in the current
+ global scope. */
+//#if target=es6-module
+const toExportForESM =
+//#endif
+(function(){
+ /**
+ In order to hide the sqlite3InitModule()'s resulting
+ Emscripten module from downstream clients (and simplify our
+ documentation by being able to elide those details), we hide that
+ function and expose a hand-written sqlite3InitModule() to return
+ the sqlite3 object (most of the time).
+
+ Unfortunately, we cannot modify the module-loader/exporter-based
+ impls which Emscripten installs at some point in the file above
+ this.
+ */
+ const originalInit = sqlite3InitModule;
+ if(!originalInit){
+ throw new Error("Expecting globalThis.sqlite3InitModule to be defined by the Emscripten build.");
+ }
+ /**
+ We need to add some state which our custom Module.locateFile()
+ can see, but an Emscripten limitation currently prevents us from
+ attaching it to the sqlite3InitModule function object:
+
+ https://github.com/emscripten-core/emscripten/issues/18071
+
+ The only(?) current workaround is to temporarily stash this state
+ into the global scope and delete it when sqlite3InitModule()
+ is called.
+ */
+ const initModuleState = globalThis.sqlite3InitModuleState = Object.assign(Object.create(null),{
+ moduleScript: globalThis?.document?.currentScript,
+ isWorker: ('undefined' !== typeof WorkerGlobalScope),
+ location: globalThis.location,
+ urlParams: globalThis?.location?.href
+ ? new URL(globalThis.location.href).searchParams
+ : new URLSearchParams()
+ });
+ initModuleState.debugModule =
+ initModuleState.urlParams.has('sqlite3.debugModule')
+ ? (...args)=>console.warn('sqlite3.debugModule:',...args)
+ : ()=>{};
+
+ if(initModuleState.urlParams.has('sqlite3.dir')){
+ initModuleState.sqlite3Dir = initModuleState.urlParams.get('sqlite3.dir') +'/';
+ }else if(initModuleState.moduleScript){
+ const li = initModuleState.moduleScript.src.split('/');
+ li.pop();
+ initModuleState.sqlite3Dir = li.join('/') + '/';
+ }
+
+ globalThis.sqlite3InitModule = function ff(...args){
+ //console.warn("Using replaced sqlite3InitModule()",globalThis.location);
+ return originalInit(...args).then((EmscriptenModule)=>{
+//#if wasmfs
+ if('undefined'!==typeof WorkerGlobalScope &&
+ EmscriptenModule['ENVIRONMENT_IS_PTHREAD']){
+ /** Workaround for wasmfs-generated worker, which calls this
+ routine from each individual thread and requires that its
+ argument be returned. The conditional criteria above are
+ fragile, based solely on inspection of the offending code,
+ not public Emscripten details. */
+ //console.warn("sqlite3InitModule() returning E-module.",EmscriptenModule);
+ return EmscriptenModule;
+ }
+//#endif
+ //console.warn("sqlite3InitModule() returning sqlite3 object.");
+ const s = EmscriptenModule.sqlite3;
+ s.scriptInfo = initModuleState;
+ //console.warn("sqlite3.scriptInfo =",s.scriptInfo);
+ if(ff.__isUnderTest) s.__isUnderTest = true;
+ const f = s.asyncPostInit;
+ delete s.asyncPostInit;
+ return f();
+ }).catch((e)=>{
+ console.error("Exception loading sqlite3 module:",e);
+ throw e;
+ });
+ };
+ globalThis.sqlite3InitModule.ready = originalInit.ready;
+
+ if(globalThis.sqlite3InitModuleState.moduleScript){
+ const sim = globalThis.sqlite3InitModuleState;
+ let src = sim.moduleScript.src.split('/');
+ src.pop();
+ sim.scriptDir = src.join('/') + '/';
+ }
+ initModuleState.debugModule('sqlite3InitModuleState =',initModuleState);
+ if(0){
+ console.warn("Replaced sqlite3InitModule()");
+ console.warn("globalThis.location.href =",globalThis.location.href);
+ if('undefined' !== typeof document){
+ console.warn("document.currentScript.src =",
+ document?.currentScript?.src);
+ }
+ }
+//#ifnot target=es6-module
+// Emscripten does not inject these module-loader bits in ES6 module
+// builds and including them here breaks JS bundlers, so elide them
+// from ESM builds.
+ /* Replace the various module exports performed by the Emscripten
+ glue... */
+ if (typeof exports === 'object' && typeof module === 'object'){
+ module.exports = sqlite3InitModule;
+ }else if (typeof exports === 'object'){
+ exports["sqlite3InitModule"] = sqlite3InitModule;
+ }
+ /* AMD modules get injected in a way we cannot override,
+ so we can't handle those here. */
+//#endif // !target=es6-module
+ return globalThis.sqlite3InitModule /* required for ESM */;
+})();
+//#if target=es6-module
+sqlite3InitModule = toExportForESM;
+export default sqlite3InitModule;
+//#endif
diff --git a/ext/wasm/api/extern-pre-js.js b/ext/wasm/api/extern-pre-js.js
new file mode 100644
index 0000000..7d47d33
--- /dev/null
+++ b/ext/wasm/api/extern-pre-js.js
@@ -0,0 +1,7 @@
+/* extern-pre-js.js must be prepended to the resulting sqlite3.js
+ file. This file is currently only used for holding snippets during
+ test and development.
+
+ It gets its name from being used as the value for the
+ --extern-pre-js=... Emscripten flag.
+*/
diff --git a/ext/wasm/api/post-js-footer.js b/ext/wasm/api/post-js-footer.js
new file mode 100644
index 0000000..58882cb
--- /dev/null
+++ b/ext/wasm/api/post-js-footer.js
@@ -0,0 +1,4 @@
+/* The current function scope was opened via post-js-header.js, which
+ gets prepended to this at build-time. This file closes that
+ scope. */
+})/*postRun.push(...)*/;
diff --git a/ext/wasm/api/post-js-header.js b/ext/wasm/api/post-js-header.js
new file mode 100644
index 0000000..0e27e1f
--- /dev/null
+++ b/ext/wasm/api/post-js-header.js
@@ -0,0 +1,26 @@
+/**
+ post-js-header.js is to be prepended to other code to create
+ post-js.js for use with Emscripten's --post-js flag. This code
+ requires that it be running in that context. The Emscripten
+ environment must have been set up already but it will not have
+ loaded its WASM when the code in this file is run. The function it
+ installs will be run after the WASM module is loaded, at which
+ point the sqlite3 JS API bits will get set up.
+*/
+if(!Module.postRun) Module.postRun = [];
+Module.postRun.push(function(Module/*the Emscripten-style module object*/){
+ 'use strict';
+ /* This function will contain at least the following:
+
+ - post-js-header.js (this file)
+ - sqlite3-api-prologue.js => Bootstrapping bits to attach the rest to
+ - common/whwasmutil.js => Replacements for much of Emscripten's glue
+ - jaccwaby/jaccwabyt.js => Jaccwabyt (C/JS struct binding)
+ - sqlite3-api-glue.js => glues previous parts together
+ - sqlite3-api-oo.js => SQLite3 OO API #1
+ - sqlite3-api-worker1.js => Worker-based API
+ - sqlite3-vfs-helper.js => Internal-use utilities for...
+ - sqlite3-vfs-opfs.js => OPFS VFS
+ - sqlite3-api-cleanup.js => final API cleanup
+ - post-js-footer.js => closes this postRun() function
+ */
diff --git a/ext/wasm/api/pre-js.c-pp.js b/ext/wasm/api/pre-js.c-pp.js
new file mode 100644
index 0000000..878f3e0
--- /dev/null
+++ b/ext/wasm/api/pre-js.c-pp.js
@@ -0,0 +1,110 @@
+/**
+ BEGIN FILE: api/pre-js.js
+
+ This file is intended to be prepended to the sqlite3.js build using
+ Emscripten's --pre-js=THIS_FILE flag (or equivalent).
+*/
+
+// See notes in extern-post-js.js
+const sqlite3InitModuleState = globalThis.sqlite3InitModuleState
+ || Object.assign(Object.create(null),{
+ debugModule: ()=>{}
+ });
+delete globalThis.sqlite3InitModuleState;
+sqlite3InitModuleState.debugModule('globalThis.location =',globalThis.location);
+
+//#ifnot target=es6-bundler-friendly
+/**
+ This custom locateFile() tries to figure out where to load `path`
+ from. The intent is to provide a way for foo/bar/X.js loaded from a
+ Worker constructor or importScripts() to be able to resolve
+ foo/bar/X.wasm (in the latter case, with some help):
+
+ 1) If URL param named the same as `path` is set, it is returned.
+
+ 2) If sqlite3InitModuleState.sqlite3Dir is set, then (thatName + path)
+ is returned (note that it's assumed to end with '/').
+
+ 3) If this code is running in the main UI thread AND it was loaded
+ from a SCRIPT tag, the directory part of that URL is used
+ as the prefix. (This form of resolution unfortunately does not
+ function for scripts loaded via importScripts().)
+
+ 4) If none of the above apply, (prefix+path) is returned.
+*/
+Module['locateFile'] = function(path, prefix) {
+//#if target=es6-module
+ return new URL(path, import.meta.url).href;
+//#else
+ 'use strict';
+ let theFile;
+ const up = this.urlParams;
+ if(up.has(path)){
+ theFile = up.get(path);
+ }else if(this.sqlite3Dir){
+ theFile = this.sqlite3Dir + path;
+ }else if(this.scriptDir){
+ theFile = this.scriptDir + path;
+ }else{
+ theFile = prefix + path;
+ }
+ sqlite3InitModuleState.debugModule(
+ "locateFile(",arguments[0], ',', arguments[1],")",
+ 'sqlite3InitModuleState.scriptDir =',this.scriptDir,
+ 'up.entries() =',Array.from(up.entries()),
+ "result =", theFile
+ );
+ return theFile;
+//#endif target=es6-module
+}.bind(sqlite3InitModuleState);
+//#endif ifnot target=es6-bundler-friendly
+
+/**
+ Bug warning: a custom Module.instantiateWasm() does not work
+ in WASMFS builds:
+
+ https://github.com/emscripten-core/emscripten/issues/17951
+
+ In such builds we must disable this.
+*/
+const xNameOfInstantiateWasm = false
+ ? 'instantiateWasm'
+ : 'emscripten-bug-17951';
+Module[xNameOfInstantiateWasm] = function callee(imports,onSuccess){
+ imports.env.foo = function(){};
+ const uri = Module.locateFile(
+ callee.uri, (
+ ('undefined'===typeof scriptDirectory/*var defined by Emscripten glue*/)
+ ? "" : scriptDirectory)
+ );
+ sqlite3InitModuleState.debugModule(
+ "instantiateWasm() uri =", uri
+ );
+ const wfetch = ()=>fetch(uri, {credentials: 'same-origin'});
+ const loadWasm = WebAssembly.instantiateStreaming
+ ? async ()=>{
+ return WebAssembly.instantiateStreaming(wfetch(), imports)
+ .then((arg)=>onSuccess(arg.instance, arg.module));
+ }
+ : async ()=>{ // Safari < v15
+ return wfetch()
+ .then(response => response.arrayBuffer())
+ .then(bytes => WebAssembly.instantiate(bytes, imports))
+ .then((arg)=>onSuccess(arg.instance, arg.module));
+ };
+ loadWasm();
+ return {};
+};
+/*
+ It is literally impossible to reliably get the name of _this_ script
+ at runtime, so impossible to derive X.wasm from script name
+ X.js. Thus we need, at build-time, to redefine
+ Module[xNameOfInstantiateWasm].uri by appending it to a build-specific
+ copy of this file with the name of the wasm file. This is apparently
+ why Emscripten hard-codes the name of the wasm file into their glue
+ scripts.
+*/
+Module[xNameOfInstantiateWasm].uri = 'sqlite3.wasm';
+/* END FILE: api/pre-js.js, noting that the build process may add a
+ line after this one to change the above .uri to a build-specific
+ one. */
diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js
new file mode 100644
index 0000000..65dbb4e
--- /dev/null
+++ b/ext/wasm/api/sqlite3-api-cleanup.js
@@ -0,0 +1,63 @@
+/*
+ 2022-07-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 the tail end of the sqlite3-api.js constellation,
+ intended to be appended after all other sqlite3-api-*.js files so
+ that it can finalize any setup and clean up any global symbols
+ temporarily used for setting up the API's various subsystems.
+*/
+'use strict';
+if('undefined' !== typeof Module){ // presumably an Emscripten build
+ /**
+ Install a suitable default configuration for sqlite3ApiBootstrap().
+ */
+ const SABC = Object.assign(
+ Object.create(null), {
+ exports: ('undefined'===typeof wasmExports)
+ ? Module['asm']/* emscripten <=3.1.43 */
+ : wasmExports /* emscripten >=3.1.44 */,
+ memory: Module.wasmMemory /* gets set if built with -sIMPORTED_MEMORY */
+ },
+ globalThis.sqlite3ApiConfig || {}
+ );
+
+ /**
+ For current (2022-08-22) purposes, automatically call
+ sqlite3ApiBootstrap(). That decision will be revisited at some
+ point, as we really want client code to be able to call this to
+ configure certain parts. Clients may modify
+ globalThis.sqlite3ApiBootstrap.defaultConfig to tweak the default
+ configuration used by a no-args call to sqlite3ApiBootstrap(),
+ but must have first loaded their WASM module in order to be
+ able to provide the necessary configuration state.
+ */
+ //console.warn("globalThis.sqlite3ApiConfig = ",globalThis.sqlite3ApiConfig);
+ globalThis.sqlite3ApiConfig = SABC;
+ let sqlite3;
+ try{
+ sqlite3 = globalThis.sqlite3ApiBootstrap();
+ }catch(e){
+ console.error("sqlite3ApiBootstrap() error:",e);
+ throw e;
+ }finally{
+ delete globalThis.sqlite3ApiBootstrap;
+ delete globalThis.sqlite3ApiConfig;
+ }
+
+ Module.sqlite3 = sqlite3 /* Needed for customized sqlite3InitModule() to be able to
+ pass the sqlite3 object off to the client. */;
+}else{
+ console.warn("This is not running in an Emscripten module context, so",
+ "globalThis.sqlite3ApiBootstrap() is _not_ being called due to lack",
+ "of config info for the WASM environment.",
+ "It must be called manually.");
+}
diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js
new file mode 100644
index 0000000..29efb3e
--- /dev/null
+++ b/ext/wasm/api/sqlite3-api-glue.js
@@ -0,0 +1,1671 @@
+/*
+ 2022-07-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 glues together disparate pieces of JS which are loaded in
+ previous steps of the sqlite3-api.js bootstrapping process:
+ sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It
+ initializes the main API pieces so that the downstream components
+ (e.g. sqlite3-api-oo1.js) have all that they need.
+*/
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ 'use strict';
+ const toss = (...args)=>{throw new Error(args.join(' '))};
+ const toss3 = sqlite3.SQLite3Error.toss;
+ const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util;
+ globalThis.WhWasmUtilInstaller(wasm);
+ delete globalThis.WhWasmUtilInstaller;
+
+ if(0){
+ /**
+ Please keep this block around as a maintenance reminder
+ that we cannot rely on this type of check.
+
+ This block fails on Safari, per a report at
+ https://sqlite.org/forum/forumpost/e5b20e1feb.
+
+ It turns out that what Safari serves from the indirect function
+ table (e.g. wasm.functionEntry(X)) is anonymous functions which
+ wrap the WASM functions, rather than returning the WASM
+ functions themselves. That means comparison of such functions
+ is useless for determining whether or not we have a specific
+ function from wasm.exports. i.e. if function X is indirection
+ function table entry N then wasm.exports.X is not equal to
+ wasm.functionEntry(N) in Safari, despite being so in the other
+ browsers.
+ */
+ /**
+ Find a mapping for SQLITE_WASM_DEALLOC, which the API
+ guarantees is a WASM pointer to the same underlying function as
+ wasm.dealloc() (noting that wasm.dealloc() is permitted to be a
+ JS wrapper around the WASM function). There is unfortunately no
+ O(1) algorithm for finding this pointer: we have to walk the
+ WASM indirect function table to find it. However, experience
+ indicates that that particular function is always very close to
+ the front of the table (it's been entry #3 in all relevant
+ tests).
+ */
+ const dealloc = wasm.exports[sqlite3.config.deallocExportName];
+ const nFunc = wasm.functionTable().length;
+ let i;
+ for(i = 0; i < nFunc; ++i){
+ const e = wasm.functionEntry(i);
+ if(dealloc === e){
+ capi.SQLITE_WASM_DEALLOC = i;
+ break;
+ }
+ }
+ if(dealloc !== wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){
+ toss("Internal error: cannot find function pointer for SQLITE_WASM_DEALLOC.");
+ }
+ }
+
+ /**
+ Signatures for the WASM-exported C-side functions. Each entry
+ is an array with 2+ elements:
+
+ [ "c-side name",
+ "result type" (wasm.xWrap() syntax),
+ [arg types in xWrap() syntax]
+ // ^^^ this needn't strictly be an array: it can be subsequent
+ // elements instead: [x,y,z] is equivalent to x,y,z
+ ]
+
+ Note that support for the API-specific data types in the
+ result/argument type strings gets plugged in at a later phase in
+ the API initialization process.
+ */
+ wasm.bindingSignatures = [
+ // Please keep these sorted by function name!
+ ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"],
+ /* sqlite3_auto_extension() has a hand-written binding. */
+ /* sqlite3_bind_blob() and sqlite3_bind_text() have hand-written
+ bindings to permit more flexible inputs. */
+ ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"],
+ ["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"],
+ ["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"],
+ ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"],
+ ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"],
+ ["sqlite3_bind_pointer", "int",
+ "sqlite3_stmt*", "int", "*", "string:static", "*"],
+ ["sqlite3_busy_handler","int", [
+ "sqlite3*",
+ new wasm.xWrap.FuncPtrAdapter({
+ signature: 'i(pi)',
+ contextKey: (argv,argIndex)=>argv[0/* sqlite3* */]
+ }),
+ "*"
+ ]],
+ ["sqlite3_busy_timeout","int", "sqlite3*", "int"],
+ /* sqlite3_cancel_auto_extension() has a hand-written binding. */
+ /* sqlite3_close_v2() is implemented by hand to perform some
+ extra work. */
+ ["sqlite3_changes", "int", "sqlite3*"],
+ ["sqlite3_clear_bindings","int", "sqlite3_stmt*"],
+ ["sqlite3_collation_needed", "int", "sqlite3*", "*", "*"/*=>v(ppis)*/],
+ ["sqlite3_column_blob","*", "sqlite3_stmt*", "int"],
+ ["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"],
+ ["sqlite3_column_count", "int", "sqlite3_stmt*"],
+ ["sqlite3_column_double","f64", "sqlite3_stmt*", "int"],
+ ["sqlite3_column_int","int", "sqlite3_stmt*", "int"],
+ ["sqlite3_column_name","string", "sqlite3_stmt*", "int"],
+ ["sqlite3_column_text","string", "sqlite3_stmt*", "int"],
+ ["sqlite3_column_type","int", "sqlite3_stmt*", "int"],
+ ["sqlite3_column_value","sqlite3_value*", "sqlite3_stmt*", "int"],
+ ["sqlite3_commit_hook", "void*", [
+ "sqlite3*",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'sqlite3_commit_hook',
+ signature: 'i(p)',
+ contextKey: (argv)=>argv[0/* sqlite3* */]
+ }),
+ '*'
+ ]],
+ ["sqlite3_compileoption_get", "string", "int"],
+ ["sqlite3_compileoption_used", "int", "string"],
+ ["sqlite3_complete", "int", "string:flexible"],
+ ["sqlite3_context_db_handle", "sqlite3*", "sqlite3_context*"],
+
+ /* sqlite3_create_function(), sqlite3_create_function_v2(), and
+ sqlite3_create_window_function() use hand-written bindings to
+ simplify handling of their function-type arguments. */
+ /* sqlite3_create_collation() and sqlite3_create_collation_v2()
+ use hand-written bindings to simplify passing of the callback
+ function.
+ ["sqlite3_create_collation", "int",
+ "sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value
+ "*", "*"],
+ ["sqlite3_create_collation_v2", "int",
+ "sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value
+ "*", "*", "*"],
+ */
+ ["sqlite3_data_count", "int", "sqlite3_stmt*"],
+ ["sqlite3_db_filename", "string", "sqlite3*", "string"],
+ ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"],
+ ["sqlite3_db_name", "string", "sqlite3*", "int"],
+ ["sqlite3_db_status", "int", "sqlite3*", "int", "*", "*", "int"],
+ ["sqlite3_errcode", "int", "sqlite3*"],
+ ["sqlite3_errmsg", "string", "sqlite3*"],
+ ["sqlite3_error_offset", "int", "sqlite3*"],
+ ["sqlite3_errstr", "string", "int"],
+ ["sqlite3_exec", "int", [
+ "sqlite3*", "string:flexible",
+ new wasm.xWrap.FuncPtrAdapter({
+ signature: 'i(pipp)',
+ bindScope: 'transient',
+ callProxy: (callback)=>{
+ let aNames;
+ return (pVoid, nCols, pColVals, pColNames)=>{
+ try {
+ const aVals = wasm.cArgvToJs(nCols, pColVals);
+ if(!aNames) aNames = wasm.cArgvToJs(nCols, pColNames);
+ return callback(aVals, aNames) | 0;
+ }catch(e){
+ /* If we set the db error state here, the higher-level
+ exec() call replaces it with its own, so we have no way
+ of reporting the exception message except the console. We
+ must not propagate exceptions through the C API. Though
+ we make an effort to report OOM here, sqlite3_exec()
+ translates that into SQLITE_ABORT as well. */
+ return e.resultCode || capi.SQLITE_ERROR;
+ }
+ }
+ }
+ }),
+ "*", "**"
+ ]],
+ ["sqlite3_expanded_sql", "string", "sqlite3_stmt*"],
+ ["sqlite3_extended_errcode", "int", "sqlite3*"],
+ ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"],
+ ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"],
+ ["sqlite3_finalize", "int", "sqlite3_stmt*"],
+ ["sqlite3_free", undefined,"*"],
+ ["sqlite3_get_autocommit", "int", "sqlite3*"],
+ ["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"],
+ ["sqlite3_initialize", undefined],
+ /*["sqlite3_interrupt", undefined, "sqlite3*"
+ ^^^ we cannot actually currently support this because JS is
+ single-threaded and we don't have a portable way to access a DB
+ from 2 SharedWorkers concurrently. ],*/
+ ["sqlite3_keyword_count", "int"],
+ ["sqlite3_keyword_name", "int", ["int", "**", "*"]],
+ ["sqlite3_keyword_check", "int", ["string", "int"]],
+ ["sqlite3_libversion", "string"],
+ ["sqlite3_libversion_number", "int"],
+ ["sqlite3_limit", "int", ["sqlite3*", "int", "int"]],
+ ["sqlite3_malloc", "*","int"],
+ ["sqlite3_open", "int", "string", "*"],
+ ["sqlite3_open_v2", "int", "string", "*", "int", "string"],
+ /* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled
+ separately due to us requiring two different sets of semantics
+ for those, depending on how their SQL argument is provided. */
+ /* sqlite3_randomness() uses a hand-written wrapper to extend
+ the range of supported argument types. */
+ ["sqlite3_progress_handler", undefined, [
+ "sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({
+ name: 'xProgressHandler',
+ signature: 'i(p)',
+ bindScope: 'context',
+ contextKey: (argv,argIndex)=>argv[0/* sqlite3* */]
+ }), "*"
+ ]],
+ ["sqlite3_realloc", "*","*","int"],
+ ["sqlite3_reset", "int", "sqlite3_stmt*"],
+ /* sqlite3_reset_auto_extension() has a hand-written binding. */
+ ["sqlite3_result_blob", undefined, "sqlite3_context*", "*", "int", "*"],
+ ["sqlite3_result_double", undefined, "sqlite3_context*", "f64"],
+ ["sqlite3_result_error", undefined, "sqlite3_context*", "string", "int"],
+ ["sqlite3_result_error_code", undefined, "sqlite3_context*", "int"],
+ ["sqlite3_result_error_nomem", undefined, "sqlite3_context*"],
+ ["sqlite3_result_error_toobig", undefined, "sqlite3_context*"],
+ ["sqlite3_result_int", undefined, "sqlite3_context*", "int"],
+ ["sqlite3_result_null", undefined, "sqlite3_context*"],
+ ["sqlite3_result_pointer", undefined,
+ "sqlite3_context*", "*", "string:static", "*"],
+ ["sqlite3_result_subtype", undefined, "sqlite3_value*", "int"],
+ ["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"],
+ ["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"],
+ ["sqlite3_rollback_hook", "void*", [
+ "sqlite3*",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'sqlite3_rollback_hook',
+ signature: 'v(p)',
+ contextKey: (argv)=>argv[0/* sqlite3* */]
+ }),
+ '*'
+ ]],
+ ["sqlite3_set_authorizer", "int", [
+ "sqlite3*",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: "sqlite3_set_authorizer::xAuth",
+ signature: "i(pi"+"ssss)",
+ contextKey: (argv, argIndex)=>argv[0/*(sqlite3*)*/],
+ callProxy: (callback)=>{
+ return (pV, iCode, s0, s1, s2, s3)=>{
+ try{
+ s0 = s0 && wasm.cstrToJs(s0); s1 = s1 && wasm.cstrToJs(s1);
+ s2 = s2 && wasm.cstrToJs(s2); s3 = s3 && wasm.cstrToJs(s3);
+ return callback(pV, iCode, s0, s1, s2, s3) || 0;
+ }catch(e){
+ return e.resultCode || capi.SQLITE_ERROR;
+ }
+ }
+ }
+ }),
+ "*"/*pUserData*/
+ ]],
+ ["sqlite3_set_auxdata", undefined, [
+ "sqlite3_context*", "int", "*",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xDestroyAuxData',
+ signature: 'v(*)',
+ contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */]
+ })
+ ]],
+ ["sqlite3_shutdown", undefined],
+ ["sqlite3_sourceid", "string"],
+ ["sqlite3_sql", "string", "sqlite3_stmt*"],
+ ["sqlite3_status", "int", "int", "*", "*", "int"],
+ ["sqlite3_step", "int", "sqlite3_stmt*"],
+ ["sqlite3_stmt_isexplain", "int", ["sqlite3_stmt*"]],
+ ["sqlite3_stmt_readonly", "int", ["sqlite3_stmt*"]],
+ ["sqlite3_stmt_status", "int", "sqlite3_stmt*", "int", "int"],
+ ["sqlite3_strglob", "int", "string","string"],
+ ["sqlite3_stricmp", "int", "string", "string"],
+ ["sqlite3_strlike", "int", "string", "string","int"],
+ ["sqlite3_strnicmp", "int", "string", "string", "int"],
+ ["sqlite3_table_column_metadata", "int",
+ "sqlite3*", "string", "string", "string",
+ "**", "**", "*", "*", "*"],
+ ["sqlite3_total_changes", "int", "sqlite3*"],
+ ["sqlite3_trace_v2", "int", [
+ "sqlite3*", "int",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'sqlite3_trace_v2::callback',
+ signature: 'i(ippp)',
+ contextKey: (argv,argIndex)=>argv[0/* sqlite3* */]
+ }),
+ "*"
+ ]],
+ ["sqlite3_txn_state", "int", ["sqlite3*","string"]],
+ /* Note that sqlite3_uri_...() have very specific requirements for
+ their first C-string arguments, so we cannot perform any value
+ conversion on those. */
+ ["sqlite3_uri_boolean", "int", "sqlite3_filename", "string", "int"],
+ ["sqlite3_uri_key", "string", "sqlite3_filename", "int"],
+ ["sqlite3_uri_parameter", "string", "sqlite3_filename", "string"],
+ ["sqlite3_user_data","void*", "sqlite3_context*"],
+ ["sqlite3_value_blob", "*", "sqlite3_value*"],
+ ["sqlite3_value_bytes","int", "sqlite3_value*"],
+ ["sqlite3_value_double","f64", "sqlite3_value*"],
+ ["sqlite3_value_dup", "sqlite3_value*", "sqlite3_value*"],
+ ["sqlite3_value_free", undefined, "sqlite3_value*"],
+ ["sqlite3_value_frombind", "int", "sqlite3_value*"],
+ ["sqlite3_value_int","int", "sqlite3_value*"],
+ ["sqlite3_value_nochange", "int", "sqlite3_value*"],
+ ["sqlite3_value_numeric_type", "int", "sqlite3_value*"],
+ ["sqlite3_value_pointer", "*", "sqlite3_value*", "string:static"],
+ ["sqlite3_value_subtype", "int", "sqlite3_value*"],
+ ["sqlite3_value_text", "string", "sqlite3_value*"],
+ ["sqlite3_value_type", "int", "sqlite3_value*"],
+ ["sqlite3_vfs_find", "*", "string"],
+ ["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"],
+ ["sqlite3_vfs_unregister", "int", "sqlite3_vfs*"]
+ ]/*wasm.bindingSignatures*/;
+
+ if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){
+ /* ^^^ "the problem" is that this is an option feature and the
+ build-time function-export list does not currently take
+ optional features into account. */
+ wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]);
+ }
+
+ if(wasm.exports.sqlite3_activate_see instanceof Function){
+ wasm.bindingSignatures.push(
+ ["sqlite3_key", "int", "sqlite3*", "string", "int"],
+ ["sqlite3_key_v2","int","sqlite3*","string","*","int"],
+ ["sqlite3_rekey", "int", "sqlite3*", "string", "int"],
+ ["sqlite3_rekey_v2", "int", "sqlite3*", "string", "*", "int"],
+ ["sqlite3_activate_see", undefined, "string"]
+ );
+ }
+ /**
+ Functions which require BigInt (int64) support are separated from
+ the others because we need to conditionally bind them or apply
+ dummy impls, depending on the capabilities of the environment.
+
+ Note that not all of these functions directly require int64
+ but are only for use with APIs which require int64. For example,
+ the vtab-related functions.
+ */
+ wasm.bindingSignatures.int64 = [
+ ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]],
+ ["sqlite3_changes64","i64", ["sqlite3*"]],
+ ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]],
+ ["sqlite3_create_module", "int",
+ ["sqlite3*","string","sqlite3_module*","*"]],
+ ["sqlite3_create_module_v2", "int",
+ ["sqlite3*","string","sqlite3_module*","*","*"]],
+ ["sqlite3_declare_vtab", "int", ["sqlite3*", "string:flexible"]],
+ ["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"]
+ /* Careful! Short version: de/serialize() are problematic because they
+ might use a different allocator than the user for managing the
+ deserialized block. de/serialize() are ONLY safe to use with
+ sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */,
+ ["sqlite3_drop_modules", "int", ["sqlite3*", "**"]],
+ ["sqlite3_last_insert_rowid", "i64", ["sqlite3*"]],
+ ["sqlite3_malloc64", "*","i64"],
+ ["sqlite3_msize", "i64", "*"],
+ ["sqlite3_overload_function", "int", ["sqlite3*","string","int"]],
+ ["sqlite3_preupdate_blobwrite", "int", "sqlite3*"],
+ ["sqlite3_preupdate_count", "int", "sqlite3*"],
+ ["sqlite3_preupdate_depth", "int", "sqlite3*"],
+ ["sqlite3_preupdate_hook", "*", [
+ "sqlite3*",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'sqlite3_preupdate_hook',
+ signature: "v(ppippjj)",
+ contextKey: (argv)=>argv[0/* sqlite3* */],
+ callProxy: (callback)=>{
+ return (p,db,op,zDb,zTbl,iKey1,iKey2)=>{
+ callback(p, db, op, wasm.cstrToJs(zDb), wasm.cstrToJs(zTbl),
+ iKey1, iKey2);
+ };
+ }
+ }),
+ "*"
+ ]],
+ ["sqlite3_preupdate_new", "int", ["sqlite3*", "int", "**"]],
+ ["sqlite3_preupdate_old", "int", ["sqlite3*", "int", "**"]],
+ ["sqlite3_realloc64", "*","*", "i64"],
+ ["sqlite3_result_int64", undefined, "*", "i64"],
+ ["sqlite3_result_zeroblob64", "int", "*", "i64"],
+ ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"],
+ ["sqlite3_set_last_insert_rowid", undefined, ["sqlite3*", "i64"]],
+ ["sqlite3_status64", "int", "int", "*", "*", "int"],
+ ["sqlite3_total_changes64", "i64", ["sqlite3*"]],
+ ["sqlite3_update_hook", "*", [
+ "sqlite3*",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'sqlite3_update_hook',
+ signature: "v(iippj)",
+ contextKey: (argv)=>argv[0/* sqlite3* */],
+ callProxy: (callback)=>{
+ return (p,op,z0,z1,rowid)=>{
+ callback(p, op, wasm.cstrToJs(z0), wasm.cstrToJs(z1), rowid);
+ };
+ }
+ }),
+ "*"
+ ]],
+ ["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]],
+ ["sqlite3_value_int64","i64", "sqlite3_value*"],
+ ["sqlite3_vtab_collation","string","sqlite3_index_info*","int"],
+ ["sqlite3_vtab_distinct","int", "sqlite3_index_info*"],
+ ["sqlite3_vtab_in","int", "sqlite3_index_info*", "int", "int"],
+ ["sqlite3_vtab_in_first", "int", "sqlite3_value*", "**"],
+ ["sqlite3_vtab_in_next", "int", "sqlite3_value*", "**"],
+ /*["sqlite3_vtab_config" is variadic and requires a hand-written
+ proxy.] */
+ ["sqlite3_vtab_nochange","int", "sqlite3_context*"],
+ ["sqlite3_vtab_on_conflict","int", "sqlite3*"],
+ ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"]
+ ];
+
+ // Add session/changeset APIs...
+ if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){
+ /* ACHTUNG: 2022-12-23: the session/changeset API bindings are
+ COMPLETELY UNTESTED. */
+ /**
+ FuncPtrAdapter options for session-related callbacks with the
+ native signature "i(ps)". This proxy converts the 2nd argument
+ from a C string to a JS string before passing the arguments on
+ to the client-provided JS callback.
+ */
+ const __ipsProxy = {
+ signature: 'i(ps)',
+ callProxy:(callback)=>{
+ return (p,s)=>{
+ try{return callback(p, wasm.cstrToJs(s)) | 0}
+ catch(e){return e.resultCode || capi.SQLITE_ERROR}
+ }
+ }
+ };
+
+ wasm.bindingSignatures.int64.push(...[
+ ['sqlite3changegroup_add', 'int', ['sqlite3_changegroup*', 'int', 'void*']],
+ ['sqlite3changegroup_add_strm', 'int', [
+ 'sqlite3_changegroup*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*'
+ ]],
+ ['sqlite3changegroup_delete', undefined, ['sqlite3_changegroup*']],
+ ['sqlite3changegroup_new', 'int', ['**']],
+ ['sqlite3changegroup_output', 'int', ['sqlite3_changegroup*', 'int*', '**']],
+ ['sqlite3changegroup_output_strm', 'int', [
+ 'sqlite3_changegroup*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient'
+ }),
+ 'void*'
+ ]],
+ ['sqlite3changeset_apply', 'int', [
+ 'sqlite3*', 'int', 'void*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xFilter', bindScope: 'transient', ...__ipsProxy
+ }),
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
+ }),
+ 'void*'
+ ]],
+ ['sqlite3changeset_apply_strm', 'int', [
+ 'sqlite3*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xFilter', bindScope: 'transient', ...__ipsProxy
+ }),
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
+ }),
+ 'void*'
+ ]],
+ ['sqlite3changeset_apply_v2', 'int', [
+ 'sqlite3*', 'int', 'void*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xFilter', bindScope: 'transient', ...__ipsProxy
+ }),
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
+ }),
+ 'void*', '**', 'int*', 'int'
+
+ ]],
+ ['sqlite3changeset_apply_v2_strm', 'int', [
+ 'sqlite3*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xFilter', bindScope: 'transient', ...__ipsProxy
+ }),
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
+ }),
+ 'void*', '**', 'int*', 'int'
+ ]],
+ ['sqlite3changeset_concat', 'int', ['int','void*', 'int', 'void*', 'int*', '**']],
+ ['sqlite3changeset_concat_strm', 'int', [
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xInputA', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xInputB', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient'
+ }),
+ 'void*'
+ ]],
+ ['sqlite3changeset_conflict', 'int', ['sqlite3_changeset_iter*', 'int', '**']],
+ ['sqlite3changeset_finalize', 'int', ['sqlite3_changeset_iter*']],
+ ['sqlite3changeset_fk_conflicts', 'int', ['sqlite3_changeset_iter*', 'int*']],
+ ['sqlite3changeset_invert', 'int', ['int', 'void*', 'int*', '**']],
+ ['sqlite3changeset_invert_strm', 'int', [
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient'
+ }),
+ 'void*'
+ ]],
+ ['sqlite3changeset_new', 'int', ['sqlite3_changeset_iter*', 'int', '**']],
+ ['sqlite3changeset_next', 'int', ['sqlite3_changeset_iter*']],
+ ['sqlite3changeset_old', 'int', ['sqlite3_changeset_iter*', 'int', '**']],
+ ['sqlite3changeset_op', 'int', [
+ 'sqlite3_changeset_iter*', '**', 'int*', 'int*','int*'
+ ]],
+ ['sqlite3changeset_pk', 'int', ['sqlite3_changeset_iter*', '**', 'int*']],
+ ['sqlite3changeset_start', 'int', ['**', 'int', '*']],
+ ['sqlite3changeset_start_strm', 'int', [
+ '**',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*'
+ ]],
+ ['sqlite3changeset_start_v2', 'int', ['**', 'int', '*', 'int']],
+ ['sqlite3changeset_start_v2_strm', 'int', [
+ '**',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*', 'int'
+ ]],
+ ['sqlite3session_attach', 'int', ['sqlite3_session*', 'string']],
+ ['sqlite3session_changeset', 'int', ['sqlite3_session*', 'int*', '**']],
+ ['sqlite3session_changeset_size', 'i64', ['sqlite3_session*']],
+ ['sqlite3session_changeset_strm', 'int', [
+ 'sqlite3_session*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*'
+ ]],
+ ['sqlite3session_config', 'int', ['int', 'void*']],
+ ['sqlite3session_create', 'int', ['sqlite3*', 'string', '**']],
+ //sqlite3session_delete() is bound manually
+ ['sqlite3session_diff', 'int', ['sqlite3_session*', 'string', 'string', '**']],
+ ['sqlite3session_enable', 'int', ['sqlite3_session*', 'int']],
+ ['sqlite3session_indirect', 'int', ['sqlite3_session*', 'int']],
+ ['sqlite3session_isempty', 'int', ['sqlite3_session*']],
+ ['sqlite3session_memory_used', 'i64', ['sqlite3_session*']],
+ ['sqlite3session_object_config', 'int', ['sqlite3_session*', 'int', 'void*']],
+ ['sqlite3session_patchset', 'int', ['sqlite3_session*', '*', '**']],
+ ['sqlite3session_patchset_strm', 'int', [
+ 'sqlite3_session*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient'
+ }),
+ 'void*'
+ ]],
+ ['sqlite3session_table_filter', undefined, [
+ 'sqlite3_session*',
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xFilter', ...__ipsProxy,
+ contextKey: (argv,argIndex)=>argv[0/* (sqlite3_session*) */]
+ }),
+ '*'
+ ]]
+ ]);
+ }/*session/changeset APIs*/
+
+ /**
+ Functions which are intended solely for API-internal use by the
+ WASM components, not client code. These get installed into
+ sqlite3.wasm. Some of them get exposed to clients via variants
+ named sqlite3_js_...().
+ */
+ wasm.bindingSignatures.wasm = [
+ ["sqlite3_wasm_db_reset", "int", "sqlite3*"],
+ ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
+ ["sqlite3_wasm_vfs_create_file", "int",
+ "sqlite3_vfs*","string","*", "int"],
+ ["sqlite3_wasm_posix_create_file", "int", "string","*", "int"],
+ ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"]
+ ];
+
+ /**
+ Install JS<->C struct bindings for the non-opaque struct types we
+ need... */
+ sqlite3.StructBinder = globalThis.Jaccwabyt({
+ heap: 0 ? wasm.memory : wasm.heap8u,
+ alloc: wasm.alloc,
+ dealloc: wasm.dealloc,
+ bigIntEnabled: wasm.bigIntEnabled,
+ memberPrefix: /* Never change this: this prefix is baked into any
+ amount of code and client-facing docs. */ '$'
+ });
+ delete globalThis.Jaccwabyt;
+
+ {// wasm.xWrap() bindings...
+
+ /* Convert Arrays and certain TypedArrays to strings for
+ 'string:flexible'-type arguments */
+ const __xString = wasm.xWrap.argAdapter('string');
+ wasm.xWrap.argAdapter(
+ 'string:flexible', (v)=>__xString(util.flexibleString(v))
+ );
+
+ /**
+ The 'string:static' argument adapter treats its argument as
+ either...
+
+ - WASM pointer: assumed to be a long-lived C-string which gets
+ returned as-is.
+
+ - Anything else: gets coerced to a JS string for use as a map
+ key. If a matching entry is found (as described next), it is
+ returned, else wasm.allocCString() is used to create a a new
+ string, map its pointer to (''+v) for the remainder of the
+ application's life, and returns that pointer value for this
+ call and all future calls which are passed a
+ string-equivalent argument.
+
+ Use case: sqlite3_bind_pointer() and sqlite3_result_pointer()
+ call for "a static string and preferably a string
+ literal". This converter is used to ensure that the string
+ value seen by those functions is long-lived and behaves as they
+ need it to.
+ */
+ wasm.xWrap.argAdapter(
+ 'string:static',
+ function(v){
+ if(wasm.isPtr(v)) return v;
+ v = ''+v;
+ let rc = this[v];
+ return rc || (this[v] = wasm.allocCString(v));
+ }.bind(Object.create(null))
+ );
+
+ /**
+ Add some descriptive xWrap() aliases for '*' intended to (A)
+ initially improve readability/correctness of
+ wasm.bindingSignatures and (B) provide automatic conversion
+ from higher-level representations, e.g. capi.sqlite3_vfs to
+ `sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
+ */
+ const __xArgPtr = wasm.xWrap.argAdapter('*');
+ const nilType = function(){}/*a class no value can ever be an instance of*/;
+ wasm.xWrap.argAdapter('sqlite3_filename', __xArgPtr)
+ ('sqlite3_context*', __xArgPtr)
+ ('sqlite3_value*', __xArgPtr)
+ ('void*', __xArgPtr)
+ ('sqlite3_changegroup*', __xArgPtr)
+ ('sqlite3_changeset_iter*', __xArgPtr)
+ //('sqlite3_rebaser*', __xArgPtr)
+ ('sqlite3_session*', __xArgPtr)
+ ('sqlite3_stmt*', (v)=>
+ __xArgPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType))
+ ? v.pointer : v))
+ ('sqlite3*', (v)=>
+ __xArgPtr((v instanceof (sqlite3?.oo1?.DB || nilType))
+ ? v.pointer : v))
+ ('sqlite3_index_info*', (v)=>
+ __xArgPtr((v instanceof (capi.sqlite3_index_info || nilType))
+ ? v.pointer : v))
+ ('sqlite3_module*', (v)=>
+ __xArgPtr((v instanceof (capi.sqlite3_module || nilType))
+ ? v.pointer : v))
+ /**
+ `sqlite3_vfs*`:
+
+ - v is-a string: use the result of sqlite3_vfs_find(v) but
+ throw if it returns 0.
+ - v is-a capi.sqlite3_vfs: use v.pointer.
+ - Else return the same as the `'*'` argument conversion.
+ */
+ ('sqlite3_vfs*', (v)=>{
+ if('string'===typeof v){
+ /* A NULL sqlite3_vfs pointer will be treated as the default
+ VFS in many contexts. We specifically do not want that
+ behavior here. */
+ return capi.sqlite3_vfs_find(v)
+ || sqlite3.SQLite3Error.toss(
+ capi.SQLITE_NOTFOUND,
+ "Unknown sqlite3_vfs name:", v
+ );
+ }
+ return __xArgPtr((v instanceof (capi.sqlite3_vfs || nilType))
+ ? v.pointer : v);
+ });
+
+ const __xRcPtr = wasm.xWrap.resultAdapter('*');
+ wasm.xWrap.resultAdapter('sqlite3*', __xRcPtr)
+ ('sqlite3_context*', __xRcPtr)
+ ('sqlite3_stmt*', __xRcPtr)
+ ('sqlite3_value*', __xRcPtr)
+ ('sqlite3_vfs*', __xRcPtr)
+ ('void*', __xRcPtr);
+
+ /**
+ Populate api object with sqlite3_...() by binding the "raw" wasm
+ exports into type-converting proxies using wasm.xWrap().
+ */
+ if(0 === wasm.exports.sqlite3_step.length){
+ /* This environment wraps exports in nullary functions, which means
+ we must disable the arg-count validation we otherwise perform
+ on the wrappers. */
+ wasm.xWrap.doArgcCheck = false;
+ sqlite3.config.warn(
+ "Disabling sqlite3.wasm.xWrap.doArgcCheck due to environmental quirks."
+ );
+ }
+ for(const e of wasm.bindingSignatures){
+ capi[e[0]] = wasm.xWrap.apply(null, e);
+ }
+ for(const e of wasm.bindingSignatures.wasm){
+ wasm[e[0]] = wasm.xWrap.apply(null, e);
+ }
+
+ /* For C API functions which cannot work properly unless
+ wasm.bigIntEnabled is true, install a bogus impl which throws
+ if called when bigIntEnabled is false. The alternative would be
+ to elide these functions altogether, which seems likely to
+ cause more confusion. */
+ const fI64Disabled = function(fname){
+ return ()=>toss(fname+"() is unavailable due to lack",
+ "of BigInt support in this build.");
+ };
+ for(const e of wasm.bindingSignatures.int64){
+ capi[e[0]] = wasm.bigIntEnabled
+ ? wasm.xWrap.apply(null, e)
+ : fI64Disabled(e[0]);
+ }
+
+ /* There's no need to expose bindingSignatures to clients,
+ implicitly making it part of the public interface. */
+ delete wasm.bindingSignatures;
+
+ if(wasm.exports.sqlite3_wasm_db_error){
+ const __db_err = wasm.xWrap(
+ 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string'
+ );
+ /**
+ Sets the given db's error state. Accepts:
+
+ - (sqlite3*, int code, string msg)
+ - (sqlite3*, Error e [,string msg = ''+e])
+
+ If passed a WasmAllocError, the message is ignored and the
+ result code is SQLITE_NOMEM. If passed any other Error type,
+ the result code defaults to SQLITE_ERROR unless the Error
+ object has a resultCode property, in which case that is used
+ (e.g. SQLite3Error has that). If passed a non-WasmAllocError
+ exception, the message string defaults to theError.message.
+
+ Returns the resulting code. Pass (pDb,0,0) to clear the error
+ state.
+ */
+ util.sqlite3_wasm_db_error = function(pDb, resultCode, message){
+ if(resultCode instanceof sqlite3.WasmAllocError){
+ resultCode = capi.SQLITE_NOMEM;
+ message = 0 /*avoid allocating message string*/;
+ }else if(resultCode instanceof Error){
+ message = message || ''+resultCode;
+ resultCode = (resultCode.resultCode || capi.SQLITE_ERROR);
+ }
+ return pDb ? __db_err(pDb, resultCode, message) : resultCode;
+ };
+ }else{
+ util.sqlite3_wasm_db_error = function(pDb,errCode,msg){
+ console.warn("sqlite3_wasm_db_error() is not exported.",arguments);
+ return errCode;
+ };
+ }
+ }/*xWrap() bindings*/
+
+ {/* Import C-level constants and structs... */
+ const cJson = wasm.xCall('sqlite3_wasm_enum_json');
+ if(!cJson){
+ toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
+ "static buffer size!");
+ }
+ //console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
+ wasm.ctype = JSON.parse(wasm.cstrToJs(cJson));
+ // Groups of SQLITE_xyz macros...
+ const defineGroups = ['access', 'authorizer',
+ 'blobFinalizers', 'changeset',
+ 'config', 'dataTypes',
+ 'dbConfig', 'dbStatus',
+ 'encodings', 'fcntl', 'flock', 'ioCap',
+ 'limits', 'openFlags',
+ 'prepareFlags', 'resultCodes',
+ 'sqlite3Status',
+ 'stmtStatus', 'syncFlags',
+ 'trace', 'txnState', 'udfFlags',
+ 'version' ];
+ if(wasm.bigIntEnabled){
+ defineGroups.push('serialize', 'session', 'vtab');
+ }
+ for(const t of defineGroups){
+ for(const e of Object.entries(wasm.ctype[t])){
+ // ^^^ [k,v] there triggers a buggy code transformation via
+ // one of the Emscripten-driven optimizers.
+ capi[e[0]] = e[1];
+ }
+ }
+ if(!wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){
+ toss("Internal error: cannot resolve exported function",
+ "entry SQLITE_WASM_DEALLOC (=="+capi.SQLITE_WASM_DEALLOC+").");
+ }
+ const __rcMap = Object.create(null);
+ for(const t of ['resultCodes']){
+ for(const e of Object.entries(wasm.ctype[t])){
+ __rcMap[e[1]] = e[0];
+ }
+ }
+ /**
+ For the given integer, returns the SQLITE_xxx result code as a
+ string, or undefined if no such mapping is found.
+ */
+ capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc];
+ /* Bind all registered C-side structs... */
+ const notThese = Object.assign(Object.create(null),{
+ // For each struct to NOT register, map its name to true:
+ WasmTestStruct: true,
+ /* We unregister the kvvfs VFS from Worker threads below. */
+ sqlite3_kvvfs_methods: !util.isUIThread(),
+ /* sqlite3_index_info and friends require int64: */
+ sqlite3_index_info: !wasm.bigIntEnabled,
+ sqlite3_index_constraint: !wasm.bigIntEnabled,
+ sqlite3_index_orderby: !wasm.bigIntEnabled,
+ sqlite3_index_constraint_usage: !wasm.bigIntEnabled
+ });
+ for(const s of wasm.ctype.structs){
+ if(!notThese[s.name]){
+ capi[s.name] = sqlite3.StructBinder(s);
+ }
+ }
+ if(capi.sqlite3_index_info){
+ /* Move these inner structs into sqlite3_index_info. Binding
+ ** them to WASM requires that we create global-scope structs to
+ ** model them with, but those are no longer needed after we've
+ ** passed them to StructBinder. */
+ for(const k of ['sqlite3_index_constraint',
+ 'sqlite3_index_orderby',
+ 'sqlite3_index_constraint_usage']){
+ capi.sqlite3_index_info[k] = capi[k];
+ delete capi[k];
+ }
+ capi.sqlite3_vtab_config = wasm.xWrap(
+ 'sqlite3_wasm_vtab_config','int',[
+ 'sqlite3*', 'int', 'int']
+ );
+ }/* end vtab-related setup */
+ }/*end C constant and struct imports*/
+
+ /**
+ Internal helper to assist in validating call argument counts in
+ the hand-written sqlite3_xyz() wrappers. We do this only for
+ consistency with non-special-case wrappings.
+ */
+ const __dbArgcMismatch = (pDb,f,n)=>{
+ return util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE,
+ f+"() requires "+n+" argument"+
+ (1===n?"":'s')+".");
+ };
+
+ /** Code duplication reducer for functions which take an encoding
+ argument and require SQLITE_UTF8. Sets the db error code to
+ SQLITE_FORMAT and returns that code. */
+ const __errEncoding = (pDb)=>{
+ return util.sqlite3_wasm_db_error(
+ pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding."
+ );
+ };
+
+ /**
+ __dbCleanupMap is infrastructure for recording registration of
+ UDFs and collations so that sqlite3_close_v2() can clean up any
+ automated JS-to-WASM function conversions installed by those.
+ */
+ const __argPDb = (pDb)=>wasm.xWrap.argAdapter('sqlite3*')(pDb);
+ const __argStr = (str)=>wasm.isPtr(str) ? wasm.cstrToJs(str) : str;
+ const __dbCleanupMap = function(
+ pDb, mode/*0=remove, >0=create if needed, <0=do not create if missing*/
+ ){
+ pDb = __argPDb(pDb);
+ let m = this.dbMap.get(pDb);
+ if(!mode){
+ this.dbMap.delete(pDb);
+ return m;
+ }else if(!m && mode>0){
+ this.dbMap.set(pDb, (m = Object.create(null)));
+ }
+ return m;
+ }.bind(Object.assign(Object.create(null),{
+ dbMap: new Map
+ }));
+
+ __dbCleanupMap.addCollation = function(pDb, name){
+ const m = __dbCleanupMap(pDb, 1);
+ if(!m.collation) m.collation = new Set;
+ m.collation.add(__argStr(name).toLowerCase());
+ };
+
+ __dbCleanupMap._addUDF = function(pDb, name, arity, map){
+ /* Map UDF name to a Set of arity values */
+ name = __argStr(name).toLowerCase();
+ let u = map.get(name);
+ if(!u) map.set(name, (u = new Set));
+ u.add((arity<0) ? -1 : arity);
+ };
+
+ __dbCleanupMap.addFunction = function(pDb, name, arity){
+ const m = __dbCleanupMap(pDb, 1);
+ if(!m.udf) m.udf = new Map;
+ this._addUDF(pDb, name, arity, m.udf);
+ };
+
+ __dbCleanupMap.addWindowFunc = function(pDb, name, arity){
+ const m = __dbCleanupMap(pDb, 1);
+ if(!m.wudf) m.wudf = new Map;
+ this._addUDF(pDb, name, arity, m.wudf);
+ };
+
+ /**
+ Intended to be called _only_ from sqlite3_close_v2(),
+ passed its non-0 db argument.
+
+ This function frees up certain automatically-installed WASM
+ function bindings which were installed on behalf of the given db,
+ as those may otherwise leak.
+
+ Notable caveat: this is only ever run via
+ sqlite3.capi.sqlite3_close_v2(). If a client, for whatever
+ reason, uses sqlite3.wasm.exports.sqlite3_close_v2() (the
+ function directly exported from WASM), this cleanup will not
+ happen.
+
+ This is not a silver bullet for avoiding automation-related
+ leaks but represents "an honest effort."
+
+ The issue being addressed here is covered at:
+
+ https://sqlite.org/wasm/doc/trunk/api-c-style.md#convert-func-ptr
+ */
+ __dbCleanupMap.cleanup = function(pDb){
+ pDb = __argPDb(pDb);
+ //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
+ /**
+ Installing NULL functions in the C API will remove those
+ bindings. The FuncPtrAdapter which sits between us and the C
+ API will also treat that as an opportunity to
+ wasm.uninstallFunction() any WASM function bindings it has
+ installed for pDb.
+ */
+ const closeArgs = [pDb];
+ for(const name of [
+ 'sqlite3_busy_handler',
+ 'sqlite3_commit_hook',
+ 'sqlite3_preupdate_hook',
+ 'sqlite3_progress_handler',
+ 'sqlite3_rollback_hook',
+ 'sqlite3_set_authorizer',
+ 'sqlite3_trace_v2',
+ 'sqlite3_update_hook'
+ ]) {
+ const x = wasm.exports[name];
+ closeArgs.length = x.length/*==argument count*/
+ /* recall that undefined entries translate to 0 when passed to
+ WASM. */;
+ try{ capi[name](...closeArgs) }
+ catch(e){
+ console.warn("close-time call of",name+"(",closeArgs,") threw:",e);
+ }
+ }
+ const m = __dbCleanupMap(pDb, 0);
+ if(!m) return;
+ if(m.collation){
+ for(const name of m.collation){
+ try{
+ capi.sqlite3_create_collation_v2(
+ pDb, name, capi.SQLITE_UTF8, 0, 0, 0
+ );
+ }catch(e){
+ /*ignored*/
+ }
+ }
+ delete m.collation;
+ }
+ let i;
+ for(i = 0; i < 2; ++i){ /* Clean up UDFs... */
+ const fmap = i ? m.wudf : m.udf;
+ if(!fmap) continue;
+ const func = i
+ ? capi.sqlite3_create_window_function
+ : capi.sqlite3_create_function_v2;
+ for(const e of fmap){
+ const name = e[0], arities = e[1];
+ const fargs = [pDb, name, 0/*arity*/, capi.SQLITE_UTF8, 0, 0, 0, 0, 0];
+ if(i) fargs.push(0);
+ for(const arity of arities){
+ try{ fargs[2] = arity; func.apply(null, fargs); }
+ catch(e){/*ignored*/}
+ }
+ arities.clear();
+ }
+ fmap.clear();
+ }
+ delete m.udf;
+ delete m.wudf;
+ }/*__dbCleanupMap.cleanup()*/;
+
+ {/* Binding of sqlite3_close_v2() */
+ const __sqlite3CloseV2 = wasm.xWrap("sqlite3_close_v2", "int", "sqlite3*");
+ capi.sqlite3_close_v2 = function(pDb){
+ if(1!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_close_v2', 1);
+ if(pDb){
+ try{__dbCleanupMap.cleanup(pDb)} catch(e){/*ignored*/}
+ }
+ return __sqlite3CloseV2(pDb);
+ };
+ }/*sqlite3_close_v2()*/
+
+ if(capi.sqlite3session_table_filter){
+ const __sqlite3SessionDelete = wasm.xWrap(
+ 'sqlite3session_delete', undefined, ['sqlite3_session*']
+ );
+ capi.sqlite3session_delete = function(pSession){
+ if(1!==arguments.length){
+ return __dbArgcMismatch(pDb, 'sqlite3session_delete', 1);
+ /* Yes, we're returning a value from a void function. That seems
+ like the lesser evil compared to not maintaining arg-count
+ consistency as we do with other similar bindings. */
+ }
+ else if(pSession){
+ //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
+ capi.sqlite3session_table_filter(pSession, 0, 0);
+ }
+ __sqlite3SessionDelete(pSession);
+ };
+ }
+
+ {/* Bindings for sqlite3_create_collation[_v2]() */
+ // contextKey() impl for wasm.xWrap.FuncPtrAdapter
+ const contextKey = (argv,argIndex)=>{
+ return 'argv['+argIndex+']:'+argv[0/* sqlite3* */]+
+ ':'+wasm.cstrToJs(argv[1/* collation name */]).toLowerCase()
+ };
+ const __sqlite3CreateCollationV2 = wasm.xWrap(
+ 'sqlite3_create_collation_v2', 'int', [
+ 'sqlite3*', 'string', 'int', '*',
+ new wasm.xWrap.FuncPtrAdapter({
+ /* int(*xCompare)(void*,int,const void*,int,const void*) */
+ name: 'xCompare', signature: 'i(pipip)', contextKey
+ }),
+ new wasm.xWrap.FuncPtrAdapter({
+ /* void(*xDestroy(void*) */
+ name: 'xDestroy', signature: 'v(p)', contextKey
+ })
+ ]
+ );
+
+ /**
+ Works exactly like C's sqlite3_create_collation_v2() except that:
+
+ 1) It returns capi.SQLITE_FORMAT if the 3rd argument contains
+ any encoding-related value other than capi.SQLITE_UTF8. No
+ other encodings are supported. As a special case, if the
+ bottom 4 bits of that argument are 0, SQLITE_UTF8 is
+ assumed.
+
+ 2) It accepts JS functions for its function-pointer arguments,
+ for which it will install WASM-bound proxies. The bindings
+ are "permanent," in that they will stay in the WASM environment
+ until it shuts down unless the client calls this again with the
+ same collation name and a value of 0 or null for the
+ the function pointer(s).
+
+ For consistency with the C API, it requires the same number of
+ arguments. It returns capi.SQLITE_MISUSE if passed any other
+ argument count.
+
+ Returns 0 on success, non-0 on error, in which case the error
+ state of pDb (of type `sqlite3*` or argument-convertible to it)
+ may contain more information.
+ */
+ capi.sqlite3_create_collation_v2 = function(pDb,zName,eTextRep,pArg,xCompare,xDestroy){
+ if(6!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_create_collation_v2', 6);
+ else if( 0 === (eTextRep & 0xf) ){
+ eTextRep |= capi.SQLITE_UTF8;
+ }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){
+ return __errEncoding(pDb);
+ }
+ try{
+ const rc = __sqlite3CreateCollationV2(pDb, zName, eTextRep, pArg, xCompare, xDestroy);
+ if(0===rc && xCompare instanceof Function){
+ __dbCleanupMap.addCollation(pDb, zName);
+ }
+ return rc;
+ }catch(e){
+ return util.sqlite3_wasm_db_error(pDb, e);
+ }
+ };
+
+ capi.sqlite3_create_collation = (pDb,zName,eTextRep,pArg,xCompare)=>{
+ return (5===arguments.length)
+ ? capi.sqlite3_create_collation_v2(pDb,zName,eTextRep,pArg,xCompare,0)
+ : __dbArgcMismatch(pDb, 'sqlite3_create_collation', 5);
+ };
+
+ }/*sqlite3_create_collation() and friends*/
+
+ {/* Special-case handling of sqlite3_create_function_v2()
+ and sqlite3_create_window_function(). */
+ /** FuncPtrAdapter for contextKey() for sqlite3_create_function()
+ and friends. */
+ const contextKey = function(argv,argIndex){
+ return (
+ argv[0/* sqlite3* */]
+ +':'+(argv[2/*number of UDF args*/] < 0 ? -1 : argv[2])
+ +':'+argIndex/*distinct for each xAbc callback type*/
+ +':'+wasm.cstrToJs(argv[1]).toLowerCase()
+ )
+ };
+
+ /**
+ JS proxies for the various sqlite3_create[_window]_function()
+ callbacks, structured in a form usable by wasm.xWrap.FuncPtrAdapter.
+ */
+ const __cfProxy = Object.assign(Object.create(null), {
+ xInverseAndStep: {
+ signature:'v(pip)', contextKey,
+ callProxy: (callback)=>{
+ return (pCtx, argc, pArgv)=>{
+ try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) }
+ catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
+ };
+ }
+ },
+ xFinalAndValue: {
+ signature:'v(p)', contextKey,
+ callProxy: (callback)=>{
+ return (pCtx)=>{
+ try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) }
+ catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
+ };
+ }
+ },
+ xFunc: {
+ signature:'v(pip)', contextKey,
+ callProxy: (callback)=>{
+ return (pCtx, argc, pArgv)=>{
+ try{
+ capi.sqlite3_result_js(
+ pCtx,
+ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv))
+ );
+ }catch(e){
+ //console.error('xFunc() caught:',e);
+ capi.sqlite3_result_error_js(pCtx, e);
+ }
+ };
+ }
+ },
+ xDestroy: {
+ signature:'v(p)', contextKey,
+ //Arguable: a well-behaved destructor doesn't require a proxy.
+ callProxy: (callback)=>{
+ return (pVoid)=>{
+ try{ callback(pVoid) }
+ catch(e){ console.error("UDF xDestroy method threw:",e) }
+ };
+ }
+ }
+ })/*__cfProxy*/;
+
+ const __sqlite3CreateFunction = wasm.xWrap(
+ "sqlite3_create_function_v2", "int", [
+ "sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
+ "int"/*eTextRep*/, "*"/*pApp*/,
+ new wasm.xWrap.FuncPtrAdapter({name: 'xFunc', ...__cfProxy.xFunc}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy})
+ ]
+ );
+
+ const __sqlite3CreateWindowFunction = wasm.xWrap(
+ "sqlite3_create_window_function", "int", [
+ "sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
+ "int"/*eTextRep*/, "*"/*pApp*/,
+ new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xValue', ...__cfProxy.xFinalAndValue}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xInverse', ...__cfProxy.xInverseAndStep}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy})
+ ]
+ );
+
+ /* Documented in the api object's initializer. */
+ capi.sqlite3_create_function_v2 = function f(
+ pDb, funcName, nArg, eTextRep, pApp,
+ xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**)
+ xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**)
+ xFinal, //void (*xFinal)(sqlite3_context*)
+ xDestroy //void (*xDestroy)(void*)
+ ){
+ if( f.length!==arguments.length ){
+ return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length);
+ }else if( 0 === (eTextRep & 0xf) ){
+ eTextRep |= capi.SQLITE_UTF8;
+ }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){
+ return __errEncoding(pDb);
+ }
+ try{
+ const rc = __sqlite3CreateFunction(pDb, funcName, nArg, eTextRep,
+ pApp, xFunc, xStep, xFinal, xDestroy);
+ if(0===rc && (xFunc instanceof Function
+ || xStep instanceof Function
+ || xFinal instanceof Function
+ || xDestroy instanceof Function)){
+ __dbCleanupMap.addFunction(pDb, funcName, nArg);
+ }
+ return rc;
+ }catch(e){
+ console.error("sqlite3_create_function_v2() setup threw:",e);
+ return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
+ }
+ };
+
+ /* Documented in the api object's initializer. */
+ capi.sqlite3_create_function = function f(
+ pDb, funcName, nArg, eTextRep, pApp,
+ xFunc, xStep, xFinal
+ ){
+ return (f.length===arguments.length)
+ ? capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep,
+ pApp, xFunc, xStep, xFinal, 0)
+ : __dbArgcMismatch(pDb,"sqlite3_create_function",f.length);
+ };
+
+ /* Documented in the api object's initializer. */
+ capi.sqlite3_create_window_function = function f(
+ pDb, funcName, nArg, eTextRep, pApp,
+ xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**)
+ xFinal, //void (*xFinal)(sqlite3_context*)
+ xValue, //void (*xValue)(sqlite3_context*)
+ xInverse,//void (*xInverse)(sqlite3_context*,int,sqlite3_value**)
+ xDestroy //void (*xDestroy)(void*)
+ ){
+ if( f.length!==arguments.length ){
+ return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length);
+ }else if( 0 === (eTextRep & 0xf) ){
+ eTextRep |= capi.SQLITE_UTF8;
+ }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){
+ return __errEncoding(pDb);
+ }
+ try{
+ const rc = __sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep,
+ pApp, xStep, xFinal, xValue,
+ xInverse, xDestroy);
+ if(0===rc && (xStep instanceof Function
+ || xFinal instanceof Function
+ || xValue instanceof Function
+ || xInverse instanceof Function
+ || xDestroy instanceof Function)){
+ __dbCleanupMap.addWindowFunc(pDb, funcName, nArg);
+ }
+ return rc;
+ }catch(e){
+ console.error("sqlite3_create_window_function() setup threw:",e);
+ return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
+ }
+ };
+ /**
+ A _deprecated_ alias for capi.sqlite3_result_js() which
+ predates the addition of that function in the public API.
+ */
+ capi.sqlite3_create_function_v2.udfSetResult =
+ capi.sqlite3_create_function.udfSetResult =
+ capi.sqlite3_create_window_function.udfSetResult = capi.sqlite3_result_js;
+
+ /**
+ A _deprecated_ alias for capi.sqlite3_values_to_js() which
+ predates the addition of that function in the public API.
+ */
+ capi.sqlite3_create_function_v2.udfConvertArgs =
+ capi.sqlite3_create_function.udfConvertArgs =
+ capi.sqlite3_create_window_function.udfConvertArgs = capi.sqlite3_values_to_js;
+
+ /**
+ A _deprecated_ alias for capi.sqlite3_result_error_js() which
+ predates the addition of that function in the public API.
+ */
+ capi.sqlite3_create_function_v2.udfSetError =
+ capi.sqlite3_create_function.udfSetError =
+ capi.sqlite3_create_window_function.udfSetError = capi.sqlite3_result_error_js;
+
+ }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/;
+
+ {/* Special-case handling of sqlite3_prepare_v2() and
+ sqlite3_prepare_v3() */
+
+ /**
+ Helper for string:flexible conversions which require a
+ byte-length counterpart argument. Passed a value and its
+ ostensible length, this function returns [V,N], where V is
+ either v or a transformed copy of v and N is either n, -1, or
+ the byte length of v (if it's a byte array or ArrayBuffer).
+ */
+ const __flexiString = (v,n)=>{
+ if('string'===typeof v){
+ n = -1;
+ }else if(util.isSQLableTypedArray(v)){
+ n = v.byteLength;
+ v = util.typedArrayToString(
+ (v instanceof ArrayBuffer) ? new Uint8Array(v) : v
+ );
+ }else if(Array.isArray(v)){
+ v = v.join("");
+ n = -1;
+ }
+ return [v, n];
+ };
+
+ /**
+ Scope-local holder of the two impls of sqlite3_prepare_v2/v3().
+ */
+ const __prepare = {
+ /**
+ This binding expects a JS string as its 2nd argument and
+ null as its final argument. In order to compile multiple
+ statements from a single string, the "full" impl (see
+ below) must be used.
+ */
+ basic: wasm.xWrap('sqlite3_prepare_v3',
+ "int", ["sqlite3*", "string",
+ "int"/*ignored for this impl!*/,
+ "int", "**",
+ "**"/*MUST be 0 or null or undefined!*/]),
+ /**
+ Impl which requires that the 2nd argument be a pointer
+ to the SQL string, instead of being converted to a
+ string. This variant is necessary for cases where we
+ require a non-NULL value for the final argument
+ (exec()'ing multiple statements from one input
+ string). For simpler cases, where only the first
+ statement in the SQL string is required, the wrapper
+ named sqlite3_prepare_v2() is sufficient and easier to
+ use because it doesn't require dealing with pointers.
+ */
+ full: wasm.xWrap('sqlite3_prepare_v3',
+ "int", ["sqlite3*", "*", "int", "int",
+ "**", "**"])
+ };
+
+ /* Documented in the capi object's initializer. */
+ capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
+ if(f.length!==arguments.length){
+ return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length);
+ }
+ const [xSql, xSqlLen] = __flexiString(sql, sqlLen);
+ switch(typeof xSql){
+ case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null);
+ case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail);
+ default:
+ return util.sqlite3_wasm_db_error(
+ pDb, capi.SQLITE_MISUSE,
+ "Invalid SQL argument type for sqlite3_prepare_v2/v3()."
+ );
+ }
+ };
+
+ /* Documented in the capi object's initializer. */
+ capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){
+ return (f.length===arguments.length)
+ ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail)
+ : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length);
+ };
+
+ }/*sqlite3_prepare_v2/v3()*/
+
+ {/*sqlite3_bind_text/blob()*/
+ const __bindText = wasm.xWrap("sqlite3_bind_text", "int", [
+ "sqlite3_stmt*", "int", "string", "int", "*"
+ ]);
+ const __bindBlob = wasm.xWrap("sqlite3_bind_blob", "int", [
+ "sqlite3_stmt*", "int", "*", "int", "*"
+ ]);
+
+ /** Documented in the capi object's initializer. */
+ capi.sqlite3_bind_text = function f(pStmt, iCol, text, nText, xDestroy){
+ if(f.length!==arguments.length){
+ return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt),
+ "sqlite3_bind_text", f.length);
+ }else if(wasm.isPtr(text) || null===text){
+ return __bindText(pStmt, iCol, text, nText, xDestroy);
+ }else if(text instanceof ArrayBuffer){
+ text = new Uint8Array(text);
+ }else if(Array.isArray(pMem)){
+ text = pMem.join('');
+ }
+ let p, n;
+ try{
+ if(util.isSQLableTypedArray(text)){
+ p = wasm.allocFromTypedArray(text);
+ n = text.byteLength;
+ }else if('string'===typeof text){
+ [p, n] = wasm.allocCString(text);
+ }else{
+ return util.sqlite3_wasm_db_error(
+ capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
+ "Invalid 3rd argument type for sqlite3_bind_text()."
+ );
+ }
+ return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
+ }catch(e){
+ wasm.dealloc(p);
+ return util.sqlite3_wasm_db_error(
+ capi.sqlite3_db_handle(pStmt), e
+ );
+ }
+ }/*sqlite3_bind_text()*/;
+
+ /** Documented in the capi object's initializer. */
+ capi.sqlite3_bind_blob = function f(pStmt, iCol, pMem, nMem, xDestroy){
+ if(f.length!==arguments.length){
+ return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt),
+ "sqlite3_bind_blob", f.length);
+ }else if(wasm.isPtr(pMem) || null===pMem){
+ return __bindBlob(pStmt, iCol, pMem, nMem, xDestroy);
+ }else if(pMem instanceof ArrayBuffer){
+ pMem = new Uint8Array(pMem);
+ }else if(Array.isArray(pMem)){
+ pMem = pMem.join('');
+ }
+ let p, n;
+ try{
+ if(util.isBindableTypedArray(pMem)){
+ p = wasm.allocFromTypedArray(pMem);
+ n = nMem>=0 ? nMem : pMem.byteLength;
+ }else if('string'===typeof pMem){
+ [p, n] = wasm.allocCString(pMem);
+ }else{
+ return util.sqlite3_wasm_db_error(
+ capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
+ "Invalid 3rd argument type for sqlite3_bind_blob()."
+ );
+ }
+ return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
+ }catch(e){
+ wasm.dealloc(p);
+ return util.sqlite3_wasm_db_error(
+ capi.sqlite3_db_handle(pStmt), e
+ );
+ }
+ }/*sqlite3_bind_blob()*/;
+
+ }/*sqlite3_bind_text/blob()*/
+
+ {/* sqlite3_config() */
+ /**
+ Wraps a small subset of the C API's sqlite3_config() options.
+ Unsupported options trigger the return of capi.SQLITE_NOTFOUND.
+ Passing fewer than 2 arguments triggers return of
+ capi.SQLITE_MISUSE.
+ */
+ capi.sqlite3_config = function(op, ...args){
+ if(arguments.length<2) return capi.SQLITE_MISUSE;
+ switch(op){
+ case capi.SQLITE_CONFIG_COVERING_INDEX_SCAN: // 20 /* int */
+ case capi.SQLITE_CONFIG_MEMSTATUS:// 9 /* boolean */
+ case capi.SQLITE_CONFIG_SMALL_MALLOC: // 27 /* boolean */
+ case capi.SQLITE_CONFIG_SORTERREF_SIZE: // 28 /* int nByte */
+ case capi.SQLITE_CONFIG_STMTJRNL_SPILL: // 26 /* int nByte */
+ case capi.SQLITE_CONFIG_URI:// 17 /* int */
+ return wasm.exports.sqlite3_wasm_config_i(op, args[0]);
+ case capi.SQLITE_CONFIG_LOOKASIDE: // 13 /* int int */
+ return wasm.exports.sqlite3_wasm_config_ii(op, args[0], args[1]);
+ case capi.SQLITE_CONFIG_MEMDB_MAXSIZE: // 29 /* sqlite3_int64 */
+ return wasm.exports.sqlite3_wasm_config_j(op, args[0]);
+ case capi.SQLITE_CONFIG_GETMALLOC: // 5 /* sqlite3_mem_methods* */
+ case capi.SQLITE_CONFIG_GETMUTEX: // 11 /* sqlite3_mutex_methods* */
+ case capi.SQLITE_CONFIG_GETPCACHE2: // 19 /* sqlite3_pcache_methods2* */
+ case capi.SQLITE_CONFIG_GETPCACHE: // 15 /* no-op */
+ case capi.SQLITE_CONFIG_HEAP: // 8 /* void*, int nByte, int min */
+ case capi.SQLITE_CONFIG_LOG: // 16 /* xFunc, void* */
+ case capi.SQLITE_CONFIG_MALLOC:// 4 /* sqlite3_mem_methods* */
+ case capi.SQLITE_CONFIG_MMAP_SIZE: // 22 /* sqlite3_int64, sqlite3_int64 */
+ case capi.SQLITE_CONFIG_MULTITHREAD: // 2 /* nil */
+ case capi.SQLITE_CONFIG_MUTEX: // 10 /* sqlite3_mutex_methods* */
+ case capi.SQLITE_CONFIG_PAGECACHE: // 7 /* void*, int sz, int N */
+ case capi.SQLITE_CONFIG_PCACHE2: // 18 /* sqlite3_pcache_methods2* */
+ case capi.SQLITE_CONFIG_PCACHE: // 14 /* no-op */
+ case capi.SQLITE_CONFIG_PCACHE_HDRSZ: // 24 /* int *psz */
+ case capi.SQLITE_CONFIG_PMASZ: // 25 /* unsigned int szPma */
+ case capi.SQLITE_CONFIG_SERIALIZED: // 3 /* nil */
+ case capi.SQLITE_CONFIG_SINGLETHREAD: // 1 /* nil */:
+ case capi.SQLITE_CONFIG_SQLLOG: // 21 /* xSqllog, void* */
+ case capi.SQLITE_CONFIG_WIN32_HEAPSIZE: // 23 /* int nByte */
+ default:
+ return capi.SQLITE_NOTFOUND;
+ }
+ };
+ }/* sqlite3_config() */
+
+ {/*auto-extension bindings.*/
+ const __autoExtFptr = new Set;
+
+ capi.sqlite3_auto_extension = function(fPtr){
+ if( fPtr instanceof Function ){
+ fPtr = wasm.installFunction('i(ppp)', fPtr);
+ }else if( 1!==arguments.length || !wasm.isPtr(fPtr) ){
+ return capi.SQLITE_MISUSE;
+ }
+ const rc = wasm.exports.sqlite3_auto_extension(fPtr);
+ if( fPtr!==arguments[0] ){
+ if(0===rc) __autoExtFptr.add(fPtr);
+ else wasm.uninstallFunction(fPtr);
+ }
+ return rc;
+ };
+
+ capi.sqlite3_cancel_auto_extension = function(fPtr){
+ /* We do not do an automatic JS-to-WASM function conversion here
+ because it would be senseless: the converted pointer would
+ never possibly match an already-installed one. */;
+ if(!fPtr || 1!==arguments.length || !wasm.isPtr(fPtr)) return 0;
+ return wasm.exports.sqlite3_cancel_auto_extension(fPtr);
+ /* Note that it "cannot happen" that a client passes a pointer which
+ is in __autoExtFptr because __autoExtFptr only contains automatic
+ conversions created inside sqlite3_auto_extension() and
+ never exposed to the client. */
+ };
+
+ capi.sqlite3_reset_auto_extension = function(){
+ wasm.exports.sqlite3_reset_auto_extension();
+ for(const fp of __autoExtFptr) wasm.uninstallFunction(fp);
+ __autoExtFptr.clear();
+ };
+ }/* auto-extension */
+
+ const pKvvfs = capi.sqlite3_vfs_find("kvvfs");
+ if( pKvvfs ){/* kvvfs-specific glue */
+ if(util.isUIThread()){
+ const kvvfsMethods = new capi.sqlite3_kvvfs_methods(
+ wasm.exports.sqlite3_wasm_kvvfs_methods()
+ );
+ delete capi.sqlite3_kvvfs_methods;
+
+ const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack,
+ pstack = wasm.pstack;
+
+ const kvvfsStorage = (zClass)=>
+ ((115/*=='s'*/===wasm.peek(zClass))
+ ? sessionStorage : localStorage);
+
+ /**
+ Implementations for members of the object referred to by
+ sqlite3_wasm_kvvfs_methods(). We swap out the native
+ implementations with these, which use localStorage or
+ sessionStorage for their backing store.
+ */
+ const kvvfsImpls = {
+ xRead: (zClass, zKey, zBuf, nBuf)=>{
+ const stack = pstack.pointer,
+ astack = wasm.scopedAllocPush();
+ try {
+ const zXKey = kvvfsMakeKey(zClass,zKey);
+ if(!zXKey) return -3/*OOM*/;
+ const jKey = wasm.cstrToJs(zXKey);
+ const jV = kvvfsStorage(zClass).getItem(jKey);
+ if(!jV) return -1;
+ const nV = jV.length /* Note that we are relying 100% on v being
+ ASCII so that jV.length is equal to the
+ C-string's byte length. */;
+ if(nBuf<=0) return nV;
+ else if(1===nBuf){
+ wasm.poke(zBuf, 0);
+ return nV;
+ }
+ const zV = wasm.scopedAllocCString(jV);
+ if(nBuf > nV + 1) nBuf = nV + 1;
+ wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1);
+ wasm.poke(zBuf + nBuf - 1, 0);
+ return nBuf - 1;
+ }catch(e){
+ console.error("kvstorageRead()",e);
+ return -2;
+ }finally{
+ pstack.restore(stack);
+ wasm.scopedAllocPop(astack);
+ }
+ },
+ xWrite: (zClass, zKey, zData)=>{
+ const stack = pstack.pointer;
+ try {
+ const zXKey = kvvfsMakeKey(zClass,zKey);
+ if(!zXKey) return 1/*OOM*/;
+ const jKey = wasm.cstrToJs(zXKey);
+ kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData));
+ return 0;
+ }catch(e){
+ console.error("kvstorageWrite()",e);
+ return capi.SQLITE_IOERR;
+ }finally{
+ pstack.restore(stack);
+ }
+ },
+ xDelete: (zClass, zKey)=>{
+ const stack = pstack.pointer;
+ try {
+ const zXKey = kvvfsMakeKey(zClass,zKey);
+ if(!zXKey) return 1/*OOM*/;
+ kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey));
+ return 0;
+ }catch(e){
+ console.error("kvstorageDelete()",e);
+ return capi.SQLITE_IOERR;
+ }finally{
+ pstack.restore(stack);
+ }
+ }
+ }/*kvvfsImpls*/;
+ for(const k of Object.keys(kvvfsImpls)){
+ kvvfsMethods[kvvfsMethods.memberKey(k)] =
+ wasm.installFunction(
+ kvvfsMethods.memberSignature(k),
+ kvvfsImpls[k]
+ );
+ }
+ }else{
+ /* Worker thread: unregister kvvfs to avoid it being used
+ for anything other than local/sessionStorage. It "can"
+ be used that way but it's not really intended to be. */
+ capi.sqlite3_vfs_unregister(pKvvfs);
+ }
+ }/*pKvvfs*/
+
+ wasm.xWrap.FuncPtrAdapter.warnOnUse = true;
+});
diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js
new file mode 100644
index 0000000..160d59d
--- /dev/null
+++ b/ext/wasm/api/sqlite3-api-oo1.js
@@ -0,0 +1,1946 @@
+//#ifnot omit-oo1
+/*
+ 2022-07-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 contains the so-called OO #1 API wrapper for the sqlite3
+ WASM build. It requires that sqlite3-api-glue.js has already run
+ and it installs its deliverable as globalThis.sqlite3.oo1.
+*/
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ const toss = (...args)=>{throw new Error(args.join(' '))};
+ const toss3 = (...args)=>{throw new sqlite3.SQLite3Error(...args)};
+
+ const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util;
+ /* What follows is colloquially known as "OO API #1". It is a
+ binding of the sqlite3 API which is designed to be run within
+ the same thread (main or worker) as the one in which the
+ sqlite3 WASM binding was initialized. This wrapper cannot use
+ the sqlite3 binding if, e.g., the wrapper is in the main thread
+ and the sqlite3 API is in a worker. */
+
+ /**
+ In order to keep clients from manipulating, perhaps
+ inadvertently, the underlying pointer values of DB and Stmt
+ instances, we'll gate access to them via the `pointer` property
+ accessor and store their real values in this map. Keys = DB/Stmt
+ objects, values = pointer values. This also unifies how those are
+ accessed, for potential use downstream via custom
+ wasm.xWrap() function signatures which know how to extract
+ it.
+ */
+ const __ptrMap = new WeakMap();
+ /**
+ Map of DB instances to objects, each object being a map of Stmt
+ wasm pointers to Stmt objects.
+ */
+ const __stmtMap = new WeakMap();
+
+ /** If object opts has _its own_ property named p then that
+ property's value is returned, else dflt is returned. */
+ const getOwnOption = (opts, p, dflt)=>{
+ const d = Object.getOwnPropertyDescriptor(opts,p);
+ return d ? d.value : dflt;
+ };
+
+ // Documented in DB.checkRc()
+ const checkSqlite3Rc = function(dbPtr, sqliteResultCode){
+ if(sqliteResultCode){
+ if(dbPtr instanceof DB) dbPtr = dbPtr.pointer;
+ toss3(
+ sqliteResultCode,
+ "sqlite3 result code",sqliteResultCode+":",
+ (dbPtr
+ ? capi.sqlite3_errmsg(dbPtr)
+ : capi.sqlite3_errstr(sqliteResultCode))
+ );
+ }
+ return arguments[0];
+ };
+
+ /**
+ sqlite3_trace_v2() callback which gets installed by the DB ctor
+ if its open-flags contain "t".
+ */
+ const __dbTraceToConsole =
+ wasm.installFunction('i(ippp)', function(t,c,p,x){
+ if(capi.SQLITE_TRACE_STMT===t){
+ // x == SQL, p == sqlite3_stmt*
+ console.log("SQL TRACE #"+(++this.counter)+' via sqlite3@'+c+':',
+ wasm.cstrToJs(x));
+ }
+ }.bind({counter: 0}));
+
+ /**
+ A map of sqlite3_vfs pointers to SQL code or a callback function
+ to run when the DB constructor opens a database with the given
+ VFS. In the latter case, the call signature is (theDbObject,sqlite3Namespace)
+ and the callback is expected to throw on error.
+ */
+ const __vfsPostOpenSql = Object.create(null);
+
+ /**
+ A proxy for DB class constructors. It must be called with the
+ being-construct DB object as its "this". See the DB constructor
+ for the argument docs. This is split into a separate function
+ in order to enable simple creation of special-case DB constructors,
+ e.g. JsStorageDb and OpfsDb.
+
+ Expects to be passed a configuration object with the following
+ properties:
+
+ - `.filename`: the db filename. It may be a special name like ":memory:"
+ or "".
+
+ - `.flags`: as documented in the DB constructor.
+
+ - `.vfs`: as documented in the DB constructor.
+
+ It also accepts those as the first 3 arguments.
+ */
+ const dbCtorHelper = function ctor(...args){
+ if(!ctor._name2vfs){
+ /**
+ Map special filenames which we handle here (instead of in C)
+ to some helpful metadata...
+
+ As of 2022-09-20, the C API supports the names :localStorage:
+ and :sessionStorage: for kvvfs. However, C code cannot
+ determine (without embedded JS code, e.g. via Emscripten's
+ EM_JS()) whether the kvvfs is legal in the current browser
+ context (namely the main UI thread). In order to help client
+ code fail early on, instead of it being delayed until they
+ try to read or write a kvvfs-backed db, we'll check for those
+ names here and throw if they're not legal in the current
+ context.
+ */
+ ctor._name2vfs = Object.create(null);
+ const isWorkerThread = ('function'===typeof importScripts/*===running in worker thread*/)
+ ? (n)=>toss3("The VFS for",n,"is only available in the main window thread.")
+ : false;
+ ctor._name2vfs[':localStorage:'] = {
+ vfs: 'kvvfs', filename: isWorkerThread || (()=>'local')
+ };
+ ctor._name2vfs[':sessionStorage:'] = {
+ vfs: 'kvvfs', filename: isWorkerThread || (()=>'session')
+ };
+ }
+ const opt = ctor.normalizeArgs(...args);
+ let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
+ if(('string'!==typeof fn && 'number'!==typeof fn)
+ || 'string'!==typeof flagsStr
+ || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){
+ sqlite3.config.error("Invalid DB ctor args",opt,arguments);
+ toss3("Invalid arguments for DB constructor.");
+ }
+ let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn;
+ const vfsCheck = ctor._name2vfs[fnJs];
+ if(vfsCheck){
+ vfsName = vfsCheck.vfs;
+ fn = fnJs = vfsCheck.filename(fnJs);
+ }
+ let pDb, oflags = 0;
+ if( flagsStr.indexOf('c')>=0 ){
+ oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
+ }
+ if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
+ if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
+ oflags |= capi.SQLITE_OPEN_EXRESCODE;
+ const stack = wasm.pstack.pointer;
+ try {
+ const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
+ let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
+ pDb = wasm.peekPtr(pPtr);
+ checkSqlite3Rc(pDb, rc);
+ capi.sqlite3_extended_result_codes(pDb, 1);
+ if(flagsStr.indexOf('t')>=0){
+ capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
+ __dbTraceToConsole, pDb);
+ }
+ }catch( e ){
+ if( pDb ) capi.sqlite3_close_v2(pDb);
+ throw e;
+ }finally{
+ wasm.pstack.restore(stack);
+ }
+ this.filename = fnJs;
+ __ptrMap.set(this, pDb);
+ __stmtMap.set(this, Object.create(null));
+ try{
+ // Check for per-VFS post-open SQL/callback...
+ const pVfs = capi.sqlite3_js_db_vfs(pDb);
+ if(!pVfs) toss3("Internal error: cannot get VFS for new db handle.");
+ const postInitSql = __vfsPostOpenSql[pVfs];
+ if(postInitSql instanceof Function){
+ postInitSql(this, sqlite3);
+ }else if(postInitSql){
+ checkSqlite3Rc(
+ pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
+ );
+ }
+ }catch(e){
+ this.close();
+ throw e;
+ }
+ };
+
+ /**
+ Sets SQL which should be exec()'d on a DB instance after it is
+ opened with the given VFS pointer. The SQL may be any type
+ supported by the "string:flexible" function argument conversion.
+ Alternately, the 2nd argument may be a function, in which case it
+ is called with (theOo1DbObject,sqlite3Namespace) at the end of
+ the DB() constructor. The function must throw on error, in which
+ case the db is closed and the exception is propagated. This
+ function is intended only for use by DB subclasses or sqlite3_vfs
+ implementations.
+ */
+ dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){
+ __vfsPostOpenSql[pVfs] = sql;
+ };
+
+ /**
+ A helper for DB constructors. It accepts either a single
+ config-style object or up to 3 arguments (filename, dbOpenFlags,
+ dbVfsName). It returns a new object containing:
+
+ { filename: ..., flags: ..., vfs: ... }
+
+ If passed an object, any additional properties it has are copied
+ as-is into the new object.
+ */
+ dbCtorHelper.normalizeArgs = function(filename=':memory:',flags = 'c',vfs = null){
+ const arg = {};
+ if(1===arguments.length && arguments[0] && 'object'===typeof arguments[0]){
+ Object.assign(arg, arguments[0]);
+ if(undefined===arg.flags) arg.flags = 'c';
+ if(undefined===arg.vfs) arg.vfs = null;
+ if(undefined===arg.filename) arg.filename = ':memory:';
+ }else{
+ arg.filename = filename;
+ arg.flags = flags;
+ arg.vfs = vfs;
+ }
+ return arg;
+ };
+ /**
+ The DB class provides a high-level OO wrapper around an sqlite3
+ db handle.
+
+ The given db filename must be resolvable using whatever
+ filesystem layer (virtual or otherwise) is set up for the default
+ sqlite3 VFS.
+
+ Note that the special sqlite3 db names ":memory:" and ""
+ (temporary db) have their normal special meanings here and need
+ not resolve to real filenames, but "" uses an on-storage
+ temporary database and requires that the VFS support that.
+
+ The second argument specifies the open/create mode for the
+ database. It must be string containing a sequence of letters (in
+ any order, but case sensitive) specifying the mode:
+
+ - "c": create if it does not exist, else fail if it does not
+ exist. Implies the "w" flag.
+
+ - "w": write. Implies "r": a db cannot be write-only.
+
+ - "r": read-only if neither "w" nor "c" are provided, else it
+ is ignored.
+
+ - "t": enable tracing of SQL executed on this database handle,
+ sending it to `console.log()`. To disable it later, call
+ `sqlite3.capi.sqlite3_trace_v2(thisDb.pointer, 0, 0, 0)`.
+
+ If "w" is not provided, the db is implicitly read-only, noting
+ that "rc" is meaningless
+
+ Any other letters are currently ignored. The default is
+ "c". These modes are ignored for the special ":memory:" and ""
+ names and _may_ be ignored altogether for certain VFSes.
+
+ The final argument is analogous to the final argument of
+ sqlite3_open_v2(): the name of an sqlite3 VFS. Pass a falsy value,
+ or none at all, to use the default. If passed a value, it must
+ be the string name of a VFS.
+
+ The constructor optionally (and preferably) takes its arguments
+ in the form of a single configuration object with the following
+ properties:
+
+ - `filename`: database file name
+ - `flags`: open-mode flags
+ - `vfs`: the VFS fname
+
+ The `filename` and `vfs` arguments may be either JS strings or
+ C-strings allocated via WASM. `flags` is required to be a JS
+ string (because it's specific to this API, which is specific
+ to JS).
+
+ For purposes of passing a DB instance to C-style sqlite3
+ functions, the DB object's read-only `pointer` property holds its
+ `sqlite3*` pointer value. That property can also be used to check
+ whether this DB instance is still open.
+
+ In the main window thread, the filenames `":localStorage:"` and
+ `":sessionStorage:"` are special: they cause the db to use either
+ localStorage or sessionStorage for storing the database using
+ the kvvfs. If one of these names are used, they trump
+ any vfs name set in the arguments.
+ */
+ const DB = function(...args){
+ dbCtorHelper.apply(this, args);
+ };
+ DB.dbCtorHelper = dbCtorHelper;
+
+ /**
+ Internal-use enum for mapping JS types to DB-bindable types.
+ These do not (and need not) line up with the SQLITE_type
+ values. All values in this enum must be truthy and distinct
+ but they need not be numbers.
+ */
+ const BindTypes = {
+ null: 1,
+ number: 2,
+ string: 3,
+ boolean: 4,
+ blob: 5
+ };
+ BindTypes['undefined'] == BindTypes.null;
+ if(wasm.bigIntEnabled){
+ BindTypes.bigint = BindTypes.number;
+ }
+
+ /**
+ This class wraps sqlite3_stmt. Calling this constructor
+ directly will trigger an exception. Use DB.prepare() to create
+ new instances.
+
+ For purposes of passing a Stmt instance to C-style sqlite3
+ functions, its read-only `pointer` property holds its `sqlite3_stmt*`
+ pointer value.
+
+ Other non-function properties include:
+
+ - `db`: the DB object which created the statement.
+
+ - `columnCount`: the number of result columns in the query, or 0
+ for queries which cannot return results. This property is a proxy
+ for sqlite3_column_count() and its use in loops should be avoided
+ because of the call overhead associated with that. The
+ `columnCount` is not cached when the Stmt is created because a
+ schema change made via a separate db connection between this
+ statement's preparation and when it is stepped may invalidate it.
+
+ - `parameterCount`: the number of bindable parameters in the query.
+ */
+ const Stmt = function(){
+ if(BindTypes!==arguments[2]){
+ toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare().");
+ }
+ this.db = arguments[0];
+ __ptrMap.set(this, arguments[1]);
+ this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer);
+ };
+
+ /** Throws if the given DB has been closed, else it is returned. */
+ const affirmDbOpen = function(db){
+ if(!db.pointer) toss3("DB has been closed.");
+ return db;
+ };
+
+ /** Throws if ndx is not an integer or if it is out of range
+ for stmt.columnCount, else returns stmt.
+
+ Reminder: this will also fail after the statement is finalized
+ but the resulting error will be about an out-of-bounds column
+ index rather than a statement-is-finalized error.
+ */
+ const affirmColIndex = function(stmt,ndx){
+ if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){
+ toss3("Column index",ndx,"is out of range.");
+ }
+ return stmt;
+ };
+
+ /**
+ Expects to be passed the `arguments` object from DB.exec(). Does
+ the argument processing/validation, throws on error, and returns
+ a new object on success:
+
+ { sql: the SQL, opt: optionsObj, cbArg: function}
+
+ The opt object is a normalized copy of any passed to this
+ function. The sql will be converted to a string if it is provided
+ in one of the supported non-string formats.
+
+ cbArg is only set if the opt.callback or opt.resultRows are set,
+ in which case it's a function which expects to be passed the
+ current Stmt and returns the callback argument of the type
+ indicated by the input arguments.
+ */
+ const parseExecArgs = function(db, args){
+ const out = Object.create(null);
+ out.opt = Object.create(null);
+ switch(args.length){
+ case 1:
+ if('string'===typeof args[0] || util.isSQLableTypedArray(args[0])){
+ out.sql = args[0];
+ }else if(Array.isArray(args[0])){
+ out.sql = args[0];
+ }else if(args[0] && 'object'===typeof args[0]){
+ out.opt = args[0];
+ out.sql = out.opt.sql;
+ }
+ break;
+ case 2:
+ out.sql = args[0];
+ out.opt = args[1];
+ break;
+ default: toss3("Invalid argument count for exec().");
+ };
+ out.sql = util.flexibleString(out.sql);
+ if('string'!==typeof out.sql){
+ toss3("Missing SQL argument or unsupported SQL value type.");
+ }
+ const opt = out.opt;
+ switch(opt.returnValue){
+ case 'resultRows':
+ if(!opt.resultRows) opt.resultRows = [];
+ out.returnVal = ()=>opt.resultRows;
+ break;
+ case 'saveSql':
+ if(!opt.saveSql) opt.saveSql = [];
+ out.returnVal = ()=>opt.saveSql;
+ break;
+ case undefined:
+ case 'this':
+ out.returnVal = ()=>db;
+ break;
+ default:
+ toss3("Invalid returnValue value:",opt.returnValue);
+ }
+ if(!opt.callback && !opt.returnValue && undefined!==opt.rowMode){
+ if(!opt.resultRows) opt.resultRows = [];
+ out.returnVal = ()=>opt.resultRows;
+ }
+ if(opt.callback || opt.resultRows){
+ switch((undefined===opt.rowMode)
+ ? 'array' : opt.rowMode) {
+ case 'object': out.cbArg = (stmt)=>stmt.get(Object.create(null)); break;
+ case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
+ case 'stmt':
+ if(Array.isArray(opt.resultRows)){
+ toss3("exec(): invalid rowMode for a resultRows array: must",
+ "be one of 'array', 'object',",
+ "a result column number, or column name reference.");
+ }
+ out.cbArg = (stmt)=>stmt;
+ break;
+ default:
+ if(util.isInt32(opt.rowMode)){
+ out.cbArg = (stmt)=>stmt.get(opt.rowMode);
+ break;
+ }else if('string'===typeof opt.rowMode
+ && opt.rowMode.length>1
+ && '$'===opt.rowMode[0]){
+ /* "$X": fetch column named "X" (case-sensitive!). Prior
+ to 2022-12-14 ":X" and "@X" were also permitted, but
+ having so many options is unnecessary and likely to
+ cause confusion. */
+ const $colName = opt.rowMode.substr(1);
+ out.cbArg = (stmt)=>{
+ const rc = stmt.get(Object.create(null))[$colName];
+ return (undefined===rc)
+ ? toss3(capi.SQLITE_NOTFOUND,
+ "exec(): unknown result column:",$colName)
+ : rc;
+ };
+ break;
+ }
+ toss3("Invalid rowMode:",opt.rowMode);
+ }
+ }
+ return out;
+ };
+
+ /**
+ Internal impl of the DB.selectValue(), selectArray(), and
+ selectObject() methods.
+ */
+ const __selectFirstRow = (db, sql, bind, ...getArgs)=>{
+ const stmt = db.prepare(sql);
+ try {
+ const rc = stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined;
+ stmt.reset(/*for INSERT...RETURNING locking case*/);
+ return rc;
+ }finally{
+ stmt.finalize();
+ }
+ };
+
+ /**
+ Internal impl of the DB.selectArrays() and selectObjects()
+ methods.
+ */
+ const __selectAll =
+ (db, sql, bind, rowMode)=>db.exec({
+ sql, bind, rowMode, returnValue: 'resultRows'
+ });
+
+ /**
+ Expects to be given a DB instance or an `sqlite3*` pointer (may
+ be null) and an sqlite3 API result code. If the result code is
+ not falsy, this function throws an SQLite3Error with an error
+ message from sqlite3_errmsg(), using db (or, if db is-a DB,
+ db.pointer) as the db handle, or sqlite3_errstr() if db is
+ falsy. Note that if it's passed a non-error code like SQLITE_ROW
+ or SQLITE_DONE, it will still throw but the error string might be
+ "Not an error." The various non-0 non-error codes need to be
+ checked for in client code where they are expected.
+
+ The thrown exception's `resultCode` property will be the value of
+ the second argument to this function.
+
+ If it does not throw, it returns its first argument.
+ */
+ DB.checkRc = (db,resultCode)=>checkSqlite3Rc(db,resultCode);
+
+ DB.prototype = {
+ /** Returns true if this db handle is open, else false. */
+ isOpen: function(){
+ return !!this.pointer;
+ },
+ /** Throws if this given DB has been closed, else returns `this`. */
+ affirmOpen: function(){
+ return affirmDbOpen(this);
+ },
+ /**
+ Finalizes all open statements and closes this database
+ connection. This is a no-op if the db has already been
+ closed. After calling close(), `this.pointer` will resolve to
+ `undefined`, so that can be used to check whether the db
+ instance is still opened.
+
+ If this.onclose.before is a function then it is called before
+ any close-related cleanup.
+
+ If this.onclose.after is a function then it is called after the
+ db is closed but before auxiliary state like this.filename is
+ cleared.
+
+ Both onclose handlers are passed this object, with the onclose
+ object as their "this," noting that the db will have been
+ closed when onclose.after is called. If this db is not opened
+ when close() is called, neither of the handlers are called. Any
+ exceptions the handlers throw are ignored because "destructors
+ must not throw."
+
+ Note that garbage collection of a db handle, if it happens at
+ all, will never trigger close(), so onclose handlers are not a
+ reliable way to implement close-time cleanup or maintenance of
+ a db.
+ */
+ close: function(){
+ if(this.pointer){
+ if(this.onclose && (this.onclose.before instanceof Function)){
+ try{this.onclose.before(this)}
+ catch(e){/*ignore*/}
+ }
+ const pDb = this.pointer;
+ Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
+ if(s && s.pointer){
+ try{s.finalize()}
+ catch(e){/*ignore*/}
+ }
+ });
+ __ptrMap.delete(this);
+ __stmtMap.delete(this);
+ capi.sqlite3_close_v2(pDb);
+ if(this.onclose && (this.onclose.after instanceof Function)){
+ try{this.onclose.after(this)}
+ catch(e){/*ignore*/}
+ }
+ delete this.filename;
+ }
+ },
+ /**
+ Returns the number of changes, as per sqlite3_changes()
+ (if the first argument is false) or sqlite3_total_changes()
+ (if it's true). If the 2nd argument is true, it uses
+ sqlite3_changes64() or sqlite3_total_changes64(), which
+ will trigger an exception if this build does not have
+ BigInt support enabled.
+ */
+ changes: function(total=false,sixtyFour=false){
+ const p = affirmDbOpen(this).pointer;
+ if(total){
+ return sixtyFour
+ ? capi.sqlite3_total_changes64(p)
+ : capi.sqlite3_total_changes(p);
+ }else{
+ return sixtyFour
+ ? capi.sqlite3_changes64(p)
+ : capi.sqlite3_changes(p);
+ }
+ },
+ /**
+ Similar to the this.filename but returns the
+ sqlite3_db_filename() value for the given database name,
+ defaulting to "main". The argument may be either a JS string
+ or a pointer to a WASM-allocated C-string.
+ */
+ dbFilename: function(dbName='main'){
+ return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName);
+ },
+ /**
+ Returns the name of the given 0-based db number, as documented
+ for sqlite3_db_name().
+ */
+ dbName: function(dbNumber=0){
+ return capi.sqlite3_db_name(affirmDbOpen(this).pointer, dbNumber);
+ },
+ /**
+ Returns the name of the sqlite3_vfs used by the given database
+ of this connection (defaulting to 'main'). The argument may be
+ either a JS string or a WASM C-string. Returns undefined if the
+ given db name is invalid. Throws if this object has been
+ close()d.
+ */
+ dbVfsName: function(dbName=0){
+ let rc;
+ const pVfs = capi.sqlite3_js_db_vfs(
+ affirmDbOpen(this).pointer, dbName
+ );
+ if(pVfs){
+ const v = new capi.sqlite3_vfs(pVfs);
+ try{ rc = wasm.cstrToJs(v.$zName) }
+ finally { v.dispose() }
+ }
+ return rc;
+ },
+ /**
+ Compiles the given SQL and returns a prepared Stmt. This is
+ the only way to create new Stmt objects. Throws on error.
+
+ The given SQL must be a string, a Uint8Array holding SQL, a
+ WASM pointer to memory holding the NUL-terminated SQL string,
+ or an array of strings. In the latter case, the array is
+ concatenated together, with no separators, to form the SQL
+ string (arrays are often a convenient way to formulate long
+ statements). If the SQL contains no statements, an
+ SQLite3Error is thrown.
+
+ Design note: the C API permits empty SQL, reporting it as a 0
+ result code and a NULL stmt pointer. Supporting that case here
+ would cause extra work for all clients: any use of the Stmt API
+ on such a statement will necessarily throw, so clients would be
+ required to check `stmt.pointer` after calling `prepare()` in
+ order to determine whether the Stmt instance is empty or not.
+ Long-time practice (with other sqlite3 script bindings)
+ suggests that the empty-prepare case is sufficiently rare that
+ supporting it here would simply hurt overall usability.
+ */
+ prepare: function(sql){
+ affirmDbOpen(this);
+ const stack = wasm.pstack.pointer;
+ let ppStmt, pStmt;
+ try{
+ ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */;
+ DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null));
+ pStmt = wasm.peekPtr(ppStmt);
+ }
+ finally {
+ wasm.pstack.restore(stack);
+ }
+ if(!pStmt) toss3("Cannot prepare empty SQL.");
+ const stmt = new Stmt(this, pStmt, BindTypes);
+ __stmtMap.get(this)[pStmt] = stmt;
+ return stmt;
+ },
+ /**
+ Executes one or more SQL statements in the form of a single
+ string. Its arguments must be either (sql,optionsObject) or
+ (optionsObject). In the latter case, optionsObject.sql must
+ contain the SQL to execute. By default it returns this object
+ but that can be changed via the `returnValue` option as
+ described below. Throws on error.
+
+ If no SQL is provided, or a non-string is provided, an
+ exception is triggered. Empty SQL, on the other hand, is
+ simply a no-op.
+
+ The optional options object may contain any of the following
+ properties:
+
+ - `sql` = the SQL to run (unless it's provided as the first
+ argument). This must be of type string, Uint8Array, or an array
+ of strings. In the latter case they're concatenated together
+ as-is, _with no separator_ between elements, before evaluation.
+ The array form is often simpler for long hand-written queries.
+
+ - `bind` = a single value valid as an argument for
+ Stmt.bind(). This is _only_ applied to the _first_ non-empty
+ statement in the SQL which has any bindable parameters. (Empty
+ statements are skipped entirely.)
+
+ - `saveSql` = an optional array. If set, the SQL of each
+ executed statement is appended to this array before the
+ statement is executed (but after it is prepared - we don't have
+ the string until after that). Empty SQL statements are elided
+ but can have odd effects in the output. e.g. SQL of: `"select
+ 1; -- empty\n; select 2"` will result in an array containing
+ `["select 1;", "--empty \n; select 2"]`. That's simply how
+ sqlite3 records the SQL for the 2nd statement.
+
+ ==================================================================
+ The following options apply _only_ to the _first_ statement
+ which has a non-zero result column count, regardless of whether
+ the statement actually produces any result rows.
+ ==================================================================
+
+ - `columnNames`: if this is an array, the column names of the
+ result set are stored in this array before the callback (if
+ any) is triggered (regardless of whether the query produces any
+ result rows). If no statement has result columns, this value is
+ unchanged. Achtung: an SQL result may have multiple columns
+ with identical names.
+
+ - `callback` = a function which gets called for each row of the
+ result set, but only if that statement has any result rows. The
+ callback's "this" is the options object, noting that this
+ function synthesizes one if the caller does not pass one to
+ exec(). The second argument passed to the callback is always
+ the current Stmt object, as it's needed if the caller wants to
+ fetch the column names or some such (noting that they could
+ also be fetched via `this.columnNames`, if the client provides
+ the `columnNames` option). If the callback returns a literal
+ `false` (as opposed to any other falsy value, e.g. an implicit
+ `undefined` return), any ongoing statement-`step()` iteration
+ stops without an error. The return value of the callback is
+ otherwise ignored.
+
+ ACHTUNG: The callback MUST NOT modify the Stmt object. Calling
+ any of the Stmt.get() variants, Stmt.getColumnName(), or
+ similar, is legal, but calling step() or finalize() is
+ not. Member methods which are illegal in this context will
+ trigger an exception, but clients must also refrain from using
+ any lower-level (C-style) APIs which might modify the
+ statement.
+
+ The first argument passed to the callback defaults to an array of
+ values from the current result row but may be changed with ...
+
+ - `rowMode` = specifies the type of he callback's first argument.
+ It may be any of...
+
+ A) A string describing what type of argument should be passed
+ as the first argument to the callback:
+
+ A.1) `'array'` (the default) causes the results of
+ `stmt.get([])` to be passed to the `callback` and/or appended
+ to `resultRows`.
+
+ A.2) `'object'` causes the results of
+ `stmt.get(Object.create(null))` to be passed to the
+ `callback` and/or appended to `resultRows`. Achtung: an SQL
+ result may have multiple columns with identical names. In
+ that case, the right-most column will be the one set in this
+ object!
+
+ A.3) `'stmt'` causes the current Stmt to be passed to the
+ callback, but this mode will trigger an exception if
+ `resultRows` is an array because appending the transient
+ statement to the array would be downright unhelpful.
+
+ B) An integer, indicating a zero-based column in the result
+ row. Only that one single value will be passed on.
+
+ C) A string with a minimum length of 2 and leading character of
+ '$' will fetch the row as an object, extract that one field,
+ and pass that field's value to the callback. Note that these
+ keys are case-sensitive so must match the case used in the
+ SQL. e.g. `"select a A from t"` with a `rowMode` of `'$A'`
+ would work but `'$a'` would not. A reference to a column not in
+ the result set will trigger an exception on the first row (as
+ the check is not performed until rows are fetched). Note also
+ that `$` is a legal identifier character in JS so need not be
+ quoted.
+
+ Any other `rowMode` value triggers an exception.
+
+ - `resultRows`: if this is an array, it functions similarly to
+ the `callback` option: each row of the result set (if any),
+ with the exception that the `rowMode` 'stmt' is not legal. It
+ is legal to use both `resultRows` and `callback`, but
+ `resultRows` is likely much simpler to use for small data sets
+ and can be used over a WebWorker-style message interface.
+ exec() throws if `resultRows` is set and `rowMode` is 'stmt'.
+
+ - `returnValue`: is a string specifying what this function
+ should return:
+
+ A) The default value is (usually) `"this"`, meaning that the
+ DB object itself should be returned. The exception is if
+ the caller passes neither of `callback` nor `returnValue`
+ but does pass an explicit `rowMode` then the default
+ `returnValue` is `"resultRows"`, described below.
+
+ B) `"resultRows"` means to return the value of the
+ `resultRows` option. If `resultRows` is not set, this
+ function behaves as if it were set to an empty array.
+
+ C) `"saveSql"` means to return the value of the
+ `saveSql` option. If `saveSql` is not set, this
+ function behaves as if it were set to an empty array.
+
+ Potential TODOs:
+
+ - `bind`: permit an array of arrays/objects to bind. The first
+ sub-array would act on the first statement which has bindable
+ parameters (as it does now). The 2nd would act on the next such
+ statement, etc.
+
+ - `callback` and `resultRows`: permit an array entries with
+ semantics similar to those described for `bind` above.
+
+ */
+ exec: function(/*(sql [,obj]) || (obj)*/){
+ affirmDbOpen(this);
+ const arg = parseExecArgs(this, arguments);
+ if(!arg.sql){
+ return toss3("exec() requires an SQL string.");
+ }
+ const opt = arg.opt;
+ const callback = opt.callback;
+ const resultRows =
+ Array.isArray(opt.resultRows) ? opt.resultRows : undefined;
+ let stmt;
+ let bind = opt.bind;
+ let evalFirstResult = !!(
+ arg.cbArg || opt.columnNames || resultRows
+ ) /* true to step through the first result-returning statement */;
+ const stack = wasm.scopedAllocPush();
+ const saveSql = Array.isArray(opt.saveSql) ? opt.saveSql : undefined;
+ try{
+ const isTA = util.isSQLableTypedArray(arg.sql)
+ /* Optimization: if the SQL is a TypedArray we can save some string
+ conversion costs. */;
+ /* Allocate the two output pointers (ppStmt, pzTail) and heap
+ space for the SQL (pSql). When prepare_v2() returns, pzTail
+ will point to somewhere in pSql. */
+ let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql);
+ const ppStmt = wasm.scopedAlloc(
+ /* output (sqlite3_stmt**) arg and pzTail */
+ (2 * wasm.ptrSizeof) + (sqlByteLen + 1/* SQL + NUL */)
+ );
+ const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */;
+ let pSql = pzTail + wasm.ptrSizeof;
+ const pSqlEnd = pSql + sqlByteLen;
+ if(isTA) wasm.heap8().set(arg.sql, pSql);
+ else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false);
+ wasm.poke(pSql + sqlByteLen, 0/*NUL terminator*/);
+ while(pSql && wasm.peek(pSql, 'i8')
+ /* Maintenance reminder:^^^ _must_ be 'i8' or else we
+ will very likely cause an endless loop. What that's
+ doing is checking for a terminating NUL byte. If we
+ use i32 or similar then we read 4 bytes, read stuff
+ around the NUL terminator, and get stuck in and
+ endless loop at the end of the SQL, endlessly
+ re-preparing an empty statement. */ ){
+ wasm.pokePtr([ppStmt, pzTail], 0);
+ DB.checkRc(this, capi.sqlite3_prepare_v3(
+ this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail
+ ));
+ const pStmt = wasm.peekPtr(ppStmt);
+ pSql = wasm.peekPtr(pzTail);
+ sqlByteLen = pSqlEnd - pSql;
+ if(!pStmt) continue;
+ if(saveSql) saveSql.push(capi.sqlite3_sql(pStmt).trim());
+ stmt = new Stmt(this, pStmt, BindTypes);
+ if(bind && stmt.parameterCount){
+ stmt.bind(bind);
+ bind = null;
+ }
+ if(evalFirstResult && stmt.columnCount){
+ /* Only forward SELECT-style results for the FIRST query
+ in the SQL which potentially has them. */
+ let gotColNames = Array.isArray(
+ opt.columnNames
+ /* As reported in
+ https://sqlite.org/forum/forumpost/7774b773937cbe0a
+ we need to delay fetching of the column names until
+ after the first step() (if we step() at all) because
+ a schema change between the prepare() and step(), via
+ another connection, may invalidate the column count
+ and names. */) ? 0 : 1;
+ evalFirstResult = false;
+ if(arg.cbArg || resultRows){
+ for(; stmt.step(); stmt._lockedByExec = false){
+ if(0===gotColNames++) stmt.getColumnNames(opt.columnNames);
+ stmt._lockedByExec = true;
+ const row = arg.cbArg(stmt);
+ if(resultRows) resultRows.push(row);
+ if(callback && false === callback.call(opt, row, stmt)){
+ break;
+ }
+ }
+ stmt._lockedByExec = false;
+ }
+ if(0===gotColNames){
+ /* opt.columnNames was provided but we visited no result rows */
+ stmt.getColumnNames(opt.columnNames);
+ }
+ }else{
+ stmt.step();
+ }
+ stmt.reset(
+ /* In order to trigger an exception in the
+ INSERT...RETURNING locking scenario:
+ https://sqlite.org/forum/forumpost/36f7a2e7494897df
+ */).finalize();
+ stmt = null;
+ }/*prepare() loop*/
+ }/*catch(e){
+ sqlite3.config.warn("DB.exec() is propagating exception",opt,e);
+ throw e;
+ }*/finally{
+ wasm.scopedAllocPop(stack);
+ if(stmt){
+ delete stmt._lockedByExec;
+ stmt.finalize();
+ }
+ }
+ return arg.returnVal();
+ }/*exec()*/,
+
+ /**
+ Creates a new UDF (User-Defined Function) which is accessible
+ via SQL code. This function may be called in any of the
+ following forms:
+
+ - (name, function)
+ - (name, function, optionsObject)
+ - (name, optionsObject)
+ - (optionsObject)
+
+ In the final two cases, the function must be defined as the
+ `callback` property of the options object (optionally called
+ `xFunc` to align with the C API documentation). In the final
+ case, the function's name must be the 'name' property.
+
+ The first two call forms can only be used for creating scalar
+ functions. Creating an aggregate or window function requires
+ the options-object form (see below for details).
+
+ UDFs can be removed as documented for
+ sqlite3_create_function_v2() and
+ sqlite3_create_window_function(), but doing so will "leak" the
+ JS-created WASM binding of those functions (meaning that their
+ entries in the WASM indirect function table still
+ exist). Eliminating that potential leak is a pending TODO.
+
+ On success, returns this object. Throws on error.
+
+ When called from SQL arguments to the UDF, and its result,
+ will be converted between JS and SQL with as much fidelity as
+ is feasible, triggering an exception if a type conversion
+ cannot be determined. The docs for sqlite3_create_function_v2()
+ describe the conversions in more detail.
+
+ The values set in the options object differ for scalar and
+ aggregate functions:
+
+ - Scalar: set the `xFunc` function-type property to the UDF
+ function.
+
+ - Aggregate: set the `xStep` and `xFinal` function-type
+ properties to the "step" and "final" callbacks for the
+ aggregate. Do not set the `xFunc` property.
+
+ - Window: set the `xStep`, `xFinal`, `xValue`, and `xInverse`
+ function-type properties. Do not set the `xFunc` property.
+
+ The options object may optionally have an `xDestroy`
+ function-type property, as per sqlite3_create_function_v2().
+ Its argument will be the WASM-pointer-type value of the `pApp`
+ property, and this function will throw if `pApp` is defined but
+ is not null, undefined, or a numeric (WASM pointer)
+ value. i.e. `pApp`, if set, must be value suitable for use as a
+ WASM pointer argument, noting that `null` or `undefined` will
+ translate to 0 for that purpose.
+
+ The options object may contain flags to modify how
+ the function is defined:
+
+ - `arity`: the number of arguments which SQL calls to this
+ function expect or require. The default value is `xFunc.length`
+ or `xStep.length` (i.e. the number of declared parameters it
+ has) **MINUS 1** (see below for why). As a special case, if the
+ `length` is 0, its arity is also 0 instead of -1. A negative
+ arity value means that the function is variadic and may accept
+ any number of arguments, up to sqlite3's compile-time
+ limits. sqlite3 will enforce the argument count if is zero or
+ greater. The callback always receives a pointer to an
+ `sqlite3_context` object as its first argument. Any arguments
+ after that are from SQL code. The leading context argument does
+ _not_ count towards the function's arity. See the docs for
+ sqlite3.capi.sqlite3_create_function_v2() for why that argument
+ is needed in the interface.
+
+ The following options-object properties correspond to flags
+ documented at:
+
+ https://sqlite.org/c3ref/create_function.html
+
+ - `deterministic` = sqlite3.capi.SQLITE_DETERMINISTIC
+ - `directOnly` = sqlite3.capi.SQLITE_DIRECTONLY
+ - `innocuous` = sqlite3.capi.SQLITE_INNOCUOUS
+
+ Sidebar: the ability to add new WASM-accessible functions to
+ the runtime requires that the WASM build is compiled with the
+ equivalent functionality as that provided by Emscripten's
+ `-sALLOW_TABLE_GROWTH` flag.
+ */
+ createFunction: function f(name, xFunc, opt){
+ const isFunc = (f)=>(f instanceof Function);
+ switch(arguments.length){
+ case 1: /* (optionsObject) */
+ opt = name;
+ name = opt.name;
+ xFunc = opt.xFunc || 0;
+ break;
+ case 2: /* (name, callback|optionsObject) */
+ if(!isFunc(xFunc)){
+ opt = xFunc;
+ xFunc = opt.xFunc || 0;
+ }
+ break;
+ case 3: /* name, xFunc, opt */
+ break;
+ default: break;
+ }
+ if(!opt) opt = {};
+ if('string' !== typeof name){
+ toss3("Invalid arguments: missing function name.");
+ }
+ let xStep = opt.xStep || 0;
+ let xFinal = opt.xFinal || 0;
+ const xValue = opt.xValue || 0;
+ const xInverse = opt.xInverse || 0;
+ let isWindow = undefined;
+ if(isFunc(xFunc)){
+ isWindow = false;
+ if(isFunc(xStep) || isFunc(xFinal)){
+ toss3("Ambiguous arguments: scalar or aggregate?");
+ }
+ xStep = xFinal = null;
+ }else if(isFunc(xStep)){
+ if(!isFunc(xFinal)){
+ toss3("Missing xFinal() callback for aggregate or window UDF.");
+ }
+ xFunc = null;
+ }else if(isFunc(xFinal)){
+ toss3("Missing xStep() callback for aggregate or window UDF.");
+ }else{
+ toss3("Missing function-type properties.");
+ }
+ if(false === isWindow){
+ if(isFunc(xValue) || isFunc(xInverse)){
+ toss3("xValue and xInverse are not permitted for non-window UDFs.");
+ }
+ }else if(isFunc(xValue)){
+ if(!isFunc(xInverse)){
+ toss3("xInverse must be provided if xValue is.");
+ }
+ isWindow = true;
+ }else if(isFunc(xInverse)){
+ toss3("xValue must be provided if xInverse is.");
+ }
+ const pApp = opt.pApp;
+ if(undefined!==pApp &&
+ null!==pApp &&
+ (('number'!==typeof pApp) || !util.isInt32(pApp))){
+ toss3("Invalid value for pApp property. Must be a legal WASM pointer value.");
+ }
+ const xDestroy = opt.xDestroy || 0;
+ if(xDestroy && !isFunc(xDestroy)){
+ toss3("xDestroy property must be a function.");
+ }
+ let fFlags = 0 /*flags for sqlite3_create_function_v2()*/;
+ if(getOwnOption(opt, 'deterministic')) fFlags |= capi.SQLITE_DETERMINISTIC;
+ if(getOwnOption(opt, 'directOnly')) fFlags |= capi.SQLITE_DIRECTONLY;
+ if(getOwnOption(opt, 'innocuous')) fFlags |= capi.SQLITE_INNOCUOUS;
+ name = name.toLowerCase();
+ const xArity = xFunc || xStep;
+ const arity = getOwnOption(opt, 'arity');
+ const arityArg = ('number'===typeof arity
+ ? arity
+ : (xArity.length ? xArity.length-1/*for pCtx arg*/ : 0));
+ let rc;
+ if( isWindow ){
+ rc = capi.sqlite3_create_window_function(
+ this.pointer, name, arityArg,
+ capi.SQLITE_UTF8 | fFlags, pApp || 0,
+ xStep, xFinal, xValue, xInverse, xDestroy);
+ }else{
+ rc = capi.sqlite3_create_function_v2(
+ this.pointer, name, arityArg,
+ capi.SQLITE_UTF8 | fFlags, pApp || 0,
+ xFunc, xStep, xFinal, xDestroy);
+ }
+ DB.checkRc(this, rc);
+ return this;
+ }/*createFunction()*/,
+ /**
+ Prepares the given SQL, step()s it one time, and returns
+ the value of the first result column. If it has no results,
+ undefined is returned.
+
+ If passed a second argument, it is treated like an argument
+ to Stmt.bind(), so may be any type supported by that
+ function. Passing the undefined value is the same as passing
+ no value, which is useful when...
+
+ If passed a 3rd argument, it is expected to be one of the
+ SQLITE_{typename} constants. Passing the undefined value is
+ the same as not passing a value.
+
+ Throws on error (e.g. malformed SQL).
+ */
+ selectValue: function(sql,bind,asType){
+ return __selectFirstRow(this, sql, bind, 0, asType);
+ },
+
+ /**
+ Runs the given query and returns an array of the values from
+ the first result column of each row of the result set. The 2nd
+ argument is an optional value for use in a single-argument call
+ to Stmt.bind(). The 3rd argument may be any value suitable for
+ use as the 2nd argument to Stmt.get(). If a 3rd argument is
+ desired but no bind data are needed, pass `undefined` for the 2nd
+ argument.
+
+ If there are no result rows, an empty array is returned.
+ */
+ selectValues: function(sql,bind,asType){
+ const stmt = this.prepare(sql), rc = [];
+ try {
+ stmt.bind(bind);
+ while(stmt.step()) rc.push(stmt.get(0,asType));
+ stmt.reset(/*for INSERT...RETURNING locking case*/);
+ }finally{
+ stmt.finalize();
+ }
+ return rc;
+ },
+
+ /**
+ Prepares the given SQL, step()s it one time, and returns an
+ array containing the values of the first result row. If it has
+ no results, `undefined` is returned.
+
+ If passed a second argument other than `undefined`, it is
+ treated like an argument to Stmt.bind(), so may be any type
+ supported by that function.
+
+ Throws on error (e.g. malformed SQL).
+ */
+ selectArray: function(sql,bind){
+ return __selectFirstRow(this, sql, bind, []);
+ },
+
+ /**
+ Prepares the given SQL, step()s it one time, and returns an
+ object containing the key/value pairs of the first result
+ row. If it has no results, `undefined` is returned.
+
+ Note that the order of returned object's keys is not guaranteed
+ to be the same as the order of the fields in the query string.
+
+ If passed a second argument other than `undefined`, it is
+ treated like an argument to Stmt.bind(), so may be any type
+ supported by that function.
+
+ Throws on error (e.g. malformed SQL).
+ */
+ selectObject: function(sql,bind){
+ return __selectFirstRow(this, sql, bind, {});
+ },
+
+ /**
+ Runs the given SQL and returns an array of all results, with
+ each row represented as an array, as per the 'array' `rowMode`
+ option to `exec()`. An empty result set resolves
+ to an empty array. The second argument, if any, is treated as
+ the 'bind' option to a call to exec().
+ */
+ selectArrays: function(sql,bind){
+ return __selectAll(this, sql, bind, 'array');
+ },
+
+ /**
+ Works identically to selectArrays() except that each value
+ in the returned array is an object, as per the 'object' `rowMode`
+ option to `exec()`.
+ */
+ selectObjects: function(sql,bind){
+ return __selectAll(this, sql, bind, 'object');
+ },
+
+ /**
+ Returns the number of currently-opened Stmt handles for this db
+ handle, or 0 if this DB instance is closed. Note that only
+ handles prepared via this.prepare() are counted, and not
+ handles prepared using capi.sqlite3_prepare_v3() (or
+ equivalent).
+ */
+ openStatementCount: function(){
+ return this.pointer ? Object.keys(__stmtMap.get(this)).length : 0;
+ },
+
+ /**
+ Starts a transaction, calls the given callback, and then either
+ rolls back or commits the savepoint, depending on whether the
+ callback throws. The callback is passed this db object as its
+ only argument. On success, returns the result of the
+ callback. Throws on error.
+
+ Note that transactions may not be nested, so this will throw if
+ it is called recursively. For nested transactions, use the
+ savepoint() method or manually manage SAVEPOINTs using exec().
+
+ If called with 2 arguments, the first must be a keyword which
+ is legal immediately after a BEGIN statement, e.g. one of
+ "DEFERRED", "IMMEDIATE", or "EXCLUSIVE". Though the exact list
+ of supported keywords is not hard-coded here, in order to be
+ future-compatible, if the argument does not look like a single
+ keyword then an exception is triggered with a description of
+ the problem.
+ */
+ transaction: function(/* [beginQualifier,] */callback){
+ let opener = 'BEGIN';
+ if(arguments.length>1){
+ if(/[^a-zA-Z]/.test(arguments[0])){
+ toss3(capi.SQLITE_MISUSE, "Invalid argument for BEGIN qualifier.");
+ }
+ opener += ' '+arguments[0];
+ callback = arguments[1];
+ }
+ affirmDbOpen(this).exec(opener);
+ try {
+ const rc = callback(this);
+ this.exec("COMMIT");
+ return rc;
+ }catch(e){
+ this.exec("ROLLBACK");
+ throw e;
+ }
+ },
+
+ /**
+ This works similarly to transaction() but uses sqlite3's SAVEPOINT
+ feature. This function starts a savepoint (with an unspecified name)
+ and calls the given callback function, passing it this db object.
+ If the callback returns, the savepoint is released (committed). If
+ the callback throws, the savepoint is rolled back. If it does not
+ throw, it returns the result of the callback.
+ */
+ savepoint: function(callback){
+ affirmDbOpen(this).exec("SAVEPOINT oo1");
+ try {
+ const rc = callback(this);
+ this.exec("RELEASE oo1");
+ return rc;
+ }catch(e){
+ this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1");
+ throw e;
+ }
+ },
+
+ /**
+ A convenience form of DB.checkRc(this,resultCode). If it does
+ not throw, it returns this object.
+ */
+ checkRc: function(resultCode){
+ return checkSqlite3Rc(this, resultCode);
+ }
+ }/*DB.prototype*/;
+
+
+ /** Throws if the given Stmt has been finalized, else stmt is
+ returned. */
+ const affirmStmtOpen = function(stmt){
+ if(!stmt.pointer) toss3("Stmt has been closed.");
+ return stmt;
+ };
+
+ /** Returns an opaque truthy value from the BindTypes
+ enum if v's type is a valid bindable type, else
+ returns a falsy value. As a special case, a value of
+ undefined is treated as a bind type of null. */
+ const isSupportedBindType = function(v){
+ let t = BindTypes[(null===v||undefined===v) ? 'null' : typeof v];
+ switch(t){
+ case BindTypes.boolean:
+ case BindTypes.null:
+ case BindTypes.number:
+ case BindTypes.string:
+ return t;
+ case BindTypes.bigint:
+ if(wasm.bigIntEnabled) return t;
+ /* else fall through */
+ default:
+ return util.isBindableTypedArray(v) ? BindTypes.blob : undefined;
+ }
+ };
+
+ /**
+ If isSupportedBindType(v) returns a truthy value, this
+ function returns that value, else it throws.
+ */
+ const affirmSupportedBindType = function(v){
+ //sqlite3.config.log('affirmSupportedBindType',v);
+ return isSupportedBindType(v) || toss3("Unsupported bind() argument type:",typeof v);
+ };
+
+ /**
+ If key is a number and within range of stmt's bound parameter
+ count, key is returned.
+
+ If key is not a number then it is checked against named
+ parameters. If a match is found, its index is returned.
+
+ Else it throws.
+ */
+ const affirmParamIndex = function(stmt,key){
+ const n = ('number'===typeof key)
+ ? key : capi.sqlite3_bind_parameter_index(stmt.pointer, key);
+ if(0===n || !util.isInt32(n)){
+ toss3("Invalid bind() parameter name: "+key);
+ }
+ else if(n<1 || n>stmt.parameterCount) toss3("Bind index",key,"is out of range.");
+ return n;
+ };
+
+ /**
+ If stmt._lockedByExec is truthy, this throws an exception
+ complaining that the 2nd argument (an operation name,
+ e.g. "bind()") is not legal while the statement is "locked".
+ Locking happens before an exec()-like callback is passed a
+ statement, to ensure that the callback does not mutate or
+ finalize the statement. If it does not throw, it returns stmt.
+ */
+ const affirmNotLockedByExec = function(stmt,currentOpName){
+ if(stmt._lockedByExec){
+ toss3("Operation is illegal when statement is locked:",currentOpName);
+ }
+ return stmt;
+ };
+
+ /**
+ Binds a single bound parameter value on the given stmt at the
+ given index (numeric or named) using the given bindType (see
+ the BindTypes enum) and value. Throws on error. Returns stmt on
+ success.
+ */
+ const bindOne = function f(stmt,ndx,bindType,val){
+ affirmNotLockedByExec(affirmStmtOpen(stmt), 'bind()');
+ if(!f._){
+ f._tooBigInt = (v)=>toss3(
+ "BigInt value is too big to store without precision loss:", v
+ );
+ f._ = {
+ string: function(stmt, ndx, val, asBlob){
+ const [pStr, n] = wasm.allocCString(val, true);
+ const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text;
+ return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_WASM_DEALLOC);
+ }
+ };
+ }/* static init */
+ affirmSupportedBindType(val);
+ ndx = affirmParamIndex(stmt,ndx);
+ let rc = 0;
+ switch((null===val || undefined===val) ? BindTypes.null : bindType){
+ case BindTypes.null:
+ rc = capi.sqlite3_bind_null(stmt.pointer, ndx);
+ break;
+ case BindTypes.string:
+ rc = f._.string(stmt, ndx, val, false);
+ break;
+ case BindTypes.number: {
+ let m;
+ if(util.isInt32(val)) m = capi.sqlite3_bind_int;
+ else if('bigint'===typeof val){
+ if(!util.bigIntFits64(val)){
+ f._tooBigInt(val);
+ }else if(wasm.bigIntEnabled){
+ m = capi.sqlite3_bind_int64;
+ }else if(util.bigIntFitsDouble(val)){
+ val = Number(val);
+ m = capi.sqlite3_bind_double;
+ }else{
+ f._tooBigInt(val);
+ }
+ }else{ // !int32, !bigint
+ val = Number(val);
+ if(wasm.bigIntEnabled && Number.isInteger(val)){
+ m = capi.sqlite3_bind_int64;
+ }else{
+ m = capi.sqlite3_bind_double;
+ }
+ }
+ rc = m(stmt.pointer, ndx, val);
+ break;
+ }
+ case BindTypes.boolean:
+ rc = capi.sqlite3_bind_int(stmt.pointer, ndx, val ? 1 : 0);
+ break;
+ case BindTypes.blob: {
+ if('string'===typeof val){
+ rc = f._.string(stmt, ndx, val, true);
+ break;
+ }else if(val instanceof ArrayBuffer){
+ val = new Uint8Array(val);
+ }else if(!util.isBindableTypedArray(val)){
+ toss3("Binding a value as a blob requires",
+ "that it be a string, Uint8Array, Int8Array, or ArrayBuffer.");
+ }
+ const pBlob = wasm.alloc(val.byteLength || 1);
+ wasm.heap8().set(val.byteLength ? val : [0], pBlob)
+ rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
+ capi.SQLITE_WASM_DEALLOC);
+ break;
+ }
+ default:
+ sqlite3.config.warn("Unsupported bind() argument type:",val);
+ toss3("Unsupported bind() argument type: "+(typeof val));
+ }
+ if(rc) DB.checkRc(stmt.db.pointer, rc);
+ stmt._mayGet = false;
+ return stmt;
+ };
+
+ Stmt.prototype = {
+ /**
+ "Finalizes" this statement. This is a no-op if the statement
+ has already been finalized. Returns the result of
+ sqlite3_finalize() (0 on success, non-0 on error), or the
+ undefined value if the statement has already been
+ finalized. Regardless of success or failure, most methods in
+ this class will throw if called after this is.
+
+ This method always throws if called when it is illegal to do
+ so. Namely, when triggered via a per-row callback handler of a
+ DB.exec() call.
+ */
+ finalize: function(){
+ if(this.pointer){
+ affirmNotLockedByExec(this,'finalize()');
+ const rc = capi.sqlite3_finalize(this.pointer);
+ delete __stmtMap.get(this.db)[this.pointer];
+ __ptrMap.delete(this);
+ delete this._mayGet;
+ delete this.parameterCount;
+ delete this._lockedByExec;
+ delete this.db;
+ return rc;
+ }
+ },
+ /**
+ Clears all bound values. Returns this object. Throws if this
+ statement has been finalized or if modification of the
+ statement is currently illegal (e.g. in the per-row callback of
+ a DB.exec() call).
+ */
+ clearBindings: function(){
+ affirmNotLockedByExec(affirmStmtOpen(this), 'clearBindings()')
+ capi.sqlite3_clear_bindings(this.pointer);
+ this._mayGet = false;
+ return this;
+ },
+ /**
+ Resets this statement so that it may be step()ed again from the
+ beginning. Returns this object. Throws if this statement has
+ been finalized, if it may not legally be reset because it is
+ currently being used from a DB.exec() callback, or if the
+ underlying call to sqlite3_reset() returns non-0.
+
+ If passed a truthy argument then this.clearBindings() is
+ also called, otherwise any existing bindings, along with
+ any memory allocated for them, are retained.
+
+ In versions 3.42.0 and earlier, this function did not throw if
+ sqlite3_reset() returns non-0, but it was discovered that
+ throwing (or significant extra client-side code) is necessary
+ in order to avoid certain silent failure scenarios, as
+ discussed at:
+
+ https://sqlite.org/forum/forumpost/36f7a2e7494897df
+ */
+ reset: function(alsoClearBinds){
+ affirmNotLockedByExec(this,'reset()');
+ if(alsoClearBinds) this.clearBindings();
+ const rc = capi.sqlite3_reset(affirmStmtOpen(this).pointer);
+ this._mayGet = false;
+ checkSqlite3Rc(this.db, rc);
+ return this;
+ },
+ /**
+ Binds one or more values to its bindable parameters. It
+ accepts 1 or 2 arguments:
+
+ If passed a single argument, it must be either an array, an
+ object, or a value of a bindable type (see below).
+
+ If passed 2 arguments, the first one is the 1-based bind
+ index or bindable parameter name and the second one must be
+ a value of a bindable type.
+
+ Bindable value types:
+
+ - null is bound as NULL.
+
+ - undefined as a standalone value is a no-op intended to
+ simplify certain client-side use cases: passing undefined as
+ a value to this function will not actually bind anything and
+ this function will skip confirmation that binding is even
+ legal. (Those semantics simplify certain client-side uses.)
+ Conversely, a value of undefined as an array or object
+ property when binding an array/object (see below) is treated
+ the same as null.
+
+ - Numbers are bound as either doubles or integers: doubles if
+ they are larger than 32 bits, else double or int32, depending
+ on whether they have a fractional part. Booleans are bound as
+ integer 0 or 1. It is not expected the distinction of binding
+ doubles which have no fractional parts is integers is
+ significant for the majority of clients due to sqlite3's data
+ typing model. If [BigInt] support is enabled then this
+ routine will bind BigInt values as 64-bit integers if they'll
+ fit in 64 bits. If that support disabled, it will store the
+ BigInt as an int32 or a double if it can do so without loss
+ of precision. If the BigInt is _too BigInt_ then it will
+ throw.
+
+ - Strings are bound as strings (use bindAsBlob() to force
+ blob binding).
+
+ - Uint8Array, Int8Array, and ArrayBuffer instances are bound as
+ blobs.
+
+ If passed an array, each element of the array is bound at
+ the parameter index equal to the array index plus 1
+ (because arrays are 0-based but binding is 1-based).
+
+ If passed an object, each object key is treated as a
+ bindable parameter name. The object keys _must_ match any
+ bindable parameter names, including any `$`, `@`, or `:`
+ prefix. Because `$` is a legal identifier chararacter in
+ JavaScript, that is the suggested prefix for bindable
+ parameters: `stmt.bind({$a: 1, $b: 2})`.
+
+ It returns this object on success and throws on
+ error. Errors include:
+
+ - Any bind index is out of range, a named bind parameter
+ does not match, or this statement has no bindable
+ parameters.
+
+ - Any value to bind is of an unsupported type.
+
+ - Passed no arguments or more than two.
+
+ - The statement has been finalized.
+ */
+ bind: function(/*[ndx,] arg*/){
+ affirmStmtOpen(this);
+ let ndx, arg;
+ switch(arguments.length){
+ case 1: ndx = 1; arg = arguments[0]; break;
+ case 2: ndx = arguments[0]; arg = arguments[1]; break;
+ default: toss3("Invalid bind() arguments.");
+ }
+ if(undefined===arg){
+ /* It might seem intuitive to bind undefined as NULL
+ but this approach simplifies certain client-side
+ uses when passing on arguments between 2+ levels of
+ functions. */
+ return this;
+ }else if(!this.parameterCount){
+ toss3("This statement has no bindable parameters.");
+ }
+ this._mayGet = false;
+ if(null===arg){
+ /* bind NULL */
+ return bindOne(this, ndx, BindTypes.null, arg);
+ }
+ else if(Array.isArray(arg)){
+ /* bind each entry by index */
+ if(1!==arguments.length){
+ toss3("When binding an array, an index argument is not permitted.");
+ }
+ arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
+ return this;
+ }else if(arg instanceof ArrayBuffer){
+ arg = new Uint8Array(arg);
+ }
+ if('object'===typeof arg/*null was checked above*/
+ && !util.isBindableTypedArray(arg)){
+ /* Treat each property of arg as a named bound parameter. */
+ if(1!==arguments.length){
+ toss3("When binding an object, an index argument is not permitted.");
+ }
+ Object.keys(arg)
+ .forEach(k=>bindOne(this, k,
+ affirmSupportedBindType(arg[k]),
+ arg[k]));
+ return this;
+ }else{
+ return bindOne(this, ndx, affirmSupportedBindType(arg), arg);
+ }
+ toss3("Should not reach this point.");
+ },
+ /**
+ Special case of bind() which binds the given value using the
+ BLOB binding mechanism instead of the default selected one for
+ the value. The ndx may be a numbered or named bind index. The
+ value must be of type string, null/undefined (both get treated
+ as null), or a TypedArray of a type supported by the bind()
+ API. This API cannot bind numbers as blobs.
+
+ If passed a single argument, a bind index of 1 is assumed and
+ the first argument is the value.
+ */
+ bindAsBlob: function(ndx,arg){
+ affirmStmtOpen(this);
+ if(1===arguments.length){
+ arg = ndx;
+ ndx = 1;
+ }
+ const t = affirmSupportedBindType(arg);
+ if(BindTypes.string !== t && BindTypes.blob !== t
+ && BindTypes.null !== t){
+ toss3("Invalid value type for bindAsBlob()");
+ }
+ return bindOne(this, ndx, BindTypes.blob, arg);
+ },
+ /**
+ Steps the statement one time. If the result indicates that a
+ row of data is available, a truthy value is returned.
+ If no row of data is available, a falsy
+ value is returned. Throws on error.
+ */
+ step: function(){
+ affirmNotLockedByExec(this, 'step()');
+ const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer);
+ switch(rc){
+ case capi.SQLITE_DONE: return this._mayGet = false;
+ case capi.SQLITE_ROW: return this._mayGet = true;
+ default:
+ this._mayGet = false;
+ sqlite3.config.warn("sqlite3_step() rc=",rc,
+ capi.sqlite3_js_rc_str(rc),
+ "SQL =", capi.sqlite3_sql(this.pointer));
+ DB.checkRc(this.db.pointer, rc);
+ }
+ },
+ /**
+ Functions exactly like step() except that...
+
+ 1) On success, it calls this.reset() and returns this object.
+ 2) On error, it throws and does not call reset().
+
+ This is intended to simplify constructs like:
+
+ ```
+ for(...) {
+ stmt.bind(...).stepReset();
+ }
+ ```
+
+ Note that the reset() call makes it illegal to call this.get()
+ after the step.
+ */
+ stepReset: function(){
+ this.step();
+ return this.reset();
+ },
+ /**
+ Functions like step() except that it calls finalize() on this
+ statement immediately after stepping, even if the step() call
+ throws.
+
+ On success, it returns true if the step indicated that a row of
+ data was available, else it returns false.
+
+ This is intended to simplify use cases such as:
+
+ ```
+ aDb.prepare("insert into foo(a) values(?)").bind(123).stepFinalize();
+ ```
+ */
+ stepFinalize: function(){
+ try{
+ const rc = this.step();
+ this.reset(/*for INSERT...RETURNING locking case*/);
+ return rc;
+ }finally{
+ try{this.finalize()}
+ catch(e){/*ignored*/}
+ }
+ },
+ /**
+ Fetches the value from the given 0-based column index of
+ the current data row, throwing if index is out of range.
+
+ Requires that step() has just returned a truthy value, else
+ an exception is thrown.
+
+ By default it will determine the data type of the result
+ automatically. If passed a second arugment, it must be one
+ of the enumeration values for sqlite3 types, which are
+ defined as members of the sqlite3 module: SQLITE_INTEGER,
+ SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value,
+ except for undefined, will trigger an exception. Passing
+ undefined is the same as not passing a value. It is legal
+ to, e.g., fetch an integer value as a string, in which case
+ sqlite3 will convert the value to a string.
+
+ If ndx is an array, this function behaves a differently: it
+ assigns the indexes of the array, from 0 to the number of
+ result columns, to the values of the corresponding column,
+ and returns that array.
+
+ If ndx is a plain object, this function behaves even
+ differentlier: it assigns the properties of the object to
+ the values of their corresponding result columns.
+
+ Blobs are returned as Uint8Array instances.
+
+ Potential TODO: add type ID SQLITE_JSON, which fetches the
+ result as a string and passes it (if it's not null) to
+ JSON.parse(), returning the result of that. Until then,
+ getJSON() can be used for that.
+ */
+ get: function(ndx,asType){
+ if(!affirmStmtOpen(this)._mayGet){
+ toss3("Stmt.step() has not (recently) returned true.");
+ }
+ if(Array.isArray(ndx)){
+ let i = 0;
+ const n = this.columnCount;
+ while(i<n){
+ ndx[i] = this.get(i++);
+ }
+ return ndx;
+ }else if(ndx && 'object'===typeof ndx){
+ let i = 0;
+ const n = this.columnCount;
+ while(i<n){
+ ndx[capi.sqlite3_column_name(this.pointer,i)] = this.get(i++);
+ }
+ return ndx;
+ }
+ affirmColIndex(this, ndx);
+ switch(undefined===asType
+ ? capi.sqlite3_column_type(this.pointer, ndx)
+ : asType){
+ case capi.SQLITE_NULL: return null;
+ case capi.SQLITE_INTEGER:{
+ if(wasm.bigIntEnabled){
+ const rc = capi.sqlite3_column_int64(this.pointer, ndx);
+ if(rc>=Number.MIN_SAFE_INTEGER && rc<=Number.MAX_SAFE_INTEGER){
+ /* Coerce "normal" number ranges to normal number values,
+ and only return BigInt-type values for numbers out of this
+ range. */
+ return Number(rc).valueOf();
+ }
+ return rc;
+ }else{
+ const rc = capi.sqlite3_column_double(this.pointer, ndx);
+ if(rc>Number.MAX_SAFE_INTEGER || rc<Number.MIN_SAFE_INTEGER){
+ /* Throwing here is arguable but, since we're explicitly
+ extracting an SQLITE_INTEGER-type value, it seems fair to throw
+ if the extracted number is out of range for that type.
+ This policy may be laxened to simply pass on the number and
+ hope for the best, as the C API would do. */
+ toss3("Integer is out of range for JS integer range: "+rc);
+ }
+ //sqlite3.config.log("get integer rc=",rc,isInt32(rc));
+ return util.isInt32(rc) ? (rc | 0) : rc;
+ }
+ }
+ case capi.SQLITE_FLOAT:
+ return capi.sqlite3_column_double(this.pointer, ndx);
+ case capi.SQLITE_TEXT:
+ return capi.sqlite3_column_text(this.pointer, ndx);
+ case capi.SQLITE_BLOB: {
+ const n = capi.sqlite3_column_bytes(this.pointer, ndx),
+ ptr = capi.sqlite3_column_blob(this.pointer, ndx),
+ rc = new Uint8Array(n);
+ //heap = n ? wasm.heap8() : false;
+ if(n) rc.set(wasm.heap8u().slice(ptr, ptr+n), 0);
+ //for(let i = 0; i < n; ++i) rc[i] = heap[ptr + i];
+ if(n && this.db._blobXfer instanceof Array){
+ /* This is an optimization soley for the
+ Worker-based API. These values will be
+ transfered to the main thread directly
+ instead of being copied. */
+ this.db._blobXfer.push(rc.buffer);
+ }
+ return rc;
+ }
+ default: toss3("Don't know how to translate",
+ "type of result column #"+ndx+".");
+ }
+ toss3("Not reached.");
+ },
+ /** Equivalent to get(ndx) but coerces the result to an
+ integer. */
+ getInt: function(ndx){return this.get(ndx,capi.SQLITE_INTEGER)},
+ /** Equivalent to get(ndx) but coerces the result to a
+ float. */
+ getFloat: function(ndx){return this.get(ndx,capi.SQLITE_FLOAT)},
+ /** Equivalent to get(ndx) but coerces the result to a
+ string. */
+ getString: function(ndx){return this.get(ndx,capi.SQLITE_TEXT)},
+ /** Equivalent to get(ndx) but coerces the result to a
+ Uint8Array. */
+ getBlob: function(ndx){return this.get(ndx,capi.SQLITE_BLOB)},
+ /**
+ A convenience wrapper around get() which fetches the value
+ as a string and then, if it is not null, passes it to
+ JSON.parse(), returning that result. Throws if parsing
+ fails. If the result is null, null is returned. An empty
+ string, on the other hand, will trigger an exception.
+ */
+ getJSON: function(ndx){
+ const s = this.get(ndx, capi.SQLITE_STRING);
+ return null===s ? s : JSON.parse(s);
+ },
+ // Design note: the only reason most of these getters have a 'get'
+ // prefix is for consistency with getVALUE_TYPE(). The latter
+ // arguably really need that prefix for API readability and the
+ // rest arguably don't, but consistency is a powerful thing.
+ /**
+ Returns the result column name of the given index, or
+ throws if index is out of bounds or this statement has been
+ finalized. This can be used without having run step()
+ first.
+ */
+ getColumnName: function(ndx){
+ return capi.sqlite3_column_name(
+ affirmColIndex(affirmStmtOpen(this),ndx).pointer, ndx
+ );
+ },
+ /**
+ If this statement potentially has result columns, this function
+ returns an array of all such names. If passed an array, it is
+ used as the target and all names are appended to it. Returns
+ the target array. Throws if this statement cannot have result
+ columns. This object's columnCount property holds the number of
+ columns.
+ */
+ getColumnNames: function(tgt=[]){
+ affirmColIndex(affirmStmtOpen(this),0);
+ const n = this.columnCount;
+ for(let i = 0; i < n; ++i){
+ tgt.push(capi.sqlite3_column_name(this.pointer, i));
+ }
+ return tgt;
+ },
+ /**
+ If this statement has named bindable parameters and the
+ given name matches one, its 1-based bind index is
+ returned. If no match is found, 0 is returned. If it has no
+ bindable parameters, the undefined value is returned.
+ */
+ getParamIndex: function(name){
+ return (affirmStmtOpen(this).parameterCount
+ ? capi.sqlite3_bind_parameter_index(this.pointer, name)
+ : undefined);
+ }
+ }/*Stmt.prototype*/;
+
+ {/* Add the `pointer` property to DB and Stmt. */
+ const prop = {
+ enumerable: true,
+ get: function(){return __ptrMap.get(this)},
+ set: ()=>toss3("The pointer property is read-only.")
+ }
+ Object.defineProperty(Stmt.prototype, 'pointer', prop);
+ Object.defineProperty(DB.prototype, 'pointer', prop);
+ }
+ /**
+ Stmt.columnCount is an interceptor for sqlite3_column_count().
+
+ This requires an unfortunate performance hit compared to caching
+ columnCount when the Stmt is created/prepared (as was done in
+ SQLite <=3.42.0), but is necessary in order to handle certain
+ corner cases, as described in
+ https://sqlite.org/forum/forumpost/7774b773937cbe0a.
+ */
+ Object.defineProperty(Stmt.prototype, 'columnCount', {
+ enumerable: false,
+ get: function(){return capi.sqlite3_column_count(this.pointer)},
+ set: ()=>toss3("The columnCount property is read-only.")
+ });
+
+ /** The OO API's public namespace. */
+ sqlite3.oo1 = {
+ DB,
+ Stmt
+ }/*oo1 object*/;
+
+ if(util.isUIThread()){
+ /**
+ Functionally equivalent to DB(storageName,'c','kvvfs') except
+ that it throws if the given storage name is not one of 'local'
+ or 'session'.
+ */
+ sqlite3.oo1.JsStorageDb = function(storageName='session'){
+ if('session'!==storageName && 'local'!==storageName){
+ toss3("JsStorageDb db name must be one of 'session' or 'local'.");
+ }
+ dbCtorHelper.call(this, {
+ filename: storageName,
+ flags: 'c',
+ vfs: "kvvfs"
+ });
+ };
+ const jdb = sqlite3.oo1.JsStorageDb;
+ jdb.prototype = Object.create(DB.prototype);
+ /** Equivalent to sqlite3_js_kvvfs_clear(). */
+ jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
+ /**
+ Clears this database instance's storage or throws if this
+ instance has been closed. Returns the number of
+ database blocks which were cleaned up.
+ */
+ jdb.prototype.clearStorage = function(){
+ return jdb.clearStorage(affirmDbOpen(this).filename);
+ };
+ /** Equivalent to sqlite3_js_kvvfs_size(). */
+ jdb.storageSize = capi.sqlite3_js_kvvfs_size;
+ /**
+ Returns the _approximate_ number of bytes this database takes
+ up in its storage or throws if this instance has been closed.
+ */
+ jdb.prototype.storageSize = function(){
+ return jdb.storageSize(affirmDbOpen(this).filename);
+ };
+ }/*main-window-only bits*/
+
+});
+//#else
+/* Built with the omit-oo1 flag. */
+//#endif ifnot omit-oo1
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js
new file mode 100644
index 0000000..ef1154f
--- /dev/null
+++ b/ext/wasm/api/sqlite3-api-prologue.js
@@ -0,0 +1,2154 @@
+/*
+ 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 combined at build-time with other
+ related code, most notably a header and footer which wraps this
+ whole file into an Emscripten Module.postRun() handler. The sqlite3
+ JS API has no hard requirements on Emscripten and does not expose
+ any Emscripten APIs to clients. It is structured such that its build
+ can be tweaked to include it in arbitrary WASM environments which
+ can supply the necessary underlying features (e.g. a POSIX file I/O
+ layer).
+
+ Main project home page: https://sqlite.org
+
+ Documentation home page: https://sqlite.org/wasm
+*/
+
+/**
+ sqlite3ApiBootstrap() is the only global symbol persistently
+ exposed by this API. It is intended to be called one time at the
+ end of the API amalgamation process, passed configuration details
+ for the current environment, and then optionally be removed from
+ the global object using `delete globalThis.sqlite3ApiBootstrap`.
+
+ This function is not intended for client-level use. It is intended
+ for use in creating bundles configured for specific WASM
+ environments.
+
+ This function expects a configuration object, intended to abstract
+ away details specific to any given WASM environment, primarily so
+ that it can be used without any _direct_ dependency on
+ Emscripten. (Note the default values for the config object!) The
+ config object is only honored the first time this is
+ called. Subsequent calls ignore the argument and return the same
+ (configured) object which gets initialized by the first call. This
+ function will throw if any of the required config options are
+ missing.
+
+ The config object properties include:
+
+ - `exports`[^1]: the "exports" object for the current WASM
+ environment. In an Emscripten-based build, this should be set to
+ `Module['asm']`.
+
+ - `memory`[^1]: optional WebAssembly.Memory object, defaulting to
+ `exports.memory`. In Emscripten environments this should be set
+ to `Module.wasmMemory` if the build uses `-sIMPORTED_MEMORY`, or be
+ left undefined/falsy to default to `exports.memory` when using
+ WASM-exported memory.
+
+ - `bigIntEnabled`: true if BigInt support is enabled. Defaults to
+ true if `globalThis.BigInt64Array` is available, else false. Some APIs
+ will throw exceptions if called without BigInt support, as BigInt
+ is required for marshalling C-side int64 into and out of JS.
+ (Sidebar: it is technically possible to add int64 support via
+ marshalling of int32 pairs, but doing so is unduly invasive.)
+
+ - `allocExportName`: the name of the function, in `exports`, of the
+ `malloc(3)`-compatible routine for the WASM environment. Defaults
+ to `"sqlite3_malloc"`. Beware that using any allocator other than
+ sqlite3_malloc() may require care in certain client-side code
+ regarding which allocator is uses. Notably, sqlite3_deserialize()
+ and sqlite3_serialize() can only safely use memory from different
+ allocators under very specific conditions. The canonical builds
+ of this API guaranty that `sqlite3_malloc()` is the JS-side
+ allocator implementation.
+
+ - `deallocExportName`: the name of the function, in `exports`, of
+ the `free(3)`-compatible routine for the WASM
+ environment. Defaults to `"sqlite3_free"`.
+
+ - `reallocExportName`: the name of the function, in `exports`, of
+ the `realloc(3)`-compatible routine for the WASM
+ environment. Defaults to `"sqlite3_realloc"`.
+
+ - `debug`, `log`, `warn`, and `error` may be functions equivalent
+ to the like-named methods of the global `console` object. By
+ default, these map directly to their `console` counterparts, but
+ can be replaced with (e.g.) empty functions to squelch all such
+ output.
+
+ - `wasmfsOpfsDir`[^1]: Specifies the "mount point" of the OPFS-backed
+ filesystem in WASMFS-capable builds.
+
+
+ [^1] = This property may optionally be a function, in which case
+ this function calls that function to fetch the value,
+ enabling delayed evaluation.
+
+ The returned object is the top-level sqlite3 namespace object.
+
+*/
+'use strict';
+globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
+ apiConfig = (globalThis.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig)
+){
+ if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */
+ console.warn("sqlite3ApiBootstrap() called multiple times.",
+ "Config and external initializers are ignored on calls after the first.");
+ return sqlite3ApiBootstrap.sqlite3;
+ }
+ const config = Object.assign(Object.create(null),{
+ exports: undefined,
+ memory: undefined,
+ bigIntEnabled: (()=>{
+ if('undefined'!==typeof Module){
+ /* Emscripten module will contain HEAPU64 when built with
+ -sWASM_BIGINT=1, else it will not. */
+ return !!Module.HEAPU64;
+ }
+ return !!globalThis.BigInt64Array;
+ })(),
+ debug: console.debug.bind(console),
+ warn: console.warn.bind(console),
+ error: console.error.bind(console),
+ log: console.log.bind(console),
+ wasmfsOpfsDir: '/opfs',
+ /**
+ useStdAlloc is just for testing allocator discrepancies. The
+ docs guarantee that this is false in the canonical builds. For
+ 99% of purposes it doesn't matter which allocators we use, but
+ it becomes significant with, e.g., sqlite3_deserialize() and
+ certain wasm.xWrap.resultAdapter()s.
+ */
+ useStdAlloc: false
+ }, apiConfig || {});
+
+ Object.assign(config, {
+ allocExportName: config.useStdAlloc ? 'malloc' : 'sqlite3_malloc',
+ deallocExportName: config.useStdAlloc ? 'free' : 'sqlite3_free',
+ reallocExportName: config.useStdAlloc ? 'realloc' : 'sqlite3_realloc'
+ }, config);
+
+ [
+ // If any of these config options are functions, replace them with
+ // the result of calling that function...
+ 'exports', 'memory', 'wasmfsOpfsDir'
+ ].forEach((k)=>{
+ if('function' === typeof config[k]){
+ config[k] = config[k]();
+ }
+ });
+ /**
+ The main sqlite3 binding API gets installed into this object,
+ mimicking the C API as closely as we can. The numerous members
+ names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as
+ possible, identically to the C-native counterparts, as documented at:
+
+ https://www.sqlite.org/c3ref/intro.html
+
+ A very few exceptions require an additional level of proxy
+ function or may otherwise require special attention in the WASM
+ environment, and all such cases are documented somewhere below
+ in this file or in sqlite3-api-glue.js. capi members which are
+ not documented are installed as 1-to-1 proxies for their
+ C-side counterparts.
+ */
+ const capi = Object.create(null);
+ /**
+ Holds state which are specific to the WASM-related
+ infrastructure and glue code.
+
+ Note that a number of members of this object are injected
+ dynamically after the api object is fully constructed, so
+ not all are documented in this file.
+ */
+ const wasm = Object.create(null);
+
+ /** Internal helper for SQLite3Error ctor. */
+ const __rcStr = (rc)=>{
+ return (capi.sqlite3_js_rc_str && capi.sqlite3_js_rc_str(rc))
+ || ("Unknown result code #"+rc);
+ };
+
+ /** Internal helper for SQLite3Error ctor. */
+ const __isInt = (n)=>'number'===typeof n && n===(n | 0);
+
+ /**
+ An Error subclass specifically for reporting DB-level errors and
+ enabling clients to unambiguously identify such exceptions.
+ The C-level APIs never throw, but some of the higher-level
+ C-style APIs do and the object-oriented APIs use exceptions
+ exclusively to report errors.
+ */
+ class SQLite3Error extends Error {
+ /**
+ Constructs this object with a message depending on its arguments:
+
+ If its first argument is an integer, it is assumed to be
+ an SQLITE_... result code and it is passed to
+ sqlite3.capi.sqlite3_js_rc_str() to stringify it.
+
+ If called with exactly 2 arguments and the 2nd is an object,
+ that object is treated as the 2nd argument to the parent
+ constructor.
+
+ The exception's message is created by concatenating its
+ arguments with a space between each, except for the
+ two-args-with-an-objec form and that the first argument will
+ get coerced to a string, as described above, if it's an
+ integer.
+
+ If passed an integer first argument, the error object's
+ `resultCode` member will be set to the given integer value,
+ else it will be set to capi.SQLITE_ERROR.
+ */
+ constructor(...args){
+ let rc;
+ if(args.length){
+ if(__isInt(args[0])){
+ rc = args[0];
+ if(1===args.length){
+ super(__rcStr(args[0]));
+ }else{
+ const rcStr = __rcStr(rc);
+ if('object'===typeof args[1]){
+ super(rcStr,args[1]);
+ }else{
+ args[0] = rcStr+':';
+ super(args.join(' '));
+ }
+ }
+ }else{
+ if(2===args.length && 'object'===typeof args[1]){
+ super(...args);
+ }else{
+ super(args.join(' '));
+ }
+ }
+ }
+ this.resultCode = rc || capi.SQLITE_ERROR;
+ this.name = 'SQLite3Error';
+ }
+ };
+
+ /**
+ Functionally equivalent to the SQLite3Error constructor but may
+ be used as part of an expression, e.g.:
+
+ ```
+ return someFunction(x) || SQLite3Error.toss(...);
+ ```
+ */
+ SQLite3Error.toss = (...args)=>{
+ throw new SQLite3Error(...args);
+ };
+ const toss3 = SQLite3Error.toss;
+
+ if(config.wasmfsOpfsDir && !/^\/[^/]+$/.test(config.wasmfsOpfsDir)){
+ toss3("config.wasmfsOpfsDir must be falsy or in the form '/dir-name'.");
+ }
+
+ /**
+ Returns true if n is a 32-bit (signed) integer, else
+ false. This is used for determining when we need to switch to
+ double-type DB operations for integer values in order to keep
+ more precision.
+ */
+ const isInt32 = (n)=>{
+ return ('bigint'!==typeof n /*TypeError: can't convert BigInt to number*/)
+ && !!(n===(n|0) && n<=2147483647 && n>=-2147483648);
+ };
+ /**
+ Returns true if the given BigInt value is small enough to fit
+ into an int64 value, else false.
+ */
+ const bigIntFits64 = function f(b){
+ if(!f._max){
+ f._max = BigInt("0x7fffffffffffffff");
+ f._min = ~f._max;
+ }
+ return b >= f._min && b <= f._max;
+ };
+
+ /**
+ Returns true if the given BigInt value is small enough to fit
+ into an int32, else false.
+ */
+ const bigIntFits32 = (b)=>(b >= (-0x7fffffffn - 1n) && b <= 0x7fffffffn);
+
+ /**
+ Returns true if the given BigInt value is small enough to fit
+ into a double value without loss of precision, else false.
+ */
+ const bigIntFitsDouble = function f(b){
+ if(!f._min){
+ f._min = Number.MIN_SAFE_INTEGER;
+ f._max = Number.MAX_SAFE_INTEGER;
+ }
+ return b >= f._min && b <= f._max;
+ };
+
+ /** Returns v if v appears to be a TypedArray, else false. */
+ const isTypedArray = (v)=>{
+ return (v && v.constructor && isInt32(v.constructor.BYTES_PER_ELEMENT)) ? v : false;
+ };
+
+
+ /** Internal helper to use in operations which need to distinguish
+ between TypedArrays which are backed by a SharedArrayBuffer
+ from those which are not. */
+ const __SAB = ('undefined'===typeof SharedArrayBuffer)
+ ? function(){} : SharedArrayBuffer;
+ /** Returns true if the given TypedArray object is backed by a
+ SharedArrayBuffer, else false. */
+ const isSharedTypedArray = (aTypedArray)=>(aTypedArray.buffer instanceof __SAB);
+
+ /**
+ Returns either aTypedArray.slice(begin,end) (if
+ aTypedArray.buffer is a SharedArrayBuffer) or
+ aTypedArray.subarray(begin,end) (if it's not).
+
+ This distinction is important for APIs which don't like to
+ work on SABs, e.g. TextDecoder, and possibly for our
+ own APIs which work on memory ranges which "might" be
+ modified by other threads while they're working.
+ */
+ const typedArrayPart = (aTypedArray, begin, end)=>{
+ return isSharedTypedArray(aTypedArray)
+ ? aTypedArray.slice(begin, end)
+ : aTypedArray.subarray(begin, end);
+ };
+
+ /**
+ Returns true if v appears to be one of our bind()-able TypedArray
+ types: Uint8Array or Int8Array or ArrayBuffer. Support for
+ TypedArrays with element sizes >1 is a potential TODO just
+ waiting on a use case to justify them. Until then, their `buffer`
+ property can be used to pass them as an ArrayBuffer. If it's not
+ a bindable array type, a falsy value is returned.
+ */
+ const isBindableTypedArray = (v)=>{
+ return v && (v instanceof Uint8Array
+ || v instanceof Int8Array
+ || v instanceof ArrayBuffer);
+ };
+
+ /**
+ Returns true if v appears to be one of the TypedArray types
+ which is legal for holding SQL code (as opposed to binary blobs).
+
+ Currently this is the same as isBindableTypedArray() but it
+ seems likely that we'll eventually want to add Uint32Array
+ and friends to the isBindableTypedArray() list but not to the
+ isSQLableTypedArray() list.
+ */
+ const isSQLableTypedArray = (v)=>{
+ return v && (v instanceof Uint8Array
+ || v instanceof Int8Array
+ || v instanceof ArrayBuffer);
+ };
+
+ /** Returns true if isBindableTypedArray(v) does, else throws with a message
+ that v is not a supported TypedArray value. */
+ const affirmBindableTypedArray = (v)=>{
+ return isBindableTypedArray(v)
+ || toss3("Value is not of a supported TypedArray type.");
+ };
+
+ const utf8Decoder = new TextDecoder('utf-8');
+
+ /**
+ Uses TextDecoder to decode the given half-open range of the
+ given TypedArray to a string. This differs from a simple
+ call to TextDecoder in that it accounts for whether the
+ first argument is backed by a SharedArrayBuffer or not,
+ and can work more efficiently if it's not (TextDecoder
+ refuses to act upon an SAB).
+ */
+ const typedArrayToString = function(typedArray, begin, end){
+ return utf8Decoder.decode(typedArrayPart(typedArray, begin,end));
+ };
+
+ /**
+ If v is-a Array, its join("") result is returned. If
+ isSQLableTypedArray(v) is true then typedArrayToString(v) is
+ returned. If it looks like a WASM pointer, wasm.cstrToJs(v) is
+ returned. Else v is returned as-is.
+ */
+ const flexibleString = function(v){
+ if(isSQLableTypedArray(v)){
+ return typedArrayToString(
+ (v instanceof ArrayBuffer) ? new Uint8Array(v) : v
+ );
+ }
+ else if(Array.isArray(v)) return v.join("");
+ else if(wasm.isPtr(v)) v = wasm.cstrToJs(v);
+ return v;
+ };
+
+ /**
+ An Error subclass specifically for reporting Wasm-level malloc()
+ failure and enabling clients to unambiguously identify such
+ exceptions.
+ */
+ class WasmAllocError extends Error {
+ /**
+ If called with 2 arguments and the 2nd one is an object, it
+ behaves like the Error constructor, else it concatenates all
+ arguments together with a single space between each to
+ construct an error message string. As a special case, if
+ called with no arguments then it uses a default error
+ message.
+ */
+ constructor(...args){
+ if(2===args.length && 'object'===typeof args[1]){
+ super(...args);
+ }else if(args.length){
+ super(args.join(' '));
+ }else{
+ super("Allocation failed.");
+ }
+ this.resultCode = capi.SQLITE_NOMEM;
+ this.name = 'WasmAllocError';
+ }
+ };
+ /**
+ Functionally equivalent to the WasmAllocError constructor but may
+ be used as part of an expression, e.g.:
+
+ ```
+ return someAllocatingFunction(x) || WasmAllocError.toss(...);
+ ```
+ */
+ WasmAllocError.toss = (...args)=>{
+ throw new WasmAllocError(...args);
+ };
+
+ Object.assign(capi, {
+ /**
+ sqlite3_bind_blob() works exactly like its C counterpart unless
+ its 3rd argument is one of:
+
+ - JS string: the 3rd argument is converted to a C string, the
+ 4th argument is ignored, and the C-string's length is used
+ in its place.
+
+ - Array: converted to a string as defined for "flexible
+ strings" and then it's treated as a JS string.
+
+ - Int8Array or Uint8Array: wasm.allocFromTypedArray() is used to
+ conver the memory to the WASM heap. If the 4th argument is
+ 0 or greater, it is used as-is, otherwise the array's byteLength
+ value is used. This is an exception to the C API's undefined
+ behavior for a negative 4th argument, but results are undefined
+ if the given 4th argument value is greater than the byteLength
+ of the input array.
+
+ - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and
+ treated as that type.
+
+ In all of those cases, the final argument (destructor) is
+ ignored and capi.SQLITE_WASM_DEALLOC is assumed.
+
+ A 3rd argument of `null` is treated as if it were a WASM pointer
+ of 0.
+
+ If the 3rd argument is neither a WASM pointer nor one of the
+ above-described types, capi.SQLITE_MISUSE is returned.
+
+ The first argument may be either an `sqlite3_stmt*` WASM
+ pointer or an sqlite3.oo1.Stmt instance.
+
+ For consistency with the C API, it requires the same number of
+ arguments. It returns capi.SQLITE_MISUSE if passed any other
+ argument count.
+ */
+ sqlite3_bind_blob: undefined/*installed later*/,
+
+ /**
+ sqlite3_bind_text() works exactly like its C counterpart unless
+ its 3rd argument is one of:
+
+ - JS string: the 3rd argument is converted to a C string, the
+ 4th argument is ignored, and the C-string's length is used
+ in its place.
+
+ - Array: converted to a string as defined for "flexible
+ strings". The 4th argument is ignored and a value of -1
+ is assumed.
+
+ - Int8Array or Uint8Array: is assumed to contain UTF-8 text, is
+ converted to a string. The 4th argument is ignored, replaced
+ by the array's byteLength value.
+
+ - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and
+ treated as that type.
+
+ In each of those cases, the final argument (text destructor) is
+ ignored and capi.SQLITE_WASM_DEALLOC is assumed.
+
+ A 3rd argument of `null` is treated as if it were a WASM pointer
+ of 0.
+
+ If the 3rd argument is neither a WASM pointer nor one of the
+ above-described types, capi.SQLITE_MISUSE is returned.
+
+ The first argument may be either an `sqlite3_stmt*` WASM
+ pointer or an sqlite3.oo1.Stmt instance.
+
+ For consistency with the C API, it requires the same number of
+ arguments. It returns capi.SQLITE_MISUSE if passed any other
+ argument count.
+
+ If client code needs to bind partial strings, it needs to
+ either parcel the string up before passing it in here or it
+ must pass in a WASM pointer for the 3rd argument and a valid
+ 4th-argument value, taking care not to pass a value which
+ truncates a multi-byte UTF-8 character. When passing
+ WASM-format strings, it is important that the final argument be
+ valid or unexpected content can result can result, or even a
+ crash if the application reads past the WASM heap bounds.
+ */
+ sqlite3_bind_text: undefined/*installed later*/,
+
+ /**
+ sqlite3_create_function_v2() differs from its native
+ counterpart only in the following ways:
+
+ 1) The fourth argument (`eTextRep`) argument must not specify
+ any encoding other than sqlite3.SQLITE_UTF8. The JS API does not
+ currently support any other encoding and likely never
+ will. This function does not replace that argument on its own
+ because it may contain other flags. As a special case, if
+ the bottom 4 bits of that argument are 0, SQLITE_UTF8 is
+ assumed.
+
+ 2) Any of the four final arguments may be either WASM pointers
+ (assumed to be function pointers) or JS Functions. In the
+ latter case, each gets bound to WASM using
+ sqlite3.capi.wasm.installFunction() and that wrapper is passed
+ on to the native implementation.
+
+ For consistency with the C API, it requires the same number of
+ arguments. It returns capi.SQLITE_MISUSE if passed any other
+ argument count.
+
+ The semantics of JS functions are:
+
+ xFunc: is passed `(pCtx, ...values)`. Its return value becomes
+ the new SQL function's result.
+
+ xStep: is passed `(pCtx, ...values)`. Its return value is
+ ignored.
+
+ xFinal: is passed `(pCtx)`. Its return value becomes the new
+ aggregate SQL function's result.
+
+ xDestroy: is passed `(void*)`. Its return value is ignored. The
+ pointer passed to it is the one from the 5th argument to
+ sqlite3_create_function_v2().
+
+ Note that:
+
+ - `pCtx` in the above descriptions is a `sqlite3_context*`. At
+ least 99 times out of a hundred, that initial argument will
+ be irrelevant for JS UDF bindings, but it needs to be there
+ so that the cases where it _is_ relevant, in particular with
+ window and aggregate functions, have full access to the
+ lower-level sqlite3 APIs.
+
+ - When wrapping JS functions, the remaining arguments are passd
+ to them as positional arguments, not as an array of
+ arguments, because that allows callback definitions to be
+ more JS-idiomatic than C-like. For example `(pCtx,a,b)=>a+b`
+ is more intuitive and legible than
+ `(pCtx,args)=>args[0]+args[1]`. For cases where an array of
+ arguments would be more convenient, the callbacks simply need
+ to be declared like `(pCtx,...args)=>{...}`, in which case
+ `args` will be an array.
+
+ - If a JS wrapper throws, it gets translated to
+ sqlite3_result_error() or sqlite3_result_error_nomem(),
+ depending on whether the exception is an
+ sqlite3.WasmAllocError object or not.
+
+ - When passing on WASM function pointers, arguments are _not_
+ converted or reformulated. They are passed on as-is in raw
+ pointer form using their native C signatures. Only JS
+ functions passed in to this routine, and thus wrapped by this
+ routine, get automatic conversions of arguments and result
+ values. The routines which perform those conversions are
+ exposed for client-side use as
+ sqlite3_create_function_v2.convertUdfArgs() and
+ sqlite3_create_function_v2.setUdfResult(). sqlite3_create_function()
+ and sqlite3_create_window_function() have those same methods.
+
+ For xFunc(), xStep(), and xFinal():
+
+ - When called from SQL, arguments to the UDF, and its result,
+ will be converted between JS and SQL with as much fidelity as
+ is feasible, triggering an exception if a type conversion
+ cannot be determined. Some freedom is afforded to numeric
+ conversions due to friction between the JS and C worlds:
+ integers which are larger than 32 bits may be treated as
+ doubles or BigInts.
+
+ If any JS-side bound functions throw, those exceptions are
+ intercepted and converted to database-side errors with the
+ exception of xDestroy(): any exception from it is ignored,
+ possibly generating a console.error() message. Destructors
+ must not throw.
+
+ Once installed, there is currently no way to uninstall the
+ automatically-converted WASM-bound JS functions from WASM. They
+ can be uninstalled from the database as documented in the C
+ API, but this wrapper currently has no infrastructure in place
+ to also free the WASM-bound JS wrappers, effectively resulting
+ in a memory leak if the client uninstalls the UDF. Improving that
+ is a potential TODO, but removing client-installed UDFs is rare
+ in practice. If this factor is relevant for a given client,
+ they can create WASM-bound JS functions themselves, hold on to their
+ pointers, and pass the pointers in to here. Later on, they can
+ free those pointers (using `wasm.uninstallFunction()` or
+ equivalent).
+
+ C reference: https://www.sqlite.org/c3ref/create_function.html
+
+ Maintenance reminder: the ability to add new
+ WASM-accessible functions to the runtime requires that the
+ WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH`
+ flag.
+ */
+ sqlite3_create_function_v2: (
+ pDb, funcName, nArg, eTextRep, pApp,
+ xFunc, xStep, xFinal, xDestroy
+ )=>{/*installed later*/},
+ /**
+ Equivalent to passing the same arguments to
+ sqlite3_create_function_v2(), with 0 as the final argument.
+ */
+ sqlite3_create_function: (
+ pDb, funcName, nArg, eTextRep, pApp,
+ xFunc, xStep, xFinal
+ )=>{/*installed later*/},
+ /**
+ The sqlite3_create_window_function() JS wrapper differs from
+ its native implementation in the exact same way that
+ sqlite3_create_function_v2() does. The additional function,
+ xInverse(), is treated identically to xStep() by the wrapping
+ layer.
+ */
+ sqlite3_create_window_function: (
+ pDb, funcName, nArg, eTextRep, pApp,
+ xStep, xFinal, xValue, xInverse, xDestroy
+ )=>{/*installed later*/},
+ /**
+ The sqlite3_prepare_v3() binding handles two different uses
+ with differing JS/WASM semantics:
+
+ 1) sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt , null)
+
+ 2) sqlite3_prepare_v3(pDb, sqlPointer, sqlByteLen, prepFlags, ppStmt, sqlPointerToPointer)
+
+ Note that the SQL length argument (the 3rd argument) must, for
+ usage (1), always be negative because it must be a byte length
+ and that value is expensive to calculate from JS (where only
+ the character length of strings is readily available). It is
+ retained in this API's interface for code/documentation
+ compatibility reasons but is currently _always_ ignored. With
+ usage (2), the 3rd argument is used as-is but is is still
+ critical that the C-style input string (2nd argument) be
+ terminated with a 0 byte.
+
+ In usage (1), the 2nd argument must be of type string,
+ Uint8Array, Int8Array, or ArrayBuffer (all of which are assumed
+ to hold SQL). If it is, this function assumes case (1) and
+ calls the underyling C function with the equivalent of:
+
+ (pDb, sqlAsString, -1, prepFlags, ppStmt, null)
+
+ The `pzTail` argument is ignored in this case because its
+ result is meaningless when a string-type value is passed
+ through: the string goes through another level of internal
+ conversion for WASM's sake and the result pointer would refer
+ to that transient conversion's memory, not the passed-in
+ string.
+
+ If the sql argument is not a string, it must be a _pointer_ to
+ a NUL-terminated string which was allocated in the WASM memory
+ (e.g. using capi.wasm.alloc() or equivalent). In that case,
+ the final argument may be 0/null/undefined or must be a pointer
+ to which the "tail" of the compiled SQL is written, as
+ documented for the C-side sqlite3_prepare_v3(). In case (2),
+ the underlying C function is called with the equivalent of:
+
+ (pDb, sqlAsPointer, sqlByteLen, prepFlags, ppStmt, pzTail)
+
+ It returns its result and compiled statement as documented in
+ the C API. Fetching the output pointers (5th and 6th
+ parameters) requires using `capi.wasm.peek()` (or
+ equivalent) and the `pzTail` will point to an address relative to
+ the `sqlAsPointer` value.
+
+ If passed an invalid 2nd argument type, this function will
+ return SQLITE_MISUSE and sqlite3_errmsg() will contain a string
+ describing the problem.
+
+ Side-note: if given an empty string, or one which contains only
+ comments or an empty SQL expression, 0 is returned but the result
+ output pointer will be NULL.
+ */
+ sqlite3_prepare_v3: (dbPtr, sql, sqlByteLen, prepFlags,
+ stmtPtrPtr, strPtrPtr)=>{}/*installed later*/,
+
+ /**
+ Equivalent to calling sqlite3_prapare_v3() with 0 as its 4th argument.
+ */
+ sqlite3_prepare_v2: (dbPtr, sql, sqlByteLen,
+ stmtPtrPtr,strPtrPtr)=>{}/*installed later*/,
+
+ /**
+ This binding enables the callback argument to be a JavaScript.
+
+ If the callback is a function, then for the duration of the
+ sqlite3_exec() call, it installs a WASM-bound function which
+ acts as a proxy for the given callback. That proxy will also
+ perform a conversion of the callback's arguments from
+ `(char**)` to JS arrays of strings. However, for API
+ consistency's sake it will still honor the C-level callback
+ parameter order and will call it like:
+
+ `callback(pVoid, colCount, listOfValues, listOfColNames)`
+
+ If the callback is not a JS function then this binding performs
+ no translation of the callback, but the sql argument is still
+ converted to a WASM string for the call using the
+ "string:flexible" argument converter.
+ */
+ sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/,
+
+ /**
+ If passed a single argument which appears to be a byte-oriented
+ TypedArray (Int8Array or Uint8Array), this function treats that
+ TypedArray as an output target, fetches `theArray.byteLength`
+ bytes of randomness, and populates the whole array with it. As
+ a special case, if the array's length is 0, this function
+ behaves as if it were passed (0,0). When called this way, it
+ returns its argument, else it returns the `undefined` value.
+
+ If called with any other arguments, they are passed on as-is
+ to the C API. Results are undefined if passed any incompatible
+ values.
+ */
+ sqlite3_randomness: (n, outPtr)=>{/*installed later*/},
+ }/*capi*/);
+
+ /**
+ Various internal-use utilities are added here as needed. They
+ are bound to an object only so that we have access to them in
+ the differently-scoped steps of the API bootstrapping
+ process. At the end of the API setup process, this object gets
+ removed. These are NOT part of the public API.
+ */
+ const util = {
+ affirmBindableTypedArray, flexibleString,
+ bigIntFits32, bigIntFits64, bigIntFitsDouble,
+ isBindableTypedArray,
+ isInt32, isSQLableTypedArray, isTypedArray,
+ typedArrayToString,
+ isUIThread: ()=>(globalThis.window===globalThis && !!globalThis.document),
+ // is this true for ESM?: 'undefined'===typeof WorkerGlobalScope
+ isSharedTypedArray,
+ toss: function(...args){throw new Error(args.join(' '))},
+ toss3,
+ typedArrayPart,
+ /**
+ Given a byte array or ArrayBuffer, this function throws if the
+ lead bytes of that buffer do not hold a SQLite3 database header,
+ else it returns without side effects.
+
+ Added in 3.44.
+ */
+ affirmDbHeader: function(bytes){
+ if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+ const header = "SQLite format 3";
+ if( header.length > bytes.byteLength ){
+ toss3("Input does not contain an SQLite3 database header.");
+ }
+ for(let i = 0; i < header.length; ++i){
+ if( header.charCodeAt(i) !== bytes[i] ){
+ toss3("Input does not contain an SQLite3 database header.");
+ }
+ }
+ },
+ /**
+ Given a byte array or ArrayBuffer, this function throws if the
+ database does not, at a cursory glance, appear to be an SQLite3
+ database. It only examines the size and header, but further
+ checks may be added in the future.
+
+ Added in 3.44.
+ */
+ affirmIsDb: function(bytes){
+ if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+ const n = bytes.byteLength;
+ if(n<512 || n%512!==0) {
+ toss3("Byte array size",n,"is invalid for an SQLite3 db.");
+ }
+ util.affirmDbHeader(bytes);
+ }
+ }/*util*/;
+
+ Object.assign(wasm, {
+ /**
+ Emscripten APIs have a deep-seated assumption that all pointers
+ are 32 bits. We'll remain optimistic that that won't always be
+ the case and will use this constant in places where we might
+ otherwise use a hard-coded 4.
+ */
+ ptrSizeof: config.wasmPtrSizeof || 4,
+ /**
+ The WASM IR (Intermediate Representation) value for
+ pointer-type values. It MUST refer to a value type of the
+ size described by this.ptrSizeof.
+ */
+ ptrIR: config.wasmPtrIR || "i32",
+ /**
+ True if BigInt support was enabled via (e.g.) the
+ Emscripten -sWASM_BIGINT flag, else false. When
+ enabled, certain 64-bit sqlite3 APIs are enabled which
+ are not otherwise enabled due to JS/WASM int64
+ impedence mismatches.
+ */
+ bigIntEnabled: !!config.bigIntEnabled,
+ /**
+ The symbols exported by the WASM environment.
+ */
+ exports: config.exports
+ || toss3("Missing API config.exports (WASM module exports)."),
+
+ /**
+ When Emscripten compiles with `-sIMPORTED_MEMORY`, it
+ initalizes the heap and imports it into wasm, as opposed to
+ the other way around. In this case, the memory is not
+ available via this.exports.memory.
+ */
+ memory: config.memory || config.exports['memory']
+ || toss3("API config object requires a WebAssembly.Memory object",
+ "in either config.exports.memory (exported)",
+ "or config.memory (imported)."),
+
+ /**
+ The API's primary point of access to the WASM-side memory
+ allocator. Works like sqlite3_malloc() but throws a
+ WasmAllocError if allocation fails. It is important that any
+ code which might pass through the sqlite3 C API NOT throw and
+ must instead return SQLITE_NOMEM (or equivalent, depending on
+ the context).
+
+ Very few cases in the sqlite3 JS APIs can result in
+ client-defined functions propagating exceptions via the C-style
+ API. Most notably, this applies to WASM-bound JS functions
+ which are created directly by clients and passed on _as WASM
+ function pointers_ to functions such as
+ sqlite3_create_function_v2(). Such bindings created
+ transparently by this API will automatically use wrappers which
+ catch exceptions and convert them to appropriate error codes.
+
+ For cases where non-throwing allocation is required, use
+ this.alloc.impl(), which is direct binding of the
+ underlying C-level allocator.
+
+ Design note: this function is not named "malloc" primarily
+ because Emscripten uses that name and we wanted to avoid any
+ confusion early on in this code's development, when it still
+ had close ties to Emscripten's glue code.
+ */
+ alloc: undefined/*installed later*/,
+
+ /**
+ Rarely necessary in JS code, this routine works like
+ sqlite3_realloc(M,N), where M is either NULL or a pointer
+ obtained from this function or this.alloc() and N is the number
+ of bytes to reallocate the block to. Returns a pointer to the
+ reallocated block or 0 if allocation fails.
+
+ If M is NULL and N is positive, this behaves like
+ this.alloc(N). If N is 0, it behaves like this.dealloc().
+ Results are undefined if N is negative (sqlite3_realloc()
+ treats that as 0, but if this code is built with a different
+ allocator it may misbehave with negative values).
+
+ Like this.alloc.impl(), this.realloc.impl() is a direct binding
+ to the underlying realloc() implementation which does not throw
+ exceptions, instead returning 0 on allocation error.
+ */
+ realloc: undefined/*installed later*/,
+
+ /**
+ The API's primary point of access to the WASM-side memory
+ deallocator. Works like sqlite3_free().
+
+ Design note: this function is not named "free" for the same
+ reason that this.alloc() is not called this.malloc().
+ */
+ dealloc: undefined/*installed later*/
+
+ /* Many more wasm-related APIs get installed later on. */
+ }/*wasm*/);
+
+ /**
+ wasm.alloc()'s srcTypedArray.byteLength bytes,
+ populates them with the values from the source
+ TypedArray, and returns the pointer to that memory. The
+ returned pointer must eventually be passed to
+ wasm.dealloc() to clean it up.
+
+ The argument may be a Uint8Array, Int8Array, or ArrayBuffer,
+ and it throws if passed any other type.
+
+ As a special case, to avoid further special cases where
+ this is used, if srcTypedArray.byteLength is 0, it
+ allocates a single byte and sets it to the value
+ 0. Even in such cases, calls must behave as if the
+ allocated memory has exactly srcTypedArray.byteLength
+ bytes.
+ */
+ wasm.allocFromTypedArray = function(srcTypedArray){
+ if(srcTypedArray instanceof ArrayBuffer){
+ srcTypedArray = new Uint8Array(srcTypedArray);
+ }
+ affirmBindableTypedArray(srcTypedArray);
+ const pRet = wasm.alloc(srcTypedArray.byteLength || 1);
+ wasm.heapForSize(srcTypedArray.constructor).set(
+ srcTypedArray.byteLength ? srcTypedArray : [0], pRet
+ );
+ return pRet;
+ };
+
+ {
+ // Set up allocators...
+ const keyAlloc = config.allocExportName,
+ keyDealloc = config.deallocExportName,
+ keyRealloc = config.reallocExportName;
+ for(const key of [keyAlloc, keyDealloc, keyRealloc]){
+ const f = wasm.exports[key];
+ if(!(f instanceof Function)) toss3("Missing required exports[",key,"] function.");
+ }
+
+ wasm.alloc = function f(n){
+ return f.impl(n) || WasmAllocError.toss("Failed to allocate",n," bytes.");
+ };
+ wasm.alloc.impl = wasm.exports[keyAlloc];
+ wasm.realloc = function f(m,n){
+ const m2 = f.impl(m,n);
+ return n ? (m2 || WasmAllocError.toss("Failed to reallocate",n," bytes.")) : 0;
+ };
+ wasm.realloc.impl = wasm.exports[keyRealloc];
+ wasm.dealloc = wasm.exports[keyDealloc];
+ }
+
+ /**
+ Reports info about compile-time options using
+ sqlite3_compileoption_get() and sqlite3_compileoption_used(). It
+ has several distinct uses:
+
+ If optName is an array then it is expected to be a list of
+ compilation options and this function returns an object
+ which maps each such option to true or false, indicating
+ whether or not the given option was included in this
+ build. That object is returned.
+
+ If optName is an object, its keys are expected to be compilation
+ options and this function sets each entry to true or false,
+ indicating whether the compilation option was used or not. That
+ object is returned.
+
+ If passed no arguments then it returns an object mapping
+ all known compilation options to their compile-time values,
+ or boolean true if they are defined with no value. This
+ result, which is relatively expensive to compute, is cached
+ and returned for future no-argument calls.
+
+ In all other cases it returns true if the given option was
+ active when when compiling the sqlite3 module, else false.
+
+ Compile-time option names may optionally include their
+ "SQLITE_" prefix. When it returns an object of all options,
+ the prefix is elided.
+ */
+ wasm.compileOptionUsed = function f(optName){
+ if(!arguments.length){
+ if(f._result) return f._result;
+ else if(!f._opt){
+ f._rx = /^([^=]+)=(.+)/;
+ f._rxInt = /^-?\d+$/;
+ f._opt = function(opt, rv){
+ const m = f._rx.exec(opt);
+ rv[0] = (m ? m[1] : opt);
+ rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true;
+ };
+ }
+ const rc = {}, ov = [0,0];
+ let i = 0, k;
+ while((k = capi.sqlite3_compileoption_get(i++))){
+ f._opt(k,ov);
+ rc[ov[0]] = ov[1];
+ }
+ return f._result = rc;
+ }else if(Array.isArray(optName)){
+ const rc = {};
+ optName.forEach((v)=>{
+ rc[v] = capi.sqlite3_compileoption_used(v);
+ });
+ return rc;
+ }else if('object' === typeof optName){
+ Object.keys(optName).forEach((k)=> {
+ optName[k] = capi.sqlite3_compileoption_used(k);
+ });
+ return optName;
+ }
+ return (
+ 'string'===typeof optName
+ ) ? !!capi.sqlite3_compileoption_used(optName) : false;
+ }/*compileOptionUsed()*/;
+
+ /**
+ sqlite3.wasm.pstack (pseudo-stack) holds a special-case
+ stack-style allocator intended only for use with _small_ data of
+ not more than (in total) a few kb in size, managed as if it were
+ stack-based.
+
+ It has only a single intended usage:
+
+ ```
+ const stackPos = pstack.pointer;
+ try{
+ const ptr = pstack.alloc(8);
+ // ==> pstack.pointer === ptr
+ const otherPtr = pstack.alloc(8);
+ // ==> pstack.pointer === otherPtr
+ ...
+ }finally{
+ pstack.restore(stackPos);
+ // ==> pstack.pointer === stackPos
+ }
+ ```
+
+ This allocator is much faster than a general-purpose one but is
+ limited to usage patterns like the one shown above.
+
+ It operates from a static range of memory which lives outside of
+ space managed by Emscripten's stack-management, so does not
+ collide with Emscripten-provided stack allocation APIs. The
+ memory lives in the WASM heap and can be used with routines such
+ as wasm.poke() and wasm.heap8u().slice().
+ */
+ wasm.pstack = Object.assign(Object.create(null),{
+ /**
+ Sets the current pstack position to the given pointer. Results
+ are undefined if the passed-in value did not come from
+ this.pointer.
+ */
+ restore: wasm.exports.sqlite3_wasm_pstack_restore,
+ /**
+ Attempts to allocate the given number of bytes from the
+ pstack. On success, it zeroes out a block of memory of the
+ given size, adjusts the pstack pointer, and returns a pointer
+ to the memory. On error, throws a WasmAllocError. The
+ memory must eventually be released using restore().
+
+ If n is a string, it must be a WASM "IR" value in the set
+ accepted by wasm.sizeofIR(), which is mapped to the size of
+ that data type. If passed a string not in that set, it throws a
+ WasmAllocError.
+
+ This method always adjusts the given value to be a multiple
+ of 8 bytes because failing to do so can lead to incorrect
+ results when reading and writing 64-bit values from/to the WASM
+ heap. Similarly, the returned address is always 8-byte aligned.
+ */
+ alloc: function(n){
+ if('string'===typeof n && !(n = wasm.sizeofIR(n))){
+ WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")");
+ }
+ return wasm.exports.sqlite3_wasm_pstack_alloc(n)
+ || WasmAllocError.toss("Could not allocate",n,
+ "bytes from the pstack.");
+ },
+ /**
+ alloc()'s n chunks, each sz bytes, as a single memory block and
+ returns the addresses as an array of n element, each holding
+ the address of one chunk.
+
+ sz may optionally be an IR string accepted by wasm.sizeofIR().
+
+ Throws a WasmAllocError if allocation fails.
+
+ Example:
+
+ ```
+ const [p1, p2, p3] = wasm.pstack.allocChunks(3,4);
+ ```
+ */
+ allocChunks: function(n,sz){
+ if('string'===typeof sz && !(sz = wasm.sizeofIR(sz))){
+ WasmAllocError.toss("Invalid size value for allocChunks(",arguments[1],")");
+ }
+ const mem = wasm.pstack.alloc(n * sz);
+ const rc = [];
+ let i = 0, offset = 0;
+ for(; i < n; ++i, offset += sz) rc.push(mem + offset);
+ return rc;
+ },
+ /**
+ A convenience wrapper for allocChunks() which sizes each chunk
+ as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if
+ safePtrSize is falsy).
+
+ How it returns its result differs depending on its first
+ argument: if it's 1, it returns a single pointer value. If it's
+ more than 1, it returns the same as allocChunks().
+
+ When a returned pointers will refer to a 64-bit value, e.g. a
+ double or int64, and that value must be written or fetched,
+ e.g. using wasm.poke() or wasm.peek(), it is
+ important that the pointer in question be aligned to an 8-byte
+ boundary or else it will not be fetched or written properly and
+ will corrupt or read neighboring memory.
+
+ However, when all pointers involved point to "small" data, it
+ is safe to pass a falsy value to save a tiny bit of memory.
+ */
+ allocPtr: (n=1,safePtrSize=true)=>{
+ return 1===n
+ ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof)
+ : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof);
+ },
+
+ /**
+ Records the current pstack position, calls the given function,
+ passing it the sqlite3 object, then restores the pstack
+ regardless of whether the function throws. Returns the result
+ of the call or propagates an exception on error.
+
+ Added in 3.44.
+ */
+ call: function(f){
+ const stackPos = wasm.pstack.pointer;
+ try{ return f(sqlite3) } finally{
+ wasm.pstack.restore(stackPos);
+ }
+ }
+
+ })/*wasm.pstack*/;
+ Object.defineProperties(wasm.pstack, {
+ /**
+ sqlite3.wasm.pstack.pointer resolves to the current pstack
+ position pointer. This value is intended _only_ to be saved
+ for passing to restore(). Writing to this memory, without
+ first reserving it via wasm.pstack.alloc() and friends, leads
+ to undefined results.
+ */
+ pointer: {
+ configurable: false, iterable: true, writeable: false,
+ get: wasm.exports.sqlite3_wasm_pstack_ptr
+ //Whether or not a setter as an alternative to restore() is
+ //clearer or would just lead to confusion is unclear.
+ //set: wasm.exports.sqlite3_wasm_pstack_restore
+ },
+ /**
+ sqlite3.wasm.pstack.quota to the total number of bytes
+ available in the pstack, including any space which is currently
+ allocated. This value is a compile-time constant.
+ */
+ quota: {
+ configurable: false, iterable: true, writeable: false,
+ get: wasm.exports.sqlite3_wasm_pstack_quota
+ },
+ /**
+ sqlite3.wasm.pstack.remaining resolves to the amount of space
+ remaining in the pstack.
+ */
+ remaining: {
+ configurable: false, iterable: true, writeable: false,
+ get: wasm.exports.sqlite3_wasm_pstack_remaining
+ }
+ })/*wasm.pstack properties*/;
+
+ capi.sqlite3_randomness = (...args)=>{
+ if(1===args.length && util.isTypedArray(args[0])
+ && 1===args[0].BYTES_PER_ELEMENT){
+ const ta = args[0];
+ if(0===ta.byteLength){
+ wasm.exports.sqlite3_randomness(0,0);
+ return ta;
+ }
+ const stack = wasm.pstack.pointer;
+ try {
+ let n = ta.byteLength, offset = 0;
+ const r = wasm.exports.sqlite3_randomness;
+ const heap = wasm.heap8u();
+ const nAlloc = n < 512 ? n : 512;
+ const ptr = wasm.pstack.alloc(nAlloc);
+ do{
+ const j = (n>nAlloc ? nAlloc : n);
+ r(j, ptr);
+ ta.set(typedArrayPart(heap, ptr, ptr+j), offset);
+ n -= j;
+ offset += j;
+ } while(n > 0);
+ }catch(e){
+ console.error("Highly unexpected (and ignored!) "+
+ "exception in sqlite3_randomness():",e);
+ }finally{
+ wasm.pstack.restore(stack);
+ }
+ return ta;
+ }
+ wasm.exports.sqlite3_randomness(...args);
+ };
+
+ /** State for sqlite3_wasmfs_opfs_dir(). */
+ let __wasmfsOpfsDir = undefined;
+ /**
+ If the wasm environment has a WASMFS/OPFS-backed persistent
+ storage directory, its path is returned by this function. If it
+ does not then it returns "" (noting that "" is a falsy value).
+
+ The first time this is called, this function inspects the current
+ environment to determine whether persistence support is available
+ and, if it is, enables it (if needed). After the first call it
+ always returns the cached result.
+
+ If the returned string is not empty, any files stored under the
+ given path (recursively) are housed in OPFS storage. If the
+ returned string is empty, this particular persistent storage
+ option is not available on the client.
+
+ Though the mount point name returned by this function is intended
+ to remain stable, clients should not hard-coded it anywhere. Always call this function to get the path.
+
+ Note that this function is a no-op in must builds of this
+ library, as the WASMFS capability requires a custom
+ build.
+ */
+ capi.sqlite3_wasmfs_opfs_dir = function(){
+ if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir;
+ // If we have no OPFS, there is no persistent dir
+ const pdir = config.wasmfsOpfsDir;
+ if(!pdir
+ || !globalThis.FileSystemHandle
+ || !globalThis.FileSystemDirectoryHandle
+ || !globalThis.FileSystemFileHandle){
+ return __wasmfsOpfsDir = "";
+ }
+ try{
+ if(pdir && 0===wasm.xCallWrapped(
+ 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir
+ )){
+ return __wasmfsOpfsDir = pdir;
+ }else{
+ return __wasmfsOpfsDir = "";
+ }
+ }catch(e){
+ // sqlite3_wasm_init_wasmfs() is not available
+ return __wasmfsOpfsDir = "";
+ }
+ };
+
+ /**
+ Returns true if sqlite3.capi.sqlite3_wasmfs_opfs_dir() is a
+ non-empty string and the given name starts with (that string +
+ '/'), else returns false.
+ */
+ capi.sqlite3_wasmfs_filename_is_persistent = function(name){
+ const p = capi.sqlite3_wasmfs_opfs_dir();
+ return (p && name) ? name.startsWith(p+'/') : false;
+ };
+
+ /**
+ Given an `sqlite3*`, an sqlite3_vfs name, and an optional db name
+ (defaulting to "main"), returns a truthy value (see below) if
+ that db uses that VFS, else returns false. If pDb is falsy then
+ the 3rd argument is ignored and this function returns a truthy
+ value if the default VFS name matches that of the 2nd
+ argument. Results are undefined if pDb is truthy but refers to an
+ invalid pointer. The 3rd argument specifies the database name of
+ the given database connection to check, defaulting to the main
+ db.
+
+ The 2nd and 3rd arguments may either be a JS string or a WASM
+ C-string. If the 2nd argument is a NULL WASM pointer, the default
+ VFS is assumed. If the 3rd is a NULL WASM pointer, "main" is
+ assumed.
+
+ The truthy value it returns is a pointer to the `sqlite3_vfs`
+ object.
+
+ To permit safe use of this function from APIs which may be called
+ via the C stack (like SQL UDFs), this function does not throw: if
+ bad arguments cause a conversion error when passing into
+ wasm-space, false is returned.
+ */
+ capi.sqlite3_js_db_uses_vfs = function(pDb,vfsName,dbName=0){
+ try{
+ const pK = capi.sqlite3_vfs_find(vfsName);
+ if(!pK) return false;
+ else if(!pDb){
+ return pK===capi.sqlite3_vfs_find(0) ? pK : false;
+ }else{
+ return pK===capi.sqlite3_js_db_vfs(pDb,dbName) ? pK : false;
+ }
+ }catch(e){
+ /* Ignore - probably bad args to a wasm-bound function. */
+ return false;
+ }
+ };
+
+ /**
+ Returns an array of the names of all currently-registered sqlite3
+ VFSes.
+ */
+ capi.sqlite3_js_vfs_list = function(){
+ const rc = [];
+ let pVfs = capi.sqlite3_vfs_find(0);
+ while(pVfs){
+ const oVfs = new capi.sqlite3_vfs(pVfs);
+ rc.push(wasm.cstrToJs(oVfs.$zName));
+ pVfs = oVfs.$pNext;
+ oVfs.dispose();
+ }
+ return rc;
+ };
+
+ /**
+ A convenience wrapper around sqlite3_serialize() which serializes
+ the given `sqlite3*` pointer to a Uint8Array. The first argument
+ may be either an `sqlite3*` or an sqlite3.oo1.DB instance.
+
+ On success it returns a Uint8Array. If the schema is empty, an
+ empty array is returned.
+
+ `schema` is the schema to serialize. It may be a WASM C-string
+ pointer or a JS string. If it is falsy, it defaults to `"main"`.
+
+ On error it throws with a description of the problem.
+ */
+ capi.sqlite3_js_db_export = function(pDb, schema=0){
+ pDb = wasm.xWrap.testConvertArg('sqlite3*', pDb);
+ if(!pDb) toss3('Invalid sqlite3* argument.');
+ if(!wasm.bigIntEnabled) toss3('BigInt64 support is not enabled.');
+ const scope = wasm.scopedAllocPush();
+ let pOut;
+ try{
+ const pSize = wasm.scopedAlloc(8/*i64*/ + wasm.ptrSizeof);
+ const ppOut = pSize + 8;
+ /**
+ Maintenance reminder, since this cost a full hour of grief
+ and confusion: if the order of pSize/ppOut are reversed in
+ that memory block, fetching the value of pSize after the
+ export reads a garbage size because it's not on an 8-byte
+ memory boundary!
+ */
+ const zSchema = schema
+ ? (wasm.isPtr(schema) ? schema : wasm.scopedAllocCString(''+schema))
+ : 0;
+ let rc = wasm.exports.sqlite3_wasm_db_serialize(
+ pDb, zSchema, ppOut, pSize, 0
+ );
+ if(rc){
+ toss3("Database serialization failed with code",
+ sqlite3.capi.sqlite3_js_rc_str(rc));
+ }
+ pOut = wasm.peekPtr(ppOut);
+ const nOut = wasm.peek(pSize, 'i64');
+ rc = nOut
+ ? wasm.heap8u().slice(pOut, pOut + Number(nOut))
+ : new Uint8Array();
+ return rc;
+ }finally{
+ if(pOut) wasm.exports.sqlite3_free(pOut);
+ wasm.scopedAllocPop(scope);
+ }
+ };
+
+ /**
+ Given a `sqlite3*` and a database name (JS string or WASM
+ C-string pointer, which may be 0), returns a pointer to the
+ sqlite3_vfs responsible for it. If the given db name is null/0,
+ or not provided, then "main" is assumed.
+ */
+ capi.sqlite3_js_db_vfs =
+ (dbPointer, dbName=0)=>wasm.sqlite3_wasm_db_vfs(dbPointer, dbName);
+
+ /**
+ A thin wrapper around capi.sqlite3_aggregate_context() which
+ behaves the same except that it throws a WasmAllocError if that
+ function returns 0. As a special case, if n is falsy it does
+ _not_ throw if that function returns 0. That special case is
+ intended for use with xFinal() implementations.
+ */
+ capi.sqlite3_js_aggregate_context = (pCtx, n)=>{
+ return capi.sqlite3_aggregate_context(pCtx, n)
+ || (n ? WasmAllocError.toss("Cannot allocate",n,
+ "bytes for sqlite3_aggregate_context()")
+ : 0);
+ };
+
+ /**
+ If the current environment supports the POSIX file APIs, this routine
+ creates (or overwrites) the given file using those APIs. This is
+ primarily intended for use in Emscripten-based builds where the POSIX
+ APIs are transparently proxied by an in-memory virtual filesystem.
+ It may behave diffrently in other environments.
+
+ The first argument must be either a JS string or WASM C-string
+ holding the filename. Note that this routine does _not_ create
+ intermediary directories if the filename has a directory part.
+
+ The 2nd argument may either a valid WASM memory pointer, an
+ ArrayBuffer, or a Uint8Array. The 3rd must be the length, in
+ bytes, of the data array to copy. If the 2nd argument is an
+ ArrayBuffer or Uint8Array and the 3rd is not a positive integer
+ then the 3rd defaults to the array's byteLength value.
+
+ Results are undefined if data is a WASM pointer and dataLen is
+ exceeds data's bounds.
+
+ Throws if any arguments are invalid or if creating or writing to
+ the file fails.
+
+ Added in 3.43 as an alternative for the deprecated
+ sqlite3_js_vfs_create_file().
+ */
+ capi.sqlite3_js_posix_create_file = function(filename, data, dataLen){
+ let pData;
+ if(data && wasm.isPtr(data)){
+ pData = data;
+ }else if(data instanceof ArrayBuffer || data instanceof Uint8Array){
+ pData = wasm.allocFromTypedArray(data);
+ if(arguments.length<3 || !util.isInt32(dataLen) || dataLen<0){
+ dataLen = data.byteLength;
+ }
+ }else{
+ SQLite3Error.toss("Invalid 2nd argument for sqlite3_js_posix_create_file().");
+ }
+ try{
+ if(!util.isInt32(dataLen) || dataLen<0){
+ SQLite3Error.toss("Invalid 3rd argument for sqlite3_js_posix_create_file().");
+ }
+ const rc = wasm.sqlite3_wasm_posix_create_file(filename, pData, dataLen);
+ if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code",
+ capi.sqlite3_js_rc_str(rc));
+ }finally{
+ wasm.dealloc(pData);
+ }
+ };
+
+ /**
+ Deprecation warning: this function does not work properly in
+ debug builds of sqlite3 because its out-of-scope use of the
+ sqlite3_vfs API triggers assertions in the core library. That
+ was unfortunately not discovered until 2023-08-11. This function
+ is now deprecated and should not be used in new code.
+
+ Alternative options:
+
+ - "unix" VFS and its variants can get equivalent functionality
+ with sqlite3_js_posix_create_file().
+
+ - OPFS: use either sqlite3.oo1.OpfsDb.importDb(), for the "opfs"
+ VFS, or the importDb() method of the PoolUtil object provided
+ by the "opfs-sahpool" OPFS (noting that its VFS name may differ
+ depending on client-side configuration). We cannot proxy those
+ from here because the former is necessarily asynchronous and
+ the latter requires information not available to this function.
+
+ Creates a file using the storage appropriate for the given
+ sqlite3_vfs. The first argument may be a VFS name (JS string
+ only, NOT a WASM C-string), WASM-managed `sqlite3_vfs*`, or
+ a capi.sqlite3_vfs instance. Pass 0 (a NULL pointer) to use the
+ default VFS. If passed a string which does not resolve using
+ sqlite3_vfs_find(), an exception is thrown. (Note that a WASM
+ C-string is not accepted because it is impossible to
+ distinguish from a C-level `sqlite3_vfs*`.)
+
+ The second argument, the filename, must be a JS or WASM C-string.
+
+ The 3rd may either be falsy, a valid WASM memory pointer, an
+ ArrayBuffer, or a Uint8Array. The 4th must be the length, in
+ bytes, of the data array to copy. If the 3rd argument is an
+ ArrayBuffer or Uint8Array and the 4th is not a positive integer
+ then the 4th defaults to the array's byteLength value.
+
+ If data is falsy then a file is created with dataLen bytes filled
+ with uninitialized data (whatever truncate() leaves there). If
+ data is not falsy then a file is created or truncated and it is
+ filled with the first dataLen bytes of the data source.
+
+ Throws if any arguments are invalid or if creating or writing to
+ the file fails.
+
+ Note that most VFSes do _not_ automatically create directory
+ parts of filenames, nor do all VFSes have a concept of
+ directories. If the given filename is not valid for the given
+ VFS, an exception will be thrown. This function exists primarily
+ to assist in implementing file-upload capability, with the caveat
+ that clients must have some idea of the VFS into which they want
+ to upload and that VFS must support the operation.
+
+ VFS-specific notes:
+
+ - "memdb": results are undefined.
+
+ - "kvvfs": will fail with an I/O error due to strict internal
+ requirments of that VFS's xTruncate().
+
+ - "unix" and related: will use the WASM build's equivalent of the
+ POSIX I/O APIs. This will work so long as neither a specific
+ VFS nor the WASM environment imposes requirements which break it.
+
+ - "opfs": uses OPFS storage and creates directory parts of the
+ filename. It can only be used to import an SQLite3 database
+ file and will fail if given anything else.
+ */
+ capi.sqlite3_js_vfs_create_file = function(vfs, filename, data, dataLen){
+ config.warn("sqlite3_js_vfs_create_file() is deprecated and",
+ "should be avoided because it can lead to C-level crashes.",
+ "See its documentation for alternative options.");
+ let pData;
+ if(data){
+ if(wasm.isPtr(data)){
+ pData = data;
+ }else if(data instanceof ArrayBuffer){
+ data = new Uint8Array(data);
+ }
+ if(data instanceof Uint8Array){
+ pData = wasm.allocFromTypedArray(data);
+ if(arguments.length<4 || !util.isInt32(dataLen) || dataLen<0){
+ dataLen = data.byteLength;
+ }
+ }else{
+ SQLite3Error.toss("Invalid 3rd argument type for sqlite3_js_vfs_create_file().");
+ }
+ }else{
+ pData = 0;
+ }
+ if(!util.isInt32(dataLen) || dataLen<0){
+ wasm.dealloc(pData);
+ SQLite3Error.toss("Invalid 4th argument for sqlite3_js_vfs_create_file().");
+ }
+ try{
+ const rc = wasm.sqlite3_wasm_vfs_create_file(vfs, filename, pData, dataLen);
+ if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code",
+ capi.sqlite3_js_rc_str(rc));
+ }finally{
+ wasm.dealloc(pData);
+ }
+ };
+
+ /**
+ Converts SQL input from a variety of convenient formats
+ to plain strings.
+
+ If v is a string, it is returned as-is. If it is-a Array, its
+ join("") result is returned. If is is a Uint8Array, Int8Array,
+ or ArrayBuffer, it is assumed to hold UTF-8-encoded text and is
+ decoded to a string. If it looks like a WASM pointer,
+ wasm.cstrToJs(sql) is returned. Else undefined is returned.
+
+ Added in 3.44
+ */
+ capi.sqlite3_js_sql_to_string = (sql)=>{
+ if('string' === typeof sql){
+ return sql;
+ }
+ const x = flexibleString(v);
+ return x===v ? undefined : x;
+ }
+
+ if( util.isUIThread() ){
+ /* Features specific to the main window thread... */
+
+ /**
+ Internal helper for sqlite3_js_kvvfs_clear() and friends.
+ Its argument should be one of ('local','session',"").
+ */
+ const __kvvfsInfo = function(which){
+ const rc = Object.create(null);
+ rc.prefix = 'kvvfs-'+which;
+ rc.stores = [];
+ if('session'===which || ""===which) rc.stores.push(globalThis.sessionStorage);
+ if('local'===which || ""===which) rc.stores.push(globalThis.localStorage);
+ return rc;
+ };
+
+ /**
+ Clears all storage used by the kvvfs DB backend, deleting any
+ DB(s) stored there. Its argument must be either 'session',
+ 'local', or "". In the first two cases, only sessionStorage
+ resp. localStorage is cleared. If it's an empty string (the
+ default) then both are cleared. Only storage keys which match
+ the pattern used by kvvfs are cleared: any other client-side
+ data are retained.
+
+ This function is only available in the main window thread.
+
+ Returns the number of entries cleared.
+ */
+ capi.sqlite3_js_kvvfs_clear = function(which=""){
+ let rc = 0;
+ const kvinfo = __kvvfsInfo(which);
+ kvinfo.stores.forEach((s)=>{
+ const toRm = [] /* keys to remove */;
+ let i;
+ for( i = 0; i < s.length; ++i ){
+ const k = s.key(i);
+ if(k.startsWith(kvinfo.prefix)) toRm.push(k);
+ }
+ toRm.forEach((kk)=>s.removeItem(kk));
+ rc += toRm.length;
+ });
+ return rc;
+ };
+
+ /**
+ This routine guesses the approximate amount of
+ window.localStorage and/or window.sessionStorage in use by the
+ kvvfs database backend. Its argument must be one of
+ ('session', 'local', ""). In the first two cases, only
+ sessionStorage resp. localStorage is counted. If it's an empty
+ string (the default) then both are counted. Only storage keys
+ which match the pattern used by kvvfs are counted. The returned
+ value is the "length" value of every matching key and value,
+ noting that JavaScript stores each character in 2 bytes.
+
+ Note that the returned size is not authoritative from the
+ perspective of how much data can fit into localStorage and
+ sessionStorage, as the precise algorithms for determining
+ those limits are unspecified and may include per-entry
+ overhead invisible to clients.
+ */
+ capi.sqlite3_js_kvvfs_size = function(which=""){
+ let sz = 0;
+ const kvinfo = __kvvfsInfo(which);
+ kvinfo.stores.forEach((s)=>{
+ let i;
+ for(i = 0; i < s.length; ++i){
+ const k = s.key(i);
+ if(k.startsWith(kvinfo.prefix)){
+ sz += k.length;
+ sz += s.getItem(k).length;
+ }
+ }
+ });
+ return sz * 2 /* because JS uses 2-byte char encoding */;
+ };
+
+ }/* main-window-only bits */
+
+ /**
+ Wraps all known variants of the C-side variadic
+ sqlite3_db_config().
+
+ Full docs: https://sqlite.org/c3ref/db_config.html
+
+ Returns capi.SQLITE_MISUSE if op is not a valid operation ID.
+
+ The variants which take `(int, int*)` arguments treat a
+ missing or falsy pointer argument as 0.
+ */
+ capi.sqlite3_db_config = function(pDb, op, ...args){
+ if(!this.s){
+ this.s = wasm.xWrap('sqlite3_wasm_db_config_s','int',
+ ['sqlite3*', 'int', 'string:static']
+ /* MAINDBNAME requires a static string */);
+ this.pii = wasm.xWrap('sqlite3_wasm_db_config_pii', 'int',
+ ['sqlite3*', 'int', '*','int', 'int']);
+ this.ip = wasm.xWrap('sqlite3_wasm_db_config_ip','int',
+ ['sqlite3*', 'int', 'int','*']);
+ }
+ switch(op){
+ case capi.SQLITE_DBCONFIG_ENABLE_FKEY:
+ case capi.SQLITE_DBCONFIG_ENABLE_TRIGGER:
+ case capi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
+ case capi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
+ case capi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
+ case capi.SQLITE_DBCONFIG_ENABLE_QPSG:
+ case capi.SQLITE_DBCONFIG_TRIGGER_EQP:
+ case capi.SQLITE_DBCONFIG_RESET_DATABASE:
+ case capi.SQLITE_DBCONFIG_DEFENSIVE:
+ case capi.SQLITE_DBCONFIG_WRITABLE_SCHEMA:
+ case capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
+ case capi.SQLITE_DBCONFIG_DQS_DML:
+ case capi.SQLITE_DBCONFIG_DQS_DDL:
+ case capi.SQLITE_DBCONFIG_ENABLE_VIEW:
+ case capi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
+ case capi.SQLITE_DBCONFIG_TRUSTED_SCHEMA:
+ case capi.SQLITE_DBCONFIG_STMT_SCANSTATUS:
+ case capi.SQLITE_DBCONFIG_REVERSE_SCANORDER:
+ return this.ip(pDb, op, args[0], args[1] || 0);
+ case capi.SQLITE_DBCONFIG_LOOKASIDE:
+ return this.pii(pDb, op, args[0], args[1], args[2]);
+ case capi.SQLITE_DBCONFIG_MAINDBNAME:
+ return this.s(pDb, op, args[0]);
+ default:
+ return capi.SQLITE_MISUSE;
+ }
+ }.bind(Object.create(null));
+
+ /**
+ Given a (sqlite3_value*), this function attempts to convert it
+ to an equivalent JS value with as much fidelity as feasible and
+ return it.
+
+ By default it throws if it cannot determine any sensible
+ conversion. If passed a falsy second argument, it instead returns
+ `undefined` if no suitable conversion is found. Note that there
+ is no conversion from SQL to JS which results in the `undefined`
+ value, so `undefined` has an unambiguous meaning here. It will
+ always throw a WasmAllocError if allocating memory for a
+ conversion fails.
+
+ Caveats:
+
+ - It does not support sqlite3_value_to_pointer() conversions
+ because those require a type name string which this function
+ does not have and cannot sensibly be given at the level of the
+ API where this is used (e.g. automatically converting UDF
+ arguments). Clients using sqlite3_value_to_pointer(), and its
+ related APIs, will need to manage those themselves.
+ */
+ capi.sqlite3_value_to_js = function(pVal,throwIfCannotConvert=true){
+ let arg;
+ const valType = capi.sqlite3_value_type(pVal);
+ switch(valType){
+ case capi.SQLITE_INTEGER:
+ if(wasm.bigIntEnabled){
+ arg = capi.sqlite3_value_int64(pVal);
+ if(util.bigIntFitsDouble(arg)) arg = Number(arg);
+ }
+ else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/;
+ break;
+ case capi.SQLITE_FLOAT:
+ arg = capi.sqlite3_value_double(pVal);
+ break;
+ case capi.SQLITE_TEXT:
+ arg = capi.sqlite3_value_text(pVal);
+ break;
+ case capi.SQLITE_BLOB:{
+ const n = capi.sqlite3_value_bytes(pVal);
+ const pBlob = capi.sqlite3_value_blob(pVal);
+ if(n && !pBlob) sqlite3.WasmAllocError.toss(
+ "Cannot allocate memory for blob argument of",n,"byte(s)"
+ );
+ arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null;
+ break;
+ }
+ case capi.SQLITE_NULL:
+ arg = null; break;
+ default:
+ if(throwIfCannotConvert){
+ toss3(capi.SQLITE_MISMATCH,
+ "Unhandled sqlite3_value_type():",valType);
+ }
+ arg = undefined;
+ }
+ return arg;
+ };
+
+ /**
+ Requires a C-style array of `sqlite3_value*` objects and the
+ number of entries in that array. Returns a JS array containing
+ the results of passing each C array entry to
+ sqlite3_value_to_js(). The 3rd argument to this function is
+ passed on as the 2nd argument to that one.
+ */
+ capi.sqlite3_values_to_js = function(argc,pArgv,throwIfCannotConvert=true){
+ let i;
+ const tgt = [];
+ for(i = 0; i < argc; ++i){
+ /**
+ Curiously: despite ostensibly requiring 8-byte
+ alignment, the pArgv array is parcelled into chunks of
+ 4 bytes (1 pointer each). The values those point to
+ have 8-byte alignment but the individual argv entries
+ do not.
+ */
+ tgt.push(capi.sqlite3_value_to_js(
+ wasm.peekPtr(pArgv + (wasm.ptrSizeof * i)),
+ throwIfCannotConvert
+ ));
+ }
+ return tgt;
+ };
+
+ /**
+ Calls either sqlite3_result_error_nomem(), if e is-a
+ WasmAllocError, or sqlite3_result_error(). In the latter case,
+ the second arugment is coerced to a string to create the error
+ message.
+
+ The first argument is a (sqlite3_context*). Returns void.
+ Does not throw.
+ */
+ capi.sqlite3_result_error_js = function(pCtx,e){
+ if(e instanceof WasmAllocError){
+ capi.sqlite3_result_error_nomem(pCtx);
+ }else{
+ /* Maintenance reminder: ''+e, rather than e.message,
+ will prefix e.message with e.name, so it includes
+ the exception's type name in the result. */;
+ capi.sqlite3_result_error(pCtx, ''+e, -1);
+ }
+ };
+
+ /**
+ This function passes its 2nd argument to one of the
+ sqlite3_result_xyz() routines, depending on the type of that
+ argument:
+
+ - If (val instanceof Error), this function passes it to
+ sqlite3_result_error_js().
+ - `null`: `sqlite3_result_null()`
+ - `boolean`: `sqlite3_result_int()` with a value of 0 or 1.
+ - `number`: `sqlite3_result_int()`, `sqlite3_result_int64()`, or
+ `sqlite3_result_double()`, depending on the range of the number
+ and whether or not int64 support is enabled.
+ - `bigint`: similar to `number` but will trigger an error if the
+ value is too big to store in an int64.
+ - `string`: `sqlite3_result_text()`
+ - Uint8Array or Int8Array or ArrayBuffer: `sqlite3_result_blob()`
+ - `undefined`: is a no-op provided to simplify certain use cases.
+
+ Anything else triggers `sqlite3_result_error()` with a
+ description of the problem.
+
+ The first argument to this function is a `(sqlite3_context*)`.
+ Returns void. Does not throw.
+ */
+ capi.sqlite3_result_js = function(pCtx,val){
+ if(val instanceof Error){
+ capi.sqlite3_result_error_js(pCtx, val);
+ return;
+ }
+ try{
+ switch(typeof val) {
+ case 'undefined':
+ /* This is a no-op. This routine originated in the create_function()
+ family of APIs and in that context, passing in undefined indicated
+ that the caller was responsible for calling sqlite3_result_xxx()
+ (if needed). */
+ break;
+ case 'boolean':
+ capi.sqlite3_result_int(pCtx, val ? 1 : 0);
+ break;
+ case 'bigint':
+ if(util.bigIntFits32(val)){
+ capi.sqlite3_result_int(pCtx, Number(val));
+ }else if(util.bigIntFitsDouble(val)){
+ capi.sqlite3_result_double(pCtx, Number(val));
+ }else if(wasm.bigIntEnabled){
+ if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val);
+ else toss3("BigInt value",val.toString(),"is too BigInt for int64.");
+ }else{
+ toss3("BigInt value",val.toString(),"is too BigInt.");
+ }
+ break;
+ case 'number': {
+ let f;
+ if(util.isInt32(val)){
+ f = capi.sqlite3_result_int;
+ }else if(wasm.bigIntEnabled
+ && Number.isInteger(val)
+ && util.bigIntFits64(BigInt(val))){
+ f = capi.sqlite3_result_int64;
+ }else{
+ f = capi.sqlite3_result_double;
+ }
+ f(pCtx, val);
+ break;
+ }
+ case 'string': {
+ const [p, n] = wasm.allocCString(val,true);
+ capi.sqlite3_result_text(pCtx, p, n, capi.SQLITE_WASM_DEALLOC);
+ break;
+ }
+ case 'object':
+ if(null===val/*yes, typeof null === 'object'*/) {
+ capi.sqlite3_result_null(pCtx);
+ break;
+ }else if(util.isBindableTypedArray(val)){
+ const pBlob = wasm.allocFromTypedArray(val);
+ capi.sqlite3_result_blob(
+ pCtx, pBlob, val.byteLength,
+ capi.SQLITE_WASM_DEALLOC
+ );
+ break;
+ }
+ // else fall through
+ default:
+ toss3("Don't not how to handle this UDF result value:",(typeof val), val);
+ }
+ }catch(e){
+ capi.sqlite3_result_error_js(pCtx, e);
+ }
+ };
+
+ /**
+ Returns the result sqlite3_column_value(pStmt,iCol) passed to
+ sqlite3_value_to_js(). The 3rd argument of this function is
+ ignored by this function except to pass it on as the second
+ argument of sqlite3_value_to_js(). If the sqlite3_column_value()
+ returns NULL (e.g. because the column index is out of range),
+ this function returns `undefined`, regardless of the 3rd
+ argument. If the 3rd argument is falsy and conversion fails,
+ `undefined` will be returned.
+
+ Note that sqlite3_column_value() returns an "unprotected" value
+ object, but in a single-threaded environment (like this one)
+ there is no distinction between protected and unprotected values.
+ */
+ capi.sqlite3_column_js = function(pStmt, iCol, throwIfCannotConvert=true){
+ const v = capi.sqlite3_column_value(pStmt, iCol);
+ return (0===v) ? undefined : capi.sqlite3_value_to_js(v, throwIfCannotConvert);
+ };
+
+ /**
+ Internal impl of sqlite3_preupdate_new/old_js() and
+ sqlite3changeset_new/old_js().
+ */
+ const __newOldValue = function(pObj, iCol, impl){
+ impl = capi[impl];
+ if(!this.ptr) this.ptr = wasm.allocPtr();
+ else wasm.pokePtr(this.ptr, 0);
+ const rc = impl(pObj, iCol, this.ptr);
+ if(rc) return SQLite3Error.toss(rc,arguments[2]+"() failed with code "+rc);
+ const pv = wasm.peekPtr(this.ptr);
+ return pv ? capi.sqlite3_value_to_js( pv, true ) : undefined;
+ }.bind(Object.create(null));
+
+ /**
+ A wrapper around sqlite3_preupdate_new() which fetches the
+ sqlite3_value at the given index and returns the result of
+ passing it to sqlite3_value_to_js(). Throws on error.
+ */
+ capi.sqlite3_preupdate_new_js =
+ (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_new');
+
+ /**
+ The sqlite3_preupdate_old() counterpart of
+ sqlite3_preupdate_new_js(), with an identical interface.
+ */
+ capi.sqlite3_preupdate_old_js =
+ (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_old');
+
+ /**
+ A wrapper around sqlite3changeset_new() which fetches the
+ sqlite3_value at the given index and returns the result of
+ passing it to sqlite3_value_to_js(). Throws on error.
+
+ If sqlite3changeset_new() succeeds but has no value to report,
+ this function returns the undefined value, noting that undefined
+ is a valid conversion from an `sqlite3_value`, so is unambiguous.
+ */
+ capi.sqlite3changeset_new_js =
+ (pChangesetIter, iCol) => __newOldValue(pChangesetIter, iCol,
+ 'sqlite3changeset_new');
+
+ /**
+ The sqlite3changeset_old() counterpart of
+ sqlite3changeset_new_js(), with an identical interface.
+ */
+ capi.sqlite3changeset_old_js =
+ (pChangesetIter, iCol)=>__newOldValue(pChangesetIter, iCol,
+ 'sqlite3changeset_old');
+
+ /* The remainder of the API will be set up in later steps. */
+ const sqlite3 = {
+ WasmAllocError: WasmAllocError,
+ SQLite3Error: SQLite3Error,
+ capi,
+ util,
+ wasm,
+ config,
+ /**
+ Holds the version info of the sqlite3 source tree from which
+ the generated sqlite3-api.js gets built. Note that its version
+ may well differ from that reported by sqlite3_libversion(), but
+ that should be considered a source file mismatch, as the JS and
+ WASM files are intended to be built and distributed together.
+
+ This object is initially a placeholder which gets replaced by a
+ build-generated object.
+ */
+ version: Object.create(null),
+
+ /**
+ The library reserves the 'client' property for client-side use
+ and promises to never define a property with this name nor to
+ ever rely on specific contents of it. It makes no such guarantees
+ for other properties.
+ */
+ client: undefined,
+
+ /**
+ This function is not part of the public interface, but a
+ piece of internal bootstrapping infrastructure.
+
+ Performs any optional asynchronous library-level initialization
+ which might be required. This function returns a Promise which
+ resolves to the sqlite3 namespace object. Any error in the
+ async init will be fatal to the init as a whole, but init
+ routines are themselves welcome to install dummy catch()
+ handlers which are not fatal if their failure should be
+ considered non-fatal. If called more than once, the second and
+ subsequent calls are no-ops which return a pre-resolved
+ Promise.
+
+ Ideally this function is called as part of the Promise chain
+ which handles the loading and bootstrapping of the API. If not
+ then it must be called by client-level code, which must not use
+ the library until the returned promise resolves.
+
+ If called multiple times it will return the same promise on
+ subsequent calls. The current build setup precludes that
+ possibility, so it's only a hypothetical problem if/when this
+ function ever needs to be invoked by clients.
+
+ In Emscripten-based builds, this function is called
+ automatically and deleted from this object.
+ */
+ asyncPostInit: async function ff(){
+ if(ff.isReady instanceof Promise) return ff.isReady;
+ let lia = sqlite3ApiBootstrap.initializersAsync;
+ delete sqlite3ApiBootstrap.initializersAsync;
+ const postInit = async ()=>{
+ if(!sqlite3.__isUnderTest){
+ /* Delete references to internal-only APIs which are used by
+ some initializers. Retain them when running in test mode
+ so that we can add tests for them. */
+ delete sqlite3.util;
+ /* It's conceivable that we might want to expose
+ StructBinder to client-side code, but it's only useful if
+ clients build their own sqlite3.wasm which contains their
+ own C struct types. */
+ delete sqlite3.StructBinder;
+ }
+ return sqlite3;
+ };
+ const catcher = (e)=>{
+ config.error("an async sqlite3 initializer failed:",e);
+ throw e;
+ };
+ if(!lia || !lia.length){
+ return ff.isReady = postInit().catch(catcher);
+ }
+ lia = lia.map((f)=>{
+ return (f instanceof Function) ? async x=>f(sqlite3) : f;
+ });
+ lia.push(postInit);
+ let p = Promise.resolve(sqlite3);
+ while(lia.length) p = p.then(lia.shift());
+ return ff.isReady = p.catch(catcher);
+ },
+ /**
+ scriptInfo ideally gets injected into this object by the
+ infrastructure which assembles the JS/WASM module. It contains
+ state which must be collected before sqlite3ApiBootstrap() can
+ be declared. It is not necessarily available to any
+ sqlite3ApiBootstrap.initializers but "should" be in place (if
+ it's added at all) by the time that
+ sqlite3ApiBootstrap.initializersAsync is processed.
+
+ This state is not part of the public API, only intended for use
+ with the sqlite3 API bootstrapping and wasm-loading process.
+ */
+ scriptInfo: undefined
+ };
+ try{
+ sqlite3ApiBootstrap.initializers.forEach((f)=>{
+ f(sqlite3);
+ });
+ }catch(e){
+ /* If we don't report this here, it can get completely swallowed
+ up and disappear into the abyss of Promises and Workers. */
+ console.error("sqlite3 bootstrap initializer threw:",e);
+ throw e;
+ }
+ delete sqlite3ApiBootstrap.initializers;
+ sqlite3ApiBootstrap.sqlite3 = sqlite3;
+ return sqlite3;
+}/*sqlite3ApiBootstrap()*/;
+/**
+ globalThis.sqlite3ApiBootstrap.initializers is an internal detail used by
+ the various pieces of the sqlite3 API's amalgamation process. It
+ must not be modified by client code except when plugging such code
+ into the amalgamation process.
+
+ Each component of the amalgamation is expected to append a function
+ to this array. When sqlite3ApiBootstrap() is called for the first
+ time, each such function will be called (in their appended order)
+ and passed the sqlite3 namespace object, into which they can install
+ their features (noting that most will also require that certain
+ features alread have been installed). At the end of that process,
+ this array is deleted.
+
+ Note that the order of insertion into this array is significant for
+ some pieces. e.g. sqlite3.capi and sqlite3.wasm cannot be fully
+ utilized until the whwasmutil.js part is plugged in via
+ sqlite3-api-glue.js.
+*/
+globalThis.sqlite3ApiBootstrap.initializers = [];
+/**
+ globalThis.sqlite3ApiBootstrap.initializersAsync is an internal detail
+ used by the sqlite3 API's amalgamation process. It must not be
+ modified by client code except when plugging such code into the
+ amalgamation process.
+
+ The counterpart of globalThis.sqlite3ApiBootstrap.initializers,
+ specifically for initializers which are asynchronous. All entries in
+ this list must be either async functions, non-async functions which
+ return a Promise, or a Promise. Each function in the list is called
+ with the sqlite3 object as its only argument.
+
+ The resolved value of any Promise is ignored and rejection will kill
+ the asyncPostInit() process (at an indeterminate point because all
+ of them are run asynchronously in parallel).
+
+ This list is not processed until the client calls
+ sqlite3.asyncPostInit(). This means, for example, that intializers
+ added to globalThis.sqlite3ApiBootstrap.initializers may push entries to
+ this list.
+*/
+globalThis.sqlite3ApiBootstrap.initializersAsync = [];
+/**
+ Client code may assign sqlite3ApiBootstrap.defaultConfig an
+ object-type value before calling sqlite3ApiBootstrap() (without
+ arguments) in order to tell that call to use this object as its
+ default config value. The intention of this is to provide
+ downstream clients with a reasonably flexible approach for plugging in
+ an environment-suitable configuration without having to define a new
+ global-scope symbol.
+*/
+globalThis.sqlite3ApiBootstrap.defaultConfig = Object.create(null);
+/**
+ Placeholder: gets installed by the first call to
+ globalThis.sqlite3ApiBootstrap(). However, it is recommended that the
+ caller of sqlite3ApiBootstrap() capture its return value and delete
+ globalThis.sqlite3ApiBootstrap after calling it. It returns the same
+ value which will be stored here.
+*/
+globalThis.sqlite3ApiBootstrap.sqlite3 = undefined;
diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js
new file mode 100644
index 0000000..3099c19
--- /dev/null
+++ b/ext/wasm/api/sqlite3-api-worker1.js
@@ -0,0 +1,695 @@
+//#ifnot omit-oo1
+/**
+ 2022-07-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 implements the initializer for SQLite's "Worker API #1", a
+ very basic DB access API intended to be scripted from a main window
+ thread via Worker-style messages. Because of limitations in that
+ type of communication, this API is minimalistic and only capable of
+ serving relatively basic DB requests (e.g. it cannot process nested
+ query loops concurrently).
+
+ This file requires that the core C-style sqlite3 API and OO API #1
+ have been loaded.
+*/
+
+/**
+ sqlite3.initWorker1API() implements a Worker-based wrapper around
+ SQLite3 OO API #1, colloquially known as "Worker API #1".
+
+ In order to permit this API to be loaded in worker threads without
+ automatically registering onmessage handlers, initializing the
+ worker API requires calling initWorker1API(). If this function is
+ called from a non-worker thread then it throws an exception. It
+ must only be called once per Worker.
+
+ When initialized, it installs message listeners to receive Worker
+ messages and then it posts a message in the form:
+
+ ```
+ {type:'sqlite3-api', result:'worker1-ready'}
+ ```
+
+ to let the client know that it has been initialized. Clients may
+ optionally depend on this function not returning until
+ initialization is complete, as the initialization is synchronous.
+ In some contexts, however, listening for the above message is
+ a better fit.
+
+ Note that the worker-based interface can be slightly quirky because
+ of its async nature. In particular, any number of messages may be posted
+ to the worker before it starts handling any of them. If, e.g., an
+ "open" operation fails, any subsequent messages will fail. The
+ Promise-based wrapper for this API (`sqlite3-worker1-promiser.js`)
+ is more comfortable to use in that regard.
+
+ The documentation for the input and output worker messages for
+ this API follows...
+
+ ====================================================================
+ Common message format...
+
+ Each message posted to the worker has an operation-independent
+ envelope and operation-dependent arguments:
+
+ ```
+ {
+ type: string, // one of: 'open', 'close', 'exec', 'export', 'config-get'
+
+ messageId: OPTIONAL arbitrary value. The worker will copy it as-is
+ into response messages to assist in client-side dispatching.
+
+ dbId: a db identifier string (returned by 'open') which tells the
+ operation which database instance to work on. If not provided, the
+ first-opened db is used. This is an "opaque" value, with no
+ inherently useful syntax or information. Its value is subject to
+ change with any given build of this API and cannot be used as a
+ basis for anything useful beyond its one intended purpose.
+
+ args: ...operation-dependent arguments...
+
+ // the framework may add other properties for testing or debugging
+ // purposes.
+
+ }
+ ```
+
+ Response messages, posted back to the main thread, look like:
+
+ ```
+ {
+ type: string. Same as above except for error responses, which have the type
+ 'error',
+
+ messageId: same value, if any, provided by the inbound message
+
+ dbId: the id of the db which was operated on, if any, as returned
+ by the corresponding 'open' operation.
+
+ result: ...operation-dependent result...
+
+ }
+ ```
+
+ ====================================================================
+ Error responses
+
+ Errors are reported messages in an operation-independent format:
+
+ ```
+ {
+ type: "error",
+
+ messageId: ...as above...,
+
+ dbId: ...as above...
+
+ result: {
+
+ operation: type of the triggering operation: 'open', 'close', ...
+
+ message: ...error message text...
+
+ errorClass: string. The ErrorClass.name property from the thrown exception.
+
+ input: the message object which triggered the error.
+
+ stack: _if available_, a stack trace array.
+
+ }
+
+ }
+ ```
+
+
+ ====================================================================
+ "config-get"
+
+ This operation fetches the serializable parts of the sqlite3 API
+ configuration.
+
+ Message format:
+
+ ```
+ {
+ type: "config-get",
+ messageId: ...as above...,
+ args: currently ignored and may be elided.
+ }
+ ```
+
+ Response:
+
+ ```
+ {
+ type: "config-get",
+ messageId: ...as above...,
+ result: {
+
+ version: sqlite3.version object
+
+ bigIntEnabled: bool. True if BigInt support is enabled.
+
+ vfsList: result of sqlite3.capi.sqlite3_js_vfs_list()
+ }
+ }
+ ```
+
+
+ ====================================================================
+ "open" a database
+
+ Message format:
+
+ ```
+ {
+ type: "open",
+ messageId: ...as above...,
+ args:{
+
+ filename [=":memory:" or "" (unspecified)]: the db filename.
+ See the sqlite3.oo1.DB constructor for peculiarities and
+ transformations,
+
+ vfs: sqlite3_vfs name. Ignored if filename is ":memory:" or "".
+ This may change how the given filename is resolved.
+ }
+ }
+ ```
+
+ Response:
+
+ ```
+ {
+ type: "open",
+ messageId: ...as above...,
+ result: {
+ filename: db filename, possibly differing from the input.
+
+ dbId: an opaque ID value which must be passed in the message
+ envelope to other calls in this API to tell them which db to
+ use. If it is not provided to future calls, they will default to
+ operating on the least-recently-opened db. This property is, for
+ API consistency's sake, also part of the containing message
+ envelope. Only the `open` operation includes it in the `result`
+ property.
+
+ persistent: true if the given filename resides in the
+ known-persistent storage, else false.
+
+ vfs: name of the VFS the "main" db is using.
+ }
+ }
+ ```
+
+ ====================================================================
+ "close" a database
+
+ Message format:
+
+ ```
+ {
+ type: "close",
+ messageId: ...as above...
+ dbId: ...as above...
+ args: OPTIONAL {unlink: boolean}
+ }
+ ```
+
+ If the `dbId` does not refer to an opened ID, this is a no-op. If
+ the `args` object contains a truthy `unlink` value then the database
+ will be unlinked (deleted) after closing it. The inability to close a
+ db (because it's not opened) or delete its file does not trigger an
+ error.
+
+ Response:
+
+ ```
+ {
+ type: "close",
+ messageId: ...as above...,
+ result: {
+
+ filename: filename of closed db, or undefined if no db was closed
+
+ }
+ }
+ ```
+
+ ====================================================================
+ "exec" SQL
+
+ All SQL execution is processed through the exec operation. It offers
+ most of the features of the oo1.DB.exec() method, with a few limitations
+ imposed by the state having to cross thread boundaries.
+
+ Message format:
+
+ ```
+ {
+ type: "exec",
+ messageId: ...as above...
+ dbId: ...as above...
+ args: string (SQL) or {... see below ...}
+ }
+ ```
+
+ Response:
+
+ ```
+ {
+ type: "exec",
+ messageId: ...as above...,
+ dbId: ...as above...
+ result: {
+ input arguments, possibly modified. See below.
+ }
+ }
+ ```
+
+ The arguments are in the same form accepted by oo1.DB.exec(), with
+ the exceptions noted below.
+
+ If the `countChanges` arguments property (added in version 3.43) is
+ truthy then the `result` property contained by the returned object
+ will have a `changeCount` property which holds the number of changes
+ made by the provided SQL. Because the SQL may contain an arbitrary
+ number of statements, the `changeCount` is calculated by calling
+ `sqlite3_total_changes()` before and after the SQL is evaluated. If
+ the value of `countChanges` is 64 then the `changeCount` property
+ will be returned as a 64-bit integer in the form of a BigInt (noting
+ that that will trigger an exception if used in a BigInt-incapable
+ build). In the latter case, the number of changes is calculated by
+ calling `sqlite3_total_changes64()` before and after the SQL is
+ evaluated.
+
+ A function-type args.callback property cannot cross
+ the window/Worker boundary, so is not useful here. If
+ args.callback is a string then it is assumed to be a
+ message type key, in which case a callback function will be
+ applied which posts each row result via:
+
+ postMessage({type: thatKeyType,
+ rowNumber: 1-based-#,
+ row: theRow,
+ columnNames: anArray
+ })
+
+ And, at the end of the result set (whether or not any result rows
+ were produced), it will post an identical message with
+ (row=undefined, rowNumber=null) to alert the caller than the result
+ set is completed. Note that a row value of `null` is a legal row
+ result for certain arg.rowMode values.
+
+ (Design note: we don't use (row=undefined, rowNumber=undefined) to
+ indicate end-of-results because fetching those would be
+ indistinguishable from fetching from an empty object unless the
+ client used hasOwnProperty() (or similar) to distinguish "missing
+ property" from "property with the undefined value". Similarly,
+ `null` is a legal value for `row` in some case , whereas the db
+ layer won't emit a result value of `undefined`.)
+
+ The callback proxy must not recurse into this interface. An exec()
+ call will tie up the Worker thread, causing any recursion attempt
+ to wait until the first exec() is completed.
+
+ The response is the input options object (or a synthesized one if
+ passed only a string), noting that options.resultRows and
+ options.columnNames may be populated by the call to db.exec().
+
+
+ ====================================================================
+ "export" the current db
+
+ To export the underlying database as a byte array...
+
+ Message format:
+
+ ```
+ {
+ type: "export",
+ messageId: ...as above...,
+ dbId: ...as above...
+ }
+ ```
+
+ Response:
+
+ ```
+ {
+ type: "export",
+ messageId: ...as above...,
+ dbId: ...as above...
+ result: {
+ byteArray: Uint8Array (as per sqlite3_js_db_export()),
+ filename: the db filename,
+ mimetype: "application/x-sqlite3"
+ }
+ }
+ ```
+
+*/
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+sqlite3.initWorker1API = function(){
+ 'use strict';
+ const toss = (...args)=>{throw new Error(args.join(' '))};
+ if(!(globalThis.WorkerGlobalScope instanceof Function)){
+ toss("initWorker1API() must be run from a Worker thread.");
+ }
+ const sqlite3 = this.sqlite3 || toss("Missing this.sqlite3 object.");
+ const DB = sqlite3.oo1.DB;
+
+ /**
+ Returns the app-wide unique ID for the given db, creating one if
+ needed.
+ */
+ const getDbId = function(db){
+ let id = wState.idMap.get(db);
+ if(id) return id;
+ id = 'db#'+(++wState.idSeq)+'@'+db.pointer;
+ /** ^^^ can't simply use db.pointer b/c closing/opening may re-use
+ the same address, which could map pending messages to a wrong
+ instance. */
+ wState.idMap.set(db, id);
+ return id;
+ };
+
+ /**
+ Internal helper for managing Worker-level state.
+ */
+ const wState = {
+ /**
+ Each opened DB is added to this.dbList, and the first entry in
+ that list is the default db. As each db is closed, its entry is
+ removed from the list.
+ */
+ dbList: [],
+ /** Sequence number of dbId generation. */
+ idSeq: 0,
+ /** Map of DB instances to dbId. */
+ idMap: new WeakMap,
+ /** Temp holder for "transferable" postMessage() state. */
+ xfer: [],
+ open: function(opt){
+ const db = new DB(opt);
+ this.dbs[getDbId(db)] = db;
+ if(this.dbList.indexOf(db)<0) this.dbList.push(db);
+ return db;
+ },
+ close: function(db,alsoUnlink){
+ if(db){
+ delete this.dbs[getDbId(db)];
+ const filename = db.filename;
+ const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0);
+ db.close();
+ const ddNdx = this.dbList.indexOf(db);
+ if(ddNdx>=0) this.dbList.splice(ddNdx, 1);
+ if(alsoUnlink && filename && pVfs){
+ sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename);
+ }
+ }
+ },
+ /**
+ Posts the given worker message value. If xferList is provided,
+ it must be an array, in which case a copy of it passed as
+ postMessage()'s second argument and xferList.length is set to
+ 0.
+ */
+ post: function(msg,xferList){
+ if(xferList && xferList.length){
+ globalThis.postMessage( msg, Array.from(xferList) );
+ xferList.length = 0;
+ }else{
+ globalThis.postMessage(msg);
+ }
+ },
+ /** Map of DB IDs to DBs. */
+ dbs: Object.create(null),
+ /** Fetch the DB for the given id. Throw if require=true and the
+ id is not valid, else return the db or undefined. */
+ getDb: function(id,require=true){
+ return this.dbs[id]
+ || (require ? toss("Unknown (or closed) DB ID:",id) : undefined);
+ }
+ };
+
+ /** Throws if the given db is falsy or not opened, else returns its
+ argument. */
+ const affirmDbOpen = function(db = wState.dbList[0]){
+ return (db && db.pointer) ? db : toss("DB is not opened.");
+ };
+
+ /** Extract dbId from the given message payload. */
+ const getMsgDb = function(msgData,affirmExists=true){
+ const db = wState.getDb(msgData.dbId,false) || wState.dbList[0];
+ return affirmExists ? affirmDbOpen(db) : db;
+ };
+
+ const getDefaultDbId = function(){
+ return wState.dbList[0] && getDbId(wState.dbList[0]);
+ };
+
+ const guessVfs = function(filename){
+ const m = /^file:.+(vfs=(\w+))/.exec(filename);
+ return sqlite3.capi.sqlite3_vfs_find(m ? m[2] : 0);
+ };
+
+ const isSpecialDbFilename = (n)=>{
+ return ""===n || ':'===n[0];
+ };
+
+ /**
+ A level of "organizational abstraction" for the Worker1
+ API. Each method in this object must map directly to a Worker1
+ message type key. The onmessage() dispatcher attempts to
+ dispatch all inbound messages to a method of this object,
+ passing it the event.data part of the inbound event object. All
+ methods must return a plain Object containing any result
+ state, which the dispatcher may amend. All methods must throw
+ on error.
+ */
+ const wMsgHandler = {
+ open: function(ev){
+ const oargs = Object.create(null), args = (ev.args || Object.create(null));
+ if(args.simulateError){ // undocumented internal testing option
+ toss("Throwing because of simulateError flag.");
+ }
+ const rc = Object.create(null);
+ let byteArray, pVfs;
+ oargs.vfs = args.vfs;
+ if(isSpecialDbFilename(args.filename)){
+ oargs.filename = args.filename || "";
+ }else{
+ oargs.filename = args.filename;
+ byteArray = args.byteArray;
+ if(byteArray) pVfs = guessVfs(args.filename);
+ }
+ if(pVfs){
+ /* 2022-11-02: this feature is as-yet untested except that
+ sqlite3_wasm_vfs_create_file() has been tested from the
+ browser dev console. */
+ let pMem;
+ try{
+ pMem = sqlite3.wasm.allocFromTypedArray(byteArray);
+ const rc = sqlite3.wasm.sqlite3_wasm_vfs_create_file(
+ pVfs, oargs.filename, pMem, byteArray.byteLength
+ );
+ if(rc) sqlite3.SQLite3Error.toss(rc);
+ }catch(e){
+ throw new sqlite3.SQLite3Error(
+ e.name+' creating '+args.filename+": "+e.message, {
+ cause: e
+ }
+ );
+ }finally{
+ if(pMem) sqlite3.wasm.dealloc(pMem);
+ }
+ }
+ const db = wState.open(oargs);
+ rc.filename = db.filename;
+ rc.persistent = !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs");
+ rc.dbId = getDbId(db);
+ rc.vfs = db.dbVfsName();
+ return rc;
+ },
+
+ close: function(ev){
+ const db = getMsgDb(ev,false);
+ const response = {
+ filename: db && db.filename
+ };
+ if(db){
+ const doUnlink = ((ev.args && 'object'===typeof ev.args)
+ ? !!ev.args.unlink : false);
+ wState.close(db, doUnlink);
+ }
+ return response;
+ },
+
+ exec: function(ev){
+ const rc = (
+ 'string'===typeof ev.args
+ ) ? {sql: ev.args} : (ev.args || Object.create(null));
+ if('stmt'===rc.rowMode){
+ toss("Invalid rowMode for 'exec': stmt mode",
+ "does not work in the Worker API.");
+ }else if(!rc.sql){
+ toss("'exec' requires input SQL.");
+ }
+ const db = getMsgDb(ev);
+ if(rc.callback || Array.isArray(rc.resultRows)){
+ // Part of a copy-avoidance optimization for blobs
+ db._blobXfer = wState.xfer;
+ }
+ const theCallback = rc.callback;
+ let rowNumber = 0;
+ const hadColNames = !!rc.columnNames;
+ if('string' === typeof theCallback){
+ if(!hadColNames) rc.columnNames = [];
+ /* Treat this as a worker message type and post each
+ row as a message of that type. */
+ rc.callback = function(row,stmt){
+ wState.post({
+ type: theCallback,
+ columnNames: rc.columnNames,
+ rowNumber: ++rowNumber,
+ row: row
+ }, wState.xfer);
+ }
+ }
+ try {
+ const changeCount = !!rc.countChanges
+ ? db.changes(true,(64===rc.countChanges))
+ : undefined;
+ db.exec(rc);
+ if(undefined !== changeCount){
+ rc.changeCount = db.changes(true,64===rc.countChanges) - changeCount;
+ }
+ if(rc.callback instanceof Function){
+ rc.callback = theCallback;
+ /* Post a sentinel message to tell the client that the end
+ of the result set has been reached (possibly with zero
+ rows). */
+ wState.post({
+ type: theCallback,
+ columnNames: rc.columnNames,
+ rowNumber: null /*null to distinguish from "property not set"*/,
+ row: undefined /*undefined because null is a legal row value
+ for some rowType values, but undefined is not*/
+ });
+ }
+ }finally{
+ delete db._blobXfer;
+ if(rc.callback) rc.callback = theCallback;
+ }
+ return rc;
+ }/*exec()*/,
+
+ 'config-get': function(){
+ const rc = Object.create(null), src = sqlite3.config;
+ [
+ 'bigIntEnabled'
+ ].forEach(function(k){
+ if(Object.getOwnPropertyDescriptor(src, k)) rc[k] = src[k];
+ });
+ rc.version = sqlite3.version;
+ rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list();
+ rc.opfsEnabled = !!sqlite3.opfs;
+ return rc;
+ },
+
+ /**
+ Exports the database to a byte array, as per
+ sqlite3_serialize(). Response is an object:
+
+ {
+ byteArray: Uint8Array (db file contents),
+ filename: the current db filename,
+ mimetype: 'application/x-sqlite3'
+ }
+ */
+ export: function(ev){
+ const db = getMsgDb(ev);
+ const response = {
+ byteArray: sqlite3.capi.sqlite3_js_db_export(db.pointer),
+ filename: db.filename,
+ mimetype: 'application/x-sqlite3'
+ };
+ wState.xfer.push(response.byteArray.buffer);
+ return response;
+ }/*export()*/,
+
+ toss: function(ev){
+ toss("Testing worker exception");
+ },
+
+ 'opfs-tree': async function(ev){
+ if(!sqlite3.opfs) toss("OPFS support is unavailable.");
+ const response = await sqlite3.opfs.treeList();
+ return response;
+ }
+ }/*wMsgHandler*/;
+
+ globalThis.onmessage = async function(ev){
+ ev = ev.data;
+ let result, dbId = ev.dbId, evType = ev.type;
+ const arrivalTime = performance.now();
+ try {
+ if(wMsgHandler.hasOwnProperty(evType) &&
+ wMsgHandler[evType] instanceof Function){
+ result = await wMsgHandler[evType](ev);
+ }else{
+ toss("Unknown db worker message type:",ev.type);
+ }
+ }catch(err){
+ evType = 'error';
+ result = {
+ operation: ev.type,
+ message: err.message,
+ errorClass: err.name,
+ input: ev
+ };
+ if(err.stack){
+ result.stack = ('string'===typeof err.stack)
+ ? err.stack.split(/\n\s*/) : err.stack;
+ }
+ if(0) sqlite3.config.warn("Worker is propagating an exception to main thread.",
+ "Reporting it _here_ for the stack trace:",err,result);
+ }
+ if(!dbId){
+ dbId = result.dbId/*from 'open' cmd*/
+ || getDefaultDbId();
+ }
+ // Timing info is primarily for use in testing this API. It's not part of
+ // the public API. arrivalTime = when the worker got the message.
+ wState.post({
+ type: evType,
+ dbId: dbId,
+ messageId: ev.messageId,
+ workerReceivedTime: arrivalTime,
+ workerRespondTime: performance.now(),
+ departureTime: ev.departureTime,
+ // TODO: move the timing bits into...
+ //timing:{
+ // departure: ev.departureTime,
+ // workerReceived: arrivalTime,
+ // workerResponse: performance.now();
+ //},
+ result: result
+ }, wState.xfer);
+ };
+ globalThis.postMessage({type:'sqlite3-api',result:'worker1-ready'});
+}.bind({sqlite3});
+});
+//#else
+/* Built with the omit-oo1 flag. */
+//#endif ifnot omit-oo1
diff --git a/ext/wasm/api/sqlite3-license-version-header.js b/ext/wasm/api/sqlite3-license-version-header.js
new file mode 100644
index 0000000..4829894
--- /dev/null
+++ b/ext/wasm/api/sqlite3-license-version-header.js
@@ -0,0 +1,25 @@
+/*
+** LICENSE for the sqlite3 WebAssembly/JavaScript APIs.
+**
+** This bundle (typically released as sqlite3.js or sqlite3.mjs)
+** is an amalgamation of JavaScript source code from two projects:
+**
+** 1) https://emscripten.org: the Emscripten "glue code" is covered by
+** the terms of the MIT license and University of Illinois/NCSA
+** Open Source License, as described at:
+**
+** https://emscripten.org/docs/introducing_emscripten/emscripten_license.html
+**
+** 2) https://sqlite.org: all code and documentation labeled as being
+** from this source are released under the same terms as the sqlite3
+** C library:
+**
+** 2022-10-16
+**
+** 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.
+*/
diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js
new file mode 100644
index 0000000..cafd296
--- /dev/null
+++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js
@@ -0,0 +1,915 @@
+/*
+ 2022-09-16
+
+ 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.
+
+ ***********************************************************************
+
+ A Worker which manages asynchronous OPFS handles on behalf of a
+ synchronous API which controls it via a combination of Worker
+ messages, SharedArrayBuffer, and Atomics. It is the asynchronous
+ counterpart of the API defined in sqlite3-vfs-opfs.js.
+
+ Highly indebted to:
+
+ https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/OriginPrivateFileSystemVFS.js
+
+ for demonstrating how to use the OPFS APIs.
+
+ This file is to be loaded as a Worker. It does not have any direct
+ access to the sqlite3 JS/WASM bits, so any bits which it needs (most
+ notably SQLITE_xxx integer codes) have to be imported into it via an
+ initialization process.
+
+ This file represents an implementation detail of a larger piece of
+ code, and not a public interface. Its details may change at any time
+ and are not intended to be used by any client-level code.
+
+ 2022-11-27: Chrome v108 changes some async methods to synchronous, as
+ documented at:
+
+ https://developer.chrome.com/blog/sync-methods-for-accesshandles/
+
+ Firefox v111 and Safari 16.4, both released in March 2023, also
+ include this.
+
+ We cannot change to the sync forms at this point without breaking
+ clients who use Chrome v104-ish or higher. truncate(), getSize(),
+ flush(), and close() are now (as of v108) synchronous. Calling them
+ with an "await", as we have to for the async forms, is still legal
+ with the sync forms but is superfluous. Calling the async forms with
+ theFunc().then(...) is not compatible with the change to
+ synchronous, but we do do not use those APIs that way. i.e. we don't
+ _need_ to change anything for this, but at some point (after Chrome
+ versions (approximately) 104-107 are extinct) should change our
+ usage of those methods to remove the "await".
+*/
+"use strict";
+const wPost = (type,...args)=>postMessage({type, payload:args});
+const installAsyncProxy = function(self){
+ const toss = function(...args){throw new Error(args.join(' '))};
+ if(globalThis.window === globalThis){
+ toss("This code cannot run from the main thread.",
+ "Load it as a Worker from a separate Worker.");
+ }else if(!navigator?.storage?.getDirectory){
+ toss("This API requires navigator.storage.getDirectory.");
+ }
+
+ /**
+ Will hold state copied to this object from the syncronous side of
+ this API.
+ */
+ const state = Object.create(null);
+
+ /**
+ verbose:
+
+ 0 = no logging output
+ 1 = only errors
+ 2 = warnings and errors
+ 3 = debug, warnings, and errors
+ */
+ state.verbose = 1;
+
+ const loggers = {
+ 0:console.error.bind(console),
+ 1:console.warn.bind(console),
+ 2:console.log.bind(console)
+ };
+ const logImpl = (level,...args)=>{
+ if(state.verbose>level) loggers[level]("OPFS asyncer:",...args);
+ };
+ const log = (...args)=>logImpl(2, ...args);
+ const warn = (...args)=>logImpl(1, ...args);
+ const error = (...args)=>logImpl(0, ...args);
+ const metrics = Object.create(null);
+ metrics.reset = ()=>{
+ let k;
+ const r = (m)=>(m.count = m.time = m.wait = 0);
+ for(k in state.opIds){
+ r(metrics[k] = Object.create(null));
+ }
+ let s = metrics.s11n = Object.create(null);
+ s = s.serialize = Object.create(null);
+ s.count = s.time = 0;
+ s = metrics.s11n.deserialize = Object.create(null);
+ s.count = s.time = 0;
+ };
+ metrics.dump = ()=>{
+ let k, n = 0, t = 0, w = 0;
+ for(k in state.opIds){
+ const m = metrics[k];
+ n += m.count;
+ t += m.time;
+ w += m.wait;
+ m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
+ }
+ console.log(globalThis?.location?.href,
+ "metrics for",globalThis?.location?.href,":\n",
+ metrics,
+ "\nTotal of",n,"op(s) for",t,"ms",
+ "approx",w,"ms spent waiting on OPFS APIs.");
+ console.log("Serialization metrics:",metrics.s11n);
+ };
+
+ /**
+ __openFiles is a map of sqlite3_file pointers (integers) to
+ metadata related to a given OPFS file handles. The pointers are, in
+ this side of the interface, opaque file handle IDs provided by the
+ synchronous part of this constellation. Each value is an object
+ with a structure demonstrated in the xOpen() impl.
+ */
+ const __openFiles = Object.create(null);
+ /**
+ __implicitLocks is a Set of sqlite3_file pointers (integers) which were
+ "auto-locked". i.e. those for which we obtained a sync access
+ handle without an explicit xLock() call. Such locks will be
+ released during db connection idle time, whereas a sync access
+ handle obtained via xLock(), or subsequently xLock()'d after
+ auto-acquisition, will not be released until xUnlock() is called.
+
+ Maintenance reminder: if we relinquish auto-locks at the end of the
+ operation which acquires them, we pay a massive performance
+ penalty: speedtest1 benchmarks take up to 4x as long. By delaying
+ the lock release until idle time, the hit is negligible.
+ */
+ const __implicitLocks = new Set();
+
+ /**
+ Expects an OPFS file path. It gets resolved, such that ".."
+ components are properly expanded, and returned. If the 2nd arg is
+ true, the result is returned as an array of path elements, else an
+ absolute path string is returned.
+ */
+ const getResolvedPath = function(filename,splitIt){
+ const p = new URL(
+ filename, 'file://irrelevant'
+ ).pathname;
+ return splitIt ? p.split('/').filter((v)=>!!v) : p;
+ };
+
+ /**
+ Takes the absolute path to a filesystem element. Returns an array
+ of [handleOfContainingDir, filename]. If the 2nd argument is truthy
+ then each directory element leading to the file is created along
+ the way. Throws if any creation or resolution fails.
+ */
+ const getDirForFilename = async function f(absFilename, createDirs = false){
+ const path = getResolvedPath(absFilename, true);
+ const filename = path.pop();
+ let dh = state.rootDir;
+ for(const dirName of path){
+ if(dirName){
+ dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs});
+ }
+ }
+ return [dh, filename];
+ };
+
+ /**
+ If the given file-holding object has a sync handle attached to it,
+ that handle is remove and asynchronously closed. Though it may
+ sound sensible to continue work as soon as the close() returns
+ (noting that it's asynchronous), doing so can cause operations
+ performed soon afterwards, e.g. a call to getSyncHandle() to fail
+ because they may happen out of order from the close(). OPFS does
+ not guaranty that the actual order of operations is retained in
+ such cases. i.e. always "await" on the result of this function.
+ */
+ const closeSyncHandle = async (fh)=>{
+ if(fh.syncHandle){
+ log("Closing sync handle for",fh.filenameAbs);
+ const h = fh.syncHandle;
+ delete fh.syncHandle;
+ delete fh.xLock;
+ __implicitLocks.delete(fh.fid);
+ return h.close();
+ }
+ };
+
+ /**
+ A proxy for closeSyncHandle() which is guaranteed to not throw.
+
+ This function is part of a lock/unlock step in functions which
+ require a sync access handle but may be called without xLock()
+ having been called first. Such calls need to release that
+ handle to avoid locking the file for all of time. This is an
+ _attempt_ at reducing cross-tab contention but it may prove
+ to be more of a problem than a solution and may need to be
+ removed.
+ */
+ const closeSyncHandleNoThrow = async (fh)=>{
+ try{await closeSyncHandle(fh)}
+ catch(e){
+ warn("closeSyncHandleNoThrow() ignoring:",e,fh);
+ }
+ };
+
+ /* Release all auto-locks. */
+ const releaseImplicitLocks = async ()=>{
+ if(__implicitLocks.size){
+ /* Release all auto-locks. */
+ for(const fid of __implicitLocks){
+ const fh = __openFiles[fid];
+ await closeSyncHandleNoThrow(fh);
+ log("Auto-unlocked",fid,fh.filenameAbs);
+ }
+ }
+ };
+
+ /**
+ An experiment in improving concurrency by freeing up implicit locks
+ sooner. This is known to impact performance dramatically but it has
+ also shown to improve concurrency considerably.
+
+ If fh.releaseImplicitLocks is truthy and fh is in __implicitLocks,
+ this routine returns closeSyncHandleNoThrow(), else it is a no-op.
+ */
+ const releaseImplicitLock = async (fh)=>{
+ if(fh.releaseImplicitLocks && __implicitLocks.has(fh.fid)){
+ return closeSyncHandleNoThrow(fh);
+ }
+ };
+
+ /**
+ An error class specifically for use with getSyncHandle(), the goal
+ of which is to eventually be able to distinguish unambiguously
+ between locking-related failures and other types, noting that we
+ cannot currently do so because createSyncAccessHandle() does not
+ define its exceptions in the required level of detail.
+
+ 2022-11-29: according to:
+
+ https://github.com/whatwg/fs/pull/21
+
+ NoModificationAllowedError will be the standard exception thrown
+ when acquisition of a sync access handle fails due to a locking
+ error. As of this writing, that error type is not visible in the
+ dev console in Chrome v109, nor is it documented in MDN, but an
+ error with that "name" property is being thrown from the OPFS
+ layer.
+ */
+ class GetSyncHandleError extends Error {
+ constructor(errorObject, ...msg){
+ super([
+ ...msg, ': '+errorObject.name+':',
+ errorObject.message
+ ].join(' '), {
+ cause: errorObject
+ });
+ this.name = 'GetSyncHandleError';
+ }
+ };
+ GetSyncHandleError.convertRc = (e,rc)=>{
+ if(1){
+ return (
+ e instanceof GetSyncHandleError
+ && ((e.cause.name==='NoModificationAllowedError')
+ /* Inconsistent exception.name from Chrome/ium with the
+ same exception.message text: */
+ || (e.cause.name==='DOMException'
+ && 0===e.cause.message.indexOf('Access Handles cannot')))
+ ) ? (
+ /*console.warn("SQLITE_BUSY",e),*/
+ state.sq3Codes.SQLITE_BUSY
+ ) : rc;
+ }else{
+ return rc;
+ }
+ }
+ /**
+ Returns the sync access handle associated with the given file
+ handle object (which must be a valid handle object, as created by
+ xOpen()), lazily opening it if needed.
+
+ In order to help alleviate cross-tab contention for a dabase, if
+ an exception is thrown while acquiring the handle, this routine
+ will wait briefly and try again, up to some fixed number of
+ times. If acquisition still fails at that point it will give up
+ and propagate the exception. Client-level code will see that as
+ an I/O error.
+ */
+ const getSyncHandle = async (fh,opName)=>{
+ if(!fh.syncHandle){
+ const t = performance.now();
+ log("Acquiring sync handle for",fh.filenameAbs);
+ const maxTries = 6,
+ msBase = state.asyncIdleWaitTime * 2;
+ let i = 1, ms = msBase;
+ for(; true; ms = msBase * ++i){
+ try {
+ //if(i<3) toss("Just testing getSyncHandle() wait-and-retry.");
+ //TODO? A config option which tells it to throw here
+ //randomly every now and then, for testing purposes.
+ fh.syncHandle = await fh.fileHandle.createSyncAccessHandle();
+ break;
+ }catch(e){
+ if(i === maxTries){
+ throw new GetSyncHandleError(
+ e, "Error getting sync handle for",opName+"().",maxTries,
+ "attempts failed.",fh.filenameAbs
+ );
+ }
+ warn("Error getting sync handle for",opName+"(). Waiting",ms,
+ "ms and trying again.",fh.filenameAbs,e);
+ Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms);
+ }
+ }
+ log("Got",opName+"() sync handle for",fh.filenameAbs,
+ 'in',performance.now() - t,'ms');
+ if(!fh.xLock){
+ __implicitLocks.add(fh.fid);
+ log("Acquired implicit lock for",opName+"()",fh.fid,fh.filenameAbs);
+ }
+ }
+ return fh.syncHandle;
+ };
+
+ /**
+ Stores the given value at state.sabOPView[state.opIds.rc] and then
+ Atomics.notify()'s it.
+ */
+ const storeAndNotify = (opName, value)=>{
+ log(opName+"() => notify(",value,")");
+ Atomics.store(state.sabOPView, state.opIds.rc, value);
+ Atomics.notify(state.sabOPView, state.opIds.rc);
+ };
+
+ /**
+ Throws if fh is a file-holding object which is flagged as read-only.
+ */
+ const affirmNotRO = function(opName,fh){
+ if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
+ };
+
+ /**
+ We track 2 different timers: the "metrics" timer records how much
+ time we spend performing work. The "wait" timer records how much
+ time we spend waiting on the underlying OPFS timer. See the calls
+ to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd()
+ throughout this file to see how they're used.
+ */
+ const __mTimer = Object.create(null);
+ __mTimer.op = undefined;
+ __mTimer.start = undefined;
+ const mTimeStart = (op)=>{
+ __mTimer.start = performance.now();
+ __mTimer.op = op;
+ //metrics[op] || toss("Maintenance required: missing metrics for",op);
+ ++metrics[op].count;
+ };
+ const mTimeEnd = ()=>(
+ metrics[__mTimer.op].time += performance.now() - __mTimer.start
+ );
+ const __wTimer = Object.create(null);
+ __wTimer.op = undefined;
+ __wTimer.start = undefined;
+ const wTimeStart = (op)=>{
+ __wTimer.start = performance.now();
+ __wTimer.op = op;
+ //metrics[op] || toss("Maintenance required: missing metrics for",op);
+ };
+ const wTimeEnd = ()=>(
+ metrics[__wTimer.op].wait += performance.now() - __wTimer.start
+ );
+
+ /**
+ Gets set to true by the 'opfs-async-shutdown' command to quit the
+ wait loop. This is only intended for debugging purposes: we cannot
+ inspect this file's state while the tight waitLoop() is running and
+ need a way to stop that loop for introspection purposes.
+ */
+ let flagAsyncShutdown = false;
+
+ /**
+ Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
+ methods, as well as helpers like mkdir(). Maintenance reminder:
+ members are in alphabetical order to simplify finding them.
+ */
+ const vfsAsyncImpls = {
+ 'opfs-async-metrics': async ()=>{
+ mTimeStart('opfs-async-metrics');
+ metrics.dump();
+ storeAndNotify('opfs-async-metrics', 0);
+ mTimeEnd();
+ },
+ 'opfs-async-shutdown': async ()=>{
+ flagAsyncShutdown = true;
+ storeAndNotify('opfs-async-shutdown', 0);
+ },
+ mkdir: async (dirname)=>{
+ mTimeStart('mkdir');
+ let rc = 0;
+ wTimeStart('mkdir');
+ try {
+ await getDirForFilename(dirname+"/filepart", true);
+ }catch(e){
+ state.s11n.storeException(2,e);
+ rc = state.sq3Codes.SQLITE_IOERR;
+ }finally{
+ wTimeEnd();
+ }
+ storeAndNotify('mkdir', rc);
+ mTimeEnd();
+ },
+ xAccess: async (filename)=>{
+ mTimeStart('xAccess');
+ /* OPFS cannot support the full range of xAccess() queries
+ sqlite3 calls for. We can essentially just tell if the file
+ is accessible, but if it is then it's automatically writable
+ (unless it's locked, which we cannot(?) know without trying
+ to open it). OPFS does not have the notion of read-only.
+
+ The return semantics of this function differ from sqlite3's
+ xAccess semantics because we are limited in what we can
+ communicate back to our synchronous communication partner: 0 =
+ accessible, non-0 means not accessible.
+ */
+ let rc = 0;
+ wTimeStart('xAccess');
+ try{
+ const [dh, fn] = await getDirForFilename(filename);
+ await dh.getFileHandle(fn);
+ }catch(e){
+ state.s11n.storeException(2,e);
+ rc = state.sq3Codes.SQLITE_IOERR;
+ }finally{
+ wTimeEnd();
+ }
+ storeAndNotify('xAccess', rc);
+ mTimeEnd();
+ },
+ xClose: async function(fid/*sqlite3_file pointer*/){
+ const opName = 'xClose';
+ mTimeStart(opName);
+ __implicitLocks.delete(fid);
+ const fh = __openFiles[fid];
+ let rc = 0;
+ wTimeStart(opName);
+ if(fh){
+ delete __openFiles[fid];
+ await closeSyncHandle(fh);
+ if(fh.deleteOnClose){
+ try{ await fh.dirHandle.removeEntry(fh.filenamePart) }
+ catch(e){ warn("Ignoring dirHandle.removeEntry() failure of",fh,e) }
+ }
+ }else{
+ state.s11n.serialize();
+ rc = state.sq3Codes.SQLITE_NOTFOUND;
+ }
+ wTimeEnd();
+ storeAndNotify(opName, rc);
+ mTimeEnd();
+ },
+ xDelete: async function(...args){
+ mTimeStart('xDelete');
+ const rc = await vfsAsyncImpls.xDeleteNoWait(...args);
+ storeAndNotify('xDelete', rc);
+ mTimeEnd();
+ },
+ xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){
+ /* The syncDir flag is, for purposes of the VFS API's semantics,
+ ignored here. However, if it has the value 0x1234 then: after
+ deleting the given file, recursively try to delete any empty
+ directories left behind in its wake (ignoring any errors and
+ stopping at the first failure).
+
+ That said: we don't know for sure that removeEntry() fails if
+ the dir is not empty because the API is not documented. It has,
+ however, a "recursive" flag which defaults to false, so
+ presumably it will fail if the dir is not empty and that flag
+ is false.
+ */
+ let rc = 0;
+ wTimeStart('xDelete');
+ try {
+ while(filename){
+ const [hDir, filenamePart] = await getDirForFilename(filename, false);
+ if(!filenamePart) break;
+ await hDir.removeEntry(filenamePart, {recursive});
+ if(0x1234 !== syncDir) break;
+ recursive = false;
+ filename = getResolvedPath(filename, true);
+ filename.pop();
+ filename = filename.join('/');
+ }
+ }catch(e){
+ state.s11n.storeException(2,e);
+ rc = state.sq3Codes.SQLITE_IOERR_DELETE;
+ }
+ wTimeEnd();
+ return rc;
+ },
+ xFileSize: async function(fid/*sqlite3_file pointer*/){
+ mTimeStart('xFileSize');
+ const fh = __openFiles[fid];
+ let rc = 0;
+ wTimeStart('xFileSize');
+ try{
+ const sz = await (await getSyncHandle(fh,'xFileSize')).getSize();
+ state.s11n.serialize(Number(sz));
+ }catch(e){
+ state.s11n.storeException(1,e);
+ rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR);
+ }
+ await releaseImplicitLock(fh);
+ wTimeEnd();
+ storeAndNotify('xFileSize', rc);
+ mTimeEnd();
+ },
+ xLock: async function(fid/*sqlite3_file pointer*/,
+ lockType/*SQLITE_LOCK_...*/){
+ mTimeStart('xLock');
+ const fh = __openFiles[fid];
+ let rc = 0;
+ const oldLockType = fh.xLock;
+ fh.xLock = lockType;
+ if( !fh.syncHandle ){
+ wTimeStart('xLock');
+ try {
+ await getSyncHandle(fh,'xLock');
+ __implicitLocks.delete(fid);
+ }catch(e){
+ state.s11n.storeException(1,e);
+ rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_LOCK);
+ fh.xLock = oldLockType;
+ }
+ wTimeEnd();
+ }
+ storeAndNotify('xLock',rc);
+ mTimeEnd();
+ },
+ xOpen: async function(fid/*sqlite3_file pointer*/, filename,
+ flags/*SQLITE_OPEN_...*/,
+ opfsFlags/*OPFS_...*/){
+ const opName = 'xOpen';
+ mTimeStart(opName);
+ const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags);
+ wTimeStart('xOpen');
+ try{
+ let hDir, filenamePart;
+ try {
+ [hDir, filenamePart] = await getDirForFilename(filename, !!create);
+ }catch(e){
+ state.s11n.storeException(1,e);
+ storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND);
+ mTimeEnd();
+ wTimeEnd();
+ return;
+ }
+ const hFile = await hDir.getFileHandle(filenamePart, {create});
+ wTimeEnd();
+ const fh = Object.assign(Object.create(null),{
+ fid: fid,
+ filenameAbs: filename,
+ filenamePart: filenamePart,
+ dirHandle: hDir,
+ fileHandle: hFile,
+ sabView: state.sabFileBufView,
+ readOnly: create
+ ? false : (state.sq3Codes.SQLITE_OPEN_READONLY & flags),
+ deleteOnClose: !!(state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags)
+ });
+ fh.releaseImplicitLocks =
+ (opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP)
+ || state.opfsFlags.defaultUnlockAsap;
+ if(0 /* this block is modelled after something wa-sqlite
+ does but it leads to immediate contention on journal files.
+ Update: this approach reportedly only works for DELETE journal
+ mode. */
+ && (0===(flags & state.sq3Codes.SQLITE_OPEN_MAIN_DB))){
+ /* sqlite does not lock these files, so go ahead and grab an OPFS
+ lock. */
+ fh.xLock = "xOpen"/* Truthy value to keep entry from getting
+ flagged as auto-locked. String value so
+ that we can easily distinguish is later
+ if needed. */;
+ await getSyncHandle(fh,'xOpen');
+ }
+ __openFiles[fid] = fh;
+ storeAndNotify(opName, 0);
+ }catch(e){
+ wTimeEnd();
+ error(opName,e);
+ state.s11n.storeException(1,e);
+ storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR);
+ }
+ mTimeEnd();
+ },
+ xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){
+ mTimeStart('xRead');
+ let rc = 0, nRead;
+ const fh = __openFiles[fid];
+ try{
+ wTimeStart('xRead');
+ nRead = (await getSyncHandle(fh,'xRead')).read(
+ fh.sabView.subarray(0, n),
+ {at: Number(offset64)}
+ );
+ wTimeEnd();
+ if(nRead < n){/* Zero-fill remaining bytes */
+ fh.sabView.fill(0, nRead, n);
+ rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ;
+ }
+ }catch(e){
+ if(undefined===nRead) wTimeEnd();
+ error("xRead() failed",e,fh);
+ state.s11n.storeException(1,e);
+ rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_READ);
+ }
+ await releaseImplicitLock(fh);
+ storeAndNotify('xRead',rc);
+ mTimeEnd();
+ },
+ xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){
+ mTimeStart('xSync');
+ const fh = __openFiles[fid];
+ let rc = 0;
+ if(!fh.readOnly && fh.syncHandle){
+ try {
+ wTimeStart('xSync');
+ await fh.syncHandle.flush();
+ }catch(e){
+ state.s11n.storeException(2,e);
+ rc = state.sq3Codes.SQLITE_IOERR_FSYNC;
+ }
+ wTimeEnd();
+ }
+ storeAndNotify('xSync',rc);
+ mTimeEnd();
+ },
+ xTruncate: async function(fid/*sqlite3_file pointer*/,size){
+ mTimeStart('xTruncate');
+ let rc = 0;
+ const fh = __openFiles[fid];
+ wTimeStart('xTruncate');
+ try{
+ affirmNotRO('xTruncate', fh);
+ await (await getSyncHandle(fh,'xTruncate')).truncate(size);
+ }catch(e){
+ error("xTruncate():",e,fh);
+ state.s11n.storeException(2,e);
+ rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_TRUNCATE);
+ }
+ await releaseImplicitLock(fh);
+ wTimeEnd();
+ storeAndNotify('xTruncate',rc);
+ mTimeEnd();
+ },
+ xUnlock: async function(fid/*sqlite3_file pointer*/,
+ lockType/*SQLITE_LOCK_...*/){
+ mTimeStart('xUnlock');
+ let rc = 0;
+ const fh = __openFiles[fid];
+ if( state.sq3Codes.SQLITE_LOCK_NONE===lockType
+ && fh.syncHandle ){
+ wTimeStart('xUnlock');
+ try { await closeSyncHandle(fh) }
+ catch(e){
+ state.s11n.storeException(1,e);
+ rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
+ }
+ wTimeEnd();
+ }
+ storeAndNotify('xUnlock',rc);
+ mTimeEnd();
+ },
+ xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){
+ mTimeStart('xWrite');
+ let rc;
+ const fh = __openFiles[fid];
+ wTimeStart('xWrite');
+ try{
+ affirmNotRO('xWrite', fh);
+ rc = (
+ n === (await getSyncHandle(fh,'xWrite'))
+ .write(fh.sabView.subarray(0, n),
+ {at: Number(offset64)})
+ ) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE;
+ }catch(e){
+ error("xWrite():",e,fh);
+ state.s11n.storeException(1,e);
+ rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_WRITE);
+ }
+ await releaseImplicitLock(fh);
+ wTimeEnd();
+ storeAndNotify('xWrite',rc);
+ mTimeEnd();
+ }
+ }/*vfsAsyncImpls*/;
+
+ const initS11n = ()=>{
+ /**
+ ACHTUNG: this code is 100% duplicated in the other half of this
+ proxy! The documentation is maintained in the "synchronous half".
+ */
+ if(state.s11n) return state.s11n;
+ const textDecoder = new TextDecoder(),
+ textEncoder = new TextEncoder('utf-8'),
+ viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
+ viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
+ state.s11n = Object.create(null);
+ const TypeIds = Object.create(null);
+ TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
+ TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
+ TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
+ TypeIds.string = { id: 4 };
+ const getTypeId = (v)=>(
+ TypeIds[typeof v]
+ || toss("Maintenance required: this value type cannot be serialized.",v)
+ );
+ const getTypeIdById = (tid)=>{
+ switch(tid){
+ case TypeIds.number.id: return TypeIds.number;
+ case TypeIds.bigint.id: return TypeIds.bigint;
+ case TypeIds.boolean.id: return TypeIds.boolean;
+ case TypeIds.string.id: return TypeIds.string;
+ default: toss("Invalid type ID:",tid);
+ }
+ };
+ state.s11n.deserialize = function(clear=false){
+ ++metrics.s11n.deserialize.count;
+ const t = performance.now();
+ const argc = viewU8[0];
+ const rc = argc ? [] : null;
+ if(argc){
+ const typeIds = [];
+ let offset = 1, i, n, v;
+ for(i = 0; i < argc; ++i, ++offset){
+ typeIds.push(getTypeIdById(viewU8[offset]));
+ }
+ for(i = 0; i < argc; ++i){
+ const t = typeIds[i];
+ if(t.getter){
+ v = viewDV[t.getter](offset, state.littleEndian);
+ offset += t.size;
+ }else{/*String*/
+ n = viewDV.getInt32(offset, state.littleEndian);
+ offset += 4;
+ v = textDecoder.decode(viewU8.slice(offset, offset+n));
+ offset += n;
+ }
+ rc.push(v);
+ }
+ }
+ if(clear) viewU8[0] = 0;
+ //log("deserialize:",argc, rc);
+ metrics.s11n.deserialize.time += performance.now() - t;
+ return rc;
+ };
+ state.s11n.serialize = function(...args){
+ const t = performance.now();
+ ++metrics.s11n.serialize.count;
+ if(args.length){
+ //log("serialize():",args);
+ const typeIds = [];
+ let i = 0, offset = 1;
+ viewU8[0] = args.length & 0xff /* header = # of args */;
+ for(; i < args.length; ++i, ++offset){
+ /* Write the TypeIds.id value into the next args.length
+ bytes. */
+ typeIds.push(getTypeId(args[i]));
+ viewU8[offset] = typeIds[i].id;
+ }
+ for(i = 0; i < args.length; ++i) {
+ /* Deserialize the following bytes based on their
+ corresponding TypeIds.id from the header. */
+ const t = typeIds[i];
+ if(t.setter){
+ viewDV[t.setter](offset, args[i], state.littleEndian);
+ offset += t.size;
+ }else{/*String*/
+ const s = textEncoder.encode(args[i]);
+ viewDV.setInt32(offset, s.byteLength, state.littleEndian);
+ offset += 4;
+ viewU8.set(s, offset);
+ offset += s.byteLength;
+ }
+ }
+ //log("serialize() result:",viewU8.slice(0,offset));
+ }else{
+ viewU8[0] = 0;
+ }
+ metrics.s11n.serialize.time += performance.now() - t;
+ };
+
+ state.s11n.storeException = state.asyncS11nExceptions
+ ? ((priority,e)=>{
+ if(priority<=state.asyncS11nExceptions){
+ state.s11n.serialize([e.name,': ',e.message].join(""));
+ }
+ })
+ : ()=>{};
+
+ return state.s11n;
+ }/*initS11n()*/;
+
+ const waitLoop = async function f(){
+ const opHandlers = Object.create(null);
+ for(let k of Object.keys(state.opIds)){
+ const vi = vfsAsyncImpls[k];
+ if(!vi) continue;
+ const o = Object.create(null);
+ opHandlers[state.opIds[k]] = o;
+ o.key = k;
+ o.f = vi;
+ }
+ while(!flagAsyncShutdown){
+ try {
+ if('not-equal'!==Atomics.wait(
+ state.sabOPView, state.opIds.whichOp, 0, state.asyncIdleWaitTime
+ )){
+ /* Maintenance note: we compare against 'not-equal' because
+
+ https://github.com/tomayac/sqlite-wasm/issues/12
+
+ is reporting that this occassionally, under high loads,
+ returns 'ok', which leads to the whichOp being 0 (which
+ isn't a valid operation ID and leads to an exception,
+ along with a corresponding ugly console log
+ message). Unfortunately, the conditions for that cannot
+ be reliably reproduced. The only place in our code which
+ writes a 0 to the state.opIds.whichOp SharedArrayBuffer
+ index is a few lines down from here, and that instance
+ is required in order for clear communication between
+ the sync half of this proxy and this half.
+ */
+ await releaseImplicitLocks();
+ continue;
+ }
+ const opId = Atomics.load(state.sabOPView, state.opIds.whichOp);
+ Atomics.store(state.sabOPView, state.opIds.whichOp, 0);
+ const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId);
+ const args = state.s11n.deserialize(
+ true /* clear s11n to keep the caller from confusing this with
+ an exception string written by the upcoming
+ operation */
+ ) || [];
+ //warn("waitLoop() whichOp =",opId, hnd, args);
+ if(hnd.f) await hnd.f(...args);
+ else error("Missing callback for opId",opId);
+ }catch(e){
+ error('in waitLoop():',e);
+ }
+ }
+ };
+
+ navigator.storage.getDirectory().then(function(d){
+ state.rootDir = d;
+ globalThis.onmessage = function({data}){
+ switch(data.type){
+ case 'opfs-async-init':{
+ /* Receive shared state from synchronous partner */
+ const opt = data.args;
+ for(const k in opt) state[k] = opt[k];
+ state.verbose = opt.verbose ?? 1;
+ state.sabOPView = new Int32Array(state.sabOP);
+ state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
+ state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
+ Object.keys(vfsAsyncImpls).forEach((k)=>{
+ if(!Number.isFinite(state.opIds[k])){
+ toss("Maintenance required: missing state.opIds[",k,"]");
+ }
+ });
+ initS11n();
+ metrics.reset();
+ log("init state",state);
+ wPost('opfs-async-inited');
+ waitLoop();
+ break;
+ }
+ case 'opfs-async-restart':
+ if(flagAsyncShutdown){
+ warn("Restarting after opfs-async-shutdown. Might or might not work.");
+ flagAsyncShutdown = false;
+ waitLoop();
+ }
+ break;
+ case 'opfs-async-metrics':
+ metrics.dump();
+ break;
+ }
+ };
+ wPost('opfs-async-loaded');
+ }).catch((e)=>error("error initializing OPFS asyncer:",e));
+}/*installAsyncProxy()*/;
+if(!globalThis.SharedArrayBuffer){
+ wPost('opfs-unavailable', "Missing SharedArrayBuffer API.",
+ "The server must emit the COOP/COEP response headers to enable that.");
+}else if(!globalThis.Atomics){
+ wPost('opfs-unavailable', "Missing Atomics API.",
+ "The server must emit the COOP/COEP response headers to enable that.");
+}else if(!globalThis.FileSystemHandle ||
+ !globalThis.FileSystemDirectoryHandle ||
+ !globalThis.FileSystemFileHandle ||
+ !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle ||
+ !navigator?.storage?.getDirectory){
+ wPost('opfs-unavailable',"Missing required OPFS APIs.");
+}else{
+ installAsyncProxy(self);
+}
diff --git a/ext/wasm/api/sqlite3-v-helper.js b/ext/wasm/api/sqlite3-v-helper.js
new file mode 100644
index 0000000..e63da8a
--- /dev/null
+++ b/ext/wasm/api/sqlite3-v-helper.js
@@ -0,0 +1,718 @@
+/*
+** 2022-11-30
+**
+** 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 installs sqlite3.vfs, and object which exists to assist
+ in the creation of JavaScript implementations of sqlite3_vfs, along
+ with its virtual table counterpart, sqlite3.vtab.
+*/
+'use strict';
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
+ const vfs = Object.create(null), vtab = Object.create(null);
+
+ const StructBinder = sqlite3.StructBinder
+ /* we require a local alias b/c StructBinder is removed from the sqlite3
+ object during the final steps of the API cleanup. */;
+ sqlite3.vfs = vfs;
+ sqlite3.vtab = vtab;
+
+ const sii = capi.sqlite3_index_info;
+ /**
+ If n is >=0 and less than this.$nConstraint, this function
+ returns either a WASM pointer to the 0-based nth entry of
+ this.$aConstraint (if passed a truthy 2nd argument) or an
+ sqlite3_index_info.sqlite3_index_constraint object wrapping that
+ address (if passed a falsy value or no 2nd argument). Returns a
+ falsy value if n is out of range.
+ */
+ sii.prototype.nthConstraint = function(n, asPtr=false){
+ if(n<0 || n>=this.$nConstraint) return false;
+ const ptr = this.$aConstraint + (
+ sii.sqlite3_index_constraint.structInfo.sizeof * n
+ );
+ return asPtr ? ptr : new sii.sqlite3_index_constraint(ptr);
+ };
+
+ /**
+ Works identically to nthConstraint() but returns state from
+ this.$aConstraintUsage, so returns an
+ sqlite3_index_info.sqlite3_index_constraint_usage instance
+ if passed no 2nd argument or a falsy 2nd argument.
+ */
+ sii.prototype.nthConstraintUsage = function(n, asPtr=false){
+ if(n<0 || n>=this.$nConstraint) return false;
+ const ptr = this.$aConstraintUsage + (
+ sii.sqlite3_index_constraint_usage.structInfo.sizeof * n
+ );
+ return asPtr ? ptr : new sii.sqlite3_index_constraint_usage(ptr);
+ };
+
+ /**
+ If n is >=0 and less than this.$nOrderBy, this function
+ returns either a WASM pointer to the 0-based nth entry of
+ this.$aOrderBy (if passed a truthy 2nd argument) or an
+ sqlite3_index_info.sqlite3_index_orderby object wrapping that
+ address (if passed a falsy value or no 2nd argument). Returns a
+ falsy value if n is out of range.
+ */
+ sii.prototype.nthOrderBy = function(n, asPtr=false){
+ if(n<0 || n>=this.$nOrderBy) return false;
+ const ptr = this.$aOrderBy + (
+ sii.sqlite3_index_orderby.structInfo.sizeof * n
+ );
+ return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr);
+ };
+
+ /**
+ Installs a StructBinder-bound function pointer member of the
+ given name and function in the given StructType target object.
+
+ It creates a WASM proxy for the given function and arranges for
+ that proxy to be cleaned up when tgt.dispose() is called. Throws
+ on the slightest hint of error, e.g. tgt is-not-a StructType,
+ name does not map to a struct-bound member, etc.
+
+ As a special case, if the given function is a pointer, then
+ `wasm.functionEntry()` is used to validate that it is a known
+ function. If so, it is used as-is with no extra level of proxying
+ or cleanup, else an exception is thrown. It is legal to pass a
+ value of 0, indicating a NULL pointer, with the caveat that 0
+ _is_ a legal function pointer in WASM but it will not be accepted
+ as such _here_. (Justification: the function at address zero must
+ be one which initially came from the WASM module, not a method we
+ want to bind to a virtual table or VFS.)
+
+ This function returns a proxy for itself which is bound to tgt
+ and takes 2 args (name,func). That function returns the same
+ thing as this one, permitting calls to be chained.
+
+ If called with only 1 arg, it has no side effects but returns a
+ func with the same signature as described above.
+
+ ACHTUNG: because we cannot generically know how to transform JS
+ exceptions into result codes, the installed functions do no
+ automatic catching of exceptions. It is critical, to avoid
+ undefined behavior in the C layer, that methods mapped via
+ this function do not throw. The exception, as it were, to that
+ rule is...
+
+ If applyArgcCheck is true then each JS function (as opposed to
+ function pointers) gets wrapped in a proxy which asserts that it
+ is passed the expected number of arguments, throwing if the
+ argument count does not match expectations. That is only intended
+ for dev-time usage for sanity checking, and will leave the C
+ environment in an undefined state.
+ */
+ const installMethod = function callee(
+ tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
+ ){
+ if(!(tgt instanceof StructBinder.StructType)){
+ toss("Usage error: target object is-not-a StructType.");
+ }else if(!(func instanceof Function) && !wasm.isPtr(func)){
+ toss("Usage errror: expecting a Function or WASM pointer to one.");
+ }
+ if(1===arguments.length){
+ return (n,f)=>callee(tgt, n, f, applyArgcCheck);
+ }
+ if(!callee.argcProxy){
+ callee.argcProxy = function(tgt, funcName, func,sig){
+ return function(...args){
+ if(func.length!==arguments.length){
+ toss("Argument mismatch for",
+ tgt.structInfo.name+"::"+funcName
+ +": Native signature is:",sig);
+ }
+ return func.apply(this, args);
+ }
+ };
+ /* An ondispose() callback for use with
+ StructBinder-created types. */
+ callee.removeFuncList = function(){
+ if(this.ondispose.__removeFuncList){
+ this.ondispose.__removeFuncList.forEach(
+ (v,ndx)=>{
+ if('number'===typeof v){
+ try{wasm.uninstallFunction(v)}
+ catch(e){/*ignore*/}
+ }
+ /* else it's a descriptive label for the next number in
+ the list. */
+ }
+ );
+ delete this.ondispose.__removeFuncList;
+ }
+ };
+ }/*static init*/
+ const sigN = tgt.memberSignature(name);
+ if(sigN.length<2){
+ toss("Member",name,"does not have a function pointer signature:",sigN);
+ }
+ const memKey = tgt.memberKey(name);
+ const fProxy = (applyArgcCheck && !wasm.isPtr(func))
+ /** This middle-man proxy is only for use during development, to
+ confirm that we always pass the proper number of
+ arguments. We know that the C-level code will always use the
+ correct argument count. */
+ ? callee.argcProxy(tgt, memKey, func, sigN)
+ : func;
+ if(wasm.isPtr(fProxy)){
+ if(fProxy && !wasm.functionEntry(fProxy)){
+ toss("Pointer",fProxy,"is not a WASM function table entry.");
+ }
+ tgt[memKey] = fProxy;
+ }else{
+ const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
+ tgt[memKey] = pFunc;
+ if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
+ tgt.addOnDispose('ondispose.__removeFuncList handler',
+ callee.removeFuncList);
+ tgt.ondispose.__removeFuncList = [];
+ }
+ tgt.ondispose.__removeFuncList.push(memKey, pFunc);
+ }
+ return (n,f)=>callee(tgt, n, f, applyArgcCheck);
+ }/*installMethod*/;
+ installMethod.installMethodArgcCheck = false;
+
+ /**
+ Installs methods into the given StructType-type instance. Each
+ entry in the given methods object must map to a known member of
+ the given StructType, else an exception will be triggered. See
+ installMethod() for more details, including the semantics of the
+ 3rd argument.
+
+ As an exception to the above, if any two or more methods in the
+ 2nd argument are the exact same function, installMethod() is
+ _not_ called for the 2nd and subsequent instances, and instead
+ those instances get assigned the same method pointer which is
+ created for the first instance. This optimization is primarily to
+ accommodate special handling of sqlite3_module::xConnect and
+ xCreate methods.
+
+ On success, returns its first argument. Throws on error.
+ */
+ const installMethods = function(
+ structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck
+ ){
+ const seen = new Map /* map of <Function, memberName> */;
+ for(const k of Object.keys(methods)){
+ const m = methods[k];
+ const prior = seen.get(m);
+ if(prior){
+ const mkey = structInstance.memberKey(k);
+ structInstance[mkey] = structInstance[structInstance.memberKey(prior)];
+ }else{
+ installMethod(structInstance, k, m, applyArgcCheck);
+ seen.set(m, k);
+ }
+ }
+ return structInstance;
+ };
+
+ /**
+ Equivalent to calling installMethod(this,...arguments) with a
+ first argument of this object. If called with 1 or 2 arguments
+ and the first is an object, it's instead equivalent to calling
+ installMethods(this,...arguments).
+ */
+ StructBinder.StructType.prototype.installMethod = function callee(
+ name, func, applyArgcCheck = installMethod.installMethodArgcCheck
+ ){
+ return (arguments.length < 3 && name && 'object'===typeof name)
+ ? installMethods(this, ...arguments)
+ : installMethod(this, ...arguments);
+ };
+
+ /**
+ Equivalent to calling installMethods() with a first argument
+ of this object.
+ */
+ StructBinder.StructType.prototype.installMethods = function(
+ methods, applyArgcCheck = installMethod.installMethodArgcCheck
+ ){
+ return installMethods(this, methods, applyArgcCheck);
+ };
+
+ /**
+ Uses sqlite3_vfs_register() to register this
+ sqlite3.capi.sqlite3_vfs. This object must have already been
+ filled out properly. If the first argument is truthy, the VFS is
+ registered as the default VFS, else it is not.
+
+ On success, returns this object. Throws on error.
+ */
+ capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){
+ if(!(this instanceof sqlite3.capi.sqlite3_vfs)){
+ toss("Expecting a sqlite3_vfs-type argument.");
+ }
+ const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0);
+ if(rc){
+ toss("sqlite3_vfs_register(",this,") failed with rc",rc);
+ }
+ if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){
+ toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
+ this);
+ }
+ return this;
+ };
+
+ /**
+ A wrapper for installMethods() or registerVfs() to reduce
+ installation of a VFS and/or its I/O methods to a single
+ call.
+
+ Accepts an object which contains the properties "io" and/or
+ "vfs", each of which is itself an object with following properties:
+
+ - `struct`: an sqlite3.StructType-type struct. This must be a
+ populated (except for the methods) object of type
+ sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
+ "vfs" entry).
+
+ - `methods`: an object mapping sqlite3_io_methods method names
+ (e.g. 'xClose') to JS implementations of those methods. The JS
+ implementations must be call-compatible with their native
+ counterparts.
+
+ For each of those object, this function passes its (`struct`,
+ `methods`, (optional) `applyArgcCheck`) properties to
+ installMethods().
+
+ If the `vfs` entry is set then:
+
+ - Its `struct` property's registerVfs() is called. The
+ `vfs` entry may optionally have an `asDefault` property, which
+ gets passed as the argument to registerVfs().
+
+ - If `struct.$zName` is falsy and the entry has a string-type
+ `name` property, `struct.$zName` is set to the C-string form of
+ that `name` value before registerVfs() is called. That string
+ gets added to the on-dispose state of the struct.
+
+ On success returns this object. Throws on error.
+ */
+ vfs.installVfs = function(opt){
+ let count = 0;
+ const propList = ['io','vfs'];
+ for(const key of propList){
+ const o = opt[key];
+ if(o){
+ ++count;
+ installMethods(o.struct, o.methods, !!o.applyArgcCheck);
+ if('vfs'===key){
+ if(!o.struct.$zName && 'string'===typeof o.name){
+ o.struct.addOnDispose(
+ o.struct.$zName = wasm.allocCString(o.name)
+ );
+ }
+ o.struct.registerVfs(!!o.asDefault);
+ }
+ }
+ }
+ if(!count) toss("Misuse: installVfs() options object requires at least",
+ "one of:", propList);
+ return this;
+ };
+
+ /**
+ Internal factory function for xVtab and xCursor impls.
+ */
+ const __xWrapFactory = function(methodName,StructType){
+ return function(ptr,removeMapping=false){
+ if(0===arguments.length) ptr = new StructType;
+ if(ptr instanceof StructType){
+ //T.assert(!this.has(ptr.pointer));
+ this.set(ptr.pointer, ptr);
+ return ptr;
+ }else if(!wasm.isPtr(ptr)){
+ sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()");
+ }
+ let rc = this.get(ptr);
+ if(removeMapping) this.delete(ptr);
+ return rc;
+ }.bind(new Map);
+ };
+
+ /**
+ A factory function which implements a simple lifetime manager for
+ mappings between C struct pointers and their JS-level wrappers.
+ The first argument must be the logical name of the manager
+ (e.g. 'xVtab' or 'xCursor'), which is only used for error
+ reporting. The second must be the capi.XYZ struct-type value,
+ e.g. capi.sqlite3_vtab or capi.sqlite3_vtab_cursor.
+
+ Returns an object with 4 methods: create(), get(), unget(), and
+ dispose(), plus a StructType member with the value of the 2nd
+ argument. The methods are documented in the body of this
+ function.
+ */
+ const StructPtrMapper = function(name, StructType){
+ const __xWrap = __xWrapFactory(name,StructType);
+ /**
+ This object houses a small API for managing mappings of (`T*`)
+ to StructType<T> objects, specifically within the lifetime
+ requirements of sqlite3_module methods.
+ */
+ return Object.assign(Object.create(null),{
+ /** The StructType object for this object's API. */
+ StructType,
+ /**
+ Creates a new StructType object, writes its `pointer`
+ value to the given output pointer, and returns that
+ object. Its intended usage depends on StructType:
+
+ sqlite3_vtab: to be called from sqlite3_module::xConnect()
+ or xCreate() implementations.
+
+ sqlite3_vtab_cursor: to be called from xOpen().
+
+ This will throw if allocation of the StructType instance
+ fails or if ppOut is not a pointer-type value.
+ */
+ create: (ppOut)=>{
+ const rc = __xWrap();
+ wasm.pokePtr(ppOut, rc.pointer);
+ return rc;
+ },
+ /**
+ Returns the StructType object previously mapped to the
+ given pointer using create(). Its intended usage depends
+ on StructType:
+
+ sqlite3_vtab: to be called from sqlite3_module methods which
+ take a (sqlite3_vtab*) pointer _except_ for
+ xDestroy()/xDisconnect(), in which case unget() or dispose().
+
+ sqlite3_vtab_cursor: to be called from any sqlite3_module methods
+ which take a `sqlite3_vtab_cursor*` argument except xClose(),
+ in which case use unget() or dispose().
+
+ Rule to remember: _never_ call dispose() on an instance
+ returned by this function.
+ */
+ get: (pCObj)=>__xWrap(pCObj),
+ /**
+ Identical to get() but also disconnects the mapping between the
+ given pointer and the returned StructType object, such that
+ future calls to this function or get() with the same pointer
+ will return the undefined value. Its intended usage depends
+ on StructType:
+
+ sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or
+ xDestroy() implementations or in error handling of a failed
+ xCreate() or xConnect().
+
+ sqlite3_vtab_cursor: to be called from xClose() or during
+ cleanup in a failed xOpen().
+
+ Calling this method obligates the caller to call dispose() on
+ the returned object when they're done with it.
+ */
+ unget: (pCObj)=>__xWrap(pCObj,true),
+ /**
+ Works like unget() plus it calls dispose() on the
+ StructType object.
+ */
+ dispose: (pCObj)=>{
+ const o = __xWrap(pCObj,true);
+ if(o) o.dispose();
+ }
+ });
+ };
+
+ /**
+ A lifetime-management object for mapping `sqlite3_vtab*`
+ instances in sqlite3_module methods to capi.sqlite3_vtab
+ objects.
+
+ The API docs are in the API-internal StructPtrMapper().
+ */
+ vtab.xVtab = StructPtrMapper('xVtab', capi.sqlite3_vtab);
+
+ /**
+ A lifetime-management object for mapping `sqlite3_vtab_cursor*`
+ instances in sqlite3_module methods to capi.sqlite3_vtab_cursor
+ objects.
+
+ The API docs are in the API-internal StructPtrMapper().
+ */
+ vtab.xCursor = StructPtrMapper('xCursor', capi.sqlite3_vtab_cursor);
+
+ /**
+ Convenience form of creating an sqlite3_index_info wrapper,
+ intended for use in xBestIndex implementations. Note that the
+ caller is expected to call dispose() on the returned object
+ before returning. Though not _strictly_ required, as that object
+ does not own the pIdxInfo memory, it is nonetheless good form.
+ */
+ vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo);
+
+ /**
+ Given an error object, this function returns
+ sqlite3.capi.SQLITE_NOMEM if (e instanceof
+ sqlite3.WasmAllocError), else it returns its
+ second argument. Its intended usage is in the methods
+ of a sqlite3_vfs or sqlite3_module:
+
+ ```
+ try{
+ let rc = ...
+ return rc;
+ }catch(e){
+ return sqlite3.vtab.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ);
+ // where SQLITE_XYZ is some call-appropriate result code.
+ }
+ ```
+ */
+ /**vfs.exceptionToRc = vtab.exceptionToRc =
+ (e, defaultRc=capi.SQLITE_ERROR)=>(
+ (e instanceof sqlite3.WasmAllocError)
+ ? capi.SQLITE_NOMEM
+ : defaultRc
+ );*/
+
+ /**
+ Given an sqlite3_module method name and error object, this
+ function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof
+ sqlite3.WasmAllocError), else it returns its second argument. Its
+ intended usage is in the methods of a sqlite3_vfs or
+ sqlite3_module:
+
+ ```
+ try{
+ let rc = ...
+ return rc;
+ }catch(e){
+ return sqlite3.vtab.xError(
+ 'xColumn', e, sqlite3.capi.SQLITE_XYZ);
+ // where SQLITE_XYZ is some call-appropriate result code.
+ }
+ ```
+
+ If no 3rd argument is provided, its default depends on
+ the error type:
+
+ - An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM.
+
+ - If err is an SQLite3Error then its `resultCode` property
+ is used.
+
+ - If all else fails, capi.SQLITE_ERROR is used.
+
+ If xError.errorReporter is a function, it is called in
+ order to report the error, else the error is not reported.
+ If that function throws, that exception is ignored.
+ */
+ vtab.xError = function f(methodName, err, defaultRc){
+ if(f.errorReporter instanceof Function){
+ try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);}
+ catch(e){/*ignored*/}
+ }
+ let rc;
+ if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM;
+ else if(arguments.length>2) rc = defaultRc;
+ else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode;
+ return rc || capi.SQLITE_ERROR;
+ };
+ vtab.xError.errorReporter = 1 ? console.error.bind(console) : false;
+
+ /**
+ "The problem" with this is that it introduces an outer function with
+ a different arity than the passed-in method callback. That means we
+ cannot do argc validation on these. Additionally, some methods (namely
+ xConnect) may have call-specific error handling. It would be a shame to
+ hard-coded that per-method support in this function.
+ */
+ /** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
+ return function(...args){
+ try { method(...args); }
+ }catch(e){ return vtab.xError(methodName, e, defaultRc) }
+ };
+ */
+
+ /**
+ A helper for sqlite3_vtab::xRowid() and xUpdate()
+ implementations. It must be passed the final argument to one of
+ those methods (an output pointer to an int64 row ID) and the
+ value to store at the output pointer's address. Returns the same
+ as wasm.poke() and will throw if the 1st or 2nd arguments
+ are invalid for that function.
+
+ Example xRowid impl:
+
+ ```
+ const xRowid = (pCursor, ppRowid64)=>{
+ const c = vtab.xCursor(pCursor);
+ vtab.xRowid(ppRowid64, c.myRowId);
+ return 0;
+ };
+ ```
+ */
+ vtab.xRowid = (ppRowid64, value)=>wasm.poke(ppRowid64, value, 'i64');
+
+ /**
+ A helper to initialize and set up an sqlite3_module object for
+ later installation into individual databases using
+ sqlite3_create_module(). Requires an object with the following
+ properties:
+
+ - `methods`: an object containing a mapping of properties with
+ the C-side names of the sqlite3_module methods, e.g. xCreate,
+ xBestIndex, etc., to JS implementations for those functions.
+ Certain special-case handling is performed, as described below.
+
+ - `catchExceptions` (default=false): if truthy, the given methods
+ are not mapped as-is, but are instead wrapped inside wrappers
+ which translate exceptions into result codes of SQLITE_ERROR or
+ SQLITE_NOMEM, depending on whether the exception is an
+ sqlite3.WasmAllocError. In the case of the xConnect and xCreate
+ methods, the exception handler also sets the output error
+ string to the exception's error string.
+
+ - OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If
+ not set, one will be created automatically. If the current
+ "this" is-a sqlite3_module then it is unconditionally used in
+ place of `struct`.
+
+ - OPTIONAL `iVersion`: if set, it must be an integer value and it
+ gets assigned to the `$iVersion` member of the struct object.
+ If it's _not_ set, and the passed-in `struct` object's `$iVersion`
+ is 0 (the default) then this function attempts to define a value
+ for that property based on the list of methods it has.
+
+ If `catchExceptions` is false, it is up to the client to ensure
+ that no exceptions escape the methods, as doing so would move
+ them through the C API, leading to undefined
+ behavior. (vtab.xError() is intended to assist in reporting
+ such exceptions.)
+
+ Certain methods may refer to the same implementation. To simplify
+ the definition of such methods:
+
+ - If `methods.xConnect` is `true` then the value of
+ `methods.xCreate` is used in its place, and vice versa. sqlite
+ treats xConnect/xCreate functions specially if they are exactly
+ the same function (same pointer value).
+
+ - If `methods.xDisconnect` is true then the value of
+ `methods.xDestroy` is used in its place, and vice versa.
+
+ This is to facilitate creation of those methods inline in the
+ passed-in object without requiring the client to explicitly get a
+ reference to one of them in order to assign it to the other
+ one.
+
+ The `catchExceptions`-installed handlers will account for
+ identical references to the above functions and will install the
+ same wrapper function for both.
+
+ The given methods are expected to return integer values, as
+ expected by the C API. If `catchExceptions` is truthy, the return
+ value of the wrapped function will be used as-is and will be
+ translated to 0 if the function returns a falsy value (e.g. if it
+ does not have an explicit return). If `catchExceptions` is _not_
+ active, the method implementations must explicitly return integer
+ values.
+
+ Throws on error. On success, returns the sqlite3_module object
+ (`this` or `opt.struct` or a new sqlite3_module instance,
+ depending on how it's called).
+ */
+ vtab.setupModule = function(opt){
+ let createdMod = false;
+ const mod = (this instanceof capi.sqlite3_module)
+ ? this : (opt.struct || (createdMod = new capi.sqlite3_module()));
+ try{
+ const methods = opt.methods || toss("Missing 'methods' object.");
+ for(const e of Object.entries({
+ // -----^ ==> [k,v] triggers a broken code transformation in
+ // some versions of the emsdk toolchain.
+ xConnect: 'xCreate', xDisconnect: 'xDestroy'
+ })){
+ // Remap X=true to X=Y for certain X/Y combinations
+ const k = e[0], v = e[1];
+ if(true === methods[k]) methods[k] = methods[v];
+ else if(true === methods[v]) methods[v] = methods[k];
+ }
+ if(opt.catchExceptions){
+ const fwrap = function(methodName, func){
+ if(['xConnect','xCreate'].indexOf(methodName) >= 0){
+ return function(pDb, pAux, argc, argv, ppVtab, pzErr){
+ try{return func(...arguments) || 0}
+ catch(e){
+ if(!(e instanceof sqlite3.WasmAllocError)){
+ wasm.dealloc(wasm.peekPtr(pzErr));
+ wasm.pokePtr(pzErr, wasm.allocCString(e.message));
+ }
+ return vtab.xError(methodName, e);
+ }
+ };
+ }else{
+ return function(...args){
+ try{return func(...args) || 0}
+ catch(e){
+ return vtab.xError(methodName, e);
+ }
+ };
+ }
+ };
+ const mnames = [
+ 'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect',
+ 'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext',
+ 'xEof', 'xColumn', 'xRowid', 'xUpdate',
+ 'xBegin', 'xSync', 'xCommit', 'xRollback',
+ 'xFindFunction', 'xRename', 'xSavepoint', 'xRelease',
+ 'xRollbackTo', 'xShadowName'
+ ];
+ const remethods = Object.create(null);
+ for(const k of mnames){
+ const m = methods[k];
+ if(!(m instanceof Function)) continue;
+ else if('xConnect'===k && methods.xCreate===m){
+ remethods[k] = methods.xCreate;
+ }else if('xCreate'===k && methods.xConnect===m){
+ remethods[k] = methods.xConnect;
+ }else{
+ remethods[k] = fwrap(k, m);
+ }
+ }
+ installMethods(mod, remethods, false);
+ }else{
+ // No automatic exception handling. Trust the client
+ // to not throw.
+ installMethods(
+ mod, methods, !!opt.applyArgcCheck/*undocumented option*/
+ );
+ }
+ if(0===mod.$iVersion){
+ let v;
+ if('number'===typeof opt.iVersion) v = opt.iVersion;
+ else if(mod.$xShadowName) v = 3;
+ else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2;
+ else v = 1;
+ mod.$iVersion = v;
+ }
+ }catch(e){
+ if(createdMod) createdMod.dispose();
+ throw e;
+ }
+ return mod;
+ }/*setupModule()*/;
+
+ /**
+ Equivalent to calling vtab.setupModule() with this sqlite3_module
+ object as the call's `this`.
+ */
+ capi.sqlite3_module.prototype.setupModule = function(opt){
+ return vtab.setupModule.call(this, opt);
+ };
+}/*sqlite3ApiBootstrap.initializers.push()*/);
diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
new file mode 100644
index 0000000..870073c
--- /dev/null
+++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
@@ -0,0 +1,1287 @@
+//#ifnot target=node
+/*
+ 2023-07-14
+
+ 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 holds a sqlite3_vfs backed by OPFS storage which uses a
+ different implementation strategy than the "opfs" VFS. This one is a
+ port of Roy Hashimoto's OPFS SyncAccessHandle pool:
+
+ https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/AccessHandlePoolVFS.js
+
+ As described at:
+
+ https://github.com/rhashimoto/wa-sqlite/discussions/67
+
+ with Roy's explicit permission to permit us to port his to our
+ infrastructure rather than having to clean-room reverse-engineer it:
+
+ https://sqlite.org/forum/forumpost/e140d84e71
+
+ Primary differences from the "opfs" VFS include:
+
+ - This one avoids the need for a sub-worker to synchronize
+ communication between the synchronous C API and the
+ only-partly-synchronous OPFS API.
+
+ - It does so by opening a fixed number of OPFS files at
+ library-level initialization time, obtaining SyncAccessHandles to
+ each, and manipulating those handles via the synchronous sqlite3_vfs
+ interface. If it cannot open them (e.g. they are already opened by
+ another tab) then the VFS will not be installed.
+
+ - Because of that, this one lacks all library-level concurrency
+ support.
+
+ - Also because of that, it does not require the SharedArrayBuffer,
+ so can function without the COOP/COEP HTTP response headers.
+
+ - It can hypothetically support Safari 16.4+, whereas the "opfs" VFS
+ requires v17 due to a subworker/storage bug in 16.x which makes it
+ incompatible with that VFS.
+
+ - This VFS requires the "semi-fully-sync" FileSystemSyncAccessHandle
+ (hereafter "SAH") APIs released with Chrome v108 (and all other
+ major browsers released since March 2023). If that API is not
+ detected, the VFS is not registered.
+*/
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ 'use strict';
+ const toss = sqlite3.util.toss;
+ const toss3 = sqlite3.util.toss3;
+ const initPromises = Object.create(null);
+ const capi = sqlite3.capi;
+ const util = sqlite3.util;
+ const wasm = sqlite3.wasm;
+ // Config opts for the VFS...
+ const SECTOR_SIZE = 4096;
+ const HEADER_MAX_PATH_SIZE = 512;
+ const HEADER_FLAGS_SIZE = 4;
+ const HEADER_DIGEST_SIZE = 8;
+ const HEADER_CORPUS_SIZE = HEADER_MAX_PATH_SIZE + HEADER_FLAGS_SIZE;
+ const HEADER_OFFSET_FLAGS = HEADER_MAX_PATH_SIZE;
+ const HEADER_OFFSET_DIGEST = HEADER_CORPUS_SIZE;
+ const HEADER_OFFSET_DATA = SECTOR_SIZE;
+ /* Bitmask of file types which may persist across sessions.
+ SQLITE_OPEN_xyz types not listed here may be inadvertently
+ left in OPFS but are treated as transient by this VFS and
+ they will be cleaned up during VFS init. */
+ const PERSISTENT_FILE_TYPES =
+ capi.SQLITE_OPEN_MAIN_DB |
+ capi.SQLITE_OPEN_MAIN_JOURNAL |
+ capi.SQLITE_OPEN_SUPER_JOURNAL |
+ capi.SQLITE_OPEN_WAL /* noting that WAL support is
+ unavailable in the WASM build.*/;
+
+ /** Subdirectory of the VFS's space where "opaque" (randomly-named)
+ files are stored. Changing this effectively invalidates the data
+ stored under older names (orphaning it), so don't do that. */
+ const OPAQUE_DIR_NAME = ".opaque";
+
+ /**
+ Returns short a string of random alphanumeric characters
+ suitable for use as a random filename.
+ */
+ const getRandomName = ()=>Math.random().toString(36).slice(2);
+
+ const textDecoder = new TextDecoder();
+ const textEncoder = new TextEncoder();
+
+ const optionDefaults = Object.assign(Object.create(null),{
+ name: 'opfs-sahpool',
+ directory: undefined /* derived from .name */,
+ initialCapacity: 6,
+ clearOnInit: false,
+ /* Logging verbosity 3+ == everything, 2 == warnings+errors, 1 ==
+ errors only. */
+ verbosity: 2
+ });
+
+ /** Logging routines, from most to least serious. */
+ const loggers = [
+ sqlite3.config.error,
+ sqlite3.config.warn,
+ sqlite3.config.log
+ ];
+ const log = sqlite3.config.log;
+ const warn = sqlite3.config.warn;
+ const error = sqlite3.config.error;
+
+ /* Maps (sqlite3_vfs*) to OpfsSAHPool instances */
+ const __mapVfsToPool = new Map();
+ const getPoolForVfs = (pVfs)=>__mapVfsToPool.get(pVfs);
+ const setPoolForVfs = (pVfs,pool)=>{
+ if(pool) __mapVfsToPool.set(pVfs, pool);
+ else __mapVfsToPool.delete(pVfs);
+ };
+ /* Maps (sqlite3_file*) to OpfsSAHPool instances */
+ const __mapSqlite3File = new Map();
+ const getPoolForPFile = (pFile)=>__mapSqlite3File.get(pFile);
+ const setPoolForPFile = (pFile,pool)=>{
+ if(pool) __mapSqlite3File.set(pFile, pool);
+ else __mapSqlite3File.delete(pFile);
+ };
+
+ /**
+ Impls for the sqlite3_io_methods methods. Maintenance reminder:
+ members are in alphabetical order to simplify finding them.
+ */
+ const ioMethods = {
+ xCheckReservedLock: function(pFile,pOut){
+ const pool = getPoolForPFile(pFile);
+ pool.log('xCheckReservedLock');
+ pool.storeErr();
+ wasm.poke32(pOut, 1);
+ return 0;
+ },
+ xClose: function(pFile){
+ const pool = getPoolForPFile(pFile);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ if(file) {
+ try{
+ pool.log(`xClose ${file.path}`);
+ pool.mapS3FileToOFile(pFile, false);
+ file.sah.flush();
+ if(file.flags & capi.SQLITE_OPEN_DELETEONCLOSE){
+ pool.deletePath(file.path);
+ }
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ }
+ return 0;
+ },
+ xDeviceCharacteristics: function(pFile){
+ return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
+ },
+ xFileControl: function(pFile, opId, pArg){
+ return capi.SQLITE_NOTFOUND;
+ },
+ xFileSize: function(pFile,pSz64){
+ const pool = getPoolForPFile(pFile);
+ pool.log(`xFileSize`);
+ const file = pool.getOFileForS3File(pFile);
+ const size = file.sah.getSize() - HEADER_OFFSET_DATA;
+ //log(`xFileSize ${file.path} ${size}`);
+ wasm.poke64(pSz64, BigInt(size));
+ return 0;
+ },
+ xLock: function(pFile,lockType){
+ const pool = getPoolForPFile(pFile);
+ pool.log(`xLock ${lockType}`);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ file.lockType = lockType;
+ return 0;
+ },
+ xRead: function(pFile,pDest,n,offset64){
+ const pool = getPoolForPFile(pFile);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ pool.log(`xRead ${file.path} ${n} @ ${offset64}`);
+ try {
+ const nRead = file.sah.read(
+ wasm.heap8u().subarray(pDest, pDest+n),
+ {at: HEADER_OFFSET_DATA + Number(offset64)}
+ );
+ if(nRead < n){
+ wasm.heap8u().fill(0, pDest + nRead, pDest + n);
+ return capi.SQLITE_IOERR_SHORT_READ;
+ }
+ return 0;
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ },
+ xSectorSize: function(pFile){
+ return SECTOR_SIZE;
+ },
+ xSync: function(pFile,flags){
+ const pool = getPoolForPFile(pFile);
+ pool.log(`xSync ${flags}`);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ //log(`xSync ${file.path} ${flags}`);
+ try{
+ file.sah.flush();
+ return 0;
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ },
+ xTruncate: function(pFile,sz64){
+ const pool = getPoolForPFile(pFile);
+ pool.log(`xTruncate ${sz64}`);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ //log(`xTruncate ${file.path} ${iSize}`);
+ try{
+ file.sah.truncate(HEADER_OFFSET_DATA + Number(sz64));
+ return 0;
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ },
+ xUnlock: function(pFile,lockType){
+ const pool = getPoolForPFile(pFile);
+ pool.log('xUnlock');
+ const file = pool.getOFileForS3File(pFile);
+ file.lockType = lockType;
+ return 0;
+ },
+ xWrite: function(pFile,pSrc,n,offset64){
+ const pool = getPoolForPFile(pFile);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ pool.log(`xWrite ${file.path} ${n} ${offset64}`);
+ try{
+ const nBytes = file.sah.write(
+ wasm.heap8u().subarray(pSrc, pSrc+n),
+ { at: HEADER_OFFSET_DATA + Number(offset64) }
+ );
+ return n===nBytes ? 0 : toss("Unknown write() failure.");
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ }
+ }/*ioMethods*/;
+
+ const opfsIoMethods = new capi.sqlite3_io_methods();
+ opfsIoMethods.$iVersion = 1;
+ sqlite3.vfs.installVfs({
+ io: {struct: opfsIoMethods, methods: ioMethods}
+ });
+
+ /**
+ Impls for the sqlite3_vfs methods. Maintenance reminder: members
+ are in alphabetical order to simplify finding them.
+ */
+ const vfsMethods = {
+ xAccess: function(pVfs,zName,flags,pOut){
+ //log(`xAccess ${wasm.cstrToJs(zName)}`);
+ const pool = getPoolForVfs(pVfs);
+ pool.storeErr();
+ try{
+ const name = pool.getPath(zName);
+ wasm.poke32(pOut, pool.hasFilename(name) ? 1 : 0);
+ }catch(e){
+ /*ignored*/
+ wasm.poke32(pOut, 0);
+ }
+ return 0;
+ },
+ xCurrentTime: function(pVfs,pOut){
+ wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000),
+ 'double');
+ return 0;
+ },
+ xCurrentTimeInt64: function(pVfs,pOut){
+ wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(),
+ 'i64');
+ return 0;
+ },
+ xDelete: function(pVfs, zName, doSyncDir){
+ const pool = getPoolForVfs(pVfs);
+ pool.log(`xDelete ${wasm.cstrToJs(zName)}`);
+ pool.storeErr();
+ try{
+ pool.deletePath(pool.getPath(zName));
+ return 0;
+ }catch(e){
+ pool.storeErr(e);
+ return capi.SQLITE_IOERR_DELETE;
+ }
+ },
+ xFullPathname: function(pVfs,zName,nOut,pOut){
+ //const pool = getPoolForVfs(pVfs);
+ //pool.log(`xFullPathname ${wasm.cstrToJs(zName)}`);
+ const i = wasm.cstrncpy(pOut, zName, nOut);
+ return i<nOut ? 0 : capi.SQLITE_CANTOPEN;
+ },
+ xGetLastError: function(pVfs,nOut,pOut){
+ const pool = getPoolForVfs(pVfs);
+ const e = pool.popErr();
+ pool.log(`xGetLastError ${nOut} e =`,e);
+ if(e){
+ const scope = wasm.scopedAllocPush();
+ try{
+ const [cMsg, n] = wasm.scopedAllocCString(e.message, true);
+ wasm.cstrncpy(pOut, cMsg, nOut);
+ if(n > nOut) wasm.poke8(pOut + nOut - 1, 0);
+ }catch(e){
+ return capi.SQLITE_NOMEM;
+ }finally{
+ wasm.scopedAllocPop(scope);
+ }
+ }
+ return e ? (e.sqlite3Rc || capi.SQLITE_IOERR) : 0;
+ },
+ //xSleep is optionally defined below
+ xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
+ const pool = getPoolForVfs(pVfs);
+ try{
+ pool.log(`xOpen ${wasm.cstrToJs(zName)} ${flags}`);
+ // First try to open a path that already exists in the file system.
+ const path = (zName && wasm.peek8(zName))
+ ? pool.getPath(zName)
+ : getRandomName();
+ let sah = pool.getSAHForPath(path);
+ if(!sah && (flags & capi.SQLITE_OPEN_CREATE)) {
+ // File not found so try to create it.
+ if(pool.getFileCount() < pool.getCapacity()) {
+ // Choose an unassociated OPFS file from the pool.
+ sah = pool.nextAvailableSAH();
+ pool.setAssociatedPath(sah, path, flags);
+ }else{
+ // File pool is full.
+ toss('SAH pool is full. Cannot create file',path);
+ }
+ }
+ if(!sah){
+ toss('file not found:',path);
+ }
+ // Subsequent I/O methods are only passed the sqlite3_file
+ // pointer, so map the relevant info we need to that pointer.
+ const file = {path, flags, sah};
+ pool.mapS3FileToOFile(pFile, file);
+ file.lockType = capi.SQLITE_LOCK_NONE;
+ const sq3File = new capi.sqlite3_file(pFile);
+ sq3File.$pMethods = opfsIoMethods.pointer;
+ sq3File.dispose();
+ wasm.poke32(pOutFlags, flags);
+ return 0;
+ }catch(e){
+ pool.storeErr(e);
+ return capi.SQLITE_CANTOPEN;
+ }
+ }/*xOpen()*/
+ }/*vfsMethods*/;
+
+ /**
+ Creates and initializes an sqlite3_vfs instance for an
+ OpfsSAHPool. The argument is the VFS's name (JS string).
+
+ Throws if the VFS name is already registered or if something
+ goes terribly wrong via sqlite3.vfs.installVfs().
+
+ Maintenance reminder: the only detail about the returned object
+ which is specific to any given OpfsSAHPool instance is the $zName
+ member. All other state is identical.
+ */
+ const createOpfsVfs = function(vfsName){
+ if( sqlite3.capi.sqlite3_vfs_find(vfsName)){
+ toss3("VFS name is already registered:", vfsName);
+ }
+ const opfsVfs = new capi.sqlite3_vfs();
+ /* We fetch the default VFS so that we can inherit some
+ methods from it. */
+ const pDVfs = capi.sqlite3_vfs_find(null);
+ const dVfs = pDVfs
+ ? new capi.sqlite3_vfs(pDVfs)
+ : null /* dVfs will be null when sqlite3 is built with
+ SQLITE_OS_OTHER. */;
+ opfsVfs.$iVersion = 2/*yes, two*/;
+ opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
+ opfsVfs.$mxPathname = HEADER_MAX_PATH_SIZE;
+ opfsVfs.addOnDispose(
+ opfsVfs.$zName = wasm.allocCString(vfsName),
+ ()=>setPoolForVfs(opfsVfs.pointer, 0)
+ );
+
+ if(dVfs){
+ /* Inherit certain VFS members from the default VFS,
+ if available. */
+ opfsVfs.$xRandomness = dVfs.$xRandomness;
+ opfsVfs.$xSleep = dVfs.$xSleep;
+ dVfs.dispose();
+ }
+ if(!opfsVfs.$xRandomness && !vfsMethods.xRandomness){
+ /* If the default VFS has no xRandomness(), add a basic JS impl... */
+ vfsMethods.xRandomness = function(pVfs, nOut, pOut){
+ const heap = wasm.heap8u();
+ let i = 0;
+ for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
+ return i;
+ };
+ }
+ if(!opfsVfs.$xSleep && !vfsMethods.xSleep){
+ vfsMethods.xSleep = (pVfs,ms)=>0;
+ }
+ sqlite3.vfs.installVfs({
+ vfs: {struct: opfsVfs, methods: vfsMethods}
+ });
+ return opfsVfs;
+ };
+
+ /**
+ Class for managing OPFS-related state for the
+ OPFS SharedAccessHandle Pool sqlite3_vfs.
+ */
+ class OpfsSAHPool {
+ /* OPFS dir in which VFS metadata is stored. */
+ vfsDir;
+ /* Directory handle to this.vfsDir. */
+ #dhVfsRoot;
+ /* Directory handle to the subdir of this.#dhVfsRoot which holds
+ the randomly-named "opaque" files. This subdir exists in the
+ hope that we can eventually support client-created files in
+ this.#dhVfsRoot. */
+ #dhOpaque;
+ /* Directory handle to this.dhVfsRoot's parent dir. Needed
+ for a VFS-wipe op. */
+ #dhVfsParent;
+ /* Maps SAHs to their opaque file names. */
+ #mapSAHToName = new Map();
+ /* Maps client-side file names to SAHs. */
+ #mapFilenameToSAH = new Map();
+ /* Set of currently-unused SAHs. */
+ #availableSAH = new Set();
+ /* Maps (sqlite3_file*) to xOpen's file objects. */
+ #mapS3FileToOFile_ = new Map();
+
+ /* Maps SAH to an abstract File Object which contains
+ various metadata about that handle. */
+ //#mapSAHToMeta = new Map();
+
+ /** Buffer used by [sg]etAssociatedPath(). */
+ #apBody = new Uint8Array(HEADER_CORPUS_SIZE);
+ // DataView for this.#apBody
+ #dvBody;
+
+ // associated sqlite3_vfs instance
+ #cVfs;
+
+ // Logging verbosity. See optionDefaults.verbosity.
+ #verbosity;
+
+ constructor(options = Object.create(null)){
+ this.#verbosity = options.verbosity ?? optionDefaults.verbosity;
+ this.vfsName = options.name || optionDefaults.name;
+ this.#cVfs = createOpfsVfs(this.vfsName);
+ setPoolForVfs(this.#cVfs.pointer, this);
+ this.vfsDir = options.directory || ("."+this.vfsName);
+ this.#dvBody =
+ new DataView(this.#apBody.buffer, this.#apBody.byteOffset);
+ this.isReady = this
+ .reset(!!(options.clearOnInit ?? optionDefaults.clearOnInit))
+ .then(()=>{
+ if(this.$error) throw this.$error;
+ return this.getCapacity()
+ ? Promise.resolve(undefined)
+ : this.addCapacity(options.initialCapacity
+ || optionDefaults.initialCapacity);
+ });
+ }
+
+ #logImpl(level,...args){
+ if(this.#verbosity>level) loggers[level](this.vfsName+":",...args);
+ };
+ log(...args){this.#logImpl(2, ...args)};
+ warn(...args){this.#logImpl(1, ...args)};
+ error(...args){this.#logImpl(0, ...args)};
+
+ getVfs(){return this.#cVfs}
+
+ /* Current pool capacity. */
+ getCapacity(){return this.#mapSAHToName.size}
+
+ /* Current number of in-use files from pool. */
+ getFileCount(){return this.#mapFilenameToSAH.size}
+
+ /* Returns an array of the names of all
+ currently-opened client-specified filenames. */
+ getFileNames(){
+ const rc = [];
+ const iter = this.#mapFilenameToSAH.keys();
+ for(const n of iter) rc.push(n);
+ return rc;
+ }
+
+// #createFileObject(sah,clientName,opaqueName){
+// const f = Object.assign(Object.create(null),{
+// clientName, opaqueName
+// });
+// this.#mapSAHToMeta.set(sah, f);
+// return f;
+// }
+// #unmapFileObject(sah){
+// this.#mapSAHToMeta.delete(sah);
+// }
+
+ /**
+ Adds n files to the pool's capacity. This change is
+ persistent across settings. Returns a Promise which resolves
+ to the new capacity.
+ */
+ async addCapacity(n){
+ for(let i = 0; i < n; ++i){
+ const name = getRandomName();
+ const h = await this.#dhOpaque.getFileHandle(name, {create:true});
+ const ah = await h.createSyncAccessHandle();
+ this.#mapSAHToName.set(ah,name);
+ this.setAssociatedPath(ah, '', 0);
+ //this.#createFileObject(ah,undefined,name);
+ }
+ return this.getCapacity();
+ }
+
+ /**
+ Reduce capacity by n, but can only reduce up to the limit
+ of currently-available SAHs. Returns a Promise which resolves
+ to the number of slots really removed.
+ */
+ async reduceCapacity(n){
+ let nRm = 0;
+ for(const ah of Array.from(this.#availableSAH)){
+ if(nRm === n || this.getFileCount() === this.getCapacity()){
+ break;
+ }
+ const name = this.#mapSAHToName.get(ah);
+ //this.#unmapFileObject(ah);
+ ah.close();
+ await this.#dhOpaque.removeEntry(name);
+ this.#mapSAHToName.delete(ah);
+ this.#availableSAH.delete(ah);
+ ++nRm;
+ }
+ return nRm;
+ }
+
+ /**
+ Releases all currently-opened SAHs. The only legal
+ operation after this is acquireAccessHandles().
+ */
+ releaseAccessHandles(){
+ for(const ah of this.#mapSAHToName.keys()) ah.close();
+ this.#mapSAHToName.clear();
+ this.#mapFilenameToSAH.clear();
+ this.#availableSAH.clear();
+ }
+
+ /**
+ Opens all files under this.vfsDir/this.#dhOpaque and acquires
+ a SAH for each. returns a Promise which resolves to no value
+ but completes once all SAHs are acquired. If acquiring an SAH
+ throws, SAHPool.$error will contain the corresponding
+ exception.
+
+ If clearFiles is true, the client-stored state of each file is
+ cleared when its handle is acquired, including its name, flags,
+ and any data stored after the metadata block.
+ */
+ async acquireAccessHandles(clearFiles){
+ const files = [];
+ for await (const [name,h] of this.#dhOpaque){
+ if('file'===h.kind){
+ files.push([name,h]);
+ }
+ }
+ return Promise.all(files.map(async([name,h])=>{
+ try{
+ const ah = await h.createSyncAccessHandle()
+ this.#mapSAHToName.set(ah, name);
+ if(clearFiles){
+ ah.truncate(HEADER_OFFSET_DATA);
+ this.setAssociatedPath(ah, '', 0);
+ }else{
+ const path = this.getAssociatedPath(ah);
+ if(path){
+ this.#mapFilenameToSAH.set(path, ah);
+ }else{
+ this.#availableSAH.add(ah);
+ }
+ }
+ }catch(e){
+ this.storeErr(e);
+ this.releaseAccessHandles();
+ throw e;
+ }
+ }));
+ }
+
+ /**
+ Given an SAH, returns the client-specified name of
+ that file by extracting it from the SAH's header.
+
+ On error, it disassociates SAH from the pool and
+ returns an empty string.
+ */
+ getAssociatedPath(sah){
+ sah.read(this.#apBody, {at: 0});
+ // Delete any unexpected files left over by previous
+ // untimely errors...
+ const flags = this.#dvBody.getUint32(HEADER_OFFSET_FLAGS);
+ if(this.#apBody[0] &&
+ ((flags & capi.SQLITE_OPEN_DELETEONCLOSE) ||
+ (flags & PERSISTENT_FILE_TYPES)===0)){
+ warn(`Removing file with unexpected flags ${flags.toString(16)}`,
+ this.#apBody);
+ this.setAssociatedPath(sah, '', 0);
+ return '';
+ }
+
+ const fileDigest = new Uint32Array(HEADER_DIGEST_SIZE / 4);
+ sah.read(fileDigest, {at: HEADER_OFFSET_DIGEST});
+ const compDigest = this.computeDigest(this.#apBody);
+ if(fileDigest.every((v,i) => v===compDigest[i])){
+ // Valid digest
+ const pathBytes = this.#apBody.findIndex((v)=>0===v);
+ if(0===pathBytes){
+ // This file is unassociated, so truncate it to avoid
+ // leaving stale db data laying around.
+ sah.truncate(HEADER_OFFSET_DATA);
+ }
+ return pathBytes
+ ? textDecoder.decode(this.#apBody.subarray(0,pathBytes))
+ : '';
+ }else{
+ // Invalid digest
+ warn('Disassociating file with bad digest.');
+ this.setAssociatedPath(sah, '', 0);
+ return '';
+ }
+ }
+
+ /**
+ Stores the given client-defined path and SQLITE_OPEN_xyz flags
+ into the given SAH. If path is an empty string then the file is
+ disassociated from the pool but its previous name is preserved
+ in the metadata.
+ */
+ setAssociatedPath(sah, path, flags){
+ const enc = textEncoder.encodeInto(path, this.#apBody);
+ if(HEADER_MAX_PATH_SIZE <= enc.written + 1/*NUL byte*/){
+ toss("Path too long:",path);
+ }
+ this.#apBody.fill(0, enc.written, HEADER_MAX_PATH_SIZE);
+ this.#dvBody.setUint32(HEADER_OFFSET_FLAGS, flags);
+
+ const digest = this.computeDigest(this.#apBody);
+ sah.write(this.#apBody, {at: 0});
+ sah.write(digest, {at: HEADER_OFFSET_DIGEST});
+ sah.flush();
+
+ if(path){
+ this.#mapFilenameToSAH.set(path, sah);
+ this.#availableSAH.delete(sah);
+ }else{
+ // This is not a persistent file, so eliminate the contents.
+ sah.truncate(HEADER_OFFSET_DATA);
+ this.#availableSAH.add(sah);
+ }
+ }
+
+ /**
+ Computes a digest for the given byte array and returns it as a
+ two-element Uint32Array. This digest gets stored in the
+ metadata for each file as a validation check. Changing this
+ algorithm invalidates all existing databases for this VFS, so
+ don't do that.
+ */
+ computeDigest(byteArray){
+ let h1 = 0xdeadbeef;
+ let h2 = 0x41c6ce57;
+ for(const v of byteArray){
+ h1 = 31 * h1 + (v * 307);
+ h2 = 31 * h2 + (v * 307);
+ }
+ return new Uint32Array([h1>>>0, h2>>>0]);
+ }
+
+ /**
+ Re-initializes the state of the SAH pool, releasing and
+ re-acquiring all handles.
+
+ See acquireAccessHandles() for the specifics of the clearFiles
+ argument.
+ */
+ async reset(clearFiles){
+ await this.isReady;
+ let h = await navigator.storage.getDirectory();
+ let prev, prevName;
+ for(const d of this.vfsDir.split('/')){
+ if(d){
+ prev = h;
+ h = await h.getDirectoryHandle(d,{create:true});
+ }
+ }
+ this.#dhVfsRoot = h;
+ this.#dhVfsParent = prev;
+ this.#dhOpaque = await this.#dhVfsRoot.getDirectoryHandle(
+ OPAQUE_DIR_NAME,{create:true}
+ );
+ this.releaseAccessHandles();
+ return this.acquireAccessHandles(clearFiles);
+ }
+
+ /**
+ Returns the pathname part of the given argument,
+ which may be any of:
+
+ - a URL object
+ - A JS string representing a file name
+ - Wasm C-string representing a file name
+
+ All "../" parts and duplicate slashes are resolve/removed from
+ the returned result.
+ */
+ getPath(arg) {
+ if(wasm.isPtr(arg)) arg = wasm.cstrToJs(arg);
+ return ((arg instanceof URL)
+ ? arg
+ : new URL(arg, 'file://localhost/')).pathname;
+ }
+
+ /**
+ Removes the association of the given client-specified file
+ name (JS string) from the pool. Returns true if a mapping
+ is found, else false.
+ */
+ deletePath(path) {
+ const sah = this.#mapFilenameToSAH.get(path);
+ if(sah) {
+ // Un-associate the name from the SAH.
+ this.#mapFilenameToSAH.delete(path);
+ this.setAssociatedPath(sah, '', 0);
+ }
+ return !!sah;
+ }
+
+ /**
+ Sets e (an Error object) as this object's current error. Pass a
+ falsy (or no) value to clear it. If code is truthy it is
+ assumed to be an SQLITE_xxx result code, defaulting to
+ SQLITE_IOERR if code is falsy.
+
+ Returns the 2nd argument.
+ */
+ storeErr(e,code){
+ if(e){
+ e.sqlite3Rc = code || capi.SQLITE_IOERR;
+ this.error(e);
+ }
+ this.$error = e;
+ return code;
+ }
+ /**
+ Pops this object's Error object and returns
+ it (a falsy value if no error is set).
+ */
+ popErr(){
+ const rc = this.$error;
+ this.$error = undefined;
+ return rc;
+ }
+
+ /**
+ Returns the next available SAH without removing
+ it from the set.
+ */
+ nextAvailableSAH(){
+ const [rc] = this.#availableSAH.keys();
+ return rc;
+ }
+
+ /**
+ Given an (sqlite3_file*), returns the mapped
+ xOpen file object.
+ */
+ getOFileForS3File(pFile){
+ return this.#mapS3FileToOFile_.get(pFile);
+ }
+ /**
+ Maps or unmaps (if file is falsy) the given (sqlite3_file*)
+ to an xOpen file object and to this pool object.
+ */
+ mapS3FileToOFile(pFile,file){
+ if(file){
+ this.#mapS3FileToOFile_.set(pFile, file);
+ setPoolForPFile(pFile, this);
+ }else{
+ this.#mapS3FileToOFile_.delete(pFile);
+ setPoolForPFile(pFile, false);
+ }
+ }
+
+ /**
+ Returns true if the given client-defined file name is in this
+ object's name-to-SAH map.
+ */
+ hasFilename(name){
+ return this.#mapFilenameToSAH.has(name)
+ }
+
+ /**
+ Returns the SAH associated with the given
+ client-defined file name.
+ */
+ getSAHForPath(path){
+ return this.#mapFilenameToSAH.get(path);
+ }
+
+ /**
+ Removes this object's sqlite3_vfs registration and shuts down
+ this object, releasing all handles, mappings, and whatnot,
+ including deleting its data directory. There is currently no
+ way to "revive" the object and reaquire its resources.
+
+ This function is intended primarily for testing.
+
+ Resolves to true if it did its job, false if the
+ VFS has already been shut down.
+ */
+ async removeVfs(){
+ if(!this.#cVfs.pointer || !this.#dhOpaque) return false;
+ capi.sqlite3_vfs_unregister(this.#cVfs.pointer);
+ this.#cVfs.dispose();
+ try{
+ this.releaseAccessHandles();
+ await this.#dhVfsRoot.removeEntry(OPAQUE_DIR_NAME, {recursive: true});
+ this.#dhOpaque = undefined;
+ await this.#dhVfsParent.removeEntry(
+ this.#dhVfsRoot.name, {recursive: true}
+ );
+ this.#dhVfsRoot = this.#dhVfsParent = undefined;
+ }catch(e){
+ sqlite3.config.error(this.vfsName,"removeVfs() failed:",e);
+ /*otherwise ignored - there is no recovery strategy*/
+ }
+ return true;
+ }
+
+
+ //! Documented elsewhere in this file.
+ exportFile(name){
+ const sah = this.#mapFilenameToSAH.get(name) || toss("File not found:",name);
+ const n = sah.getSize() - HEADER_OFFSET_DATA;
+ const b = new Uint8Array(n>0 ? n : 0);
+ if(n>0){
+ const nRead = sah.read(b, {at: HEADER_OFFSET_DATA});
+ if(nRead != n){
+ toss("Expected to read "+n+" bytes but read "+nRead+".");
+ }
+ }
+ return b;
+ }
+
+ //! Impl for importDb() when its 2nd arg is a function.
+ async importDbChunked(name, callback){
+ const sah = this.#mapFilenameToSAH.get(name)
+ || this.nextAvailableSAH()
+ || toss("No available handles to import to.");
+ sah.truncate(0);
+ let nWrote = 0, chunk, checkedHeader = false, err = false;
+ try{
+ while( undefined !== (chunk = await callback()) ){
+ if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk);
+ if( 0===nWrote && chunk.byteLength>=15 ){
+ util.affirmDbHeader(chunk);
+ checkedHeader = true;
+ }
+ sah.write(chunk, {at: HEADER_OFFSET_DATA + nWrote});
+ nWrote += chunk.byteLength;
+ }
+ if( nWrote < 512 || 0!==nWrote % 512 ){
+ toss("Input size",nWrote,"is not correct for an SQLite database.");
+ }
+ if( !checkedHeader ){
+ const header = new Uint8Array(20);
+ sah.read( header, {at: 0} );
+ util.affirmDbHeader( header );
+ }
+ sah.write(new Uint8Array([1,1]), {
+ at: HEADER_OFFSET_DATA + 18
+ }/*force db out of WAL mode*/);
+ }catch(e){
+ this.setAssociatedPath(sah, '', 0);
+ throw e;
+ }
+ this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB);
+ return nWrote;
+ }
+
+ //! Documented elsewhere in this file.
+ importDb(name, bytes){
+ if( bytes instanceof ArrayBuffer ) bytes = new Uint8Array(bytes);
+ else if( bytes instanceof Function ) return this.importDbChunked(name, bytes);
+ const sah = this.#mapFilenameToSAH.get(name)
+ || this.nextAvailableSAH()
+ || toss("No available handles to import to.");
+ const n = bytes.byteLength;
+ if(n<512 || n%512!=0){
+ toss("Byte array size is invalid for an SQLite db.");
+ }
+ const header = "SQLite format 3";
+ for(let i = 0; i < header.length; ++i){
+ if( header.charCodeAt(i) !== bytes[i] ){
+ toss("Input does not contain an SQLite database header.");
+ }
+ }
+ const nWrote = sah.write(bytes, {at: HEADER_OFFSET_DATA});
+ if(nWrote != n){
+ this.setAssociatedPath(sah, '', 0);
+ toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
+ }else{
+ sah.write(new Uint8Array([1,1]), {at: HEADER_OFFSET_DATA+18}
+ /* force db out of WAL mode */);
+ this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB);
+ }
+ return nWrote;
+ }
+
+ }/*class OpfsSAHPool*/;
+
+
+ /**
+ A OpfsSAHPoolUtil instance is exposed to clients in order to
+ manipulate an OpfsSAHPool object without directly exposing that
+ object and allowing for some semantic changes compared to that
+ class.
+
+ Class docs are in the client-level docs for
+ installOpfsSAHPoolVfs().
+ */
+ class OpfsSAHPoolUtil {
+ /* This object's associated OpfsSAHPool. */
+ #p;
+
+ constructor(sahPool){
+ this.#p = sahPool;
+ this.vfsName = sahPool.vfsName;
+ }
+
+ async addCapacity(n){ return this.#p.addCapacity(n) }
+
+ async reduceCapacity(n){ return this.#p.reduceCapacity(n) }
+
+ getCapacity(){ return this.#p.getCapacity(this.#p) }
+
+ getFileCount(){ return this.#p.getFileCount() }
+ getFileNames(){ return this.#p.getFileNames() }
+
+ async reserveMinimumCapacity(min){
+ const c = this.#p.getCapacity();
+ return (c < min) ? this.#p.addCapacity(min - c) : c;
+ }
+
+ exportFile(name){ return this.#p.exportFile(name) }
+
+ importDb(name, bytes){ return this.#p.importDb(name,bytes) }
+
+ async wipeFiles(){ return this.#p.reset(true) }
+
+ unlink(filename){ return this.#p.deletePath(filename) }
+
+ async removeVfs(){ return this.#p.removeVfs() }
+
+ }/* class OpfsSAHPoolUtil */;
+
+ /**
+ Returns a resolved Promise if the current environment
+ has a "fully-sync" SAH impl, else a rejected Promise.
+ */
+ const apiVersionCheck = async ()=>{
+ const dh = await navigator.storage.getDirectory();
+ const fn = '.opfs-sahpool-sync-check-'+getRandomName();
+ const fh = await dh.getFileHandle(fn, { create: true });
+ const ah = await fh.createSyncAccessHandle();
+ const close = ah.close();
+ await close;
+ await dh.removeEntry(fn);
+ if(close?.then){
+ toss("The local OPFS API is too old for opfs-sahpool:",
+ "it has an async FileSystemSyncAccessHandle.close() method.");
+ }
+ return true;
+ };
+
+ /** Only for testing a rejection case. */
+ let instanceCounter = 0;
+
+ /**
+ installOpfsSAHPoolVfs() asynchronously initializes the OPFS
+ SyncAccessHandle (a.k.a. SAH) Pool VFS. It returns a Promise which
+ either resolves to a utility object described below or rejects with
+ an Error value.
+
+ Initialization of this VFS is not automatic because its
+ registration requires that it lock all resources it
+ will potentially use, even if client code does not want
+ to use them. That, in turn, can lead to locking errors
+ when, for example, one page in a given origin has loaded
+ this VFS but does not use it, then another page in that
+ origin tries to use the VFS. If the VFS were automatically
+ registered, the second page would fail to load the VFS
+ due to OPFS locking errors.
+
+ If this function is called more than once with a given "name"
+ option (see below), it will return the same Promise. Calls for
+ different names will return different Promises which resolve to
+ independent objects and refer to different VFS registrations.
+
+ On success, the resulting Promise resolves to a utility object
+ which can be used to query and manipulate the pool. Its API is
+ described at the end of these docs.
+
+ This function accepts an options object to configure certain
+ parts but it is only acknowledged for the very first call and
+ ignored for all subsequent calls.
+
+ The options, in alphabetical order:
+
+ - `clearOnInit`: (default=false) if truthy, contents and filename
+ mapping are removed from each SAH it is acquired during
+ initalization of the VFS, leaving the VFS's storage in a pristine
+ state. Use this only for databases which need not survive a page
+ reload.
+
+ - `initialCapacity`: (default=6) Specifies the default capacity of
+ the VFS. This should not be set unduly high because the VFS has
+ to open (and keep open) a file for each entry in the pool. This
+ setting only has an effect when the pool is initially empty. It
+ does not have any effect if a pool already exists.
+
+ - `directory`: (default="."+`name`) Specifies the OPFS directory
+ name in which to store metadata for the `"opfs-sahpool"`
+ sqlite3_vfs. Only one instance of this VFS can be installed per
+ JavaScript engine, and any two engines with the same storage
+ directory name will collide with each other, leading to locking
+ errors and the inability to register the VFS in the second and
+ subsequent engine. Using a different directory name for each
+ application enables different engines in the same HTTP origin to
+ co-exist, but their data are invisible to each other. Changing
+ this name will effectively orphan any databases stored under
+ previous names. The default is unspecified but descriptive. This
+ option may contain multiple path elements, e.g. "foo/bar/baz",
+ and they are created automatically. In practice there should be
+ no driving need to change this. ACHTUNG: all files in this
+ directory are assumed to be managed by the VFS. Do not place
+ other files in that directory, as they may be deleted or
+ otherwise modified by the VFS.
+
+ - `name`: (default="opfs-sahpool") sets the name to register this
+ VFS under. Normally this should not be changed, but it is
+ possible to register this VFS under multiple names so long as
+ each has its own separate directory to work from. The storage for
+ each is invisible to all others. The name must be a string
+ compatible with `sqlite3_vfs_register()` and friends and suitable
+ for use in URI-style database file names.
+
+ Achtung: if a custom `name` is provided, a custom `directory`
+ must also be provided if any other instance is registered with
+ the default directory. If no directory is explicitly provided
+ then a directory name is synthesized from the `name` option.
+
+ Peculiarities of this VFS:
+
+ - Paths given to it _must_ be absolute. Relative paths will not
+ be properly recognized. This is arguably a bug but correcting it
+ requires some hoop-jumping in routines which have no business
+ doing tricks.
+
+ - It is possible to install multiple instances under different
+ names, each sandboxed from one another inside their own private
+ directory. This feature exists primarily as a way for disparate
+ applications within a given HTTP origin to use this VFS without
+ introducing locking issues between them.
+
+
+ The API for the utility object passed on by this function's
+ Promise, in alphabetical order...
+
+ - [async] number addCapacity(n)
+
+ Adds `n` entries to the current pool. This change is persistent
+ across sessions so should not be called automatically at each app
+ startup (but see `reserveMinimumCapacity()`). Its returned Promise
+ resolves to the new capacity. Because this operation is necessarily
+ asynchronous, the C-level VFS API cannot call this on its own as
+ needed.
+
+ - byteArray exportFile(name)
+
+ Synchronously reads the contents of the given file into a Uint8Array
+ and returns it. This will throw if the given name is not currently
+ in active use or on I/O error. Note that the given name is _not_
+ visible directly in OPFS (or, if it is, it's not from this VFS).
+
+ - number getCapacity()
+
+ Returns the number of files currently contained
+ in the SAH pool. The default capacity is only large enough for one
+ or two databases and their associated temp files.
+
+ - number getFileCount()
+
+ Returns the number of files from the pool currently allocated to
+ slots. This is not the same as the files being "opened".
+
+ - array getFileNames()
+
+ Returns an array of the names of the files currently allocated to
+ slots. This list is the same length as getFileCount().
+
+ - void importDb(name, bytes)
+
+ Imports the contents of an SQLite database, provided as a byte
+ array or ArrayBuffer, under the given name, overwriting any
+ existing content. Throws if the pool has no available file slots,
+ on I/O error, or if the input does not appear to be a
+ database. In the latter case, only a cursory examination is made.
+ Note that this routine is _only_ for importing database files,
+ not arbitrary files, the reason being that this VFS will
+ automatically clean up any non-database files so importing them
+ is pointless.
+
+ If passed a function for its second argument, its behavior
+ changes to asynchronous and it imports its data in chunks fed to
+ it by the given callback function. It calls the callback (which
+ may be async) repeatedly, expecting either a Uint8Array or
+ ArrayBuffer (to denote new input) or undefined (to denote
+ EOF). For so long as the callback continues to return
+ non-undefined, it will append incoming data to the given
+ VFS-hosted database file. The result of the resolved Promise when
+ called this way is the size of the resulting database.
+
+ On succes this routine rewrites the database header bytes in the
+ output file (not the input array) to force disabling of WAL mode.
+
+ On a write error, the handle is removed from the pool and made
+ available for re-use.
+
+ - [async] number reduceCapacity(n)
+
+ Removes up to `n` entries from the pool, with the caveat that it can
+ only remove currently-unused entries. It returns a Promise which
+ resolves to the number of entries actually removed.
+
+ - [async] boolean removeVfs()
+
+ Unregisters the opfs-sahpool VFS and removes its directory from OPFS
+ (which means that _all client content_ is removed). After calling
+ this, the VFS may no longer be used and there is no way to re-add it
+ aside from reloading the current JavaScript context.
+
+ Results are undefined if a database is currently in use with this
+ VFS.
+
+ The returned Promise resolves to true if it performed the removal
+ and false if the VFS was not installed.
+
+ If the VFS has a multi-level directory, e.g. "/foo/bar/baz", _only_
+ the bottom-most directory is removed because this VFS cannot know for
+ certain whether the higher-level directories contain data which
+ should be removed.
+
+ - [async] number reserveMinimumCapacity(min)
+
+ If the current capacity is less than `min`, the capacity is
+ increased to `min`, else this returns with no side effects. The
+ resulting Promise resolves to the new capacity.
+
+ - boolean unlink(filename)
+
+ If a virtual file exists with the given name, disassociates it from
+ the pool and returns true, else returns false without side
+ effects. Results are undefined if the file is currently in active
+ use.
+
+ - string vfsName
+
+ The SQLite VFS name under which this pool's VFS is registered.
+
+ - [async] void wipeFiles()
+
+ Clears all client-defined state of all SAHs and makes all of them
+ available for re-use by the pool. Results are undefined if any such
+ handles are currently in use, e.g. by an sqlite3 db.
+ */
+ sqlite3.installOpfsSAHPoolVfs = async function(options=Object.create(null)){
+ const vfsName = options.name || optionDefaults.name;
+ if(0 && 2===++instanceCounter){
+ throw new Error("Just testing rejection.");
+ }
+ if(initPromises[vfsName]){
+ //console.warn("Returning same OpfsSAHPool result",options,vfsName,initPromises[vfsName]);
+ return initPromises[vfsName];
+ }
+ if(!globalThis.FileSystemHandle ||
+ !globalThis.FileSystemDirectoryHandle ||
+ !globalThis.FileSystemFileHandle ||
+ !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle ||
+ !navigator?.storage?.getDirectory){
+ return (initPromises[vfsName] = Promise.reject(new Error("Missing required OPFS APIs.")));
+ }
+
+ /**
+ Maintenance reminder: the order of ASYNC ops in this function
+ is significant. We need to have them all chained at the very
+ end in order to be able to catch a race condition where
+ installOpfsSAHPoolVfs() is called twice in rapid succession,
+ e.g.:
+
+ installOpfsSAHPoolVfs().then(console.warn.bind(console));
+ installOpfsSAHPoolVfs().then(console.warn.bind(console));
+
+ If the timing of the async calls is not "just right" then that
+ second call can end up triggering the init a second time and chaos
+ ensues.
+ */
+ return initPromises[vfsName] = apiVersionCheck().then(async function(){
+ if(options.$testThrowInInit){
+ throw options.$testThrowInInit;
+ }
+ const thePool = new OpfsSAHPool(options);
+ return thePool.isReady.then(async()=>{
+ /** The poolUtil object will be the result of the
+ resolved Promise. */
+ const poolUtil = new OpfsSAHPoolUtil(thePool);
+ if(sqlite3.oo1){
+ const oo1 = sqlite3.oo1;
+ const theVfs = thePool.getVfs();
+ const OpfsSAHPoolDb = function(...args){
+ const opt = oo1.DB.dbCtorHelper.normalizeArgs(...args);
+ opt.vfs = theVfs.$zName;
+ oo1.DB.dbCtorHelper.call(this, opt);
+ };
+ OpfsSAHPoolDb.prototype = Object.create(oo1.DB.prototype);
+ // yes or no? OpfsSAHPoolDb.PoolUtil = poolUtil;
+ poolUtil.OpfsSAHPoolDb = OpfsSAHPoolDb;
+ oo1.DB.dbCtorHelper.setVfsPostOpenSql(
+ theVfs.pointer,
+ function(oo1Db, sqlite3){
+ sqlite3.capi.sqlite3_exec(oo1Db, [
+ /* See notes in sqlite3-vfs-opfs.js */
+ "pragma journal_mode=DELETE;",
+ "pragma cache_size=-16384;"
+ ], 0, 0, 0);
+ }
+ );
+ }/*extend sqlite3.oo1*/
+ thePool.log("VFS initialized.");
+ return poolUtil;
+ }).catch(async (e)=>{
+ await thePool.removeVfs().catch(()=>{});
+ return e;
+ });
+ }).catch((err)=>{
+ //error("rejecting promise:",err);
+ return initPromises[vfsName] = Promise.reject(err);
+ });
+ }/*installOpfsSAHPoolVfs()*/;
+}/*sqlite3ApiBootstrap.initializers*/);
+//#else
+/*
+ The OPFS SAH Pool VFS parts are elided from builds targeting
+ node.js.
+*/
+//#endif target=node
diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
new file mode 100644
index 0000000..af89f21
--- /dev/null
+++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
@@ -0,0 +1,1469 @@
+//#ifnot target=node
+/*
+ 2022-09-18
+
+ 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 holds the synchronous half of an sqlite3_vfs
+ implementation which proxies, in a synchronous fashion, the
+ asynchronous Origin-Private FileSystem (OPFS) APIs using a second
+ Worker, implemented in sqlite3-opfs-async-proxy.js. This file is
+ intended to be appended to the main sqlite3 JS deliverable somewhere
+ after sqlite3-api-oo1.js and before sqlite3-api-cleanup.js.
+*/
+'use strict';
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+/**
+ installOpfsVfs() returns a Promise which, on success, installs an
+ sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs
+ which accept a VFS. It is intended to be called via
+ sqlite3ApiBootstrap.initializers or an equivalent mechanism.
+
+ The installed VFS uses the Origin-Private FileSystem API for
+ all file storage. On error it is rejected with an exception
+ explaining the problem. Reasons for rejection include, but are
+ not limited to:
+
+ - The counterpart Worker (see below) could not be loaded.
+
+ - The environment does not support OPFS. That includes when
+ this function is called from the main window thread.
+
+ Significant notes and limitations:
+
+ - As of this writing, OPFS is still very much in flux and only
+ available in bleeding-edge versions of Chrome (v102+, noting that
+ that number will increase as the OPFS API matures).
+
+ - The OPFS features used here are only available in dedicated Worker
+ threads. This file tries to detect that case, resulting in a
+ rejected Promise if those features do not seem to be available.
+
+ - It requires the SharedArrayBuffer and Atomics classes, and the
+ former is only available if the HTTP server emits the so-called
+ COOP and COEP response headers. These features are required for
+ proxying OPFS's synchronous API via the synchronous interface
+ required by the sqlite3_vfs API.
+
+ - This function may only be called a single time. When called, this
+ function removes itself from the sqlite3 object.
+
+ All arguments to this function are for internal/development purposes
+ only. They do not constitute a public API and may change at any
+ time.
+
+ The argument may optionally be a plain object with the following
+ configuration options:
+
+ - proxyUri: as described above
+
+ - verbose (=2): an integer 0-3. 0 disables all logging, 1 enables
+ logging of errors. 2 enables logging of warnings and errors. 3
+ additionally enables debugging info.
+
+ - sanityChecks (=false): if true, some basic sanity tests are
+ run on the OPFS VFS API after it's initialized, before the
+ returned Promise resolves.
+
+ On success, the Promise resolves to the top-most sqlite3 namespace
+ object and that object gets a new object installed in its
+ `opfs` property, containing several OPFS-specific utilities.
+*/
+const installOpfsVfs = function callee(options){
+ if(!globalThis.SharedArrayBuffer
+ || !globalThis.Atomics){
+ return Promise.reject(
+ new Error("Cannot install OPFS: Missing SharedArrayBuffer and/or Atomics. "+
+ "The server must emit the COOP/COEP response headers to enable those. "+
+ "See https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep")
+ );
+ }else if('undefined'===typeof WorkerGlobalScope){
+ return Promise.reject(
+ new Error("The OPFS sqlite3_vfs cannot run in the main thread "+
+ "because it requires Atomics.wait().")
+ );
+ }else if(!globalThis.FileSystemHandle ||
+ !globalThis.FileSystemDirectoryHandle ||
+ !globalThis.FileSystemFileHandle ||
+ !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle ||
+ !navigator?.storage?.getDirectory){
+ return Promise.reject(
+ new Error("Missing required OPFS APIs.")
+ );
+ }
+ if(!options || 'object'!==typeof options){
+ options = Object.create(null);
+ }
+ const urlParams = new URL(globalThis.location.href).searchParams;
+ if(urlParams.has('opfs-disable')){
+ //sqlite3.config.warn('Explicitly not installing "opfs" VFS due to opfs-disable flag.');
+ return Promise.resolve(sqlite3);
+ }
+ if(undefined===options.verbose){
+ options.verbose = urlParams.has('opfs-verbose')
+ ? (+urlParams.get('opfs-verbose') || 2) : 1;
+ }
+ if(undefined===options.sanityChecks){
+ options.sanityChecks = urlParams.has('opfs-sanity-check');
+ }
+ if(undefined===options.proxyUri){
+ options.proxyUri = callee.defaultProxyUri;
+ }
+
+ //sqlite3.config.warn("OPFS options =",options,globalThis.location);
+
+ if('function' === typeof options.proxyUri){
+ options.proxyUri = options.proxyUri();
+ }
+ const thePromise = new Promise(function(promiseResolve_, promiseReject_){
+ const loggers = [
+ sqlite3.config.error,
+ sqlite3.config.warn,
+ sqlite3.config.log
+ ];
+ const logImpl = (level,...args)=>{
+ if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
+ };
+ const log = (...args)=>logImpl(2, ...args);
+ const warn = (...args)=>logImpl(1, ...args);
+ const error = (...args)=>logImpl(0, ...args);
+ const toss = sqlite3.util.toss;
+ const capi = sqlite3.capi;
+ const util = sqlite3.util;
+ const wasm = sqlite3.wasm;
+ const sqlite3_vfs = capi.sqlite3_vfs;
+ const sqlite3_file = capi.sqlite3_file;
+ const sqlite3_io_methods = capi.sqlite3_io_methods;
+ /**
+ Generic utilities for working with OPFS. This will get filled out
+ by the Promise setup and, on success, installed as sqlite3.opfs.
+
+ ACHTUNG: do not rely on these APIs in client code. They are
+ experimental and subject to change or removal as the
+ OPFS-specific sqlite3_vfs evolves.
+ */
+ const opfsUtil = Object.create(null);
+
+ /**
+ Returns true if _this_ thread has access to the OPFS APIs.
+ */
+ const thisThreadHasOPFS = ()=>{
+ return globalThis.FileSystemHandle &&
+ globalThis.FileSystemDirectoryHandle &&
+ globalThis.FileSystemFileHandle &&
+ globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle &&
+ navigator?.storage?.getDirectory;
+ };
+
+ /**
+ Not part of the public API. Solely for internal/development
+ use.
+ */
+ opfsUtil.metrics = {
+ dump: function(){
+ let k, n = 0, t = 0, w = 0;
+ for(k in state.opIds){
+ const m = metrics[k];
+ n += m.count;
+ t += m.time;
+ w += m.wait;
+ m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
+ m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
+ }
+ sqlite3.config.log(globalThis.location.href,
+ "metrics for",globalThis.location.href,":",metrics,
+ "\nTotal of",n,"op(s) for",t,
+ "ms (incl. "+w+" ms of waiting on the async side)");
+ sqlite3.config.log("Serialization metrics:",metrics.s11n);
+ W.postMessage({type:'opfs-async-metrics'});
+ },
+ reset: function(){
+ let k;
+ const r = (m)=>(m.count = m.time = m.wait = 0);
+ for(k in state.opIds){
+ r(metrics[k] = Object.create(null));
+ }
+ let s = metrics.s11n = Object.create(null);
+ s = s.serialize = Object.create(null);
+ s.count = s.time = 0;
+ s = metrics.s11n.deserialize = Object.create(null);
+ s.count = s.time = 0;
+ }
+ }/*metrics*/;
+ const opfsIoMethods = new sqlite3_io_methods();
+ const opfsVfs = new sqlite3_vfs()
+ .addOnDispose( ()=>opfsIoMethods.dispose());
+ let promiseWasRejected = undefined;
+ const promiseReject = (err)=>{
+ promiseWasRejected = true;
+ opfsVfs.dispose();
+ return promiseReject_(err);
+ };
+ const promiseResolve = ()=>{
+ promiseWasRejected = false;
+ return promiseResolve_(sqlite3);
+ };
+ const W =
+//#if target=es6-bundler-friendly
+ new Worker(new URL("sqlite3-opfs-async-proxy.js", import.meta.url));
+//#elif target=es6-module
+ new Worker(new URL(options.proxyUri, import.meta.url));
+//#else
+ new Worker(options.proxyUri);
+//#endif
+ setTimeout(()=>{
+ /* At attempt to work around a browser-specific quirk in which
+ the Worker load is failing in such a way that we neither
+ resolve nor reject it. This workaround gives that resolve/reject
+ a time limit and rejects if that timer expires. Discussion:
+ https://sqlite.org/forum/forumpost/a708c98dcb3ef */
+ if(undefined===promiseWasRejected){
+ promiseReject(
+ new Error("Timeout while waiting for OPFS async proxy worker.")
+ );
+ }
+ }, 4000);
+ W._originalOnError = W.onerror /* will be restored later */;
+ W.onerror = function(err){
+ // The error object doesn't contain any useful info when the
+ // failure is, e.g., that the remote script is 404.
+ error("Error initializing OPFS asyncer:",err);
+ promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
+ };
+ const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
+ const dVfs = pDVfs
+ ? new sqlite3_vfs(pDVfs)
+ : null /* dVfs will be null when sqlite3 is built with
+ SQLITE_OS_OTHER. */;
+ opfsIoMethods.$iVersion = 1;
+ opfsVfs.$iVersion = 2/*yes, two*/;
+ opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
+ opfsVfs.$mxPathname = 1024/*sure, why not?*/;
+ opfsVfs.$zName = wasm.allocCString("opfs");
+ // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
+ opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
+ opfsVfs.addOnDispose(
+ '$zName', opfsVfs.$zName,
+ 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null)
+ );
+ /**
+ Pedantic sidebar about opfsVfs.ondispose: the entries in that array
+ are items to clean up when opfsVfs.dispose() is called, but in this
+ environment it will never be called. The VFS instance simply
+ hangs around until the WASM module instance is cleaned up. We
+ "could" _hypothetically_ clean it up by "importing" an
+ sqlite3_os_end() impl into the wasm build, but the shutdown order
+ of the wasm engine and the JS one are undefined so there is no
+ guaranty that the opfsVfs instance would be available in one
+ environment or the other when sqlite3_os_end() is called (_if_ it
+ gets called at all in a wasm build, which is undefined).
+ */
+ /**
+ State which we send to the async-api Worker or share with it.
+ This object must initially contain only cloneable or sharable
+ objects. After the worker's "inited" message arrives, other types
+ of data may be added to it.
+
+ For purposes of Atomics.wait() and Atomics.notify(), we use a
+ SharedArrayBuffer with one slot reserved for each of the API
+ proxy's methods. The sync side of the API uses Atomics.wait()
+ on the corresponding slot and the async side uses
+ Atomics.notify() on that slot.
+
+ The approach of using a single SAB to serialize comms for all
+ instances might(?) lead to deadlock situations in multi-db
+ cases. We should probably have one SAB here with a single slot
+ for locking a per-file initialization step and then allocate a
+ separate SAB like the above one for each file. That will
+ require a bit of acrobatics but should be feasible. The most
+ problematic part is that xOpen() would have to use
+ postMessage() to communicate its SharedArrayBuffer, and mixing
+ that approach with Atomics.wait/notify() gets a bit messy.
+ */
+ const state = Object.create(null);
+ state.verbose = options.verbose;
+ state.littleEndian = (()=>{
+ const buffer = new ArrayBuffer(2);
+ new DataView(buffer).setInt16(0, 256, true /* ==>littleEndian */);
+ // Int16Array uses the platform's endianness.
+ return new Int16Array(buffer)[0] === 256;
+ })();
+ /**
+ asyncIdleWaitTime is how long (ms) to wait, in the async proxy,
+ for each Atomics.wait() when waiting on inbound VFS API calls.
+ We need to wake up periodically to give the thread a chance to
+ do other things. If this is too high (e.g. 500ms) then even two
+ workers/tabs can easily run into locking errors. Some multiple
+ of this value is also used for determining how long to wait on
+ lock contention to free up.
+ */
+ state.asyncIdleWaitTime = 150;
+
+ /**
+ Whether the async counterpart should log exceptions to
+ the serialization channel. That produces a great deal of
+ noise for seemingly innocuous things like xAccess() checks
+ for missing files, so this option may have one of 3 values:
+
+ 0 = no exception logging.
+
+ 1 = only log exceptions for "significant" ops like xOpen(),
+ xRead(), and xWrite().
+
+ 2 = log all exceptions.
+ */
+ state.asyncS11nExceptions = 1;
+ /* Size of file I/O buffer block. 64k = max sqlite3 page size, and
+ xRead/xWrite() will never deal in blocks larger than that. */
+ state.fileBufferSize = 1024 * 64;
+ state.sabS11nOffset = state.fileBufferSize;
+ /**
+ The size of the block in our SAB for serializing arguments and
+ result values. Needs to be large enough to hold serialized
+ values of any of the proxied APIs. Filenames are the largest
+ part but are limited to opfsVfs.$mxPathname bytes. We also
+ store exceptions there, so it needs to be long enough to hold
+ a reasonably long exception string.
+ */
+ state.sabS11nSize = opfsVfs.$mxPathname * 2;
+ /**
+ The SAB used for all data I/O between the synchronous and
+ async halves (file i/o and arg/result s11n).
+ */
+ state.sabIO = new SharedArrayBuffer(
+ state.fileBufferSize/* file i/o block */
+ + state.sabS11nSize/* argument/result serialization block */
+ );
+ state.opIds = Object.create(null);
+ const metrics = Object.create(null);
+ {
+ /* Indexes for use in our SharedArrayBuffer... */
+ let i = 0;
+ /* SAB slot used to communicate which operation is desired
+ between both workers. This worker writes to it and the other
+ listens for changes. */
+ state.opIds.whichOp = i++;
+ /* Slot for storing return values. This worker listens to that
+ slot and the other worker writes to it. */
+ state.opIds.rc = i++;
+ /* Each function gets an ID which this worker writes to
+ the whichOp slot. The async-api worker uses Atomic.wait()
+ on the whichOp slot to figure out which operation to run
+ next. */
+ state.opIds.xAccess = i++;
+ state.opIds.xClose = i++;
+ state.opIds.xDelete = i++;
+ state.opIds.xDeleteNoWait = i++;
+ state.opIds.xFileSize = i++;
+ state.opIds.xLock = i++;
+ state.opIds.xOpen = i++;
+ state.opIds.xRead = i++;
+ state.opIds.xSleep = i++;
+ state.opIds.xSync = i++;
+ state.opIds.xTruncate = i++;
+ state.opIds.xUnlock = i++;
+ state.opIds.xWrite = i++;
+ state.opIds.mkdir = i++;
+ state.opIds['opfs-async-metrics'] = i++;
+ state.opIds['opfs-async-shutdown'] = i++;
+ /* The retry slot is used by the async part for wait-and-retry
+ semantics. Though we could hypothetically use the xSleep slot
+ for that, doing so might lead to undesired side effects. */
+ state.opIds.retry = i++;
+ state.sabOP = new SharedArrayBuffer(
+ i * 4/* ==sizeof int32, noting that Atomics.wait() and friends
+ can only function on Int32Array views of an SAB. */);
+ opfsUtil.metrics.reset();
+ }
+ /**
+ SQLITE_xxx constants to export to the async worker
+ counterpart...
+ */
+ state.sq3Codes = Object.create(null);
+ [
+ 'SQLITE_ACCESS_EXISTS',
+ 'SQLITE_ACCESS_READWRITE',
+ 'SQLITE_BUSY',
+ 'SQLITE_ERROR',
+ 'SQLITE_IOERR',
+ 'SQLITE_IOERR_ACCESS',
+ 'SQLITE_IOERR_CLOSE',
+ 'SQLITE_IOERR_DELETE',
+ 'SQLITE_IOERR_FSYNC',
+ 'SQLITE_IOERR_LOCK',
+ 'SQLITE_IOERR_READ',
+ 'SQLITE_IOERR_SHORT_READ',
+ 'SQLITE_IOERR_TRUNCATE',
+ 'SQLITE_IOERR_UNLOCK',
+ 'SQLITE_IOERR_WRITE',
+ 'SQLITE_LOCK_EXCLUSIVE',
+ 'SQLITE_LOCK_NONE',
+ 'SQLITE_LOCK_PENDING',
+ 'SQLITE_LOCK_RESERVED',
+ 'SQLITE_LOCK_SHARED',
+ 'SQLITE_LOCKED',
+ 'SQLITE_MISUSE',
+ 'SQLITE_NOTFOUND',
+ 'SQLITE_OPEN_CREATE',
+ 'SQLITE_OPEN_DELETEONCLOSE',
+ 'SQLITE_OPEN_MAIN_DB',
+ 'SQLITE_OPEN_READONLY'
+ ].forEach((k)=>{
+ if(undefined === (state.sq3Codes[k] = capi[k])){
+ toss("Maintenance required: not found:",k);
+ }
+ });
+ state.opfsFlags = Object.assign(Object.create(null),{
+ /**
+ Flag for use with xOpen(). "opfs-unlock-asap=1" enables
+ this. See defaultUnlockAsap, below.
+ */
+ OPFS_UNLOCK_ASAP: 0x01,
+ /**
+ If true, any async routine which implicitly acquires a sync
+ access handle (i.e. an OPFS lock) will release that locks at
+ the end of the call which acquires it. If false, such
+ "autolocks" are not released until the VFS is idle for some
+ brief amount of time.
+
+ The benefit of enabling this is much higher concurrency. The
+ down-side is much-reduced performance (as much as a 4x decrease
+ in speedtest1).
+ */
+ defaultUnlockAsap: false
+ });
+
+ /**
+ Runs the given operation (by name) in the async worker
+ counterpart, waits for its response, and returns the result
+ which the async worker writes to SAB[state.opIds.rc]. The
+ 2nd and subsequent arguments must be the aruguments for the
+ async op.
+ */
+ const opRun = (op,...args)=>{
+ const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
+ state.s11n.serialize(...args);
+ Atomics.store(state.sabOPView, state.opIds.rc, -1);
+ Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
+ Atomics.notify(state.sabOPView, state.opIds.whichOp)
+ /* async thread will take over here */;
+ const t = performance.now();
+ Atomics.wait(state.sabOPView, state.opIds.rc, -1)
+ /* When this wait() call returns, the async half will have
+ completed the operation and reported its results. */;
+ const rc = Atomics.load(state.sabOPView, state.opIds.rc);
+ metrics[op].wait += performance.now() - t;
+ if(rc && state.asyncS11nExceptions){
+ const err = state.s11n.deserialize();
+ if(err) error(op+"() async error:",...err);
+ }
+ return rc;
+ };
+
+ /**
+ Not part of the public API. Only for test/development use.
+ */
+ opfsUtil.debug = {
+ asyncShutdown: ()=>{
+ warn("Shutting down OPFS async listener. The OPFS VFS will no longer work.");
+ opRun('opfs-async-shutdown');
+ },
+ asyncRestart: ()=>{
+ warn("Attempting to restart OPFS VFS async listener. Might work, might not.");
+ W.postMessage({type: 'opfs-async-restart'});
+ }
+ };
+
+ const initS11n = ()=>{
+ /**
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ ACHTUNG: this code is 100% duplicated in the other half of
+ this proxy! The documentation is maintained in the
+ "synchronous half".
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ This proxy de/serializes cross-thread function arguments and
+ output-pointer values via the state.sabIO SharedArrayBuffer,
+ using the region defined by (state.sabS11nOffset,
+ state.sabS11nOffset + state.sabS11nSize]. Only one dataset is
+ recorded at a time.
+
+ This is not a general-purpose format. It only supports the
+ range of operations, and data sizes, needed by the
+ sqlite3_vfs and sqlite3_io_methods operations. Serialized
+ data are transient and this serialization algorithm may
+ change at any time.
+
+ The data format can be succinctly summarized as:
+
+ Nt...Td...D
+
+ Where:
+
+ - N = number of entries (1 byte)
+
+ - t = type ID of first argument (1 byte)
+
+ - ...T = type IDs of the 2nd and subsequent arguments (1 byte
+ each).
+
+ - d = raw bytes of first argument (per-type size).
+
+ - ...D = raw bytes of the 2nd and subsequent arguments (per-type
+ size).
+
+ All types except strings have fixed sizes. Strings are stored
+ using their TextEncoder/TextDecoder representations. It would
+ arguably make more sense to store them as Int16Arrays of
+ their JS character values, but how best/fastest to get that
+ in and out of string form is an open point. Initial
+ experimentation with that approach did not gain us any speed.
+
+ Historical note: this impl was initially about 1% this size by
+ using using JSON.stringify/parse(), but using fit-to-purpose
+ serialization saves considerable runtime.
+ */
+ if(state.s11n) return state.s11n;
+ const textDecoder = new TextDecoder(),
+ textEncoder = new TextEncoder('utf-8'),
+ viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
+ viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
+ state.s11n = Object.create(null);
+ /* Only arguments and return values of these types may be
+ serialized. This covers the whole range of types needed by the
+ sqlite3_vfs API. */
+ const TypeIds = Object.create(null);
+ TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
+ TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
+ TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
+ TypeIds.string = { id: 4 };
+
+ const getTypeId = (v)=>(
+ TypeIds[typeof v]
+ || toss("Maintenance required: this value type cannot be serialized.",v)
+ );
+ const getTypeIdById = (tid)=>{
+ switch(tid){
+ case TypeIds.number.id: return TypeIds.number;
+ case TypeIds.bigint.id: return TypeIds.bigint;
+ case TypeIds.boolean.id: return TypeIds.boolean;
+ case TypeIds.string.id: return TypeIds.string;
+ default: toss("Invalid type ID:",tid);
+ }
+ };
+
+ /**
+ Returns an array of the deserialized state stored by the most
+ recent serialize() operation (from from this thread or the
+ counterpart thread), or null if the serialization buffer is
+ empty. If passed a truthy argument, the serialization buffer
+ is cleared after deserialization.
+ */
+ state.s11n.deserialize = function(clear=false){
+ ++metrics.s11n.deserialize.count;
+ const t = performance.now();
+ const argc = viewU8[0];
+ const rc = argc ? [] : null;
+ if(argc){
+ const typeIds = [];
+ let offset = 1, i, n, v;
+ for(i = 0; i < argc; ++i, ++offset){
+ typeIds.push(getTypeIdById(viewU8[offset]));
+ }
+ for(i = 0; i < argc; ++i){
+ const t = typeIds[i];
+ if(t.getter){
+ v = viewDV[t.getter](offset, state.littleEndian);
+ offset += t.size;
+ }else{/*String*/
+ n = viewDV.getInt32(offset, state.littleEndian);
+ offset += 4;
+ v = textDecoder.decode(viewU8.slice(offset, offset+n));
+ offset += n;
+ }
+ rc.push(v);
+ }
+ }
+ if(clear) viewU8[0] = 0;
+ //log("deserialize:",argc, rc);
+ metrics.s11n.deserialize.time += performance.now() - t;
+ return rc;
+ };
+
+ /**
+ Serializes all arguments to the shared buffer for consumption
+ by the counterpart thread.
+
+ This routine is only intended for serializing OPFS VFS
+ arguments and (in at least one special case) result values,
+ and the buffer is sized to be able to comfortably handle
+ those.
+
+ If passed no arguments then it zeroes out the serialization
+ state.
+ */
+ state.s11n.serialize = function(...args){
+ const t = performance.now();
+ ++metrics.s11n.serialize.count;
+ if(args.length){
+ //log("serialize():",args);
+ const typeIds = [];
+ let i = 0, offset = 1;
+ viewU8[0] = args.length & 0xff /* header = # of args */;
+ for(; i < args.length; ++i, ++offset){
+ /* Write the TypeIds.id value into the next args.length
+ bytes. */
+ typeIds.push(getTypeId(args[i]));
+ viewU8[offset] = typeIds[i].id;
+ }
+ for(i = 0; i < args.length; ++i) {
+ /* Deserialize the following bytes based on their
+ corresponding TypeIds.id from the header. */
+ const t = typeIds[i];
+ if(t.setter){
+ viewDV[t.setter](offset, args[i], state.littleEndian);
+ offset += t.size;
+ }else{/*String*/
+ const s = textEncoder.encode(args[i]);
+ viewDV.setInt32(offset, s.byteLength, state.littleEndian);
+ offset += 4;
+ viewU8.set(s, offset);
+ offset += s.byteLength;
+ }
+ }
+ //log("serialize() result:",viewU8.slice(0,offset));
+ }else{
+ viewU8[0] = 0;
+ }
+ metrics.s11n.serialize.time += performance.now() - t;
+ };
+ return state.s11n;
+ }/*initS11n()*/;
+
+ /**
+ Generates a random ASCII string len characters long, intended for
+ use as a temporary file name.
+ */
+ const randomFilename = function f(len=16){
+ if(!f._chars){
+ f._chars = "abcdefghijklmnopqrstuvwxyz"+
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
+ "012346789";
+ f._n = f._chars.length;
+ }
+ const a = [];
+ let i = 0;
+ for( ; i < len; ++i){
+ const ndx = Math.random() * (f._n * 64) % f._n | 0;
+ a[i] = f._chars[ndx];
+ }
+ return a.join("");
+ /*
+ An alternative impl. with an unpredictable length
+ but much simpler:
+
+ Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36)
+ */
+ };
+
+ /**
+ Map of sqlite3_file pointers to objects constructed by xOpen().
+ */
+ const __openFiles = Object.create(null);
+
+ const opTimer = Object.create(null);
+ opTimer.op = undefined;
+ opTimer.start = undefined;
+ const mTimeStart = (op)=>{
+ opTimer.start = performance.now();
+ opTimer.op = op;
+ ++metrics[op].count;
+ };
+ const mTimeEnd = ()=>(
+ metrics[opTimer.op].time += performance.now() - opTimer.start
+ );
+
+ /**
+ Impls for the sqlite3_io_methods methods. Maintenance reminder:
+ members are in alphabetical order to simplify finding them.
+ */
+ const ioSyncWrappers = {
+ xCheckReservedLock: function(pFile,pOut){
+ /**
+ As of late 2022, only a single lock can be held on an OPFS
+ file. We have no way of checking whether any _other_ db
+ connection has a lock except by trying to obtain and (on
+ success) release a sync-handle for it, but doing so would
+ involve an inherent race condition. For the time being,
+ pending a better solution, we simply report whether the
+ given pFile is open.
+ */
+ const f = __openFiles[pFile];
+ wasm.poke(pOut, f.lockType ? 1 : 0, 'i32');
+ return 0;
+ },
+ xClose: function(pFile){
+ mTimeStart('xClose');
+ let rc = 0;
+ const f = __openFiles[pFile];
+ if(f){
+ delete __openFiles[pFile];
+ rc = opRun('xClose', pFile);
+ if(f.sq3File) f.sq3File.dispose();
+ }
+ mTimeEnd();
+ return rc;
+ },
+ xDeviceCharacteristics: function(pFile){
+ //debug("xDeviceCharacteristics(",pFile,")");
+ return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
+ },
+ xFileControl: function(pFile, opId, pArg){
+ /*mTimeStart('xFileControl');
+ mTimeEnd();*/
+ return capi.SQLITE_NOTFOUND;
+ },
+ xFileSize: function(pFile,pSz64){
+ mTimeStart('xFileSize');
+ let rc = opRun('xFileSize', pFile);
+ if(0==rc){
+ try {
+ const sz = state.s11n.deserialize()[0];
+ wasm.poke(pSz64, sz, 'i64');
+ }catch(e){
+ error("Unexpected error reading xFileSize() result:",e);
+ rc = state.sq3Codes.SQLITE_IOERR;
+ }
+ }
+ mTimeEnd();
+ return rc;
+ },
+ xLock: function(pFile,lockType){
+ mTimeStart('xLock');
+ const f = __openFiles[pFile];
+ let rc = 0;
+ /* All OPFS locks are exclusive locks. If xLock() has
+ previously succeeded, do nothing except record the lock
+ type. If no lock is active, have the async counterpart
+ lock the file. */
+ if( !f.lockType ) {
+ rc = opRun('xLock', pFile, lockType);
+ if( 0===rc ) f.lockType = lockType;
+ }else{
+ f.lockType = lockType;
+ }
+ mTimeEnd();
+ return rc;
+ },
+ xRead: function(pFile,pDest,n,offset64){
+ mTimeStart('xRead');
+ const f = __openFiles[pFile];
+ let rc;
+ try {
+ rc = opRun('xRead',pFile, n, Number(offset64));
+ if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
+ /**
+ Results get written to the SharedArrayBuffer f.sabView.
+ Because the heap is _not_ a SharedArrayBuffer, we have
+ to copy the results. TypedArray.set() seems to be the
+ fastest way to copy this. */
+ wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
+ }
+ }catch(e){
+ error("xRead(",arguments,") failed:",e,f);
+ rc = capi.SQLITE_IOERR_READ;
+ }
+ mTimeEnd();
+ return rc;
+ },
+ xSync: function(pFile,flags){
+ mTimeStart('xSync');
+ ++metrics.xSync.count;
+ const rc = opRun('xSync', pFile, flags);
+ mTimeEnd();
+ return rc;
+ },
+ xTruncate: function(pFile,sz64){
+ mTimeStart('xTruncate');
+ const rc = opRun('xTruncate', pFile, Number(sz64));
+ mTimeEnd();
+ return rc;
+ },
+ xUnlock: function(pFile,lockType){
+ mTimeStart('xUnlock');
+ const f = __openFiles[pFile];
+ let rc = 0;
+ if( capi.SQLITE_LOCK_NONE === lockType
+ && f.lockType ){
+ rc = opRun('xUnlock', pFile, lockType);
+ }
+ if( 0===rc ) f.lockType = lockType;
+ mTimeEnd();
+ return rc;
+ },
+ xWrite: function(pFile,pSrc,n,offset64){
+ mTimeStart('xWrite');
+ const f = __openFiles[pFile];
+ let rc;
+ try {
+ f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
+ rc = opRun('xWrite', pFile, n, Number(offset64));
+ }catch(e){
+ error("xWrite(",arguments,") failed:",e,f);
+ rc = capi.SQLITE_IOERR_WRITE;
+ }
+ mTimeEnd();
+ return rc;
+ }
+ }/*ioSyncWrappers*/;
+
+ /**
+ Impls for the sqlite3_vfs methods. Maintenance reminder: members
+ are in alphabetical order to simplify finding them.
+ */
+ const vfsSyncWrappers = {
+ xAccess: function(pVfs,zName,flags,pOut){
+ mTimeStart('xAccess');
+ const rc = opRun('xAccess', wasm.cstrToJs(zName));
+ wasm.poke( pOut, (rc ? 0 : 1), 'i32' );
+ mTimeEnd();
+ return 0;
+ },
+ xCurrentTime: function(pVfs,pOut){
+ /* If it turns out that we need to adjust for timezone, see:
+ https://stackoverflow.com/a/11760121/1458521 */
+ wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000),
+ 'double');
+ return 0;
+ },
+ xCurrentTimeInt64: function(pVfs,pOut){
+ wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(),
+ 'i64');
+ return 0;
+ },
+ xDelete: function(pVfs, zName, doSyncDir){
+ mTimeStart('xDelete');
+ const rc = opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false);
+ mTimeEnd();
+ return rc;
+ },
+ xFullPathname: function(pVfs,zName,nOut,pOut){
+ /* Until/unless we have some notion of "current dir"
+ in OPFS, simply copy zName to pOut... */
+ const i = wasm.cstrncpy(pOut, zName, nOut);
+ return i<nOut ? 0 : capi.SQLITE_CANTOPEN
+ /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
+ },
+ xGetLastError: function(pVfs,nOut,pOut){
+ /* TODO: store exception.message values from the async
+ partner in a dedicated SharedArrayBuffer, noting that we'd have
+ to encode them... TextEncoder can do that for us. */
+ warn("OPFS xGetLastError() has nothing sensible to return.");
+ return 0;
+ },
+ //xSleep is optionally defined below
+ xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
+ mTimeStart('xOpen');
+ let opfsFlags = 0;
+ if(0===zName){
+ zName = randomFilename();
+ }else if('number'===typeof zName){
+ if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){
+ /* -----------------------^^^^^ MUST pass the untranslated
+ C-string here. */
+ opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP;
+ }
+ zName = wasm.cstrToJs(zName);
+ }
+ const fh = Object.create(null);
+ fh.fid = pFile;
+ fh.filename = zName;
+ fh.sab = new SharedArrayBuffer(state.fileBufferSize);
+ fh.flags = flags;
+ const rc = opRun('xOpen', pFile, zName, flags, opfsFlags);
+ if(!rc){
+ /* Recall that sqlite3_vfs::xClose() will be called, even on
+ error, unless pFile->pMethods is NULL. */
+ if(fh.readOnly){
+ wasm.poke(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
+ }
+ __openFiles[pFile] = fh;
+ fh.sabView = state.sabFileBufView;
+ fh.sq3File = new sqlite3_file(pFile);
+ fh.sq3File.$pMethods = opfsIoMethods.pointer;
+ fh.lockType = capi.SQLITE_LOCK_NONE;
+ }
+ mTimeEnd();
+ return rc;
+ }/*xOpen()*/
+ }/*vfsSyncWrappers*/;
+
+ if(dVfs){
+ opfsVfs.$xRandomness = dVfs.$xRandomness;
+ opfsVfs.$xSleep = dVfs.$xSleep;
+ }
+ if(!opfsVfs.$xRandomness){
+ /* If the default VFS has no xRandomness(), add a basic JS impl... */
+ vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
+ const heap = wasm.heap8u();
+ let i = 0;
+ for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
+ return i;
+ };
+ }
+ if(!opfsVfs.$xSleep){
+ /* If we can inherit an xSleep() impl from the default VFS then
+ assume it's sane and use it, otherwise install a JS-based
+ one. */
+ vfsSyncWrappers.xSleep = function(pVfs,ms){
+ Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
+ return 0;
+ };
+ }
+
+ /**
+ Expects an OPFS file path. It gets resolved, such that ".."
+ components are properly expanded, and returned. If the 2nd arg
+ is true, the result is returned as an array of path elements,
+ else an absolute path string is returned.
+ */
+ opfsUtil.getResolvedPath = function(filename,splitIt){
+ const p = new URL(filename, "file://irrelevant").pathname;
+ return splitIt ? p.split('/').filter((v)=>!!v) : p;
+ };
+
+ /**
+ Takes the absolute path to a filesystem element. Returns an
+ array of [handleOfContainingDir, filename]. If the 2nd argument
+ is truthy then each directory element leading to the file is
+ created along the way. Throws if any creation or resolution
+ fails.
+ */
+ opfsUtil.getDirForFilename = async function f(absFilename, createDirs = false){
+ const path = opfsUtil.getResolvedPath(absFilename, true);
+ const filename = path.pop();
+ let dh = opfsUtil.rootDirectory;
+ for(const dirName of path){
+ if(dirName){
+ dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs});
+ }
+ }
+ return [dh, filename];
+ };
+
+ /**
+ Creates the given directory name, recursively, in
+ the OPFS filesystem. Returns true if it succeeds or the
+ directory already exists, else false.
+ */
+ opfsUtil.mkdir = async function(absDirName){
+ try {
+ await opfsUtil.getDirForFilename(absDirName+"/filepart", true);
+ return true;
+ }catch(e){
+ //sqlite3.config.warn("mkdir(",absDirName,") failed:",e);
+ return false;
+ }
+ };
+ /**
+ Checks whether the given OPFS filesystem entry exists,
+ returning true if it does, false if it doesn't.
+ */
+ opfsUtil.entryExists = async function(fsEntryName){
+ try {
+ const [dh, fn] = await opfsUtil.getDirForFilename(fsEntryName);
+ await dh.getFileHandle(fn);
+ return true;
+ }catch(e){
+ return false;
+ }
+ };
+
+ /**
+ Generates a random ASCII string, intended for use as a
+ temporary file name. Its argument is the length of the string,
+ defaulting to 16.
+ */
+ opfsUtil.randomFilename = randomFilename;
+
+ /**
+ Re-registers the OPFS VFS. This is intended only for odd use
+ cases which have to call sqlite3_shutdown() as part of their
+ initialization process, which will unregister the VFS
+ registered by installOpfsVfs(). If passed a truthy value, the
+ OPFS VFS is registered as the default VFS, else it is not made
+ the default. Returns the result of the the
+ sqlite3_vfs_register() call.
+
+ Design note: the problem of having to re-register things after
+ a shutdown/initialize pair is more general. How to best plug
+ that in to the library is unclear. In particular, we cannot
+ hook in to any C-side calls to sqlite3_initialize(), so we
+ cannot add an after-initialize callback mechanism.
+ */
+ opfsUtil.registerVfs = (asDefault=false)=>{
+ return wasm.exports.sqlite3_vfs_register(
+ opfsVfs.pointer, asDefault ? 1 : 0
+ );
+ };
+
+ /**
+ Returns a promise which resolves to an object which represents
+ all files and directories in the OPFS tree. The top-most object
+ has two properties: `dirs` is an array of directory entries
+ (described below) and `files` is a list of file names for all
+ files in that directory.
+
+ Traversal starts at sqlite3.opfs.rootDirectory.
+
+ Each `dirs` entry is an object in this form:
+
+ ```
+ { name: directoryName,
+ dirs: [...subdirs],
+ files: [...file names]
+ }
+ ```
+
+ The `files` and `subdirs` entries are always set but may be
+ empty arrays.
+
+ The returned object has the same structure but its `name` is
+ an empty string. All returned objects are created with
+ Object.create(null), so have no prototype.
+
+ Design note: the entries do not contain more information,
+ e.g. file sizes, because getting such info is not only
+ expensive but is subject to locking-related errors.
+ */
+ opfsUtil.treeList = async function(){
+ const doDir = async function callee(dirHandle,tgt){
+ tgt.name = dirHandle.name;
+ tgt.dirs = [];
+ tgt.files = [];
+ for await (const handle of dirHandle.values()){
+ if('directory' === handle.kind){
+ const subDir = Object.create(null);
+ tgt.dirs.push(subDir);
+ await callee(handle, subDir);
+ }else{
+ tgt.files.push(handle.name);
+ }
+ }
+ };
+ const root = Object.create(null);
+ await doDir(opfsUtil.rootDirectory, root);
+ return root;
+ };
+
+ /**
+ Irrevocably deletes _all_ files in the current origin's OPFS.
+ Obviously, this must be used with great caution. It may throw
+ an exception if removal of anything fails (e.g. a file is
+ locked), but the precise conditions under which the underlying
+ APIs will throw are not documented (so we cannot tell you what
+ they are).
+ */
+ opfsUtil.rmfr = async function(){
+ const dir = opfsUtil.rootDirectory, opt = {recurse: true};
+ for await (const handle of dir.values()){
+ dir.removeEntry(handle.name, opt);
+ }
+ };
+
+ /**
+ Deletes the given OPFS filesystem entry. As this environment
+ has no notion of "current directory", the given name must be an
+ absolute path. If the 2nd argument is truthy, deletion is
+ recursive (use with caution!).
+
+ The returned Promise resolves to true if the deletion was
+ successful, else false (but...). The OPFS API reports the
+ reason for the failure only in human-readable form, not
+ exceptions which can be type-checked to determine the
+ failure. Because of that...
+
+ If the final argument is truthy then this function will
+ propagate any exception on error, rather than returning false.
+ */
+ opfsUtil.unlink = async function(fsEntryName, recursive = false,
+ throwOnError = false){
+ try {
+ const [hDir, filenamePart] =
+ await opfsUtil.getDirForFilename(fsEntryName, false);
+ await hDir.removeEntry(filenamePart, {recursive});
+ return true;
+ }catch(e){
+ if(throwOnError){
+ throw new Error("unlink(",arguments[0],") failed: "+e.message,{
+ cause: e
+ });
+ }
+ return false;
+ }
+ };
+
+ /**
+ Traverses the OPFS filesystem, calling a callback for each one.
+ The argument may be either a callback function or an options object
+ with any of the following properties:
+
+ - `callback`: function which gets called for each filesystem
+ entry. It gets passed 3 arguments: 1) the
+ FileSystemFileHandle or FileSystemDirectoryHandle of each
+ entry (noting that both are instanceof FileSystemHandle). 2)
+ the FileSystemDirectoryHandle of the parent directory. 3) the
+ current depth level, with 0 being at the top of the tree
+ relative to the starting directory. If the callback returns a
+ literal false, as opposed to any other falsy value, traversal
+ stops without an error. Any exceptions it throws are
+ propagated. Results are undefined if the callback manipulate
+ the filesystem (e.g. removing or adding entries) because the
+ how OPFS iterators behave in the face of such changes is
+ undocumented.
+
+ - `recursive` [bool=true]: specifies whether to recurse into
+ subdirectories or not. Whether recursion is depth-first or
+ breadth-first is unspecified!
+
+ - `directory` [FileSystemDirectoryEntry=sqlite3.opfs.rootDirectory]
+ specifies the starting directory.
+
+ If this function is passed a function, it is assumed to be the
+ callback.
+
+ Returns a promise because it has to (by virtue of being async)
+ but that promise has no specific meaning: the traversal it
+ performs is synchronous. The promise must be used to catch any
+ exceptions propagated by the callback, however.
+
+ TODO: add an option which specifies whether to traverse
+ depth-first or breadth-first. We currently do depth-first but
+ an incremental file browsing widget would benefit more from
+ breadth-first.
+ */
+ opfsUtil.traverse = async function(opt){
+ const defaultOpt = {
+ recursive: true,
+ directory: opfsUtil.rootDirectory
+ };
+ if('function'===typeof opt){
+ opt = {callback:opt};
+ }
+ opt = Object.assign(defaultOpt, opt||{});
+ const doDir = async function callee(dirHandle, depth){
+ for await (const handle of dirHandle.values()){
+ if(false === opt.callback(handle, dirHandle, depth)) return false;
+ else if(opt.recursive && 'directory' === handle.kind){
+ if(false === await callee(handle, depth + 1)) break;
+ }
+ }
+ };
+ doDir(opt.directory, 0);
+ };
+
+ /**
+ impl of importDb() when it's given a function as its second
+ argument.
+ */
+ const importDbChunked = async function(filename, callback){
+ const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true);
+ const hFile = await hDir.getFileHandle(fnamePart, {create:true});
+ let sah = await hFile.createSyncAccessHandle();
+ let nWrote = 0, chunk, checkedHeader = false, err = false;
+ try{
+ sah.truncate(0);
+ while( undefined !== (chunk = await callback()) ){
+ if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk);
+ if( 0===nWrote && chunk.byteLength>=15 ){
+ util.affirmDbHeader(chunk);
+ checkedHeader = true;
+ }
+ sah.write(chunk, {at: nWrote});
+ nWrote += chunk.byteLength;
+ }
+ if( nWrote < 512 || 0!==nWrote % 512 ){
+ toss("Input size",nWrote,"is not correct for an SQLite database.");
+ }
+ if( !checkedHeader ){
+ const header = new Uint8Array(20);
+ sah.read( header, {at: 0} );
+ util.affirmDbHeader( header );
+ }
+ sah.write(new Uint8Array([1,1]), {at: 18}/*force db out of WAL mode*/);
+ return nWrote;
+ }catch(e){
+ await sah.close();
+ sah = undefined;
+ await hDir.removeEntry( fnamePart ).catch(()=>{});
+ throw e;
+ }finally {
+ if( sah ) await sah.close();
+ }
+ };
+
+ /**
+ Asynchronously imports the given bytes (a byte array or
+ ArrayBuffer) into the given database file.
+
+ If passed a function for its second argument, its behaviour
+ changes to async and it imports its data in chunks fed to it by
+ the given callback function. It calls the callback (which may
+ be async) repeatedly, expecting either a Uint8Array or
+ ArrayBuffer (to denote new input) or undefined (to denote
+ EOF). For so long as the callback continues to return
+ non-undefined, it will append incoming data to the given
+ VFS-hosted database file. When called this way, the resolved
+ value of the returned Promise is the number of bytes written to
+ the target file.
+
+ It very specifically requires the input to be an SQLite3
+ database and throws if that's not the case. It does so in
+ order to prevent this function from taking on a larger scope
+ than it is specifically intended to. i.e. we do not want it to
+ become a convenience for importing arbitrary files into OPFS.
+
+ This routine rewrites the database header bytes in the output
+ file (not the input array) to force disabling of WAL mode.
+
+ On error this throws and the state of the input file is
+ undefined (it depends on where the exception was triggered).
+
+ On success, resolves to the number of bytes written.
+ */
+ opfsUtil.importDb = async function(filename, bytes){
+ if( bytes instanceof Function ){
+ return importDbChunked(filename, bytes);
+ }
+ if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+ util.affirmIsDb(bytes);
+ const n = bytes.byteLength;
+ const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true);
+ let sah, err, nWrote = 0;
+ try {
+ const hFile = await hDir.getFileHandle(fnamePart, {create:true});
+ sah = await hFile.createSyncAccessHandle();
+ sah.truncate(0);
+ nWrote = sah.write(bytes, {at: 0});
+ if(nWrote != n){
+ toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
+ }
+ sah.write(new Uint8Array([1,1]), {at: 18}) /* force db out of WAL mode */;
+ return nWrote;
+ }catch(e){
+ if( sah ){ await sah.close(); sah = undefined; }
+ await hDir.removeEntry( fnamePart ).catch(()=>{});
+ throw e;
+ }finally{
+ if( sah ) await sah.close();
+ }
+ };
+
+ if(sqlite3.oo1){
+ const OpfsDb = function(...args){
+ const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
+ opt.vfs = opfsVfs.$zName;
+ sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
+ };
+ OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
+ sqlite3.oo1.OpfsDb = OpfsDb;
+ OpfsDb.importDb = opfsUtil.importDb;
+ sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
+ opfsVfs.pointer,
+ function(oo1Db, sqlite3){
+ /* Set a relatively high default busy-timeout handler to
+ help OPFS dbs deal with multi-tab/multi-worker
+ contention. */
+ sqlite3.capi.sqlite3_busy_timeout(oo1Db, 10000);
+ sqlite3.capi.sqlite3_exec(oo1Db, [
+ /* As of July 2023, the PERSIST journal mode on OPFS is
+ somewhat slower than DELETE or TRUNCATE (it was faster
+ before Chrome version 108 or 109). TRUNCATE and DELETE
+ have very similar performance on OPFS.
+
+ Roy Hashimoto notes that TRUNCATE and PERSIST modes may
+ decrease OPFS concurrency because multiple connections
+ can open the journal file in those modes:
+
+ https://github.com/rhashimoto/wa-sqlite/issues/68
+
+ Given that, and the fact that testing has not revealed
+ any appreciable difference between performance of
+ TRUNCATE and DELETE modes on OPFS, we currently (as of
+ 2023-07-13) default to DELETE mode.
+ */
+ "pragma journal_mode=DELETE;",
+ /*
+ This vfs benefits hugely from cache on moderate/large
+ speedtest1 --size 50 and --size 100 workloads. We
+ currently rely on setting a non-default cache size when
+ building sqlite3.wasm. If that policy changes, the cache
+ can be set here.
+ */
+ "pragma cache_size=-16384;"
+ ], 0, 0, 0);
+ }
+ );
+ }/*extend sqlite3.oo1*/
+
+ const sanityCheck = function(){
+ const scope = wasm.scopedAllocPush();
+ const sq3File = new sqlite3_file();
+ try{
+ const fid = sq3File.pointer;
+ const openFlags = capi.SQLITE_OPEN_CREATE
+ | capi.SQLITE_OPEN_READWRITE
+ //| capi.SQLITE_OPEN_DELETEONCLOSE
+ | capi.SQLITE_OPEN_MAIN_DB;
+ const pOut = wasm.scopedAlloc(8);
+ const dbFile = "/sanity/check/file"+randomFilename(8);
+ const zDbFile = wasm.scopedAllocCString(dbFile);
+ let rc;
+ state.s11n.serialize("This is ä string.");
+ rc = state.s11n.deserialize();
+ log("deserialize() says:",rc);
+ if("This is ä string."!==rc[0]) toss("String d13n error.");
+ vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
+ rc = wasm.peek(pOut,'i32');
+ log("xAccess(",dbFile,") exists ?=",rc);
+ rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
+ fid, openFlags, pOut);
+ log("open rc =",rc,"state.sabOPView[xOpen] =",
+ state.sabOPView[state.opIds.xOpen]);
+ if(0!==rc){
+ error("open failed with code",rc);
+ return;
+ }
+ vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
+ rc = wasm.peek(pOut,'i32');
+ if(!rc) toss("xAccess() failed to detect file.");
+ rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
+ if(rc) toss('sync failed w/ rc',rc);
+ rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
+ if(rc) toss('truncate failed w/ rc',rc);
+ wasm.poke(pOut,0,'i64');
+ rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
+ if(rc) toss('xFileSize failed w/ rc',rc);
+ log("xFileSize says:",wasm.peek(pOut, 'i64'));
+ rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
+ if(rc) toss("xWrite() failed!");
+ const readBuf = wasm.scopedAlloc(16);
+ rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
+ wasm.poke(readBuf+6,0);
+ let jRead = wasm.cstrToJs(readBuf);
+ log("xRead() got:",jRead);
+ if("sanity"!==jRead) toss("Unexpected xRead() value.");
+ if(vfsSyncWrappers.xSleep){
+ log("xSleep()ing before close()ing...");
+ vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
+ log("waking up from xSleep()");
+ }
+ rc = ioSyncWrappers.xClose(fid);
+ log("xClose rc =",rc,"sabOPView =",state.sabOPView);
+ log("Deleting file:",dbFile);
+ vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
+ vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
+ rc = wasm.peek(pOut,'i32');
+ if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
+ warn("End of OPFS sanity checks.");
+ }finally{
+ sq3File.dispose();
+ wasm.scopedAllocPop(scope);
+ }
+ }/*sanityCheck()*/;
+
+ W.onmessage = function({data}){
+ //log("Worker.onmessage:",data);
+ switch(data.type){
+ case 'opfs-unavailable':
+ /* Async proxy has determined that OPFS is unavailable. There's
+ nothing more for us to do here. */
+ promiseReject(new Error(data.payload.join(' ')));
+ break;
+ case 'opfs-async-loaded':
+ /* Arrives as soon as the asyc proxy finishes loading.
+ Pass our config and shared state on to the async
+ worker. */
+ W.postMessage({type: 'opfs-async-init',args: state});
+ break;
+ case 'opfs-async-inited': {
+ /* Indicates that the async partner has received the 'init'
+ and has finished initializing, so the real work can
+ begin... */
+ if(true===promiseWasRejected){
+ break /* promise was already rejected via timer */;
+ }
+ try {
+ sqlite3.vfs.installVfs({
+ io: {struct: opfsIoMethods, methods: ioSyncWrappers},
+ vfs: {struct: opfsVfs, methods: vfsSyncWrappers}
+ });
+ state.sabOPView = new Int32Array(state.sabOP);
+ state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
+ state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
+ initS11n();
+ if(options.sanityChecks){
+ warn("Running sanity checks because of opfs-sanity-check URL arg...");
+ sanityCheck();
+ }
+ if(thisThreadHasOPFS()){
+ navigator.storage.getDirectory().then((d)=>{
+ W.onerror = W._originalOnError;
+ delete W._originalOnError;
+ sqlite3.opfs = opfsUtil;
+ opfsUtil.rootDirectory = d;
+ log("End of OPFS sqlite3_vfs setup.", opfsVfs);
+ promiseResolve();
+ }).catch(promiseReject);
+ }else{
+ promiseResolve();
+ }
+ }catch(e){
+ error(e);
+ promiseReject(e);
+ }
+ break;
+ }
+ default: {
+ const errMsg = (
+ "Unexpected message from the OPFS async worker: " +
+ JSON.stringify(data)
+ );
+ error(errMsg);
+ promiseReject(new Error(errMsg));
+ break;
+ }
+ }/*switch(data.type)*/
+ }/*W.onmessage()*/;
+ })/*thePromise*/;
+ return thePromise;
+}/*installOpfsVfs()*/;
+installOpfsVfs.defaultProxyUri =
+ "sqlite3-opfs-async-proxy.js";
+globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
+ try{
+ let proxyJs = installOpfsVfs.defaultProxyUri;
+ if(sqlite3.scriptInfo.sqlite3Dir){
+ installOpfsVfs.defaultProxyUri =
+ sqlite3.scriptInfo.sqlite3Dir + proxyJs;
+ //sqlite3.config.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri);
+ }
+ return installOpfsVfs().catch((e)=>{
+ sqlite3.config.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message);
+ });
+ }catch(e){
+ sqlite3.config.error("installOpfsVfs() exception:",e);
+ return Promise.reject(e);
+ }
+});
+}/*sqlite3ApiBootstrap.initializers.push()*/);
+//#else
+/* The OPFS VFS parts are elided from builds targeting node.js. */
+//#endif target=node
diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c
new file mode 100644
index 0000000..618d0f0
--- /dev/null
+++ b/ext/wasm/api/sqlite3-wasm.c
@@ -0,0 +1,1928 @@
+/*
+** This file requires access to sqlite3.c static state in order to
+** implement certain WASM-specific features, and thus directly
+** includes that file. Unlike the rest of sqlite3.c, this file
+** requires compiling with -std=c99 (or equivalent, or a later C
+** version) because it makes use of features not available in C89.
+**
+** At its simplest, to build sqlite3.wasm either place this file
+** in the same directory as sqlite3.c/h before compilation or use the
+** -I/path flag to tell the compiler where to find both of those
+** files, then compile this file. For example:
+**
+** emcc -o sqlite3.wasm ... -I/path/to/sqlite3-c-and-h sqlite3-wasm.c
+*/
+#define SQLITE_WASM
+#ifdef SQLITE_WASM_ENABLE_C_TESTS
+/*
+** Code blocked off by SQLITE_WASM_TESTS is intended solely for use in
+** unit/regression testing. They may be safely omitted from
+** client-side builds. The main unit test script, tester1.js, will
+** skip related tests if it doesn't find the corresponding functions
+** in the WASM exports.
+*/
+# define SQLITE_WASM_TESTS 1
+#else
+# define SQLITE_WASM_TESTS 0
+#endif
+
+/*
+** Threading and file locking: JS is single-threaded. Each Worker
+** thread is a separate instance of the JS engine so can never access
+** the same db handle as another thread, thus multi-threading support
+** is unnecessary in the library. Because the filesystems are virtual
+** and local to a given wasm runtime instance, two Workers can never
+** access the same db file at once, with the exception of OPFS.
+**
+** Summary: except for the case of OPFS, which supports locking using
+** its own API, threading and file locking support are unnecessary in
+** the wasm build.
+*/
+
+/*
+** Undefine any SQLITE_... config flags which we specifically do not
+** want defined. Please keep these alphabetized.
+*/
+#undef SQLITE_OMIT_DESERIALIZE
+#undef SQLITE_OMIT_MEMORYDB
+
+/*
+** Define any SQLITE_... config defaults we want if they aren't
+** overridden by the builder. Please keep these alphabetized.
+*/
+
+/**********************************************************************/
+/* SQLITE_D... */
+#ifndef SQLITE_DEFAULT_CACHE_SIZE
+/*
+** The OPFS impls benefit tremendously from an increased cache size
+** when working on large workloads, e.g. speedtest1 --size 50 or
+** higher. On smaller workloads, e.g. speedtest1 --size 25, they
+** clearly benefit from having 4mb of cache, but not as much as a
+** larger cache benefits the larger workloads. Speed differences
+** between 2x and nearly 3x have been measured with ample page cache.
+*/
+# define SQLITE_DEFAULT_CACHE_SIZE -16384
+#endif
+#if !defined(SQLITE_DEFAULT_PAGE_SIZE)
+/*
+** OPFS performance is improved by approx. 12% with a page size of 8kb
+** instead of 4kb. Performance with 16kb is equivalent to 8kb.
+**
+** Performance difference of kvvfs with a page size of 8kb compared to
+** 4kb, as measured by speedtest1 --size 4, is indeterminate:
+** measurements are all over the place either way and not
+** significantly different.
+*/
+# define SQLITE_DEFAULT_PAGE_SIZE 8192
+#endif
+#ifndef SQLITE_DEFAULT_UNIX_VFS
+# define SQLITE_DEFAULT_UNIX_VFS "unix-none"
+#endif
+#undef SQLITE_DQS
+#define SQLITE_DQS 0
+
+/**********************************************************************/
+/* SQLITE_ENABLE_... */
+/*
+** Unconditionally enable API_ARMOR in the WASM build. It ensures that
+** public APIs behave predictable in the face of passing illegal NULLs
+** or ranges which might otherwise invoke undefined behavior.
+*/
+#undef SQLITE_ENABLE_API_ARMOR
+#define SQLITE_ENABLE_API_ARMOR 1
+
+#ifndef SQLITE_ENABLE_BYTECODE_VTAB
+# define SQLITE_ENABLE_BYTECODE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBPAGE_VTAB
+# define SQLITE_ENABLE_DBPAGE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBSTAT_VTAB
+# define SQLITE_ENABLE_DBSTAT_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS
+# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1
+#endif
+#ifndef SQLITE_ENABLE_FTS4
+# define SQLITE_ENABLE_FTS4 1
+#endif
+#ifndef SQLITE_ENABLE_MATH_FUNCTIONS
+# define SQLITE_ENABLE_MATH_FUNCTIONS 1
+#endif
+#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
+# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
+#endif
+#ifndef SQLITE_ENABLE_PREUPDATE_HOOK
+# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/
+#endif
+#ifndef SQLITE_ENABLE_RTREE
+# define SQLITE_ENABLE_RTREE 1
+#endif
+#ifndef SQLITE_ENABLE_SESSION
+# define SQLITE_ENABLE_SESSION 1
+#endif
+#ifndef SQLITE_ENABLE_STMTVTAB
+# define SQLITE_ENABLE_STMTVTAB 1
+#endif
+#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+#endif
+
+/**********************************************************************/
+/* SQLITE_O... */
+#ifndef SQLITE_OMIT_DEPRECATED
+# define SQLITE_OMIT_DEPRECATED 1
+#endif
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+# define SQLITE_OMIT_LOAD_EXTENSION 1
+#endif
+#ifndef SQLITE_OMIT_SHARED_CACHE
+# define SQLITE_OMIT_SHARED_CACHE 1
+#endif
+#ifndef SQLITE_OMIT_UTF16
+# define SQLITE_OMIT_UTF16 1
+#endif
+#ifndef SQLITE_OS_KV_OPTIONAL
+# define SQLITE_OS_KV_OPTIONAL 1
+#endif
+
+/**********************************************************************/
+/* SQLITE_S... */
+#ifndef SQLITE_STRICT_SUBTYPE
+# define SQLITE_STRICT_SUBTYPE 1
+#endif
+
+/**********************************************************************/
+/* SQLITE_T... */
+#ifndef SQLITE_TEMP_STORE
+# define SQLITE_TEMP_STORE 2
+#endif
+#ifndef SQLITE_THREADSAFE
+# define SQLITE_THREADSAFE 0
+#endif
+
+/**********************************************************************/
+/* SQLITE_USE_... */
+#ifndef SQLITE_USE_URI
+# define SQLITE_USE_URI 1
+#endif
+
+#ifdef SQLITE_WASM_EXTRA_INIT
+# define SQLITE_EXTRA_INIT sqlite3_wasm_extra_init
+#endif
+
+#include <assert.h>
+
+/*
+** SQLITE_WASM_EXPORT is functionally identical to EMSCRIPTEN_KEEPALIVE
+** but is not Emscripten-specific. It explicitly marks functions for
+** export into the target wasm file without requiring explicit listing
+** of those functions in Emscripten's -sEXPORTED_FUNCTIONS=... list
+** (or equivalent in other build platforms). Any function with neither
+** this attribute nor which is listed as an explicit export will not
+** be exported from the wasm file (but may still be used internally
+** within the wasm file).
+**
+** The functions in this file (sqlite3-wasm.c) which require exporting
+** are marked with this flag. They may also be added to any explicit
+** build-time export list but need not be. All of these APIs are
+** intended for use only within the project's own JS/WASM code, and
+** not by client code, so an argument can be made for reducing their
+** visibility by not including them in any build-time export lists.
+**
+** 2022-09-11: it's not yet _proven_ that this approach works in
+** non-Emscripten builds. If not, such builds will need to export
+** those using the --export=... wasm-ld flag (or equivalent). As of
+** this writing we are tied to Emscripten for various reasons
+** and cannot test the library with other build environments.
+*/
+#define SQLITE_WASM_EXPORT __attribute__((used,visibility("default")))
+// See also:
+//__attribute__((export_name("theExportedName"), used, visibility("default")))
+
+/*
+** Which sqlite3.c we're using needs to be configurable to enable
+** building against a custom copy, e.g. the SEE variant. Note that we
+** #include the .c file, rather than the header, so that the WASM
+** extensions have access to private API internals.
+**
+** The caveat here is that custom variants need to account for
+** exporting any necessary symbols (e.g. sqlite3_activate_see()). We
+** cannot export them from here using SQLITE_WASM_EXPORT because that
+** attribute (apparently) has to be part of the function definition.
+*/
+#ifndef SQLITE_C
+# define SQLITE_C sqlite3.c /* yes, .c instead of .h. */
+#endif
+#define INC__STRINGIFY_(f) #f
+#define INC__STRINGIFY(f) INC__STRINGIFY_(f)
+#include INC__STRINGIFY(SQLITE_C)
+#undef INC__STRINGIFY_
+#undef INC__STRINGIFY
+#undef SQLITE_C
+
+#if defined(__EMSCRIPTEN__)
+# include <emscripten/console.h>
+#endif
+
+#if 0
+/*
+** An EXPERIMENT in implementing a stack-based allocator analog to
+** Emscripten's stackSave(), stackAlloc(), stackRestore().
+** Unfortunately, this cannot work together with Emscripten because
+** Emscripten defines its own native one and we'd stomp on each
+** other's memory. Other than that complication, basic tests show it
+** to work just fine.
+**
+** Another option is to malloc() a chunk of our own and call that our
+** "stack".
+*/
+SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_end(void){
+ extern void __heap_base
+ /* see https://stackoverflow.com/questions/10038964 */;
+ return &__heap_base;
+}
+SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_begin(void){
+ extern void __data_end;
+ return &__data_end;
+}
+static void * pWasmStackPtr = 0;
+SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_ptr(void){
+ if(!pWasmStackPtr) pWasmStackPtr = sqlite3_wasm_stack_end();
+ return pWasmStackPtr;
+}
+SQLITE_WASM_EXPORT void sqlite3_wasm_stack_restore(void * p){
+ pWasmStackPtr = p;
+}
+SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_alloc(int n){
+ if(n<=0) return 0;
+ n = (n + 7) & ~7 /* align to 8-byte boundary */;
+ unsigned char * const p = (unsigned char *)sqlite3_wasm_stack_ptr();
+ unsigned const char * const b = (unsigned const char *)sqlite3_wasm_stack_begin();
+ if(b + n >= p || b + n < b/*overflow*/) return 0;
+ return pWasmStackPtr = p - n;
+}
+#endif /* stack allocator experiment */
+
+/*
+** State for the "pseudo-stack" allocator implemented in
+** sqlite3_wasm_pstack_xyz(). In order to avoid colliding with
+** Emscripten-controled stack space, it carves out a bit of stack
+** memory to use for that purpose. This memory ends up in the
+** WASM-managed memory, such that routines which manipulate the wasm
+** heap can also be used to manipulate this memory.
+**
+** This particular allocator is intended for small allocations such as
+** storage for output pointers. We cannot reasonably size it large
+** enough for general-purpose string conversions because some of our
+** tests use input files (strings) of 16MB+.
+*/
+static unsigned char PStack_mem[512 * 8] = {0};
+static struct {
+ unsigned const char * const pBegin;/* Start (inclusive) of memory */
+ unsigned const char * const pEnd; /* One-after-the-end of memory */
+ unsigned char * pPos; /* Current stack pointer */
+} PStack = {
+ &PStack_mem[0],
+ &PStack_mem[0] + sizeof(PStack_mem),
+ &PStack_mem[0] + sizeof(PStack_mem)
+};
+/*
+** Returns the current pstack position.
+*/
+SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_ptr(void){
+ return PStack.pPos;
+}
+/*
+** Sets the pstack position poitner to p. Results are undefined if the
+** given value did not come from sqlite3_wasm_pstack_ptr().
+*/
+SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){
+ assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos);
+ assert(0==((unsigned long long)p & 0x7));
+ if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){
+ PStack.pPos = p;
+ }
+}
+/*
+** Allocate and zero out n bytes from the pstack. Returns a pointer to
+** the memory on success, 0 on error (including a negative n value). n
+** is always adjusted to be a multiple of 8 and returned memory is
+** always zeroed out before returning (because this keeps the client
+** JS code from having to do so, and most uses of the pstack will
+** call for doing so).
+*/
+SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_alloc(int n){
+ if( n<=0 ) return 0;
+ //if( n & 0x7 ) n += 8 - (n & 0x7) /* align to 8-byte boundary */;
+ n = (n + 7) & ~7 /* align to 8-byte boundary */;
+ if( PStack.pBegin + n > PStack.pPos /*not enough space left*/
+ || PStack.pBegin + n <= PStack.pBegin /*overflow*/ ) return 0;
+ memset((PStack.pPos = PStack.pPos - n), 0, (unsigned int)n);
+ return PStack.pPos;
+}
+/*
+** Return the number of bytes left which can be
+** sqlite3_wasm_pstack_alloc()'d.
+*/
+SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_remaining(void){
+ assert(PStack.pPos >= PStack.pBegin);
+ assert(PStack.pPos <= PStack.pEnd);
+ return (int)(PStack.pPos - PStack.pBegin);
+}
+
+/*
+** Return the total number of bytes available in the pstack, including
+** any space which is currently allocated. This value is a
+** compile-time constant.
+*/
+SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_quota(void){
+ return (int)(PStack.pEnd - PStack.pBegin);
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** For purposes of certain hand-crafted C/Wasm function bindings, we
+** need a way of reporting errors which is consistent with the rest of
+** the C API, as opposed to throwing JS exceptions. To that end, this
+** internal-use-only function is a thin proxy around
+** sqlite3ErrorWithMessage(). The intent is that it only be used from
+** Wasm bindings such as sqlite3_prepare_v2/v3(), and definitely not
+** from client code.
+**
+** Returns err_code.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){
+ if( db!=0 ){
+ if( 0!=zMsg ){
+ const int nMsg = sqlite3Strlen30(zMsg);
+ sqlite3_mutex_enter(sqlite3_db_mutex(db));
+ sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
+ sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ }else{
+ sqlite3ErrorWithMsg(db, err_code, NULL);
+ }
+ }
+ return err_code;
+}
+
+#if SQLITE_WASM_TESTS
+struct WasmTestStruct {
+ int v4;
+ void * ppV;
+ const char * cstr;
+ int64_t v8;
+ void (*xFunc)(void*);
+};
+typedef struct WasmTestStruct WasmTestStruct;
+SQLITE_WASM_EXPORT
+void sqlite3_wasm_test_struct(WasmTestStruct * s){
+ if(s){
+ s->v4 *= 2;
+ s->v8 = s->v4 * 2;
+ s->ppV = s;
+ s->cstr = __FILE__;
+ if(s->xFunc) s->xFunc(s);
+ }
+ return;
+}
+#endif /* SQLITE_WASM_TESTS */
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings. Unlike the
+** rest of the sqlite3 API, this part requires C99 for snprintf() and
+** variadic macros.
+**
+** Returns a string containing a JSON-format "enum" of C-level
+** constants and struct-related metadata intended to be imported into
+** the JS environment. The JSON is initialized the first time this
+** function is called and that result is reused for all future calls.
+**
+** If this function returns NULL then it means that the internal
+** buffer is not large enough for the generated JSON and needs to be
+** increased. In debug builds that will trigger an assert().
+*/
+SQLITE_WASM_EXPORT
+const char * sqlite3_wasm_enum_json(void){
+ static char aBuffer[1024 * 20] = {0} /* where the JSON goes */;
+ int n = 0, nChildren = 0, nStruct = 0
+ /* output counters for figuring out where commas go */;
+ char * zPos = &aBuffer[1] /* skip first byte for now to help protect
+ ** against a small race condition */;
+ char const * const zEnd = &aBuffer[0] + sizeof(aBuffer) /* one-past-the-end */;
+ if(aBuffer[0]) return aBuffer;
+ /* Leave aBuffer[0] at 0 until the end to help guard against a tiny
+ ** race condition. If this is called twice concurrently, they might
+ ** end up both writing to aBuffer, but they'll both write the same
+ ** thing, so that's okay. If we set byte 0 up front then the 2nd
+ ** instance might return and use the string before the 1st instance
+ ** is done filling it. */
+
+/* Core output macros... */
+#define lenCheck assert(zPos < zEnd - 128 \
+ && "sqlite3_wasm_enum_json() buffer is too small."); \
+ if( zPos >= zEnd - 128 ) return 0
+#define outf(format,...) \
+ zPos += snprintf(zPos, ((size_t)(zEnd - zPos)), format, __VA_ARGS__); \
+ lenCheck
+#define out(TXT) outf("%s",TXT)
+#define CloseBrace(LEVEL) \
+ assert(LEVEL<5); memset(zPos, '}', LEVEL); zPos+=LEVEL; lenCheck
+
+/* Macros for emitting maps of integer- and string-type macros to
+** their values. */
+#define DefGroup(KEY) n = 0; \
+ outf("%s\"" #KEY "\": {",(nChildren++ ? "," : ""));
+#define DefInt(KEY) \
+ outf("%s\"%s\": %d", (n++ ? ", " : ""), #KEY, (int)KEY)
+#define DefStr(KEY) \
+ outf("%s\"%s\": \"%s\"", (n++ ? ", " : ""), #KEY, KEY)
+#define _DefGroup CloseBrace(1)
+
+ /* The following groups are sorted alphabetic by group name. */
+ DefGroup(access){
+ DefInt(SQLITE_ACCESS_EXISTS);
+ DefInt(SQLITE_ACCESS_READWRITE);
+ DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/;
+ } _DefGroup;
+
+ DefGroup(authorizer){
+ DefInt(SQLITE_DENY);
+ DefInt(SQLITE_IGNORE);
+ DefInt(SQLITE_CREATE_INDEX);
+ DefInt(SQLITE_CREATE_TABLE);
+ DefInt(SQLITE_CREATE_TEMP_INDEX);
+ DefInt(SQLITE_CREATE_TEMP_TABLE);
+ DefInt(SQLITE_CREATE_TEMP_TRIGGER);
+ DefInt(SQLITE_CREATE_TEMP_VIEW);
+ DefInt(SQLITE_CREATE_TRIGGER);
+ DefInt(SQLITE_CREATE_VIEW);
+ DefInt(SQLITE_DELETE);
+ DefInt(SQLITE_DROP_INDEX);
+ DefInt(SQLITE_DROP_TABLE);
+ DefInt(SQLITE_DROP_TEMP_INDEX);
+ DefInt(SQLITE_DROP_TEMP_TABLE);
+ DefInt(SQLITE_DROP_TEMP_TRIGGER);
+ DefInt(SQLITE_DROP_TEMP_VIEW);
+ DefInt(SQLITE_DROP_TRIGGER);
+ DefInt(SQLITE_DROP_VIEW);
+ DefInt(SQLITE_INSERT);
+ DefInt(SQLITE_PRAGMA);
+ DefInt(SQLITE_READ);
+ DefInt(SQLITE_SELECT);
+ DefInt(SQLITE_TRANSACTION);
+ DefInt(SQLITE_UPDATE);
+ DefInt(SQLITE_ATTACH);
+ DefInt(SQLITE_DETACH);
+ DefInt(SQLITE_ALTER_TABLE);
+ DefInt(SQLITE_REINDEX);
+ DefInt(SQLITE_ANALYZE);
+ DefInt(SQLITE_CREATE_VTABLE);
+ DefInt(SQLITE_DROP_VTABLE);
+ DefInt(SQLITE_FUNCTION);
+ DefInt(SQLITE_SAVEPOINT);
+ //DefInt(SQLITE_COPY) /* No longer used */;
+ DefInt(SQLITE_RECURSIVE);
+ } _DefGroup;
+
+ DefGroup(blobFinalizers) {
+ /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as
+ ** integers to avoid casting-related warnings. */
+ out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1");
+ outf(",\"SQLITE_WASM_DEALLOC\": %lld",
+ (sqlite3_int64)(sqlite3_free));
+ } _DefGroup;
+
+ DefGroup(changeset){
+ DefInt(SQLITE_CHANGESETSTART_INVERT);
+ DefInt(SQLITE_CHANGESETAPPLY_NOSAVEPOINT);
+ DefInt(SQLITE_CHANGESETAPPLY_INVERT);
+ DefInt(SQLITE_CHANGESETAPPLY_IGNORENOOP);
+
+ DefInt(SQLITE_CHANGESET_DATA);
+ DefInt(SQLITE_CHANGESET_NOTFOUND);
+ DefInt(SQLITE_CHANGESET_CONFLICT);
+ DefInt(SQLITE_CHANGESET_CONSTRAINT);
+ DefInt(SQLITE_CHANGESET_FOREIGN_KEY);
+
+ DefInt(SQLITE_CHANGESET_OMIT);
+ DefInt(SQLITE_CHANGESET_REPLACE);
+ DefInt(SQLITE_CHANGESET_ABORT);
+ } _DefGroup;
+
+ DefGroup(config){
+ DefInt(SQLITE_CONFIG_SINGLETHREAD);
+ DefInt(SQLITE_CONFIG_MULTITHREAD);
+ DefInt(SQLITE_CONFIG_SERIALIZED);
+ DefInt(SQLITE_CONFIG_MALLOC);
+ DefInt(SQLITE_CONFIG_GETMALLOC);
+ DefInt(SQLITE_CONFIG_SCRATCH);
+ DefInt(SQLITE_CONFIG_PAGECACHE);
+ DefInt(SQLITE_CONFIG_HEAP);
+ DefInt(SQLITE_CONFIG_MEMSTATUS);
+ DefInt(SQLITE_CONFIG_MUTEX);
+ DefInt(SQLITE_CONFIG_GETMUTEX);
+/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
+ DefInt(SQLITE_CONFIG_LOOKASIDE);
+ DefInt(SQLITE_CONFIG_PCACHE);
+ DefInt(SQLITE_CONFIG_GETPCACHE);
+ DefInt(SQLITE_CONFIG_LOG);
+ DefInt(SQLITE_CONFIG_URI);
+ DefInt(SQLITE_CONFIG_PCACHE2);
+ DefInt(SQLITE_CONFIG_GETPCACHE2);
+ DefInt(SQLITE_CONFIG_COVERING_INDEX_SCAN);
+ DefInt(SQLITE_CONFIG_SQLLOG);
+ DefInt(SQLITE_CONFIG_MMAP_SIZE);
+ DefInt(SQLITE_CONFIG_WIN32_HEAPSIZE);
+ DefInt(SQLITE_CONFIG_PCACHE_HDRSZ);
+ DefInt(SQLITE_CONFIG_PMASZ);
+ DefInt(SQLITE_CONFIG_STMTJRNL_SPILL);
+ DefInt(SQLITE_CONFIG_SMALL_MALLOC);
+ DefInt(SQLITE_CONFIG_SORTERREF_SIZE);
+ DefInt(SQLITE_CONFIG_MEMDB_MAXSIZE);
+ } _DefGroup;
+
+ DefGroup(dataTypes) {
+ DefInt(SQLITE_INTEGER);
+ DefInt(SQLITE_FLOAT);
+ DefInt(SQLITE_TEXT);
+ DefInt(SQLITE_BLOB);
+ DefInt(SQLITE_NULL);
+ } _DefGroup;
+
+ DefGroup(dbConfig){
+ DefInt(SQLITE_DBCONFIG_MAINDBNAME);
+ DefInt(SQLITE_DBCONFIG_LOOKASIDE);
+ DefInt(SQLITE_DBCONFIG_ENABLE_FKEY);
+ DefInt(SQLITE_DBCONFIG_ENABLE_TRIGGER);
+ DefInt(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER);
+ DefInt(SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION);
+ DefInt(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE);
+ DefInt(SQLITE_DBCONFIG_ENABLE_QPSG);
+ DefInt(SQLITE_DBCONFIG_TRIGGER_EQP);
+ DefInt(SQLITE_DBCONFIG_RESET_DATABASE);
+ DefInt(SQLITE_DBCONFIG_DEFENSIVE);
+ DefInt(SQLITE_DBCONFIG_WRITABLE_SCHEMA);
+ DefInt(SQLITE_DBCONFIG_LEGACY_ALTER_TABLE);
+ DefInt(SQLITE_DBCONFIG_DQS_DML);
+ DefInt(SQLITE_DBCONFIG_DQS_DDL);
+ DefInt(SQLITE_DBCONFIG_ENABLE_VIEW);
+ DefInt(SQLITE_DBCONFIG_LEGACY_FILE_FORMAT);
+ DefInt(SQLITE_DBCONFIG_TRUSTED_SCHEMA);
+ DefInt(SQLITE_DBCONFIG_STMT_SCANSTATUS);
+ DefInt(SQLITE_DBCONFIG_REVERSE_SCANORDER);
+ DefInt(SQLITE_DBCONFIG_MAX);
+ } _DefGroup;
+
+ DefGroup(dbStatus){
+ DefInt(SQLITE_DBSTATUS_LOOKASIDE_USED);
+ DefInt(SQLITE_DBSTATUS_CACHE_USED);
+ DefInt(SQLITE_DBSTATUS_SCHEMA_USED);
+ DefInt(SQLITE_DBSTATUS_STMT_USED);
+ DefInt(SQLITE_DBSTATUS_LOOKASIDE_HIT);
+ DefInt(SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE);
+ DefInt(SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL);
+ DefInt(SQLITE_DBSTATUS_CACHE_HIT);
+ DefInt(SQLITE_DBSTATUS_CACHE_MISS);
+ DefInt(SQLITE_DBSTATUS_CACHE_WRITE);
+ DefInt(SQLITE_DBSTATUS_DEFERRED_FKS);
+ DefInt(SQLITE_DBSTATUS_CACHE_USED_SHARED);
+ DefInt(SQLITE_DBSTATUS_CACHE_SPILL);
+ DefInt(SQLITE_DBSTATUS_MAX);
+ } _DefGroup;
+
+ DefGroup(encodings) {
+ /* Noting that the wasm binding only aims to support UTF-8. */
+ DefInt(SQLITE_UTF8);
+ DefInt(SQLITE_UTF16LE);
+ DefInt(SQLITE_UTF16BE);
+ DefInt(SQLITE_UTF16);
+ /*deprecated DefInt(SQLITE_ANY); */
+ DefInt(SQLITE_UTF16_ALIGNED);
+ } _DefGroup;
+
+ DefGroup(fcntl) {
+ DefInt(SQLITE_FCNTL_LOCKSTATE);
+ DefInt(SQLITE_FCNTL_GET_LOCKPROXYFILE);
+ DefInt(SQLITE_FCNTL_SET_LOCKPROXYFILE);
+ DefInt(SQLITE_FCNTL_LAST_ERRNO);
+ DefInt(SQLITE_FCNTL_SIZE_HINT);
+ DefInt(SQLITE_FCNTL_CHUNK_SIZE);
+ DefInt(SQLITE_FCNTL_FILE_POINTER);
+ DefInt(SQLITE_FCNTL_SYNC_OMITTED);
+ DefInt(SQLITE_FCNTL_WIN32_AV_RETRY);
+ DefInt(SQLITE_FCNTL_PERSIST_WAL);
+ DefInt(SQLITE_FCNTL_OVERWRITE);
+ DefInt(SQLITE_FCNTL_VFSNAME);
+ DefInt(SQLITE_FCNTL_POWERSAFE_OVERWRITE);
+ DefInt(SQLITE_FCNTL_PRAGMA);
+ DefInt(SQLITE_FCNTL_BUSYHANDLER);
+ DefInt(SQLITE_FCNTL_TEMPFILENAME);
+ DefInt(SQLITE_FCNTL_MMAP_SIZE);
+ DefInt(SQLITE_FCNTL_TRACE);
+ DefInt(SQLITE_FCNTL_HAS_MOVED);
+ DefInt(SQLITE_FCNTL_SYNC);
+ DefInt(SQLITE_FCNTL_COMMIT_PHASETWO);
+ DefInt(SQLITE_FCNTL_WIN32_SET_HANDLE);
+ DefInt(SQLITE_FCNTL_WAL_BLOCK);
+ DefInt(SQLITE_FCNTL_ZIPVFS);
+ DefInt(SQLITE_FCNTL_RBU);
+ DefInt(SQLITE_FCNTL_VFS_POINTER);
+ DefInt(SQLITE_FCNTL_JOURNAL_POINTER);
+ DefInt(SQLITE_FCNTL_WIN32_GET_HANDLE);
+ DefInt(SQLITE_FCNTL_PDB);
+ DefInt(SQLITE_FCNTL_BEGIN_ATOMIC_WRITE);
+ DefInt(SQLITE_FCNTL_COMMIT_ATOMIC_WRITE);
+ DefInt(SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE);
+ DefInt(SQLITE_FCNTL_LOCK_TIMEOUT);
+ DefInt(SQLITE_FCNTL_DATA_VERSION);
+ DefInt(SQLITE_FCNTL_SIZE_LIMIT);
+ DefInt(SQLITE_FCNTL_CKPT_DONE);
+ DefInt(SQLITE_FCNTL_RESERVE_BYTES);
+ DefInt(SQLITE_FCNTL_CKPT_START);
+ DefInt(SQLITE_FCNTL_EXTERNAL_READER);
+ DefInt(SQLITE_FCNTL_CKSM_FILE);
+ DefInt(SQLITE_FCNTL_RESET_CACHE);
+ } _DefGroup;
+
+ DefGroup(flock) {
+ DefInt(SQLITE_LOCK_NONE);
+ DefInt(SQLITE_LOCK_SHARED);
+ DefInt(SQLITE_LOCK_RESERVED);
+ DefInt(SQLITE_LOCK_PENDING);
+ DefInt(SQLITE_LOCK_EXCLUSIVE);
+ } _DefGroup;
+
+ DefGroup(ioCap) {
+ DefInt(SQLITE_IOCAP_ATOMIC);
+ DefInt(SQLITE_IOCAP_ATOMIC512);
+ DefInt(SQLITE_IOCAP_ATOMIC1K);
+ DefInt(SQLITE_IOCAP_ATOMIC2K);
+ DefInt(SQLITE_IOCAP_ATOMIC4K);
+ DefInt(SQLITE_IOCAP_ATOMIC8K);
+ DefInt(SQLITE_IOCAP_ATOMIC16K);
+ DefInt(SQLITE_IOCAP_ATOMIC32K);
+ DefInt(SQLITE_IOCAP_ATOMIC64K);
+ DefInt(SQLITE_IOCAP_SAFE_APPEND);
+ DefInt(SQLITE_IOCAP_SEQUENTIAL);
+ DefInt(SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN);
+ DefInt(SQLITE_IOCAP_POWERSAFE_OVERWRITE);
+ DefInt(SQLITE_IOCAP_IMMUTABLE);
+ DefInt(SQLITE_IOCAP_BATCH_ATOMIC);
+ } _DefGroup;
+
+ DefGroup(limits) {
+ DefInt(SQLITE_MAX_ALLOCATION_SIZE);
+ DefInt(SQLITE_LIMIT_LENGTH);
+ DefInt(SQLITE_MAX_LENGTH);
+ DefInt(SQLITE_LIMIT_SQL_LENGTH);
+ DefInt(SQLITE_MAX_SQL_LENGTH);
+ DefInt(SQLITE_LIMIT_COLUMN);
+ DefInt(SQLITE_MAX_COLUMN);
+ DefInt(SQLITE_LIMIT_EXPR_DEPTH);
+ DefInt(SQLITE_MAX_EXPR_DEPTH);
+ DefInt(SQLITE_LIMIT_COMPOUND_SELECT);
+ DefInt(SQLITE_MAX_COMPOUND_SELECT);
+ DefInt(SQLITE_LIMIT_VDBE_OP);
+ DefInt(SQLITE_MAX_VDBE_OP);
+ DefInt(SQLITE_LIMIT_FUNCTION_ARG);
+ DefInt(SQLITE_MAX_FUNCTION_ARG);
+ DefInt(SQLITE_LIMIT_ATTACHED);
+ DefInt(SQLITE_MAX_ATTACHED);
+ DefInt(SQLITE_LIMIT_LIKE_PATTERN_LENGTH);
+ DefInt(SQLITE_MAX_LIKE_PATTERN_LENGTH);
+ DefInt(SQLITE_LIMIT_VARIABLE_NUMBER);
+ DefInt(SQLITE_MAX_VARIABLE_NUMBER);
+ DefInt(SQLITE_LIMIT_TRIGGER_DEPTH);
+ DefInt(SQLITE_MAX_TRIGGER_DEPTH);
+ DefInt(SQLITE_LIMIT_WORKER_THREADS);
+ DefInt(SQLITE_MAX_WORKER_THREADS);
+ } _DefGroup;
+
+ DefGroup(openFlags) {
+ /* Noting that not all of these will have any effect in
+ ** WASM-space. */
+ DefInt(SQLITE_OPEN_READONLY);
+ DefInt(SQLITE_OPEN_READWRITE);
+ DefInt(SQLITE_OPEN_CREATE);
+ DefInt(SQLITE_OPEN_URI);
+ DefInt(SQLITE_OPEN_MEMORY);
+ DefInt(SQLITE_OPEN_NOMUTEX);
+ DefInt(SQLITE_OPEN_FULLMUTEX);
+ DefInt(SQLITE_OPEN_SHAREDCACHE);
+ DefInt(SQLITE_OPEN_PRIVATECACHE);
+ DefInt(SQLITE_OPEN_EXRESCODE);
+ DefInt(SQLITE_OPEN_NOFOLLOW);
+ /* OPEN flags for use with VFSes... */
+ DefInt(SQLITE_OPEN_MAIN_DB);
+ DefInt(SQLITE_OPEN_MAIN_JOURNAL);
+ DefInt(SQLITE_OPEN_TEMP_DB);
+ DefInt(SQLITE_OPEN_TEMP_JOURNAL);
+ DefInt(SQLITE_OPEN_TRANSIENT_DB);
+ DefInt(SQLITE_OPEN_SUBJOURNAL);
+ DefInt(SQLITE_OPEN_SUPER_JOURNAL);
+ DefInt(SQLITE_OPEN_WAL);
+ DefInt(SQLITE_OPEN_DELETEONCLOSE);
+ DefInt(SQLITE_OPEN_EXCLUSIVE);
+ } _DefGroup;
+
+ DefGroup(prepareFlags) {
+ DefInt(SQLITE_PREPARE_PERSISTENT);
+ DefInt(SQLITE_PREPARE_NORMALIZE);
+ DefInt(SQLITE_PREPARE_NO_VTAB);
+ } _DefGroup;
+
+ DefGroup(resultCodes) {
+ DefInt(SQLITE_OK);
+ DefInt(SQLITE_ERROR);
+ DefInt(SQLITE_INTERNAL);
+ DefInt(SQLITE_PERM);
+ DefInt(SQLITE_ABORT);
+ DefInt(SQLITE_BUSY);
+ DefInt(SQLITE_LOCKED);
+ DefInt(SQLITE_NOMEM);
+ DefInt(SQLITE_READONLY);
+ DefInt(SQLITE_INTERRUPT);
+ DefInt(SQLITE_IOERR);
+ DefInt(SQLITE_CORRUPT);
+ DefInt(SQLITE_NOTFOUND);
+ DefInt(SQLITE_FULL);
+ DefInt(SQLITE_CANTOPEN);
+ DefInt(SQLITE_PROTOCOL);
+ DefInt(SQLITE_EMPTY);
+ DefInt(SQLITE_SCHEMA);
+ DefInt(SQLITE_TOOBIG);
+ DefInt(SQLITE_CONSTRAINT);
+ DefInt(SQLITE_MISMATCH);
+ DefInt(SQLITE_MISUSE);
+ DefInt(SQLITE_NOLFS);
+ DefInt(SQLITE_AUTH);
+ DefInt(SQLITE_FORMAT);
+ DefInt(SQLITE_RANGE);
+ DefInt(SQLITE_NOTADB);
+ DefInt(SQLITE_NOTICE);
+ DefInt(SQLITE_WARNING);
+ DefInt(SQLITE_ROW);
+ DefInt(SQLITE_DONE);
+ // Extended Result Codes
+ DefInt(SQLITE_ERROR_MISSING_COLLSEQ);
+ DefInt(SQLITE_ERROR_RETRY);
+ DefInt(SQLITE_ERROR_SNAPSHOT);
+ DefInt(SQLITE_IOERR_READ);
+ DefInt(SQLITE_IOERR_SHORT_READ);
+ DefInt(SQLITE_IOERR_WRITE);
+ DefInt(SQLITE_IOERR_FSYNC);
+ DefInt(SQLITE_IOERR_DIR_FSYNC);
+ DefInt(SQLITE_IOERR_TRUNCATE);
+ DefInt(SQLITE_IOERR_FSTAT);
+ DefInt(SQLITE_IOERR_UNLOCK);
+ DefInt(SQLITE_IOERR_RDLOCK);
+ DefInt(SQLITE_IOERR_DELETE);
+ DefInt(SQLITE_IOERR_BLOCKED);
+ DefInt(SQLITE_IOERR_NOMEM);
+ DefInt(SQLITE_IOERR_ACCESS);
+ DefInt(SQLITE_IOERR_CHECKRESERVEDLOCK);
+ DefInt(SQLITE_IOERR_LOCK);
+ DefInt(SQLITE_IOERR_CLOSE);
+ DefInt(SQLITE_IOERR_DIR_CLOSE);
+ DefInt(SQLITE_IOERR_SHMOPEN);
+ DefInt(SQLITE_IOERR_SHMSIZE);
+ DefInt(SQLITE_IOERR_SHMLOCK);
+ DefInt(SQLITE_IOERR_SHMMAP);
+ DefInt(SQLITE_IOERR_SEEK);
+ DefInt(SQLITE_IOERR_DELETE_NOENT);
+ DefInt(SQLITE_IOERR_MMAP);
+ DefInt(SQLITE_IOERR_GETTEMPPATH);
+ DefInt(SQLITE_IOERR_CONVPATH);
+ DefInt(SQLITE_IOERR_VNODE);
+ DefInt(SQLITE_IOERR_AUTH);
+ DefInt(SQLITE_IOERR_BEGIN_ATOMIC);
+ DefInt(SQLITE_IOERR_COMMIT_ATOMIC);
+ DefInt(SQLITE_IOERR_ROLLBACK_ATOMIC);
+ DefInt(SQLITE_IOERR_DATA);
+ DefInt(SQLITE_IOERR_CORRUPTFS);
+ DefInt(SQLITE_LOCKED_SHAREDCACHE);
+ DefInt(SQLITE_LOCKED_VTAB);
+ DefInt(SQLITE_BUSY_RECOVERY);
+ DefInt(SQLITE_BUSY_SNAPSHOT);
+ DefInt(SQLITE_BUSY_TIMEOUT);
+ DefInt(SQLITE_CANTOPEN_NOTEMPDIR);
+ DefInt(SQLITE_CANTOPEN_ISDIR);
+ DefInt(SQLITE_CANTOPEN_FULLPATH);
+ DefInt(SQLITE_CANTOPEN_CONVPATH);
+ //DefInt(SQLITE_CANTOPEN_DIRTYWAL)/*docs say not used*/;
+ DefInt(SQLITE_CANTOPEN_SYMLINK);
+ DefInt(SQLITE_CORRUPT_VTAB);
+ DefInt(SQLITE_CORRUPT_SEQUENCE);
+ DefInt(SQLITE_CORRUPT_INDEX);
+ DefInt(SQLITE_READONLY_RECOVERY);
+ DefInt(SQLITE_READONLY_CANTLOCK);
+ DefInt(SQLITE_READONLY_ROLLBACK);
+ DefInt(SQLITE_READONLY_DBMOVED);
+ DefInt(SQLITE_READONLY_CANTINIT);
+ DefInt(SQLITE_READONLY_DIRECTORY);
+ DefInt(SQLITE_ABORT_ROLLBACK);
+ DefInt(SQLITE_CONSTRAINT_CHECK);
+ DefInt(SQLITE_CONSTRAINT_COMMITHOOK);
+ DefInt(SQLITE_CONSTRAINT_FOREIGNKEY);
+ DefInt(SQLITE_CONSTRAINT_FUNCTION);
+ DefInt(SQLITE_CONSTRAINT_NOTNULL);
+ DefInt(SQLITE_CONSTRAINT_PRIMARYKEY);
+ DefInt(SQLITE_CONSTRAINT_TRIGGER);
+ DefInt(SQLITE_CONSTRAINT_UNIQUE);
+ DefInt(SQLITE_CONSTRAINT_VTAB);
+ DefInt(SQLITE_CONSTRAINT_ROWID);
+ DefInt(SQLITE_CONSTRAINT_PINNED);
+ DefInt(SQLITE_CONSTRAINT_DATATYPE);
+ DefInt(SQLITE_NOTICE_RECOVER_WAL);
+ DefInt(SQLITE_NOTICE_RECOVER_ROLLBACK);
+ DefInt(SQLITE_WARNING_AUTOINDEX);
+ DefInt(SQLITE_AUTH_USER);
+ DefInt(SQLITE_OK_LOAD_PERMANENTLY);
+ //DefInt(SQLITE_OK_SYMLINK) /* internal use only */;
+ } _DefGroup;
+
+ DefGroup(serialize){
+ DefInt(SQLITE_SERIALIZE_NOCOPY);
+ DefInt(SQLITE_DESERIALIZE_FREEONCLOSE);
+ DefInt(SQLITE_DESERIALIZE_READONLY);
+ DefInt(SQLITE_DESERIALIZE_RESIZEABLE);
+ } _DefGroup;
+
+ DefGroup(session){
+ DefInt(SQLITE_SESSION_CONFIG_STRMSIZE);
+ DefInt(SQLITE_SESSION_OBJCONFIG_SIZE);
+ } _DefGroup;
+
+ DefGroup(sqlite3Status){
+ DefInt(SQLITE_STATUS_MEMORY_USED);
+ DefInt(SQLITE_STATUS_PAGECACHE_USED);
+ DefInt(SQLITE_STATUS_PAGECACHE_OVERFLOW);
+ //DefInt(SQLITE_STATUS_SCRATCH_USED) /* NOT USED */;
+ //DefInt(SQLITE_STATUS_SCRATCH_OVERFLOW) /* NOT USED */;
+ DefInt(SQLITE_STATUS_MALLOC_SIZE);
+ DefInt(SQLITE_STATUS_PARSER_STACK);
+ DefInt(SQLITE_STATUS_PAGECACHE_SIZE);
+ //DefInt(SQLITE_STATUS_SCRATCH_SIZE) /* NOT USED */;
+ DefInt(SQLITE_STATUS_MALLOC_COUNT);
+ } _DefGroup;
+
+ DefGroup(stmtStatus){
+ DefInt(SQLITE_STMTSTATUS_FULLSCAN_STEP);
+ DefInt(SQLITE_STMTSTATUS_SORT);
+ DefInt(SQLITE_STMTSTATUS_AUTOINDEX);
+ DefInt(SQLITE_STMTSTATUS_VM_STEP);
+ DefInt(SQLITE_STMTSTATUS_REPREPARE);
+ DefInt(SQLITE_STMTSTATUS_RUN);
+ DefInt(SQLITE_STMTSTATUS_FILTER_MISS);
+ DefInt(SQLITE_STMTSTATUS_FILTER_HIT);
+ DefInt(SQLITE_STMTSTATUS_MEMUSED);
+ } _DefGroup;
+
+ DefGroup(syncFlags) {
+ DefInt(SQLITE_SYNC_NORMAL);
+ DefInt(SQLITE_SYNC_FULL);
+ DefInt(SQLITE_SYNC_DATAONLY);
+ } _DefGroup;
+
+ DefGroup(trace) {
+ DefInt(SQLITE_TRACE_STMT);
+ DefInt(SQLITE_TRACE_PROFILE);
+ DefInt(SQLITE_TRACE_ROW);
+ DefInt(SQLITE_TRACE_CLOSE);
+ } _DefGroup;
+
+ DefGroup(txnState){
+ DefInt(SQLITE_TXN_NONE);
+ DefInt(SQLITE_TXN_READ);
+ DefInt(SQLITE_TXN_WRITE);
+ } _DefGroup;
+
+ DefGroup(udfFlags) {
+ DefInt(SQLITE_DETERMINISTIC);
+ DefInt(SQLITE_DIRECTONLY);
+ DefInt(SQLITE_INNOCUOUS);
+ DefInt(SQLITE_SUBTYPE);
+ DefInt(SQLITE_RESULT_SUBTYPE);
+ } _DefGroup;
+
+ DefGroup(version) {
+ DefInt(SQLITE_VERSION_NUMBER);
+ DefStr(SQLITE_VERSION);
+ DefStr(SQLITE_SOURCE_ID);
+ } _DefGroup;
+
+ DefGroup(vtab) {
+ DefInt(SQLITE_INDEX_SCAN_UNIQUE);
+ DefInt(SQLITE_INDEX_CONSTRAINT_EQ);
+ DefInt(SQLITE_INDEX_CONSTRAINT_GT);
+ DefInt(SQLITE_INDEX_CONSTRAINT_LE);
+ DefInt(SQLITE_INDEX_CONSTRAINT_LT);
+ DefInt(SQLITE_INDEX_CONSTRAINT_GE);
+ DefInt(SQLITE_INDEX_CONSTRAINT_MATCH);
+ DefInt(SQLITE_INDEX_CONSTRAINT_LIKE);
+ DefInt(SQLITE_INDEX_CONSTRAINT_GLOB);
+ DefInt(SQLITE_INDEX_CONSTRAINT_REGEXP);
+ DefInt(SQLITE_INDEX_CONSTRAINT_NE);
+ DefInt(SQLITE_INDEX_CONSTRAINT_ISNOT);
+ DefInt(SQLITE_INDEX_CONSTRAINT_ISNOTNULL);
+ DefInt(SQLITE_INDEX_CONSTRAINT_ISNULL);
+ DefInt(SQLITE_INDEX_CONSTRAINT_IS);
+ DefInt(SQLITE_INDEX_CONSTRAINT_LIMIT);
+ DefInt(SQLITE_INDEX_CONSTRAINT_OFFSET);
+ DefInt(SQLITE_INDEX_CONSTRAINT_FUNCTION);
+ DefInt(SQLITE_VTAB_CONSTRAINT_SUPPORT);
+ DefInt(SQLITE_VTAB_INNOCUOUS);
+ DefInt(SQLITE_VTAB_DIRECTONLY);
+ DefInt(SQLITE_VTAB_USES_ALL_SCHEMAS);
+ DefInt(SQLITE_ROLLBACK);
+ //DefInt(SQLITE_IGNORE); // Also used by sqlite3_authorizer() callback
+ DefInt(SQLITE_FAIL);
+ //DefInt(SQLITE_ABORT); // Also an error code
+ DefInt(SQLITE_REPLACE);
+ } _DefGroup;
+
+#undef DefGroup
+#undef DefStr
+#undef DefInt
+#undef _DefGroup
+
+ /*
+ ** Emit an array of "StructBinder" struct descripions, which look
+ ** like:
+ **
+ ** {
+ ** "name": "MyStruct",
+ ** "sizeof": 16,
+ ** "members": {
+ ** "member1": {"offset": 0,"sizeof": 4,"signature": "i"},
+ ** "member2": {"offset": 4,"sizeof": 4,"signature": "p"},
+ ** "member3": {"offset": 8,"sizeof": 8,"signature": "j"}
+ ** }
+ ** }
+ **
+ ** Detailed documentation for those bits are in the docs for the
+ ** Jaccwabyt JS-side component.
+ */
+
+ /** Macros for emitting StructBinder description. */
+#define StructBinder__(TYPE) \
+ n = 0; \
+ outf("%s{", (nStruct++ ? ", " : "")); \
+ out("\"name\": \"" # TYPE "\","); \
+ outf("\"sizeof\": %d", (int)sizeof(TYPE)); \
+ out(",\"members\": {");
+#define StructBinder_(T) StructBinder__(T)
+ /** ^^^ indirection needed to expand CurrentStruct */
+#define StructBinder StructBinder_(CurrentStruct)
+#define _StructBinder CloseBrace(2)
+#define M(MEMBER,SIG) \
+ outf("%s\"%s\": " \
+ "{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \
+ (n++ ? ", " : ""), #MEMBER, \
+ (int)offsetof(CurrentStruct,MEMBER), \
+ (int)sizeof(((CurrentStruct*)0)->MEMBER), \
+ SIG)
+
+ nStruct = 0;
+ out(", \"structs\": ["); {
+
+#define CurrentStruct sqlite3_vfs
+ StructBinder {
+ M(iVersion, "i");
+ M(szOsFile, "i");
+ M(mxPathname, "i");
+ M(pNext, "p");
+ M(zName, "s");
+ M(pAppData, "p");
+ M(xOpen, "i(pppip)");
+ M(xDelete, "i(ppi)");
+ M(xAccess, "i(ppip)");
+ M(xFullPathname, "i(ppip)");
+ M(xDlOpen, "p(pp)");
+ M(xDlError, "p(pip)");
+ M(xDlSym, "p()");
+ M(xDlClose, "v(pp)");
+ M(xRandomness, "i(pip)");
+ M(xSleep, "i(pi)");
+ M(xCurrentTime, "i(pp)");
+ M(xGetLastError, "i(pip)");
+ M(xCurrentTimeInt64, "i(pp)");
+ M(xSetSystemCall, "i(ppp)");
+ M(xGetSystemCall, "p(pp)");
+ M(xNextSystemCall, "p(pp)");
+ } _StructBinder;
+#undef CurrentStruct
+
+#define CurrentStruct sqlite3_io_methods
+ StructBinder {
+ M(iVersion, "i");
+ M(xClose, "i(p)");
+ M(xRead, "i(ppij)");
+ M(xWrite, "i(ppij)");
+ M(xTruncate, "i(pj)");
+ M(xSync, "i(pi)");
+ M(xFileSize, "i(pp)");
+ M(xLock, "i(pi)");
+ M(xUnlock, "i(pi)");
+ M(xCheckReservedLock, "i(pp)");
+ M(xFileControl, "i(pip)");
+ M(xSectorSize, "i(p)");
+ M(xDeviceCharacteristics, "i(p)");
+ M(xShmMap, "i(piiip)");
+ M(xShmLock, "i(piii)");
+ M(xShmBarrier, "v(p)");
+ M(xShmUnmap, "i(pi)");
+ M(xFetch, "i(pjip)");
+ M(xUnfetch, "i(pjp)");
+ } _StructBinder;
+#undef CurrentStruct
+
+#define CurrentStruct sqlite3_file
+ StructBinder {
+ M(pMethods, "p");
+ } _StructBinder;
+#undef CurrentStruct
+
+#define CurrentStruct sqlite3_kvvfs_methods
+ StructBinder {
+ M(xRead, "i(sspi)");
+ M(xWrite, "i(sss)");
+ M(xDelete, "i(ss)");
+ M(nKeySize, "i");
+ } _StructBinder;
+#undef CurrentStruct
+
+
+#define CurrentStruct sqlite3_vtab
+ StructBinder {
+ M(pModule, "p");
+ M(nRef, "i");
+ M(zErrMsg, "p");
+ } _StructBinder;
+#undef CurrentStruct
+
+#define CurrentStruct sqlite3_vtab_cursor
+ StructBinder {
+ M(pVtab, "p");
+ } _StructBinder;
+#undef CurrentStruct
+
+#define CurrentStruct sqlite3_module
+ StructBinder {
+ M(iVersion, "i");
+ M(xCreate, "i(ppippp)");
+ M(xConnect, "i(ppippp)");
+ M(xBestIndex, "i(pp)");
+ M(xDisconnect, "i(p)");
+ M(xDestroy, "i(p)");
+ M(xOpen, "i(pp)");
+ M(xClose, "i(p)");
+ M(xFilter, "i(pisip)");
+ M(xNext, "i(p)");
+ M(xEof, "i(p)");
+ M(xColumn, "i(ppi)");
+ M(xRowid, "i(pp)");
+ M(xUpdate, "i(pipp)");
+ M(xBegin, "i(p)");
+ M(xSync, "i(p)");
+ M(xCommit, "i(p)");
+ M(xRollback, "i(p)");
+ M(xFindFunction, "i(pispp)");
+ M(xRename, "i(ps)");
+ // ^^^ v1. v2+ follows...
+ M(xSavepoint, "i(pi)");
+ M(xRelease, "i(pi)");
+ M(xRollbackTo, "i(pi)");
+ // ^^^ v2. v3+ follows...
+ M(xShadowName, "i(s)");
+ } _StructBinder;
+#undef CurrentStruct
+
+ /**
+ ** Workaround: in order to map the various inner structs from
+ ** sqlite3_index_info, we have to uplift those into constructs we
+ ** can access by type name. These structs _must_ match their
+ ** in-sqlite3_index_info counterparts byte for byte.
+ */
+ typedef struct {
+ int iColumn;
+ unsigned char op;
+ unsigned char usable;
+ int iTermOffset;
+ } sqlite3_index_constraint;
+ typedef struct {
+ int iColumn;
+ unsigned char desc;
+ } sqlite3_index_orderby;
+ typedef struct {
+ int argvIndex;
+ unsigned char omit;
+ } sqlite3_index_constraint_usage;
+ { /* Validate that the above struct sizeof()s match
+ ** expectations. We could improve upon this by
+ ** checking the offsetof() for each member. */
+ const sqlite3_index_info siiCheck;
+#define IndexSzCheck(T,M) \
+ (sizeof(T) == sizeof(*siiCheck.M))
+ if(!IndexSzCheck(sqlite3_index_constraint,aConstraint)
+ || !IndexSzCheck(sqlite3_index_orderby,aOrderBy)
+ || !IndexSzCheck(sqlite3_index_constraint_usage,aConstraintUsage)){
+ assert(!"sizeof mismatch in sqlite3_index_... struct(s)");
+ return 0;
+ }
+#undef IndexSzCheck
+ }
+
+#define CurrentStruct sqlite3_index_constraint
+ StructBinder {
+ M(iColumn, "i");
+ M(op, "C");
+ M(usable, "C");
+ M(iTermOffset, "i");
+ } _StructBinder;
+#undef CurrentStruct
+
+#define CurrentStruct sqlite3_index_orderby
+ StructBinder {
+ M(iColumn, "i");
+ M(desc, "C");
+ } _StructBinder;
+#undef CurrentStruct
+
+#define CurrentStruct sqlite3_index_constraint_usage
+ StructBinder {
+ M(argvIndex, "i");
+ M(omit, "C");
+ } _StructBinder;
+#undef CurrentStruct
+
+#define CurrentStruct sqlite3_index_info
+ StructBinder {
+ M(nConstraint, "i");
+ M(aConstraint, "p");
+ M(nOrderBy, "i");
+ M(aOrderBy, "p");
+ M(aConstraintUsage, "p");
+ M(idxNum, "i");
+ M(idxStr, "p");
+ M(needToFreeIdxStr, "i");
+ M(orderByConsumed, "i");
+ M(estimatedCost, "d");
+ M(estimatedRows, "j");
+ M(idxFlags, "i");
+ M(colUsed, "j");
+ } _StructBinder;
+#undef CurrentStruct
+
+#if SQLITE_WASM_TESTS
+#define CurrentStruct WasmTestStruct
+ StructBinder {
+ M(v4, "i");
+ M(cstr, "s");
+ M(ppV, "p");
+ M(v8, "j");
+ M(xFunc, "v(p)");
+ } _StructBinder;
+#undef CurrentStruct
+#endif
+
+ } out( "]"/*structs*/);
+
+ out("}"/*top-level object*/);
+ *zPos = 0;
+ aBuffer[0] = '{'/*end of the race-condition workaround*/;
+ return aBuffer;
+#undef StructBinder
+#undef StructBinder_
+#undef StructBinder__
+#undef M
+#undef _StructBinder
+#undef CloseBrace
+#undef out
+#undef outf
+#undef lenCheck
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** This function invokes the xDelete method of the given VFS (or the
+** default VFS if pVfs is NULL), passing on the given filename. If
+** zName is NULL, no default VFS is found, or it has no xDelete
+** method, SQLITE_MISUSE is returned, else the result of the xDelete()
+** call is returned.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
+ int rc = SQLITE_MISUSE /* ??? */;
+ if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0);
+ if( zName && pVfs && pVfs->xDelete ){
+ rc = pVfs->xDelete(pVfs, zName, 1);
+ }
+ return rc;
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Returns a pointer to the given DB's VFS for the given DB name,
+** defaulting to "main" if zDbName is 0. Returns 0 if no db with the
+** given name is open.
+*/
+SQLITE_WASM_EXPORT
+sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
+ sqlite3_vfs * pVfs = 0;
+ sqlite3_file_control(pDb, zDbName ? zDbName : "main",
+ SQLITE_FCNTL_VFS_POINTER, &pVfs);
+ return pVfs;
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** This function resets the given db pointer's database as described at
+**
+** https://sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigresetdatabase
+**
+** But beware: virtual tables destroyed that way do not have their
+** xDestroy() called, so will leak if they require that function for
+** proper cleanup.
+**
+** Returns 0 on success, an SQLITE_xxx code on error. Returns
+** SQLITE_MISUSE if pDb is NULL.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_db_reset(sqlite3 *pDb){
+ int rc = SQLITE_MISUSE;
+ if( pDb ){
+ sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0);
+ rc = sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
+ if( 0==rc ){
+ rc = sqlite3_exec(pDb, "VACUUM", 0, 0, 0);
+ sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
+ }
+ }
+ return rc;
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Uses the given database's VFS xRead to stream the db file's
+** contents out to the given callback. The callback gets a single
+** chunk of size n (its 2nd argument) on each call and must return 0
+** on success, non-0 on error. This function returns 0 on success,
+** SQLITE_NOTFOUND if no db is open, or propagates any other non-0
+** code from the callback. Note that this is not thread-friendly: it
+** expects that it will be the only thread reading the db file and
+** takes no measures to ensure that is the case.
+**
+** This implementation appears to work fine, but
+** sqlite3_wasm_db_serialize() is arguably the better way to achieve
+** this.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_db_export_chunked( sqlite3* pDb,
+ int (*xCallback)(unsigned const char *zOut, int n) ){
+ sqlite3_int64 nSize = 0;
+ sqlite3_int64 nPos = 0;
+ sqlite3_file * pFile = 0;
+ unsigned char buf[1024 * 8];
+ int nBuf = (int)sizeof(buf);
+ int rc = pDb
+ ? sqlite3_file_control(pDb, "main",
+ SQLITE_FCNTL_FILE_POINTER, &pFile)
+ : SQLITE_NOTFOUND;
+ if( rc ) return rc;
+ rc = pFile->pMethods->xFileSize(pFile, &nSize);
+ if( rc ) return rc;
+ if(nSize % nBuf){
+ /* DB size is not an even multiple of the buffer size. Reduce
+ ** buffer size so that we do not unduly inflate the db size
+ ** with zero-padding when exporting. */
+ if(0 == nSize % 4096) nBuf = 4096;
+ else if(0 == nSize % 2048) nBuf = 2048;
+ else if(0 == nSize % 1024) nBuf = 1024;
+ else nBuf = 512;
+ }
+ for( ; 0==rc && nPos<nSize; nPos += nBuf ){
+ rc = pFile->pMethods->xRead(pFile, buf, nBuf, nPos);
+ if( SQLITE_IOERR_SHORT_READ == rc ){
+ rc = (nPos + nBuf) < nSize ? rc : 0/*assume EOF*/;
+ }
+ if( 0==rc ) rc = xCallback(buf, nBuf);
+ }
+ return rc;
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** A proxy for sqlite3_serialize() which serializes the schema zSchema
+** of pDb, placing the serialized output in pOut and nOut. nOut may be
+** NULL. If zSchema is NULL then "main" is assumed. If pDb or pOut are
+** NULL then SQLITE_MISUSE is returned. If allocation of the
+** serialized copy fails, SQLITE_NOMEM is returned. On success, 0 is
+** returned and `*pOut` will contain a pointer to the memory unless
+** mFlags includes SQLITE_SERIALIZE_NOCOPY and the database has no
+** contiguous memory representation, in which case `*pOut` will be
+** NULL but 0 will be returned.
+**
+** If `*pOut` is not NULL, the caller is responsible for passing it to
+** sqlite3_free() to free it.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
+ unsigned char **pOut,
+ sqlite3_int64 *nOut, unsigned int mFlags ){
+ unsigned char * z;
+ if( !pDb || !pOut ) return SQLITE_MISUSE;
+ if( nOut ) *nOut = 0;
+ z = sqlite3_serialize(pDb, zSchema ? zSchema : "main", nOut, mFlags);
+ if( z || (SQLITE_SERIALIZE_NOCOPY & mFlags) ){
+ *pOut = z;
+ return 0;
+ }else{
+ return SQLITE_NOMEM;
+ }
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** ACHTUNG: it was discovered on 2023-08-11 that, with SQLITE_DEBUG,
+** this function's out-of-scope use of the sqlite3_vfs/file/io_methods
+** APIs leads to triggering of assertions in the core library. Its use
+** is now deprecated and VFS-specific APIs for importing files need to
+** be found to replace it. sqlite3_wasm_posix_create_file() is
+** suitable for the "unix" family of VFSes.
+**
+** Creates a new file using the I/O API of the given VFS, containing
+** the given number of bytes of the given data. If the file exists, it
+** is truncated to the given length and populated with the given
+** data.
+**
+** This function exists so that we can implement the equivalent of
+** Emscripten's FS.createDataFile() in a VFS-agnostic way. This
+** functionality is intended for use in uploading database files.
+**
+** Not all VFSes support this functionality, e.g. the "kvvfs" does
+** not.
+**
+** If pVfs is NULL, sqlite3_vfs_find(0) is used.
+**
+** If zFile is NULL, pVfs is NULL (and sqlite3_vfs_find(0) returns
+** NULL), or nData is negative, SQLITE_MISUSE are returned.
+**
+** On success, it creates a new file with the given name, populated
+** with the fist nData bytes of pData. If pData is NULL, the file is
+** created and/or truncated to nData bytes.
+**
+** Whether or not directory components of zFilename are created
+** automatically or not is unspecified: that detail is left to the
+** VFS. The "opfs" VFS, for example, creates them.
+**
+** If an error happens while populating or truncating the file, the
+** target file will be deleted (if needed) if this function created
+** it. If this function did not create it, it is not deleted but may
+** be left in an undefined state.
+**
+** Returns 0 on success. On error, it returns a code described above
+** or propagates a code from one of the I/O methods.
+**
+** Design note: nData is an integer, instead of int64, for WASM
+** portability, so that the API can still work in builds where BigInt
+** support is disabled or unavailable.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs,
+ const char *zFilename,
+ const unsigned char * pData,
+ int nData ){
+ int rc;
+ sqlite3_file *pFile = 0;
+ sqlite3_io_methods const *pIo;
+ const int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
+#if 0 && defined(SQLITE_DEBUG)
+ | SQLITE_OPEN_MAIN_JOURNAL
+ /* ^^^^ This is for testing a horrible workaround to avoid
+ triggering a specific assert() in os_unix.c:unixOpen(). Please
+ do not enable this in real builds. */
+#endif
+ ;
+ int flagsOut = 0;
+ int fileExisted = 0;
+ int doUnlock = 0;
+ const unsigned char *pPos = pData;
+ const int blockSize = 512
+ /* Because we are using pFile->pMethods->xWrite() for writing, and
+ ** it may have a buffer limit related to sqlite3's pager size, we
+ ** conservatively write in 512-byte blocks (smallest page
+ ** size). */;
+ //fprintf(stderr, "pVfs=%p, zFilename=%s, nData=%d\n", pVfs, zFilename, nData);
+ if( !pVfs ) pVfs = sqlite3_vfs_find(0);
+ if( !pVfs || !zFilename || nData<0 ) return SQLITE_MISUSE;
+ pVfs->xAccess(pVfs, zFilename, SQLITE_ACCESS_EXISTS, &fileExisted);
+ rc = sqlite3OsOpenMalloc(pVfs, zFilename, &pFile, openFlags, &flagsOut);
+#if 0
+# define RC fprintf(stderr,"create_file(%s,%s) @%d rc=%d\n", \
+ pVfs->zName, zFilename, __LINE__, rc);
+#else
+# define RC
+#endif
+ RC;
+ if(rc) return rc;
+ pIo = pFile->pMethods;
+ if( pIo->xLock ) {
+ /* We need xLock() in order to accommodate the OPFS VFS, as it
+ ** obtains a writeable handle via the lock operation and releases
+ ** it in xUnlock(). If we don't do those here, we have to add code
+ ** to the VFS to account check whether it was locked before
+ ** xFileSize(), xTruncate(), and the like, and release the lock
+ ** only if it was unlocked when the op was started. */
+ rc = pIo->xLock(pFile, SQLITE_LOCK_EXCLUSIVE);
+ RC;
+ doUnlock = 0==rc;
+ }
+ if( 0==rc ){
+ rc = pIo->xTruncate(pFile, nData);
+ RC;
+ }
+ if( 0==rc && 0!=pData && nData>0 ){
+ while( 0==rc && nData>0 ){
+ const int n = nData>=blockSize ? blockSize : nData;
+ rc = pIo->xWrite(pFile, pPos, n, (sqlite3_int64)(pPos - pData));
+ RC;
+ nData -= n;
+ pPos += n;
+ }
+ if( 0==rc && nData>0 ){
+ assert( nData<blockSize );
+ rc = pIo->xWrite(pFile, pPos, nData,
+ (sqlite3_int64)(pPos - pData));
+ RC;
+ }
+ }
+ if( pIo->xUnlock && doUnlock!=0 ){
+ pIo->xUnlock(pFile, SQLITE_LOCK_NONE);
+ }
+ pIo->xClose(pFile);
+ if( rc!=0 && 0==fileExisted ){
+ pVfs->xDelete(pVfs, zFilename, 1);
+ }
+ RC;
+#undef RC
+ return rc;
+}
+
+/**
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Creates or overwrites a file using the POSIX file API,
+** i.e. Emscripten's virtual filesystem. Creates or truncates
+** zFilename, appends pData bytes to it, and returns 0 on success or
+** SQLITE_IOERR on error.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_posix_create_file( const char *zFilename,
+ const unsigned char * pData,
+ int nData ){
+ int rc;
+ FILE * pFile = 0;
+ int fileExisted = 0;
+ size_t nWrote = 1;
+
+ if( !zFilename || nData<0 || (pData==0 && nData>0) ) return SQLITE_MISUSE;
+ pFile = fopen(zFilename, "w");
+ if( 0==pFile ) return SQLITE_IOERR;
+ if( nData>0 ){
+ nWrote = fwrite(pData, (size_t)nData, 1, pFile);
+ }
+ fclose(pFile);
+ return 1==nWrote ? 0 : SQLITE_IOERR;
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Allocates sqlite3KvvfsMethods.nKeySize bytes from
+** sqlite3_wasm_pstack_alloc() and returns 0 if that allocation fails,
+** else it passes that string to kvstorageMakeKey() and returns a
+** NUL-terminated pointer to that string. It is up to the caller to
+** use sqlite3_wasm_pstack_restore() to free the returned pointer.
+*/
+SQLITE_WASM_EXPORT
+char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass,
+ const char *zKeyIn){
+ assert(sqlite3KvvfsMethods.nKeySize>24);
+ char *zKeyOut =
+ (char *)sqlite3_wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize);
+ if(zKeyOut){
+ kvstorageMakeKey(zClass, zKeyIn, zKeyOut);
+ }
+ return zKeyOut;
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Returns the pointer to the singleton object which holds the kvvfs
+** I/O methods and associated state.
+*/
+SQLITE_WASM_EXPORT
+sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){
+ return &sqlite3KvvfsMethods;
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** This is a proxy for the variadic sqlite3_vtab_config() which passes
+** its argument on, or not, to sqlite3_vtab_config(), depending on the
+** value of its 2nd argument. Returns the result of
+** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a
+** valid value.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){
+ switch(op){
+ case SQLITE_VTAB_DIRECTONLY:
+ case SQLITE_VTAB_INNOCUOUS:
+ return sqlite3_vtab_config(pDb, op);
+ case SQLITE_VTAB_CONSTRAINT_SUPPORT:
+ return sqlite3_vtab_config(pDb, op, arg);
+ default:
+ return SQLITE_MISUSE;
+ }
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Wrapper for the variants of sqlite3_db_config() which take
+** (int,int*) variadic args.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
+ switch(op){
+ case SQLITE_DBCONFIG_ENABLE_FKEY:
+ case SQLITE_DBCONFIG_ENABLE_TRIGGER:
+ case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
+ case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
+ case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
+ case SQLITE_DBCONFIG_ENABLE_QPSG:
+ case SQLITE_DBCONFIG_TRIGGER_EQP:
+ case SQLITE_DBCONFIG_RESET_DATABASE:
+ case SQLITE_DBCONFIG_DEFENSIVE:
+ case SQLITE_DBCONFIG_WRITABLE_SCHEMA:
+ case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
+ case SQLITE_DBCONFIG_DQS_DML:
+ case SQLITE_DBCONFIG_DQS_DDL:
+ case SQLITE_DBCONFIG_ENABLE_VIEW:
+ case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
+ case SQLITE_DBCONFIG_TRUSTED_SCHEMA:
+ case SQLITE_DBCONFIG_STMT_SCANSTATUS:
+ case SQLITE_DBCONFIG_REVERSE_SCANORDER:
+ return sqlite3_db_config(pDb, op, arg1, pArg2);
+ default: return SQLITE_MISUSE;
+ }
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Wrapper for the variants of sqlite3_db_config() which take
+** (void*,int,int) variadic args.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){
+ switch(op){
+ case SQLITE_DBCONFIG_LOOKASIDE:
+ return sqlite3_db_config(pDb, op, pArg1, arg2, arg3);
+ default: return SQLITE_MISUSE;
+ }
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Wrapper for the variants of sqlite3_db_config() which take
+** (const char *) variadic args.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
+ switch(op){
+ case SQLITE_DBCONFIG_MAINDBNAME:
+ return sqlite3_db_config(pDb, op, zArg);
+ default: return SQLITE_MISUSE;
+ }
+}
+
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Binding for combinations of sqlite3_config() arguments which take
+** a single integer argument.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_config_i(int op, int arg){
+ return sqlite3_config(op, arg);
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Binding for combinations of sqlite3_config() arguments which take
+** two int arguments.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_config_ii(int op, int arg1, int arg2){
+ return sqlite3_config(op, arg1, arg2);
+}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Binding for combinations of sqlite3_config() arguments which take
+** a single i64 argument.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){
+ return sqlite3_config(op, arg);
+}
+
+#if 0
+// Pending removal after verification of a workaround discussed in the
+// forum post linked to below.
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Returns a pointer to sqlite3_free(). In compliant browsers the
+** return value, when passed to sqlite3.wasm.exports.functionEntry(),
+** must resolve to the same function as
+** sqlite3.wasm.exports.sqlite3_free. i.e. from a dev console where
+** sqlite3 is exported globally, the following must be true:
+**
+** ```
+** sqlite3.wasm.functionEntry(
+** sqlite3.wasm.exports.sqlite3_wasm_ptr_to_sqlite3_free()
+** ) === sqlite3.wasm.exports.sqlite3_free
+** ```
+**
+** Using a function to return this pointer, as opposed to exporting it
+** via sqlite3_wasm_enum_json(), is an attempt to work around a
+** Safari-specific quirk covered at
+** https://sqlite.org/forum/info/e5b20e1feb37a19a.
+**/
+SQLITE_WASM_EXPORT
+void * sqlite3_wasm_ptr_to_sqlite3_free(void){
+ return (void*)sqlite3_free;
+}
+#endif
+
+#if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS)
+#include <emscripten/wasmfs.h>
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings, specifically
+** only when building with Emscripten's WASMFS support.
+**
+** This function should only be called if the JS side detects the
+** existence of the Origin-Private FileSystem (OPFS) APIs in the
+** client. The first time it is called, this function instantiates a
+** WASMFS backend impl for OPFS. On success, subsequent calls are
+** no-ops.
+**
+** This function may be passed a "mount point" name, which must have a
+** leading "/" and is currently restricted to a single path component,
+** e.g. "/foo" is legal but "/foo/" and "/foo/bar" are not. If it is
+** NULL or empty, it defaults to "/opfs".
+**
+** Returns 0 on success, SQLITE_NOMEM if instantiation of the backend
+** object fails, SQLITE_IOERR if mkdir() of the zMountPoint dir in
+** the virtual FS fails. In builds compiled without SQLITE_ENABLE_WASMFS
+** defined, SQLITE_NOTFOUND is returned without side effects.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_init_wasmfs(const char *zMountPoint){
+ static backend_t pOpfs = 0;
+ if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs";
+ if( !pOpfs ){
+ pOpfs = wasmfs_create_opfs_backend();
+ }
+ /** It's not enough to instantiate the backend. We have to create a
+ mountpoint in the VFS and attach the backend to it. */
+ if( pOpfs && 0!=access(zMountPoint, F_OK) ){
+ /* Note that this check and is not robust but it will
+ hypothetically suffice for the transient wasm-based virtual
+ filesystem we're currently running in. */
+ const int rc = wasmfs_create_directory(zMountPoint, 0777, pOpfs);
+ /*emscripten_console_logf("OPFS mkdir(%s) rc=%d", zMountPoint, rc);*/
+ if(rc) return SQLITE_IOERR;
+ }
+ return pOpfs ? 0 : SQLITE_NOMEM;
+}
+#else
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_init_wasmfs(const char *zUnused){
+ //emscripten_console_warn("WASMFS OPFS is not compiled in.");
+ if(zUnused){/*unused*/}
+ return SQLITE_NOTFOUND;
+}
+#endif /* __EMSCRIPTEN__ && SQLITE_ENABLE_WASMFS */
+
+#if SQLITE_WASM_TESTS
+
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_test_intptr(int * p){
+ return *p = *p * 2;
+}
+
+SQLITE_WASM_EXPORT
+void * sqlite3_wasm_test_voidptr(void * p){
+ return p;
+}
+
+SQLITE_WASM_EXPORT
+int64_t sqlite3_wasm_test_int64_max(void){
+ return (int64_t)0x7fffffffffffffff;
+}
+
+SQLITE_WASM_EXPORT
+int64_t sqlite3_wasm_test_int64_min(void){
+ return ~sqlite3_wasm_test_int64_max();
+}
+
+SQLITE_WASM_EXPORT
+int64_t sqlite3_wasm_test_int64_times2(int64_t x){
+ return x * 2;
+}
+
+SQLITE_WASM_EXPORT
+void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){
+ *max = sqlite3_wasm_test_int64_max();
+ *min = sqlite3_wasm_test_int64_min();
+ /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
+}
+
+SQLITE_WASM_EXPORT
+int64_t sqlite3_wasm_test_int64ptr(int64_t * p){
+ /*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
+ return *p = *p * 2;
+}
+
+SQLITE_WASM_EXPORT
+void sqlite3_wasm_test_stack_overflow(int recurse){
+ if(recurse) sqlite3_wasm_test_stack_overflow(recurse);
+}
+
+/* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */
+SQLITE_WASM_EXPORT
+char * sqlite3_wasm_test_str_hello(int fail){
+ char * s = fail ? 0 : (char *)sqlite3_malloc(6);
+ if(s){
+ memcpy(s, "hello", 5);
+ s[5] = 0;
+ }
+ return s;
+}
+
+/*
+** For testing using SQLTester scripts.
+**
+** Return non-zero if string z matches glob pattern zGlob and zero if the
+** pattern does not match.
+**
+** To repeat:
+**
+** zero == no match
+** non-zero == match
+**
+** Globbing rules:
+**
+** '*' Matches any sequence of zero or more characters.
+**
+** '?' Matches exactly one character.
+**
+** [...] Matches one character from the enclosed list of
+** characters.
+**
+** [^...] Matches one character not in the enclosed list.
+**
+** '#' Matches any sequence of one or more digits with an
+** optional + or - sign in front, or a hexadecimal
+** literal of the form 0x...
+*/
+static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
+ int c, c2;
+ int invert;
+ int seen;
+ typedef int (*recurse_f)(const char *,const char *);
+ static const recurse_f recurse = sqlite3_wasm_SQLTester_strnotglob;
+
+ while( (c = (*(zGlob++)))!=0 ){
+ if( c=='*' ){
+ while( (c=(*(zGlob++))) == '*' || c=='?' ){
+ if( c=='?' && (*(z++))==0 ) return 0;
+ }
+ if( c==0 ){
+ return 1;
+ }else if( c=='[' ){
+ while( *z && recurse(zGlob-1,z)==0 ){
+ z++;
+ }
+ return (*z)!=0;
+ }
+ while( (c2 = (*(z++)))!=0 ){
+ while( c2!=c ){
+ c2 = *(z++);
+ if( c2==0 ) return 0;
+ }
+ if( recurse(zGlob,z) ) return 1;
+ }
+ return 0;
+ }else if( c=='?' ){
+ if( (*(z++))==0 ) return 0;
+ }else if( c=='[' ){
+ int prior_c = 0;
+ seen = 0;
+ invert = 0;
+ c = *(z++);
+ if( c==0 ) return 0;
+ c2 = *(zGlob++);
+ if( c2=='^' ){
+ invert = 1;
+ c2 = *(zGlob++);
+ }
+ if( c2==']' ){
+ if( c==']' ) seen = 1;
+ c2 = *(zGlob++);
+ }
+ while( c2 && c2!=']' ){
+ if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
+ c2 = *(zGlob++);
+ if( c>=prior_c && c<=c2 ) seen = 1;
+ prior_c = 0;
+ }else{
+ if( c==c2 ){
+ seen = 1;
+ }
+ prior_c = c2;
+ }
+ c2 = *(zGlob++);
+ }
+ if( c2==0 || (seen ^ invert)==0 ) return 0;
+ }else if( c=='#' ){
+ if( z[0]=='0'
+ && (z[1]=='x' || z[1]=='X')
+ && sqlite3Isxdigit(z[2])
+ ){
+ z += 3;
+ while( sqlite3Isxdigit(z[0]) ){ z++; }
+ }else{
+ if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++;
+ if( !sqlite3Isdigit(z[0]) ) return 0;
+ z++;
+ while( sqlite3Isdigit(z[0]) ){ z++; }
+ }
+ }else{
+ if( c!=(*(z++)) ) return 0;
+ }
+ }
+ return *z==0;
+}
+
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_SQLTester_strglob(const char *zGlob, const char *z){
+ return !sqlite3_wasm_SQLTester_strnotglob(zGlob, z);
+}
+
+
+#endif /* SQLITE_WASM_TESTS */
+
+#undef SQLITE_WASM_EXPORT
diff --git a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js
new file mode 100644
index 0000000..cd78ed4
--- /dev/null
+++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js
@@ -0,0 +1,282 @@
+//#ifnot omit-oo1
+/*
+ 2022-08-24
+
+ 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 implements a Promise-based proxy for the sqlite3 Worker
+ API #1. It is intended to be included either from the main thread or
+ a Worker, but only if (A) the environment supports nested Workers
+ and (B) it's _not_ a Worker which loads the sqlite3 WASM/JS
+ module. This file's features will load that module and provide a
+ slightly simpler client-side interface than the slightly-lower-level
+ Worker API does.
+
+ This script necessarily exposes one global symbol, but clients may
+ freely `delete` that symbol after calling it.
+*/
+'use strict';
+/**
+ Configures an sqlite3 Worker API #1 Worker such that it can be
+ manipulated via a Promise-based interface and returns a factory
+ function which returns Promises for communicating with the worker.
+ This proxy has an _almost_ identical interface to the normal
+ worker API, with any exceptions documented below.
+
+ It requires a configuration object with the following properties:
+
+ - `worker` (required): a Worker instance which loads
+ `sqlite3-worker1.js` or a functional equivalent. Note that the
+ promiser factory replaces the worker.onmessage property. This
+ config option may alternately be a function, in which case this
+ function re-assigns this property with the result of calling that
+ function, enabling delayed instantiation of a Worker.
+
+ - `onready` (optional, but...): this callback is called with no
+ arguments when the worker fires its initial
+ 'sqlite3-api'/'worker1-ready' message, which it does when
+ sqlite3.initWorker1API() completes its initialization. This is
+ the simplest way to tell the worker to kick off work at the
+ earliest opportunity.
+
+ - `onunhandled` (optional): a callback which gets passed the
+ message event object for any worker.onmessage() events which
+ are not handled by this proxy. Ideally that "should" never
+ happen, as this proxy aims to handle all known message types.
+
+ - `generateMessageId` (optional): a function which, when passed an
+ about-to-be-posted message object, generates a _unique_ message ID
+ for the message, which this API then assigns as the messageId
+ property of the message. It _must_ generate unique IDs on each call
+ so that dispatching can work. If not defined, a default generator
+ is used (which should be sufficient for most or all cases).
+
+ - `debug` (optional): a console.debug()-style function for logging
+ information about messages.
+
+ This function returns a stateful factory function with the
+ following interfaces:
+
+ - Promise function(messageType, messageArgs)
+ - Promise function({message object})
+
+ The first form expects the "type" and "args" values for a Worker
+ message. The second expects an object in the form {type:...,
+ args:...} plus any other properties the client cares to set. This
+ function will always set the `messageId` property on the object,
+ even if it's already set, and will set the `dbId` property to the
+ current database ID if it is _not_ set in the message object.
+
+ The function throws on error.
+
+ The function installs a temporary message listener, posts a
+ message to the configured Worker, and handles the message's
+ response via the temporary message listener. The then() callback
+ of the returned Promise is passed the `message.data` property from
+ the resulting message, i.e. the payload from the worker, stripped
+ of the lower-level event state which the onmessage() handler
+ receives.
+
+ Example usage:
+
+ ```
+ const config = {...};
+ const sq3Promiser = sqlite3Worker1Promiser(config);
+ sq3Promiser('open', {filename:"/foo.db"}).then(function(msg){
+ console.log("open response",msg); // => {type:'open', result: {filename:'/foo.db'}, ...}
+ });
+ sq3Promiser({type:'close'}).then((msg)=>{
+ console.log("close response",msg); // => {type:'close', result: {filename:'/foo.db'}, ...}
+ });
+ ```
+
+ Differences from Worker API #1:
+
+ - exec's {callback: STRING} option does not work via this
+ interface (it triggers an exception), but {callback: function}
+ does and works exactly like the STRING form does in the Worker:
+ the callback is called one time for each row of the result set,
+ passed the same worker message format as the worker API emits:
+
+ {type:typeString,
+ row:VALUE,
+ rowNumber:1-based-#,
+ columnNames: array}
+
+ Where `typeString` is an internally-synthesized message type string
+ used temporarily for worker message dispatching. It can be ignored
+ by all client code except that which tests this API. The `row`
+ property contains the row result in the form implied by the
+ `rowMode` option (defaulting to `'array'`). The `rowNumber` is a
+ 1-based integer value incremented by 1 on each call into the
+ callback.
+
+ At the end of the result set, the same event is fired with
+ (row=undefined, rowNumber=null) to indicate that
+ the end of the result set has been reached. Note that the rows
+ arrive via worker-posted messages, with all the implications
+ of that.
+
+ Notable shortcomings:
+
+ - This API was not designed with ES6 modules in mind. Neither Firefox
+ nor Safari support, as of March 2023, the {type:"module"} flag to the
+ Worker constructor, so that particular usage is not something we're going
+ to target for the time being:
+
+ https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker
+*/
+globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
+ // Inspired by: https://stackoverflow.com/a/52439530
+ if(1===arguments.length && 'function'===typeof arguments[0]){
+ const f = config;
+ config = Object.assign(Object.create(null), callee.defaultConfig);
+ config.onready = f;
+ }else{
+ config = Object.assign(Object.create(null), callee.defaultConfig, config);
+ }
+ const handlerMap = Object.create(null);
+ const noop = function(){};
+ const err = config.onerror
+ || noop /* config.onerror is intentionally undocumented
+ pending finding a less ambiguous name */;
+ const debug = config.debug || noop;
+ const idTypeMap = config.generateMessageId ? undefined : Object.create(null);
+ const genMsgId = config.generateMessageId || function(msg){
+ return msg.type+'#'+(idTypeMap[msg.type] = (idTypeMap[msg.type]||0) + 1);
+ };
+ const toss = (...args)=>{throw new Error(args.join(' '))};
+ if(!config.worker) config.worker = callee.defaultConfig.worker;
+ if('function'===typeof config.worker) config.worker = config.worker();
+ let dbId;
+ config.worker.onmessage = function(ev){
+ ev = ev.data;
+ debug('worker1.onmessage',ev);
+ let msgHandler = handlerMap[ev.messageId];
+ if(!msgHandler){
+ if(ev && 'sqlite3-api'===ev.type && 'worker1-ready'===ev.result) {
+ /*fired one time when the Worker1 API initializes*/
+ if(config.onready) config.onready();
+ return;
+ }
+ msgHandler = handlerMap[ev.type] /* check for exec per-row callback */;
+ if(msgHandler && msgHandler.onrow){
+ msgHandler.onrow(ev);
+ return;
+ }
+ if(config.onunhandled) config.onunhandled(arguments[0]);
+ else err("sqlite3Worker1Promiser() unhandled worker message:",ev);
+ return;
+ }
+ delete handlerMap[ev.messageId];
+ switch(ev.type){
+ case 'error':
+ msgHandler.reject(ev);
+ return;
+ case 'open':
+ if(!dbId) dbId = ev.dbId;
+ break;
+ case 'close':
+ if(ev.dbId===dbId) dbId = undefined;
+ break;
+ default:
+ break;
+ }
+ try {msgHandler.resolve(ev)}
+ catch(e){msgHandler.reject(e)}
+ }/*worker.onmessage()*/;
+ return function(/*(msgType, msgArgs) || (msgEnvelope)*/){
+ let msg;
+ if(1===arguments.length){
+ msg = arguments[0];
+ }else if(2===arguments.length){
+ msg = Object.create(null);
+ msg.type = arguments[0];
+ msg.args = arguments[1];
+ msg.dbId = msg.args.dbId;
+ }else{
+ toss("Invalid arugments for sqlite3Worker1Promiser()-created factory.");
+ }
+ if(!msg.dbId && msg.type!=='open') msg.dbId = dbId;
+ msg.messageId = genMsgId(msg);
+ msg.departureTime = performance.now();
+ const proxy = Object.create(null);
+ proxy.message = msg;
+ let rowCallbackId /* message handler ID for exec on-row callback proxy */;
+ if('exec'===msg.type && msg.args){
+ if('function'===typeof msg.args.callback){
+ rowCallbackId = msg.messageId+':row';
+ proxy.onrow = msg.args.callback;
+ msg.args.callback = rowCallbackId;
+ handlerMap[rowCallbackId] = proxy;
+ }else if('string' === typeof msg.args.callback){
+ toss("exec callback may not be a string when using the Promise interface.");
+ /**
+ Design note: the reason for this limitation is that this
+ API takes over worker.onmessage() and the client has no way
+ of adding their own message-type handlers to it. Per-row
+ callbacks are implemented as short-lived message.type
+ mappings for worker.onmessage().
+
+ We "could" work around this by providing a new
+ config.fallbackMessageHandler (or some such) which contains
+ a map of event type names to callbacks. Seems like overkill
+ for now, seeing as the client can pass callback functions
+ to this interface (whereas the string-form "callback" is
+ needed for the over-the-Worker interface).
+ */
+ }
+ }
+ //debug("requestWork", msg);
+ let p = new Promise(function(resolve, reject){
+ proxy.resolve = resolve;
+ proxy.reject = reject;
+ handlerMap[msg.messageId] = proxy;
+ debug("Posting",msg.type,"message to Worker dbId="+(dbId||'default')+':',msg);
+ config.worker.postMessage(msg);
+ });
+ if(rowCallbackId) p = p.finally(()=>delete handlerMap[rowCallbackId]);
+ return p;
+ };
+}/*sqlite3Worker1Promiser()*/;
+globalThis.sqlite3Worker1Promiser.defaultConfig = {
+ worker: function(){
+//#if target=es6-bundler-friendly
+ return new Worker(new URL("sqlite3-worker1-bundler-friendly.mjs", import.meta.url),{
+ type: 'module'
+ });
+//#else
+ let theJs = "sqlite3-worker1.js";
+ if(this.currentScript){
+ const src = this.currentScript.src.split('/');
+ src.pop();
+ theJs = src.join('/')+'/' + theJs;
+ //sqlite3.config.warn("promiser currentScript, theJs =",this.currentScript,theJs);
+ }else if(globalThis.location){
+ //sqlite3.config.warn("promiser globalThis.location =",globalThis.location);
+ const urlParams = new URL(globalThis.location.href).searchParams;
+ if(urlParams.has('sqlite3.dir')){
+ theJs = urlParams.get('sqlite3.dir') + '/' + theJs;
+ }
+ }
+ return new Worker(theJs + globalThis.location.search);
+//#endif
+ }
+//#ifnot target=es6-bundler-friendly
+ .bind({
+ currentScript: globalThis?.document?.currentScript
+ })
+//#endif
+ ,
+ onerror: (...args)=>console.error('worker1 promiser error',...args)
+};
+//#else
+/* Built with the omit-oo1 flag. */
+//#endif ifnot omit-oo1
diff --git a/ext/wasm/api/sqlite3-worker1.c-pp.js b/ext/wasm/api/sqlite3-worker1.c-pp.js
new file mode 100644
index 0000000..74de9ec
--- /dev/null
+++ b/ext/wasm/api/sqlite3-worker1.c-pp.js
@@ -0,0 +1,54 @@
+//#ifnot omit-oo1
+/*
+ 2022-05-23
+
+ 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 is a JS Worker file for the main sqlite3 api. It loads
+ sqlite3.js, initializes the module, and postMessage()'s a message
+ after the module is initialized:
+
+ {type: 'sqlite3-api', result: 'worker1-ready'}
+
+ This seemingly superfluous level of indirection is necessary when
+ loading sqlite3.js via a Worker. Instantiating a worker with new
+ Worker("sqlite.js") will not (cannot) call sqlite3InitModule() to
+ initialize the module due to a timing/order-of-operations conflict
+ (and that symbol is not exported in a way that a Worker loading it
+ that way can see it). Thus JS code wanting to load the sqlite3
+ Worker-specific API needs to pass _this_ file (or equivalent) to the
+ Worker constructor and then listen for an event in the form shown
+ above in order to know when the module has completed initialization.
+
+ This file accepts a URL arguments to adjust how it loads sqlite3.js:
+
+ - `sqlite3.dir`, if set, treats the given directory name as the
+ directory from which `sqlite3.js` will be loaded.
+*/
+//#if target=es6-bundler-friendly
+import {default as sqlite3InitModule} from './sqlite3-bundler-friendly.mjs';
+//#else
+"use strict";
+{
+ const urlParams = globalThis.location
+ ? new URL(globalThis.location.href).searchParams
+ : new URLSearchParams();
+ let theJs = 'sqlite3.js';
+ if(urlParams.has('sqlite3.dir')){
+ theJs = urlParams.get('sqlite3.dir') + '/' + theJs;
+ }
+ //console.warn("worker1 theJs =",theJs);
+ importScripts(theJs);
+}
+//#endif
+sqlite3InitModule().then(sqlite3 => sqlite3.initWorker1API());
+//#else
+/* Built with the omit-oo1 flag. */
+//#endif ifnot omit-oo1