Adding the sqlite sourcecode

This commit is contained in:
Jiří Stach committed 2025-07-17 11:54:48 +02:00
1 parent bdb54a1f8f
commit 406b584760
2208 files changed
+1172785 -1

No files matched your search

@@ -0,0 +1,155 @@
_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_parameter_name
_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_decltype
_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_data_count
_sqlite3_db_filename
_sqlite3_db_handle
_sqlite3_db_name
_sqlite3_db_readonly
_sqlite3_db_status
_sqlite3_deserialize
_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_interrupt
_sqlite3_is_interrupted
_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_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_auxdata
_sqlite3_set_last_insert_rowid
_sqlite3_shutdown
_sqlite3_sourceid
_sqlite3_sql
_sqlite3_status
_sqlite3_status64
_sqlite3_step
_sqlite3_stmt_busy
_sqlite3_stmt_explain
_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
@@ -0,0 +1,63 @@
_sqlite3_create_window_function
_sqlite3_progress_handler
_sqlite3_set_authorizer
_sqlite3_preupdate_blobwrite
_sqlite3_preupdate_count
_sqlite3_preupdate_depth
_sqlite3_preupdate_hook
_sqlite3_preupdate_new
_sqlite3_preupdate_old
_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
_sqlite3_create_module
_sqlite3_create_module_v2
_sqlite3_declare_vtab
_sqlite3_drop_modules
_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
@@ -0,0 +1,5 @@
_sqlite3_key
_sqlite3_key_v2
_sqlite3_rekey
_sqlite3_rekey_v2
_sqlite3_activate_see
@@ -0,0 +1,3 @@
FS
wasmMemory
+170
View File
@@ -0,0 +1,170 @@
# 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-vfs-helper.js`**\
Installs the `sqlite3.vfs` namespace, which contain helpers for use
by downstream code which creates `sqlite3_vfs` implementations.
- **`sqlite3-vtab-helper.js`**\
Installs the `sqlite3.vtab` namespace, which contain helpers for use
by downstream code which creates `sqlite3_module` implementations.
- **`sqlite3-vfs-opfs.c-pp.js`**\
is an sqlite3 VFS implementation which supports the [Origin-Private
FileSystem (OPFS)][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).
[OPFS]: https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system
+130
View File
@@ -0,0 +1,130 @@
/* ^^^^ 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(){
//console.warn("this is extern-post-js");
/**
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)=>{
//console.warn("originalInit() then() arg =",EmscriptenModule);
//console.warn("initModuleState =",initModuleState);
EmscriptenModule.runSQLite3PostLoadInit(EmscriptenModule);
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;
const rv = f();
//#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
return rv;
}).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
+7
View File
@@ -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.
*/
+6
View File
@@ -0,0 +1,6 @@
/* The current function scope was opened via post-js-header.js, which
gets prepended to this at build-time. This file closes that
scope. */
//console.warn("This is the end of the Module.runSQLite3PostLoadInit handler.");
}/*Module.runSQLite3PostLoadInit()*/;
//console.warn("This is the end of the setup of the (pending) Module.runSQLite3PostLoadInit");
+33
View File
@@ -0,0 +1,33 @@
/**
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.
*/
Module.runSQLite3PostLoadInit = function(EmscriptenModule/*the Emscripten-style module object*/){
/** ^^^ Don't use Module.postRun, as that runs a different time
depending on whether this file is built with emcc 3.1.x or
4.0.x. This function name is intentionally obnoxiously verbose to
ensure that we don't collide with current and future Emscripten
symbol names. */
'use strict';
//console.warn("This is the start of Module.runSQLite3PostLoadInit()");
/* 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
- jaccwabyt/jaccwabyt.js => Jaccwabyt (C/JS struct binding)
- sqlite3-api-glue.js => glues previous parts together
- sqlite3-api-oo1.js => SQLite3 OO API #1
- sqlite3-api-worker1.js => Worker-based API
- sqlite3-vfs-helper.c-pp.js => Utilities for VFS impls
- sqlite3-vtab-helper.c-pp.js => Utilities for virtual table impls
- sqlite3-vfs-opfs.c-pp.js => OPFS VFS
- sqlite3-vfs-opfs-sahpool.c-pp.js => OPFS SAHPool VFS
- sqlite3-api-cleanup.js => final API cleanup
- post-js-footer.js => closes this function
*/
+121
View File
@@ -0,0 +1,121 @@
/**
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;
}
this.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
//#if custom-Module.instantiateModule
/**
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 =
//#if wasmfs
false
//#else
true /* This works, but it does not have the testing coverage in the
wild which Emscripten's default impl does, so we'll save
this option until we really need a custom
Module.instantiateWasm() */
//#endif
? '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
);
//console.warn("Custom instantiateModule",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';
//#endif custom-Module.instantiateModule
/* 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. */
+66
View File
@@ -0,0 +1,66 @@
/*
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.
In Emscripten builds it's run in the context of a Module.postRun
handler.
*/
'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.");
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+671
View File
@@ -0,0 +1,671 @@
//#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 `args.countChanges` (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.
If the `args.lastInsertRowId` (added in version 3.50.0) is truthy
then the `result` property contained by the returned object will
have a `lastInsertRowId` will hold a BigInt-type value corresponding
to the result of sqlite3_last_insert_rowid(). This value is only
fetched once, after the SQL is run, regardless of how many
statements the SQL contains. This API has no idea whether the SQL
contains any INSERTs, so it is up to the client to apply/rely on
this property only when it makes sense to do so.
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){
const util = sqlite3.util;
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 = util.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){
util.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 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);
oargs.vfs = args.vfs;
oargs.filename = args.filename || "";
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;
}
const lastInsertRowId = !!rc.lastInsertRowId
? sqlite3.capi.sqlite3_last_insert_rowid(db)
: undefined;
if( undefined!==lastInsertRowId ){
rc.lastInsertRowId = lastInsertRowId;
}
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();
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");
}
}/*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
@@ -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.
*/
+805
View File
@@ -0,0 +1,805 @@
/*
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(){
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 synchronous 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);
/**
__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 removed 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';
}
};
/**
Attempts to find a suitable SQLITE_xyz result code for Error
object e. Returns either such a translation or rc if if it does
not know how to translate the exception.
*/
GetSyncHandleError.convertRc = (e,rc)=>{
if( e instanceof GetSyncHandleError ){
if( 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')) ){
return state.sq3Codes.SQLITE_BUSY;
}else if( 'NotFoundError'===e.cause.name ){
/**
Maintenance reminder: SQLITE_NOTFOUND, though it looks like
a good match, has different semantics than NotFoundError
and is not suitable here.
*/
return state.sq3Codes.SQLITE_CANTOPEN;
}
}else if( 'NotFoundError'===e?.name ){
return state.sq3Codes.SQLITE_CANTOPEN;
}
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.
2024-06-12: there is a rare race condition here which has been
reported a single time:
https://sqlite.org/forum/forumpost/9ee7f5340802d600
What appears to be happening is that file we're waiting for a
lock on is deleted while we wait. What currently happens here is
that a locking exception is thrown but the exception type is
NotFoundError. In such cases, we very probably should attempt to
re-open/re-create the file an obtain the lock on it (noting that
there's another race condition there). That's easy to say but
creating a viable test for that condition has proven challenging
so far.
*/
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);
};
/**
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().
*/
const vfsAsyncImpls = {
'opfs-async-shutdown': async ()=>{
flagAsyncShutdown = true;
storeAndNotify('opfs-async-shutdown', 0);
},
mkdir: async (dirname)=>{
let rc = 0;
try {
await getDirForFilename(dirname+"/filepart", true);
}catch(e){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR;
}
storeAndNotify('mkdir', rc);
},
xAccess: async (filename)=>{
/* 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;
try{
const [dh, fn] = await getDirForFilename(filename);
await dh.getFileHandle(fn);
}catch(e){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR;
}
storeAndNotify('xAccess', rc);
},
xClose: async function(fid/*sqlite3_file pointer*/){
const opName = 'xClose';
__implicitLocks.delete(fid);
const fh = __openFiles[fid];
let rc = 0;
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;
}
storeAndNotify(opName, rc);
},
xDelete: async function(...args){
const rc = await vfsAsyncImpls.xDeleteNoWait(...args);
storeAndNotify('xDelete', rc);
},
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;
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;
}
return rc;
},
xFileSize: async function(fid/*sqlite3_file pointer*/){
const fh = __openFiles[fid];
let rc = 0;
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);
storeAndNotify('xFileSize', rc);
},
xLock: async function(fid/*sqlite3_file pointer*/,
lockType/*SQLITE_LOCK_...*/){
const fh = __openFiles[fid];
let rc = 0;
const oldLockType = fh.xLock;
fh.xLock = lockType;
if( !fh.syncHandle ){
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;
}
}
storeAndNotify('xLock',rc);
},
xOpen: async function(fid/*sqlite3_file pointer*/, filename,
flags/*SQLITE_OPEN_...*/,
opfsFlags/*OPFS_...*/){
const opName = 'xOpen';
const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags);
try{
let hDir, filenamePart;
try {
[hDir, filenamePart] = await getDirForFilename(filename, !!create);
}catch(e){
state.s11n.storeException(1,e);
storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND);
return;
}
if( state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags ){
try{
await hDir.removeEntry(filenamePart);
}catch(e){
/* ignoring */
//warn("Ignoring failed Unlink of",filename,":",e);
}
}
const hFile = await hDir.getFileHandle(filenamePart, {create});
const fh = Object.assign(Object.create(null),{
fid: fid,
filenameAbs: filename,
filenamePart: filenamePart,
dirHandle: hDir,
fileHandle: hFile,
sabView: state.sabFileBufView,
readOnly: !create && !!(state.sq3Codes.SQLITE_OPEN_READONLY & flags),
deleteOnClose: !!(state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags)
});
fh.releaseImplicitLocks =
(opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP)
|| state.opfsFlags.defaultUnlockAsap;
__openFiles[fid] = fh;
storeAndNotify(opName, 0);
}catch(e){
error(opName,e);
state.s11n.storeException(1,e);
storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR);
}
},
xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){
let rc = 0, nRead;
const fh = __openFiles[fid];
try{
nRead = (await getSyncHandle(fh,'xRead')).read(
fh.sabView.subarray(0, n),
{at: Number(offset64)}
);
if(nRead < n){/* Zero-fill remaining bytes */
fh.sabView.fill(0, nRead, n);
rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ;
}
}catch(e){
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);
},
xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){
const fh = __openFiles[fid];
let rc = 0;
if(!fh.readOnly && fh.syncHandle){
try {
await fh.syncHandle.flush();
}catch(e){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR_FSYNC;
}
}
storeAndNotify('xSync',rc);
},
xTruncate: async function(fid/*sqlite3_file pointer*/,size){
let rc = 0;
const fh = __openFiles[fid];
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);
storeAndNotify('xTruncate',rc);
},
xUnlock: async function(fid/*sqlite3_file pointer*/,
lockType/*SQLITE_LOCK_...*/){
let rc = 0;
const fh = __openFiles[fid];
if( fh.syncHandle
&& state.sq3Codes.SQLITE_LOCK_NONE===lockType
/* Note that we do not differentiate between lock types in
this VFS. We're either locked or unlocked. */ ){
try { await closeSyncHandle(fh) }
catch(e){
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
}
}
storeAndNotify('xUnlock',rc);
},
xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){
let rc;
const fh = __openFiles[fid];
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);
storeAndNotify('xWrite',rc);
}
}/*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){
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);
return rc;
};
state.s11n.serialize = function(...args){
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;
}
};
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 occasionally, 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();
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;
}
};
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();
}
+103
View File
@@ -0,0 +1,103 @@
/*
** 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, a namespace of helpers for use in
the creation of JavaScript implementations of sqlite3_vfs.
*/
'use strict';
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
const vfs = Object.create(null);
sqlite3.vfs = vfs;
/**
Uses sqlite3_vfs_register() to register this
sqlite3.capi.sqlite3_vfs instance. 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
sqlite3.StructBinder.StructType.prototype.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.StructBinder.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;
o.struct.installMethods(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;
};
}/*sqlite3ApiBootstrap.initializers.push()*/);
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+427
View File
@@ -0,0 +1,427 @@
/*
** 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.vtab, a namespace of helpers for use in
the creation of JavaScript implementations virtual tables. If built
without virtual table support then this function does nothing.
*/
'use strict';
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if( !sqlite3.wasm.exports.sqlite3_declare_vtab ){
return;
}
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
const vtab = Object.create(null);
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);
};
/**
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 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;
/**
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);
}
}
mod.installMethods(remethods, false);
}else{
// No automatic exception handling. Trust the client
// to not throw.
mod.installMethods(
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()*/);
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,346 @@
//#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, and the only way to know when the worker module has
completed loading. The irony of using a callback for this, instead
of returning a promise from sqlite3Worker1Promiser() is not lost on
the developers: see sqlite3Worker1Promiser.v2() which uses a
Promise instead.
- `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;
let promiserFunc;
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(promiserFunc);
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 promiserFunc = 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 arguments 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-module
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-module
.bind({
currentScript: globalThis?.document?.currentScript
})
//#endif
,
onerror: (...args)=>console.error('worker1 promiser error',...args)
}/*defaultConfig*/;
/**
sqlite3Worker1Promiser.v2(), added in 3.46, works identically to
sqlite3Worker1Promiser() except that it returns a Promise instead
of relying an an onready callback in the config object. The Promise
resolves to the same factory function which
sqlite3Worker1Promiser() returns.
If config is-a function or is an object which contains an onready
function, that function is replaced by a proxy which will resolve
after calling the original function and will reject if that
function throws.
*/
sqlite3Worker1Promiser.v2 = function(config){
let oldFunc;
if( 'function' == typeof config ){
oldFunc = config;
config = {};
}else if('function'===typeof config?.onready){
oldFunc = config.onready;
delete config.onready;
}
const promiseProxy = Object.create(null);
config = Object.assign((config || Object.create(null)),{
onready: async function(func){
try {
if( oldFunc ) await oldFunc(func);
promiseProxy.resolve(func);
}
catch(e){promiseProxy.reject(e)}
}
});
const p = new Promise(function(resolve,reject){
promiseProxy.resolve = resolve;
promiseProxy.reject = reject;
});
try{
this.original(config);
}catch(e){
promiseProxy.reject(e);
}
return p;
}.bind({
/* We do this because clients are
recommended to delete globalThis.sqlite3Worker1Promiser. */
original: sqlite3Worker1Promiser
});
//#if target=es6-module
/**
When built as a module, we export sqlite3Worker1Promiser.v2()
instead of sqlite3Worker1Promise() because (A) its interface is more
conventional for ESM usage and (B) the ESM export option for this
API did not exist until v2 was created, so there's no backwards
incompatibility.
*/
export default sqlite3Worker1Promiser.v2;
//#endif /* target=es6-module */
//#else
/* Built with the omit-oo1 flag. */
//#endif ifnot omit-oo1
+54
View File
@@ -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