aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/wasm/api/sqlite3-api-cleanup.js68
-rw-r--r--ext/wasm/api/sqlite3-api-glue.js23
-rw-r--r--ext/wasm/api/sqlite3-api-oo1.js8
-rw-r--r--ext/wasm/api/sqlite3-api-opfs.js9
-rw-r--r--ext/wasm/api/sqlite3-api-prologue.js149
-rw-r--r--ext/wasm/api/sqlite3-api-worker1.js7
-rw-r--r--ext/wasm/api/sqlite3-wasm.c23
7 files changed, 193 insertions, 94 deletions
diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js
index a2f921a5d..ce24ad013 100644
--- a/ext/wasm/api/sqlite3-api-cleanup.js
+++ b/ext/wasm/api/sqlite3-api-cleanup.js
@@ -16,29 +16,45 @@
various subsystems.
*/
'use strict';
-self.sqlite3.postInit.forEach(
- self.importScripts/*global is a Worker*/
- ? function(f){
- /** We try/catch/report for the sake of failures which happen in
- a Worker, as those exceptions can otherwise get completely
- swallowed, leading to confusing downstream errors which have
- nothing to do with this failure. */
- try{ f(self, self.sqlite3) }
- catch(e){
- console.error("Error in postInit() function:",e);
- throw e;
- }
- }
- : (f)=>f(self, self.sqlite3)
-);
-delete self.sqlite3.postInit;
-if(self.location && +self.location.port > 1024){
- console.warn("Installing sqlite3 bits as global S for dev-testing purposes.");
- self.S = self.sqlite3;
-}
-/* Clean up temporary global-scope references to our APIs... */
-self.sqlite3.config.Module.sqlite3 = self.sqlite3
-/* ^^^^ Currently needed by test code and Worker API setup */;
-delete self.sqlite3.capi.util /* arguable, but these are (currently) internal-use APIs */;
-delete self.sqlite3 /* clean up our global-scope reference */;
-//console.warn("Module.sqlite3 =",Module.sqlite3);
+(function(){
+ /**
+ Replace sqlite3ApiBootstrap() with a variant which plugs in the
+ Emscripten-based config for all config options which the client
+ does not provide.
+ */
+ const SAB = self.sqlite3ApiBootstrap;
+ self.sqlite3ApiBootstrap = function(apiConfig){
+ apiConfig = apiConfig||{};
+ const configDefaults = {
+ Module: Module /* ==> Emscripten-style Module object. Currently
+ needs to be exposed here for test code. NOT part
+ of the public API. */,
+ exports: Module['asm'],
+ memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */
+ };
+ const config = {};
+ Object.keys(configDefaults).forEach(function(k){
+ config[k] = Object.prototype.hasOwnProperty.call(apiConfig, k)
+ ? apiConfig[k] : configDefaults[k];
+ });
+ return SAB(config);
+ };
+
+ /**
+ 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.
+ */
+ const sqlite3 = self.sqlite3ApiBootstrap();
+
+ if(self.location && +self.location.port > 1024){
+ console.warn("Installing sqlite3 bits as global S for dev-testing purposes.");
+ self.S = sqlite3;
+ }
+
+ /* Clean up temporary references to our APIs... */
+ delete self.sqlite3ApiBootstrap;
+ Module.sqlite3 = sqlite3 /* Currently needed by test code */;
+ delete sqlite3.capi.util /* arguable, but these are (currently) internal-use APIs */;
+ //console.warn("Module.sqlite3 =",Module.sqlite3);
+})();
diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js
index efcd6fea9..84bbdb10a 100644
--- a/ext/wasm/api/sqlite3-api-glue.js
+++ b/ext/wasm/api/sqlite3-api-glue.js
@@ -16,23 +16,9 @@
initializes the main API pieces so that the downstream components
(e.g. sqlite3-api-oo1.js) have all that they need.
*/
-(function(self){
+self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
'use strict';
const toss = (...args)=>{throw new Error(args.join(' '))};
-
- self.sqlite3 = self.sqlite3ApiBootstrap({
- Module: Module /* ==> Emscripten-style Module object. Currently
- needs to be exposed here for test code. NOT part
- of the public API. */,
- exports: Module['asm'],
- memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */,
- bigIntEnabled: !!self.BigInt64Array,
- allocExportName: 'malloc',
- deallocExportName: 'free'
- });
- delete self.sqlite3ApiBootstrap;
-
- const sqlite3 = self.sqlite3;
const capi = sqlite3.capi, wasm = capi.wasm, util = capi.util;
self.WhWasmUtilInstaller(capi.wasm);
delete self.WhWasmUtilInstaller;
@@ -57,7 +43,7 @@
return oldP(v);
};
wasm.xWrap.argAdapter('.pointer', adapter);
- }
+ } /* ".pointer" xWrap() argument adapter */
// WhWasmUtil.xWrap() bindings...
{
@@ -78,7 +64,7 @@
capi[e[0]] = wasm.xWrap.apply(null, e);
}
- /* For functions which cannot work properly unless
+ /* For C API functions which cannot work properly unless
wasm.bigIntEnabled is true, install a bogus impl which
throws if called when bigIntEnabled is false. */
const fI64Disabled = function(fname){
@@ -198,5 +184,4 @@
capi[s.name] = sqlite3.StructBinder(s);
}
}
-
-})(self);
+});
diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js
index 0e04b63eb..be9d8af5a 100644
--- a/ext/wasm/api/sqlite3-api-oo1.js
+++ b/ext/wasm/api/sqlite3-api-oo1.js
@@ -14,10 +14,9 @@
WASM build. It requires that sqlite3-api-glue.js has already run
and it installs its deliverable as self.sqlite3.oo1.
*/
-(function(self){
+self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const toss = (...args)=>{throw new Error(args.join(' '))};
- const sqlite3 = self.sqlite3 || toss("Missing main sqlite3 object.");
const capi = sqlite3.capi, util = capi.util;
/* What follows is colloquially known as "OO API #1". It is a
binding of the sqlite3 API which is designed to be run within
@@ -1547,5 +1546,6 @@
},
DB,
Stmt
- }/*SQLite3 object*/;
-})(self);
+ }/*oo1 object*/;
+});
+
diff --git a/ext/wasm/api/sqlite3-api-opfs.js b/ext/wasm/api/sqlite3-api-opfs.js
index 4acab7770..693432b35 100644
--- a/ext/wasm/api/sqlite3-api-opfs.js
+++ b/ext/wasm/api/sqlite3-api-opfs.js
@@ -31,12 +31,13 @@
// FileSystemDirectoryHandle
// FileSystemFileHandle
// FileSystemFileHandle.prototype.createSyncAccessHandle
-self.sqlite3.postInit.push(function(self, sqlite3){
+self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const warn = console.warn.bind(console),
error = console.error.bind(console);
- if(!self.importScripts || !self.FileSystemFileHandle
- || !self.FileSystemFileHandle.prototype.createSyncAccessHandle){
- warn("OPFS not found or its sync API is not available in this environment.");
+ if(!self.importScripts || !self.FileSystemFileHandle){
+ //|| !self.FileSystemFileHandle.prototype.createSyncAccessHandle){
+ // ^^^ sync API is not required with WASMFS/OPFS backend.
+ warn("OPFS is not available in this environment.");
return;
}else if(!sqlite3.capi.wasm.bigIntEnabled){
error("OPFS requires BigInt support but sqlite3.capi.wasm.bigIntEnabled is false.");
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js
index cf9fd896a..87cc40b41 100644
--- a/ext/wasm/api/sqlite3-api-prologue.js
+++ b/ext/wasm/api/sqlite3-api-prologue.js
@@ -78,25 +78,88 @@
*/
/**
- 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 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. 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:
+
+ - `Module`: Emscripten-style module object. Currently only required
+ by certain test code and is _not_ part of the public interface.
+ (TODO: rename this to EmscriptenModule to be more explicit.)
+
+ - `exports`: the "exports" object for the current WASM
+ environment. In an Emscripten build, this should be set to
+ `Module['asm']`.
+
+ - `memory`: 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"`.
+
+ - `persistentDirName`: 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.
*/
-self.sqlite3ApiBootstrap = function(config){
+self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(apiConfig){
'use strict';
+
+ 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 = {
+ Module: undefined/*needed for some test code, not part of the public API*/,
+ exports: undefined,
+ memory: undefined,
+ bigIntEnabled: !!self.BigInt64Array,
+ allocExportName: 'malloc',
+ deallocExportName: 'free',
+ persistentDirName: '/persistent'
+ };
+ Object.keys(configDefaults).forEach(function(k){
+ config[k] = Object.prototype.hasOwnProperty.call(apiConfig, k)
+ ? apiConfig[k] : configDefaults[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.persistentDirName && !/^\/[^/]+$/.test(config.persistentDirName)){
+ toss("config.persistentDirName 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 +206,18 @@ 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)
+ );
+ };
/**
An Error subclass specifically for reporting Wasm-level malloc()
@@ -591,9 +665,6 @@ self.sqlite3ApiBootstrap = function(config){
TODOs and caveats:
- - The directory name (mount point) for persistent storage is
- currently hard-coded. It needs to be configurable.
-
- If persistent storage is available at the root of the virtual
filesystem, this interface cannot currently distinguish that
from the lack of persistence. That case cannot currently (with
@@ -604,12 +675,17 @@ self.sqlite3ApiBootstrap = function(config){
capi.sqlite3_web_persistent_dir = function(){
if(undefined !== __persistentDir) return __persistentDir;
// If we have no OPFS, there is no persistent dir
- if(!self.FileSystemHandle || !self.FileSystemDirectoryHandle
+ const pdir = config.persistentDirName;
+ if(!pdir
+ || !self.FileSystemHandle
+ || !self.FileSystemDirectoryHandle
|| !self.FileSystemFileHandle){
return __persistentDir = "";
}
try{
- if(0===this.wasm.xCall('sqlite3_wasm_init_opfs')){
+ if(pdir && 0===this.wasm.xCallWrapped(
+ 'sqlite3_wasm_init_opfs', 'i32', ['string'], pdir
+ )){
/** OPFS does not support locking and will trigger errors if
we try to lock. We don't _really_ want to
_unconditionally_ install a non-locking sqlite3 VFS as the
@@ -617,13 +693,12 @@ self.sqlite3ApiBootstrap = function(config){
time being. That said: locking is a no-op on all of the
current WASM storage, so this isn't (currently) as bad as
it may initially seem. */
- const pVfs = this.sqlite3_vfs_find("unix-none");
+ const pVfs = sqlite3.capi.sqlite3_vfs_find("unix-none");
if(pVfs){
- this.sqlite3_vfs_register(pVfs,1);
- //warn("Installed 'unix-none' as the default sqlite3 VFS.");
+ capi.sqlite3_vfs_register(pVfs,1);
+ console.warn("Installed 'unix-none' as the default sqlite3 VFS.");
}
- return __persistentDir =
- "/persistent" /* name is hard-coded in sqlite3_wasm_init_opfs()!*/;
+ return __persistentDir = pdir;
}else{
return __persistentDir = "";
}
@@ -644,19 +719,29 @@ self.sqlite3ApiBootstrap = function(config){
}.bind(capi);
/* The remainder of the API will be set up in later steps. */
- return {
- /**
- An Error subclass which is thrown by this.wasm.alloc() on OOM.
- */
+ 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
};
+ 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.
+*/
+self.sqlite3ApiBootstrap.initializers = [];
+self.sqlite3ApiBootstrap.sqlite3 = undefined /* installed at first call */;
diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js
index 565946bbc..a7b759851 100644
--- a/ext/wasm/api/sqlite3-api-worker1.js
+++ b/ext/wasm/api/sqlite3-api-worker1.js
@@ -43,7 +43,8 @@
In some contexts, however, listening for the above message is
a better fit.
*/
-self.sqlite3.initWorker1API = function(){
+self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+sqlite3.initWorker1API = function(){
'use strict';
/**
UNDER CONSTRUCTION
@@ -418,4 +419,6 @@ self.sqlite3.initWorker1API = function(){
wState.post(evType, response, wMsgHandler.xfer);
};
setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'worker1-ready'}), 0);
-}.bind({self, sqlite3: self.sqlite3});
+}.bind({self, sqlite3});
+});
+
diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c
index 487baecf1..070282b8e 100644
--- a/ext/wasm/api/sqlite3-wasm.c
+++ b/ext/wasm/api/sqlite3-wasm.c
@@ -435,7 +435,8 @@ int sqlite3_wasm_vfs_unlink(const char * zName){
#include <emscripten/console.h>
/*
** This function is NOT part of the sqlite3 public API. It is strictly
-** for use by the sqlite project's own JS/WASM bindings.
+** for use by the sqlite project's own JS/WASM bindings, specifically
+** only when building with Emscripten's WASMFS support.
**
** This function should only be called if the JS side detects the
** existence of the Origin-Private FileSystem (OPFS) APIs in the
@@ -443,14 +444,19 @@ int sqlite3_wasm_vfs_unlink(const char * zName){
** WASMFS backend impl for OPFS. On success, subsequent calls are
** no-ops.
**
+** This function may be passed a "mount point" name, which must have a
+** leading "/" and is currently restricted to a single path component,
+** e.g. "/foo" is legal but "/foo/" and "/foo/bar" are not. If it is
+** NULL or empty, it defaults to "/persistent".
+**
** Returns 0 on success, SQLITE_NOMEM if instantiation of the backend
-** object fails, SQLITE_IOERR if mkdir() of the "/persistent" dir in
+** object fails, SQLITE_IOERR if mkdir() of the zMountPoint dir in
** the virtual FS fails. In builds compiled without SQLITE_WASM_OPFS
** defined, SQLITE_NOTFOUND is returned without side effects.
*/
-int sqlite3_wasm_init_opfs(void){
+int sqlite3_wasm_init_opfs(const char *zMountPoint){
static backend_t pOpfs = 0;
- static const char * zDir = "/persistent";
+ if( !zMountPoint || !*zMountPoint ) zMountPoint = "/persistent";
if( !pOpfs ){
pOpfs = wasmfs_create_opfs_backend();
if( pOpfs ){
@@ -459,12 +465,15 @@ int sqlite3_wasm_init_opfs(void){
}
/** It's not enough to instantiate the backend. We have to create a
mountpoint in the VFS and attach the backend to it. */
- if( pOpfs && 0!=access(zDir, F_OK) ){
+ if( pOpfs && 0!=access(zMountPoint, F_OK) ){
/* mkdir() simply hangs when called from fiddle app. Cause is
not yet determined but the hypothesis is an init-order
issue. */
- const int rc = wasmfs_create_directory(zDir, 0777, pOpfs);
- emscripten_console_log(rc ? "OPFS mkdir failed." : "OPFS mkdir ok.");
+ /* Note that this check and is not robust but it will
+ hypothetically suffice for the transient wasm-based virtual
+ filesystem we're currently running in. */
+ const int rc = wasmfs_create_directory(zMountPoint, 0777, pOpfs);
+ emscripten_console_logf("OPFS mkdir rc=%d", rc);
if(rc) return SQLITE_IOERR;
}
return pOpfs ? 0 : SQLITE_NOMEM;