Adding the sqlite sourcecode
This commit is contained in:
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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -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");
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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. */
|
||||
@@ -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
@@ -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.
|
||||
*/
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user