aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api/sqlite3-api-prologue.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/api/sqlite3-api-prologue.js')
-rw-r--r--ext/wasm/api/sqlite3-api-prologue.js691
1 files changed, 576 insertions, 115 deletions
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js
index 60ed61477..a6e055ff4 100644
--- a/ext/wasm/api/sqlite3-api-prologue.js
+++ b/ext/wasm/api/sqlite3-api-prologue.js
@@ -43,10 +43,7 @@
- Insofar as possible, support client-side storage using JS
filesystem APIs. As of this writing, such things are still very
- much TODO. Initial testing with using IndexedDB as backing storage
- showed it to work reasonably well, but it's also too easy to
- corrupt by using a web page in two browser tabs because IndexedDB
- lacks the locking features needed to support that.
+ much under development.
Specific non-goals of this project:
@@ -54,8 +51,10 @@
Encodings in that realm, there are no currently plans to support
the UTF16-related sqlite3 APIs. They would add a complication to
the bindings for no appreciable benefit. Though web-related
- implementation details take priority, the lower-level WASM module
- "should" work in non-web WASM environments.
+ implementation details take priority, and the JavaScript
+ components of the API specifically focus on browser clients, the
+ lower-level WASM module "should" work in non-web WASM
+ environments.
- Supporting old or niche-market platforms. WASM is built for a
modern web and requires modern platforms.
@@ -78,25 +77,113 @@
*/
/**
- This global symbol is is only a temporary measure: the JS-side
- post-processing will remove that object from the global scope when
- setup is complete. We require it there temporarily in order to glue
- disparate parts together during the loading of the API (which spans
- several components).
+ sqlite3ApiBootstrap() is the only global symbol persistently
+ exposed by this API. It is intended to be called one time at the
+ end of the API amalgamation process, passed configuration details
+ for the current environment, and then optionally be removed from
+ the global object using `delete self.sqlite3ApiBootstrap`.
- This function requires a configuration object intended to abstract
+ This function expects a configuration object, intended to abstract
away details specific to any given WASM environment, primarily so
- that it can be used without any _direct_ dependency on Emscripten.
- (That said, OO API #1 requires, as of this writing, Emscripten's
- virtual filesystem API. Baby steps.)
+ that it can be used without any _direct_ dependency on
+ Emscripten. (Note the default values for the config object!) The
+ config object is only honored the first time this is
+ called. Subsequent calls ignore the argument and return the same
+ (configured) object which gets initialized by the first call.
+
+ The config object properties include:
+
+ - `exports`[^1]: the "exports" object for the current WASM
+ environment. In an Emscripten build, this should be set to
+ `Module['asm']`.
+
+ - `memory`[^1]: optional WebAssembly.Memory object, defaulting to
+ `exports.memory`. In Emscripten environments this should be set
+ to `Module.wasmMemory` if the build uses `-sIMPORT_MEMORY`, or be
+ left undefined/falsy to default to `exports.memory` when using
+ WASM-exported memory.
+
+ - `bigIntEnabled`: true if BigInt support is enabled. Defaults to
+ true if self.BigInt64Array is available, else false. Some APIs
+ will throw exceptions if called without BigInt support, as BigInt
+ is required for marshalling C-side int64 into and out of JS.
+
+ - `allocExportName`: the name of the function, in `exports`, of the
+ `malloc(3)`-compatible routine for the WASM environment. Defaults
+ to `"malloc"`.
+
+ - `deallocExportName`: the name of the function, in `exports`, of
+ the `free(3)`-compatible routine for the WASM
+ environment. Defaults to `"free"`.
+
+ - `wasmfsOpfsDir`[^1]: if the environment supports persistent storage, this
+ directory names the "mount point" for that directory. It must be prefixed
+ by `/` and may currently contain only a single directory-name part. Using
+ the root directory name is not supported by any current persistent backend.
+
+
+ [^1] = This property may optionally be a function, in which case this
+ function re-assigns it to the value returned from that function,
+ enabling delayed evaluation.
+
*/
-self.sqlite3ApiBootstrap = function(config){
- 'use strict';
+'use strict';
+self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
+ apiConfig = (self.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig)
+){
+ if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */
+ console.warn("sqlite3ApiBootstrap() called multiple times.",
+ "Config and external initializers are ignored on calls after the first.");
+ return sqlite3ApiBootstrap.sqlite3;
+ }
+ apiConfig = apiConfig || {};
+ const config = Object.create(null);
+ {
+ const configDefaults = {
+ exports: undefined,
+ memory: undefined,
+ bigIntEnabled: (()=>{
+ if('undefined'!==typeof Module){
+ /* Emscripten module will contain HEAPU64 when built with
+ -sWASM_BIGINT=1, else it will not. */
+ return !!Module.HEAPU64;
+ }
+ return !!self.BigInt64Array;
+ })(),
+ allocExportName: 'malloc',
+ deallocExportName: 'free',
+ wasmfsOpfsDir: '/opfs'
+ };
+ Object.keys(configDefaults).forEach(function(k){
+ config[k] = Object.getOwnPropertyDescriptor(apiConfig, k)
+ ? apiConfig[k] : configDefaults[k];
+ });
+ // Copy over any properties apiConfig defines but configDefaults does not...
+ Object.keys(apiConfig).forEach(function(k){
+ if(!Object.getOwnPropertyDescriptor(config, k)){
+ config[k] = apiConfig[k];
+ }
+ });
+ }
+
+ [
+ // If any of these config options are functions, replace them with
+ // the result of calling that function...
+ 'exports', 'memory', 'wasmfsOpfsDir'
+ ].forEach((k)=>{
+ if('function' === typeof config[k]){
+ config[k] = config[k]();
+ }
+ });
/** Throws a new Error, the message of which is the concatenation
all args with a space between each. */
const toss = (...args)=>{throw new Error(args.join(' '))};
+ if(config.wasmfsOpfsDir && !/^\/[^/]+$/.test(config.wasmfsOpfsDir)){
+ toss("config.wasmfsOpfsDir must be falsy or in the form '/dir-name'.");
+ }
+
/**
Returns true if n is a 32-bit (signed) integer, else
false. This is used for determining when we need to switch to
@@ -143,7 +230,29 @@ self.sqlite3ApiBootstrap = function(config){
};
const utf8Decoder = new TextDecoder('utf-8');
- const typedArrayToString = (str)=>utf8Decoder.decode(str);
+
+ /** Internal helper to use in operations which need to distinguish
+ between SharedArrayBuffer heap memory and non-shared heap. */
+ const __SAB = ('undefined'===typeof SharedArrayBuffer)
+ ? function(){} : SharedArrayBuffer;
+ const typedArrayToString = function(arrayBuffer, begin, end){
+ return utf8Decoder.decode(
+ (arrayBuffer.buffer instanceof __SAB)
+ ? arrayBuffer.slice(begin, end)
+ : arrayBuffer.subarray(begin, end)
+ );
+ };
+
+ /**
+ If v is-a Array, its join('') result is returned. If
+ isSQLableTypedArray(v) then typedArrayToString(v) is
+ returned. Else v is returned as-is.
+ */
+ const arrayToString = function(v){
+ if(isSQLableTypedArray(v)) return typedArrayToString(v);
+ else if(Array.isArray(v)) return v.join('');
+ return v;
+ };
/**
An Error subclass specifically for reporting Wasm-level malloc()
@@ -173,43 +282,13 @@ self.sqlite3ApiBootstrap = function(config){
*/
const capi = {
/**
- An Error subclass which is thrown by this object's alloc() method
- on OOM.
- */
- WasmAllocError: WasmAllocError,
- /**
- The API's one single point of access to the WASM-side memory
- allocator. Works like malloc(3) (and is likely bound to
- malloc()) but throws an WasmAllocError if allocation fails. It is
- important that any code which might pass through the sqlite3 C
- API NOT throw and must instead return SQLITE_NOMEM (or
- equivalent, depending on the context).
-
- That said, very few cases in the API can result in
- client-defined functions propagating exceptions via the C-style
- API. Most notably, this applies ot User-defined SQL Functions
- (UDFs) registered via sqlite3_create_function_v2(). For that
- specific case it is recommended that all UDF creation be
- funneled through a utility function and that a wrapper function
- be added around the UDF which catches any exception and sets
- the error state to OOM. (The overall complexity of registering
- UDFs essentially requires a helper for doing so!)
- */
- alloc: undefined/*installed later*/,
- /**
- The API's one single point of access to the WASM-side memory
- deallocator. Works like free(3) (and is likely bound to
- free()).
- */
- dealloc: undefined/*installed later*/,
- /**
When using sqlite3_open_v2() it is important to keep the following
in mind:
https://www.sqlite.org/c3ref/open.html
- The flags for use with its 3rd argument are installed in this
- object using the C-cide names, e.g. SQLITE_OPEN_CREATE.
+ object using their C-side names, e.g. SQLITE_OPEN_CREATE.
- If the combination of flags passed to it are invalid,
behavior is undefined. Thus is is never okay to call this
@@ -220,18 +299,17 @@ self.sqlite3ApiBootstrap = function(config){
and cache-related flags, but they are retained in this
API for consistency's sake.
- - The final argument to this function specifies the VFS to
- use, which is largely (but not entirely!) meaningless in
- the WASM environment. It should always be null or
- undefined, and it is safe to elide that argument when
- calling this function.
+ - The final argument to this function specifies the VFS to use,
+ which is largely (but not entirely!) meaningless in the WASM
+ environment. It may be null, undefined, or 0 to denote the
+ default.
*/
sqlite3_open_v2: function(filename,dbPtrPtr,flags,vfsStr){}/*installed later*/,
/**
The sqlite3_prepare_v3() binding handles two different uses
with differing JS/WASM semantics:
- 1) sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt [, null])
+ 1) sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt , null)
2) sqlite3_prepare_v3(pDb, sqlPointer, sqlByteLen, prepFlags, ppStmt, sqlPointerToPointer)
@@ -252,55 +330,66 @@ self.sqlite3ApiBootstrap = function(config){
(pDb, sqlAsString, -1, prepFlags, ppStmt, null)
- The pzTail argument is ignored in this case because its result
- is meaningless when a string-type value is passed through
- (because the string goes through another level of internal
+ The `pzTail` argument is ignored in this case because its
+ result is meaningless when a string-type value is passed
+ through: the string goes through another level of internal
conversion for WASM's sake and the result pointer would refer
to that transient conversion's memory, not the passed-in
- string).
+ string.
If the sql argument is not a string, it must be a _pointer_ to
a NUL-terminated string which was allocated in the WASM memory
- (e.g. using cwapi.wasm.alloc() or equivalent). In that case,
+ (e.g. using capi.wasm.alloc() or equivalent). In that case,
the final argument may be 0/null/undefined or must be a pointer
to which the "tail" of the compiled SQL is written, as
documented for the C-side sqlite3_prepare_v3(). In case (2),
the underlying C function is called with the equivalent of:
- (pDb, sqlAsPointer, (sqlByteLen||-1), prepFlags, ppStmt, pzTail)
+ (pDb, sqlAsPointer, sqlByteLen, prepFlags, ppStmt, pzTail)
It returns its result and compiled statement as documented in
the C API. Fetching the output pointers (5th and 6th
- parameters) requires using capi.wasm.getMemValue() (or
- equivalent) and the pzTail will point to an address relative to
- the sqlAsPointer value.
+ parameters) requires using `capi.wasm.getMemValue()` (or
+ equivalent) and the `pzTail` will point to an address relative to
+ the `sqlAsPointer` value.
If passed an invalid 2nd argument type, this function will
- return SQLITE_MISUSE but will unfortunately be able to return
- any additional error information because we have no way to set
- the db's error state such that this function could return a
- non-0 integer and the client could call sqlite3_errcode() or
- sqlite3_errmsg() to fetch it. See the RFE at:
-
- https://sqlite.org/forum/forumpost/f9eb79b11aefd4fc81d
+ return SQLITE_MISUSE and sqlite3_errmsg() will contain a string
+ describing the problem.
- The alternative would be to throw an exception for that case,
- but that would be in strong constrast to the rest of the
- C-level API and seems likely to cause more confusion.
-
- Side-note: in the C API the function does not fail if provided
- an empty string but its result output pointer will be NULL.
+ Side-note: if given an empty string, or one which contains only
+ comments or an empty SQL expression, 0 is returned but the result
+ output pointer will be NULL.
*/
- sqlite3_prepare_v3: function(dbPtr, sql, sqlByteLen, prepFlags,
- stmtPtrPtr, strPtrPtr){}/*installed later*/,
+ sqlite3_prepare_v3: (dbPtr, sql, sqlByteLen, prepFlags,
+ stmtPtrPtr, strPtrPtr)=>{}/*installed later*/,
/**
Equivalent to calling sqlite3_prapare_v3() with 0 as its 4th argument.
*/
- sqlite3_prepare_v2: function(dbPtr, sql, sqlByteLen, stmtPtrPtr,
- strPtrPtr){}/*installed later*/,
+ sqlite3_prepare_v2: (dbPtr, sql, sqlByteLen,
+ stmtPtrPtr,strPtrPtr)=>{}/*installed later*/,
/**
+ This binding enables the callback argument to be a JavaScript.
+
+ If the callback is a function, then for the duration of the
+ sqlite3_exec() call, it installs a WASM-bound function which
+ acts as a proxy for the given callback. That proxy will
+ also perform a conversion of the callback's arguments from
+ `(char**)` to JS arrays of strings. However, for API
+ consistency's sake it will still honor the C-level
+ callback parameter order and will call it like:
+
+ `callback(pVoid, colCount, listOfValues, listOfColNames)`
+
+ If the callback is not a JS function then this binding performs
+ no translation of the callback, but the sql argument is still
+ converted to a WASM string for the call using the
+ "flexible-string" argument converter.
+ */
+ sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/,
+ /**
Various internal-use utilities are added here as needed. They
are bound to an object only so that we have access to them in
the differently-scoped steps of the API bootstrapping
@@ -308,8 +397,12 @@ self.sqlite3ApiBootstrap = function(config){
removed.
*/
util:{
- isInt32, isTypedArray, isBindableTypedArray, isSQLableTypedArray,
- affirmBindableTypedArray, typedArrayToString
+ affirmBindableTypedArray, arrayToString, isBindableTypedArray,
+ isInt32, isSQLableTypedArray, isTypedArray,
+ typedArrayToString,
+ isMainWindow: ()=>{
+ return self.window===self && self.document;
+ }
},
/**
@@ -365,6 +458,33 @@ self.sqlite3ApiBootstrap = function(config){
|| toss("API config object requires a WebAssembly.Memory object",
"in either config.exports.memory (exported)",
"or config.memory (imported)."),
+
+ /**
+ The API's one single point of access to the WASM-side memory
+ allocator. Works like malloc(3) (and is likely bound to
+ malloc()) but throws an WasmAllocError if allocation fails. It is
+ important that any code which might pass through the sqlite3 C
+ API NOT throw and must instead return SQLITE_NOMEM (or
+ equivalent, depending on the context).
+
+ That said, very few cases in the API can result in
+ client-defined functions propagating exceptions via the C-style
+ API. Most notably, this applies ot User-defined SQL Functions
+ (UDFs) registered via sqlite3_create_function_v2(). For that
+ specific case it is recommended that all UDF creation be
+ funneled through a utility function and that a wrapper function
+ be added around the UDF which catches any exception and sets
+ the error state to OOM. (The overall complexity of registering
+ UDFs essentially requires a helper for doing so!)
+ */
+ alloc: undefined/*installed later*/,
+ /**
+ The API's one single point of access to the WASM-side memory
+ deallocator. Works like free(3) (and is likely bound to
+ free()).
+ */
+ dealloc: undefined/*installed later*/
+
/* Many more wasm-related APIs get installed later on. */
}/*wasm*/
}/*capi*/;
@@ -387,7 +507,7 @@ self.sqlite3ApiBootstrap = function(config){
Int8Array types and will throw if srcTypedArray is of
any other type.
*/
- capi.wasm.mallocFromTypedArray = function(srcTypedArray){
+ capi.wasm.allocFromTypedArray = function(srcTypedArray){
affirmBindableTypedArray(srcTypedArray);
const pRet = this.alloc(srcTypedArray.byteLength || 1);
this.heapForSize(srcTypedArray.constructor).set(srcTypedArray.byteLength ? srcTypedArray : [0], pRet);
@@ -400,11 +520,13 @@ self.sqlite3ApiBootstrap = function(config){
const f = capi.wasm.exports[key];
if(!(f instanceof Function)) toss("Missing required exports[",key,"] function.");
}
+
capi.wasm.alloc = function(n){
const m = this.exports[keyAlloc](n);
if(!m) throw new WasmAllocError("Failed to allocate "+n+" bytes.");
return m;
}.bind(capi.wasm)
+
capi.wasm.dealloc = (m)=>capi.wasm.exports[keyDealloc](m);
/**
@@ -472,26 +594,38 @@ self.sqlite3ApiBootstrap = function(config){
) ? !!capi.sqlite3_compileoption_used(optName) : false;
}/*compileOptionUsed()*/;
+ /**
+ Signatures for the WASM-exported C-side functions. Each entry
+ is an array with 2+ elements:
+
+ [ "c-side name",
+ "result type" (capi.wasm.xWrap() syntax),
+ [arg types in xWrap() syntax]
+ // ^^^ this needn't strictly be an array: it can be subsequent
+ // elements instead: [x,y,z] is equivalent to x,y,z
+ ]
+
+ Note that support for the API-specific data types in the
+ result/argument type strings gets plugged in at a later phase in
+ the API initialization process.
+ */
capi.wasm.bindingSignatures = [
- /**
- Signatures for the WASM-exported C-side functions. Each entry
- is an array with 2+ elements:
-
- ["c-side name",
- "result type" (capi.wasm.xWrap() syntax),
- [arg types in xWrap() syntax]
- // ^^^ this needn't strictly be an array: it can be subsequent
- // elements instead: [x,y,z] is equivalent to x,y,z
- ]
- */
// Please keep these sorted by function name!
- ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"],
+ ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"
+ /* We should arguably write a custom wrapper which knows how
+ to handle Blob, TypedArrays, and JS strings. */
+ ],
["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"],
["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"],
["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"],
["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"],
["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"],
- ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int"],
+ ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int"
+ /* We should arguably create a hand-written binding
+ which does more flexible text conversion, along the lines of
+ sqlite3_prepare_v3(). The slightly problematic part is the
+ final argument (text destructor). */
+ ],
["sqlite3_close_v2", "int", "sqlite3*"],
["sqlite3_changes", "int", "sqlite3*"],
["sqlite3_clear_bindings","int", "sqlite3_stmt*"],
@@ -509,17 +643,25 @@ self.sqlite3ApiBootstrap = function(config){
"sqlite3*", "string", "int", "int", "*", "*", "*", "*", "*"],
["sqlite3_data_count", "int", "sqlite3_stmt*"],
["sqlite3_db_filename", "string", "sqlite3*", "string"],
+ ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"],
["sqlite3_db_name", "string", "sqlite3*", "int"],
+ ["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"]
+ /* Careful! Short version: de/serialize() are problematic because they
+ might use a different allocator that the user for managing the
+ deserialized block. de/serialize() are ONLY safe to use with
+ sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */,
["sqlite3_errmsg", "string", "sqlite3*"],
["sqlite3_error_offset", "int", "sqlite3*"],
["sqlite3_errstr", "string", "int"],
- //["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**"],
- // ^^^ TODO: we need a wrapper to support passing a function pointer or a function
- // for the callback.
+ /*["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**"
+ Handled seperately to perform translation of the callback
+ into a WASM-usable one. ],*/
["sqlite3_expanded_sql", "string", "sqlite3_stmt*"],
["sqlite3_extended_errcode", "int", "sqlite3*"],
["sqlite3_extended_result_codes", "int", "sqlite3*", "int"],
+ ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"],
["sqlite3_finalize", "int", "sqlite3_stmt*"],
+ ["sqlite3_free", undefined,"*"],
["sqlite3_initialize", undefined],
["sqlite3_interrupt", undefined, "sqlite3*"
/* ^^^ we cannot actually currently support this because JS is
@@ -527,11 +669,13 @@ self.sqlite3ApiBootstrap = function(config){
from 2 SharedWorkers concurrently. */],
["sqlite3_libversion", "string"],
["sqlite3_libversion_number", "int"],
+ ["sqlite3_malloc", "*","int"],
["sqlite3_open", "int", "string", "*"],
["sqlite3_open_v2", "int", "string", "*", "int", "string"],
/* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled
separately due to us requiring two different sets of semantics
for those, depending on how their SQL argument is provided. */
+ ["sqlite3_realloc", "*","*","int"],
["sqlite3_reset", "int", "sqlite3_stmt*"],
["sqlite3_result_blob",undefined, "*", "*", "int", "*"],
["sqlite3_result_double",undefined, "*", "f64"],
@@ -542,12 +686,17 @@ self.sqlite3ApiBootstrap = function(config){
["sqlite3_result_int",undefined, "*", "int"],
["sqlite3_result_null",undefined, "*"],
["sqlite3_result_text",undefined, "*", "string", "int", "*"],
+ ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"],
+ ["sqlite3_shutdown", undefined],
["sqlite3_sourceid", "string"],
["sqlite3_sql", "string", "sqlite3_stmt*"],
["sqlite3_step", "int", "sqlite3_stmt*"],
["sqlite3_strglob", "int", "string","string"],
["sqlite3_strlike", "int", "string","string","int"],
["sqlite3_total_changes", "int", "sqlite3*"],
+ ["sqlite3_uri_boolean", "int", "string", "string", "int"],
+ ["sqlite3_uri_key", "string", "string", "int"],
+ ["sqlite3_uri_parameter", "string", "string", "string"],
["sqlite3_value_blob", "*", "*"],
["sqlite3_value_bytes","int", "*"],
["sqlite3_value_double","f64", "*"],
@@ -570,24 +719,336 @@ self.sqlite3ApiBootstrap = function(config){
dummy impls, depending on the capabilities of the environment.
*/
capi.wasm.bindingSignatures.int64 = [
- ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]],
- ["sqlite3_changes64","i64", ["sqlite3*"]],
- ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]],
- ["sqlite3_total_changes64", "i64", ["sqlite3*"]]
+ ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]],
+ ["sqlite3_changes64","i64", ["sqlite3*"]],
+ ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]],
+ ["sqlite3_malloc64", "*","i64"],
+ ["sqlite3_msize", "i64", "*"],
+ ["sqlite3_realloc64", "*","*", "i64"],
+ ["sqlite3_total_changes64", "i64", ["sqlite3*"]],
+ ["sqlite3_uri_int64", "i64", ["string", "string", "i64"]]
+ ];
+
+ /**
+ Functions which are intended solely for API-internal use by the
+ WASM components, not client code. These get installed into
+ capi.wasm.
+ */
+ capi.wasm.bindingSignatures.wasm = [
+ ["sqlite3_wasm_vfs_unlink", "int", "string"]
];
+ /** State for sqlite3_wasmfs_opfs_dir(). */
+ let __persistentDir = undefined;
+ /**
+ An experiment. Do not use in client code.
+
+ If the wasm environment has a persistent storage directory,
+ its path is returned by this function. If it does not then
+ it returns "" (noting that "" is a falsy value).
+
+ The first time this is called, this function inspects the current
+ environment to determine whether persistence filesystem support
+ is available and, if it is, enables it (if needed).
+
+ This function currently only recognizes the WASMFS/OPFS storage
+ combination. "Plain" OPFS is provided via a separate VFS which
+ is optionally be installed via sqlite3.asyncPostInit().
+
+ TODOs and caveats:
+
+ - If persistent storage is available at the root of the virtual
+ filesystem, this interface cannot currently distinguish that
+ from the lack of persistence. That can (in the mean time)
+ happen when using the JS-native "opfs" VFS, as opposed to the
+ WASMFS/OPFS combination.
+ */
+ capi.sqlite3_wasmfs_opfs_dir = function(){
+ if(undefined !== __persistentDir) return __persistentDir;
+ // If we have no OPFS, there is no persistent dir
+ const pdir = config.wasmfsOpfsDir;
+ if(!pdir
+ || !self.FileSystemHandle
+ || !self.FileSystemDirectoryHandle
+ || !self.FileSystemFileHandle){
+ return __persistentDir = "";
+ }
+ try{
+ if(pdir && 0===capi.wasm.xCallWrapped(
+ 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir
+ )){
+ return __persistentDir = pdir;
+ }else{
+ return __persistentDir = "";
+ }
+ }catch(e){
+ // sqlite3_wasm_init_wasmfs() is not available
+ return __persistentDir = "";
+ }
+ };
+
+ /**
+ Experimental and subject to change or removal.
+
+ Returns true if sqlite3.capi.sqlite3_wasmfs_opfs_dir() is a
+ non-empty string and the given name starts with (that string +
+ '/'), else returns false.
+
+ Potential (but arguable) TODO: return true if the name is one of
+ (":localStorage:", "local", ":sessionStorage:", "session") and
+ kvvfs is available.
+ */
+ capi.sqlite3_web_filename_is_persistent = function(name){
+ const p = capi.sqlite3_wasmfs_opfs_dir();
+ return (p && name) ? name.startsWith(p+'/') : false;
+ };
+
+ // This bit is highly arguable and is incompatible with the fiddle shell.
+ if(false && 0===capi.wasm.exports.sqlite3_vfs_find(0)){
+ /* Assume that sqlite3_initialize() has not yet been called.
+ This will be the case in an SQLITE_OS_KV build. */
+ capi.wasm.exports.sqlite3_initialize();
+ }
+
+ /**
+ Given an `sqlite3*`, an sqlite3_vfs name, and an optional db
+ name, returns a truthy value (see below) if that db handle uses
+ that VFS, else returns false. If pDb is falsy then the 3rd
+ argument is ignored and this function returns a truthy value if
+ the default VFS name matches that of the 2nd argument. Results
+ are undefined if pDb is truthy but refers to an invalid
+ pointer. The 3rd argument specifies the database name of the
+ given database connection to check, defaulting to the main db.
+
+ The 2nd and 3rd arguments may either be a JS string or a C-string
+ allocated from the wasm environment.
+
+ The truthy value it returns is a pointer to the `sqlite3_vfs`
+ object.
+
+ To permit safe use of this function from APIs which may be called
+ via the C stack (like SQL UDFs), this function does not throw: if
+ bad arguments cause a conversion error when passing into
+ wasm-space, false is returned.
+ */
+ capi.sqlite3_web_db_uses_vfs = function(pDb,vfsName,dbName="main"){
+ try{
+ const pK = capi.sqlite3_vfs_find(vfsName);
+ if(!pK) return false;
+ else if(!pDb){
+ return capi.sqlite3_vfs_find(0)===pK ? pK : false;
+ }
+ const ppVfs = capi.wasm.allocPtr();
+ try{
+ return (
+ (0===capi.sqlite3_file_control(
+ pDb, dbName, capi.SQLITE_FCNTL_VFS_POINTER, ppVfs
+ )) && (capi.wasm.getPtrValue(ppVfs) === pK)
+ ) ? pK : false;
+ }finally{
+ capi.wasm.dealloc(ppVfs);
+ }
+ }catch(e){
+ /* Ignore - probably bad args to a wasm-bound function. */
+ return false;
+ }
+ };
+
+ /**
+ Returns an array of the names of all currently-registered sqlite3
+ VFSes.
+ */
+ capi.sqlite3_web_vfs_list = function(){
+ const rc = [];
+ let pVfs = capi.sqlite3_vfs_find(0);
+ while(pVfs){
+ const oVfs = new capi.sqlite3_vfs(pVfs);
+ rc.push(capi.wasm.cstringToJs(oVfs.$zName));
+ pVfs = oVfs.$pNext;
+ oVfs.dispose();
+ }
+ return rc;
+ };
+
+ if( capi.util.isMainWindow() ){
+ /* Features specific to the main window thread... */
+
+ /**
+ Internal helper for sqlite3_web_kvvfs_clear() and friends.
+ Its argument should be one of ('local','session','').
+ */
+ const __kvvfsInfo = function(which){
+ const rc = Object.create(null);
+ rc.prefix = 'kvvfs-'+which;
+ rc.stores = [];
+ if('session'===which || ''===which) rc.stores.push(self.sessionStorage);
+ if('local'===which || ''===which) rc.stores.push(self.localStorage);
+ return rc;
+ };
+
+ /**
+ Clears all storage used by the kvvfs DB backend, deleting any
+ DB(s) stored there. Its argument must be either 'session',
+ 'local', or ''. In the first two cases, only sessionStorage
+ resp. localStorage is cleared. If it's an empty string (the
+ default) then both are cleared. Only storage keys which match
+ the pattern used by kvvfs are cleared: any other client-side
+ data are retained.
+
+ This function is only available in the main window thread.
+
+ Returns the number of entries cleared.
+ */
+ capi.sqlite3_web_kvvfs_clear = function(which=''){
+ let rc = 0;
+ const kvinfo = __kvvfsInfo(which);
+ kvinfo.stores.forEach((s)=>{
+ const toRm = [] /* keys to remove */;
+ let i;
+ for( i = 0; i < s.length; ++i ){
+ const k = s.key(i);
+ if(k.startsWith(kvinfo.prefix)) toRm.push(k);
+ }
+ toRm.forEach((kk)=>s.removeItem(kk));
+ rc += toRm.length;
+ });
+ return rc;
+ };
+
+ /**
+ This routine guesses the approximate amount of
+ window.localStorage and/or window.sessionStorage in use by the
+ kvvfs database backend. Its argument must be one of
+ ('session', 'local', ''). In the first two cases, only
+ sessionStorage resp. localStorage is counted. If it's an empty
+ string (the default) then both are counted. Only storage keys
+ which match the pattern used by kvvfs are counted. The returned
+ value is the "length" value of every matching key and value,
+ noting that JavaScript stores each character in 2 bytes.
+
+ Note that the returned size is not authoritative from the
+ perspective of how much data can fit into localStorage and
+ sessionStorage, as the precise algorithms for determining
+ those limits are unspecified and may include per-entry
+ overhead invisible to clients.
+ */
+ capi.sqlite3_web_kvvfs_size = function(which=''){
+ let sz = 0;
+ const kvinfo = __kvvfsInfo(which);
+ kvinfo.stores.forEach((s)=>{
+ let i;
+ for(i = 0; i < s.length; ++i){
+ const k = s.key(i);
+ if(k.startsWith(kvinfo.prefix)){
+ sz += k.length;
+ sz += s.getItem(k).length;
+ }
+ }
+ });
+ return sz * 2 /* because JS uses UC16 encoding */;
+ };
+
+ }/* main-window-only bits */
+
+
/* The remainder of the API will be set up in later steps. */
- return {
+ const sqlite3 = {
+ WasmAllocError: WasmAllocError,
capi,
- postInit: [
- /* some pieces of the API may install functions into this array,
- and each such function will be called, passed (self,sqlite3),
- at the very end of the API load/init process, where self is
- the current global object and sqlite3 is the object returned
- from sqlite3ApiBootstrap(). This array will be removed at the
- end of the API setup process. */],
- /** Config is needed downstream for gluing pieces together. It
- will be removed at the end of the API setup process. */
- config
+ config,
+ /**
+ Performs any optional asynchronous library-level initialization
+ which might be required. This function returns a Promise which
+ resolves to the sqlite3 namespace object. It _ignores any
+ errors_ in the asynchronous init process, as such components
+ are all optional. If called more than once, the second and
+ subsequent calls are no-ops which return a pre-resolved
+ Promise.
+
+ Ideally this function is called as part of the Promise chain
+ which handles the loading and bootstrapping of the API. If not
+ then it must be called by client-level code, which must not use
+ the library until the returned promise resolves.
+
+ Bug: if called while a prior call is still resolving, the 2nd
+ call will resolve prematurely, before the 1st call has finished
+ resolving. The current build setup precludes that possibility,
+ so it's only a hypothetical problem if/when this function
+ ever needs to be invoked by clients.
+
+ In Emscripten-based builds, this function is called
+ automatically and deleted from this object.
+ */
+ asyncPostInit: async function(){
+ let lip = sqlite3ApiBootstrap.initializersAsync;
+ delete sqlite3ApiBootstrap.initializersAsync;
+ if(!lip || !lip.length) return Promise.resolve(sqlite3);
+ // Is it okay to resolve these in parallel or do we need them
+ // to resolve in order? We currently only have 1, so it
+ // makes no difference.
+ lip = lip.map((f)=>f(sqlite3).catch(()=>{}));
+ //let p = lip.shift();
+ //while(lip.length) p = p.then(lip.shift());
+ //return p.then(()=>sqlite3);
+ return Promise.all(lip).then(()=>sqlite3);
+ }
};
+ sqlite3ApiBootstrap.initializers.forEach((f)=>f(sqlite3));
+ delete sqlite3ApiBootstrap.initializers;
+ sqlite3ApiBootstrap.sqlite3 = sqlite3;
+ return sqlite3;
}/*sqlite3ApiBootstrap()*/;
+/**
+ self.sqlite3ApiBootstrap.initializers is an internal detail used by
+ the various pieces of the sqlite3 API's amalgamation process. It
+ must not be modified by client code except when plugging such code
+ into the amalgamation process.
+
+ Each component of the amalgamation is expected to append a function
+ to this array. When sqlite3ApiBootstrap() is called for the first
+ time, each such function will be called (in their appended order)
+ and passed the sqlite3 namespace object, into which they can install
+ their features (noting that most will also require that certain
+ features alread have been installed). At the end of that process,
+ this array is deleted.
+
+ Note that the order of insertion into this array is significant for
+ some pieces. e.g. sqlite3.capi.wasm cannot be fully utilized until
+ the whwasmutil.js part is plugged in.
+*/
+self.sqlite3ApiBootstrap.initializers = [];
+/**
+ self.sqlite3ApiBootstrap.initializersAsync is an internal detail
+ used by the sqlite3 API's amalgamation process. It must not be
+ modified by client code except when plugging such code into the
+ amalgamation process.
+
+ Counterpart of self.sqlite3ApiBootstrap.initializers, specifically
+ for initializers which are asynchronous. All functions in this list
+ take the sqlite3 object as their argument and MUST return a
+ Promise. Both the resolved value and rejection cases are ignored.
+
+ This list is not processed until the client calls
+ sqlite3.asyncPostInit(). This means, for example, that intializers
+ added to self.sqlite3ApiBootstrap.initializers may push entries to
+ this list.
+*/
+self.sqlite3ApiBootstrap.initializersAsync = [];
+/**
+ Client code may assign sqlite3ApiBootstrap.defaultConfig an
+ object-type value before calling sqlite3ApiBootstrap() (without
+ arguments) in order to tell that call to use this object as its
+ default config value. The intention of this is to provide
+ downstream clients with a reasonably flexible approach for plugging in
+ an environment-suitable configuration without having to define a new
+ global-scope symbol.
+*/
+self.sqlite3ApiBootstrap.defaultConfig = Object.create(null);
+/**
+ Placeholder: gets installed by the first call to
+ self.sqlite3ApiBootstrap(). However, it is recommended that the
+ caller of sqlite3ApiBootstrap() capture its return value and delete
+ self.sqlite3ApiBootstrap after calling it. It returns the same
+ value which will be stored here.
+*/
+self.sqlite3ApiBootstrap.sqlite3 = undefined;