aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/api')
-rw-r--r--ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api2
-rw-r--r--ext/wasm/api/README.md8
-rw-r--r--ext/wasm/api/sqlite3-api-cleanup.js68
-rw-r--r--ext/wasm/api/sqlite3-api-glue.js36
-rw-r--r--ext/wasm/api/sqlite3-api-oo1.js229
-rw-r--r--ext/wasm/api/sqlite3-api-opfs.js9
-rw-r--r--ext/wasm/api/sqlite3-api-prologue.js260
-rw-r--r--ext/wasm/api/sqlite3-api-worker1.js (renamed from ext/wasm/api/sqlite3-api-worker.js)226
-rw-r--r--ext/wasm/api/sqlite3-wasm.c72
-rw-r--r--ext/wasm/api/sqlite3-worker.js31
10 files changed, 624 insertions, 317 deletions
diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
index 8f103c7c0..4a6b71b8e 100644
--- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
+++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
@@ -68,5 +68,7 @@ _sqlite3_vfs_find
_sqlite3_vfs_register
_sqlite3_wasm_db_error
_sqlite3_wasm_enum_json
+_sqlite3_wasm_init_opfs
+_sqlite3_wasm_vfs_unlink
_malloc
_free
diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md
index 43d2b0dd5..9000697b2 100644
--- a/ext/wasm/api/README.md
+++ b/ext/wasm/api/README.md
@@ -60,17 +60,17 @@ browser client:
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-worker.js`\
+- `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-worker.js`\
+ - `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 API which is implemented in
- `sqlite3-api-worker.js`.
+ and runs the Worker #1 API which is implemented in
+ `sqlite3-api-worker1.js`.
- `sqlite3-api-opfs.js`\
is an in-development/experimental sqlite3 VFS wrapper, the goal of
which being to use Google Chrome's Origin-Private FileSystem (OPFS)
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 e962c93b6..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){
@@ -128,7 +114,7 @@
*/
__prepare.basic = wasm.xWrap('sqlite3_prepare_v3',
"int", ["sqlite3*", "string",
- "int"/*MUST always be negative*/,
+ "int"/*ignored for this impl!*/,
"int", "**",
"**"/*MUST be 0 or null or undefined!*/]);
/**
@@ -148,19 +134,10 @@
/* Documented in the api object's initializer. */
capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
- /* 2022-07-08: xWrap() 'string' arg handling may be able do this
- special-case handling for us. It needs to be tested. Or maybe
- not: we always want to treat pzTail as null when passed a
- non-pointer SQL string and the argument adapters don't have
- enough state to know that. Maybe they could/should, by passing
- the currently-collected args as an array as the 2nd arg to the
- argument adapters? Or maybe we collect all args in an array,
- pass that to an optional post-args-collected callback, and give
- it a chance to manipulate the args before we pass them on? */
if(util.isSQLableTypedArray(sql)) sql = util.typedArrayToString(sql);
switch(typeof sql){
case 'string': return __prepare.basic(pDb, sql, -1, prepFlags, ppStmt, null);
- case 'number': return __prepare.full(pDb, sql, sqlLen||-1, prepFlags, ppStmt, pzTail);
+ case 'number': return __prepare.full(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail);
default:
return util.sqlite3_wasm_db_error(
pDb, capi.SQLITE_MISUSE,
@@ -207,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 9e5473396..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
@@ -59,12 +58,16 @@
enabling clients to unambiguously identify such exceptions.
*/
class SQLite3Error extends Error {
+ /**
+ Constructs this object with a message equal to all arguments
+ concatenated with a space between each one.
+ */
constructor(...args){
- super(...args);
+ super(args.join(' '));
this.name = 'SQLite3Error';
}
};
- const toss3 = (...args)=>{throw new SQLite3Error(args)};
+ const toss3 = (...args)=>{throw new SQLite3Error(...args)};
sqlite3.SQLite3Error = SQLite3Error;
/**
@@ -80,35 +83,57 @@
not resolve to real filenames, but "" uses an on-storage
temporary database and requires that the VFS support that.
- The db is currently opened with a fixed set of flags:
- (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
- SQLITE_OPEN_EXRESCODE). This API will change in the future
- permit the caller to provide those flags via an additional
- argument.
+ The second argument specifies the open/create mode for the
+ database. It must be string containing a sequence of letters (in
+ any order, but case sensitive) specifying the mode:
+
+ - "c" => create if it does not exist, else fail if it does not
+ exist. Implies the "w" flag.
+
+ - "w" => write. Implies "r": a db cannot be write-only.
+
+ - "r" => read-only if neither "w" nor "c" are provided, else it
+ is ignored.
+
+ If "w" is not provided, the db is implicitly read-only, noting that
+ "rc" is meaningless
+
+ Any other letters are currently ignored. The default is
+ "c". These modes are ignored for the special ":memory:" and ""
+ names.
+
+ The final argument is currently unimplemented but will eventually
+ be used to specify an optional sqlite3 VFS implementation name,
+ as for the final argument to sqlite3_open_v2().
For purposes of passing a DB instance to C-style sqlite3
- functions, its read-only `pointer` property holds its `sqlite3*`
- pointer value. That property can also be used to check whether
- this DB instance is still open.
+ functions, the DB object's read-only `pointer` property holds its
+ `sqlite3*` pointer value. That property can also be used to check
+ whether this DB instance is still open.
*/
- const DB = function ctor(fn=':memory:'){
+ const DB = function ctor(fn=':memory:', flags='c', vtab="not yet implemented"){
if('string'!==typeof fn){
toss3("Invalid filename for DB constructor.");
}
+ let ptr, oflags = 0;
+ if( flags.indexOf('c')>=0 ){
+ oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
+ }
+ if( flags.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
+ if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
+ oflags |= capi.SQLITE_OPEN_EXRESCODE;
const stack = capi.wasm.scopedAllocPush();
- let ptr;
try {
const ppDb = capi.wasm.scopedAllocPtr() /* output (sqlite3**) arg */;
- const rc = capi.sqlite3_open_v2(fn, ppDb, capi.SQLITE_OPEN_READWRITE
- | capi.SQLITE_OPEN_CREATE
- | capi.SQLITE_OPEN_EXRESCODE, null);
+ const rc = capi.sqlite3_open_v2(fn, ppDb, oflags, null);
ptr = capi.wasm.getMemValue(ppDb, '*');
ctor.checkRc(ptr, rc);
- }catch(e){
- if(ptr) capi.sqlite3_close_v2(ptr);
+ }catch( e ){
+ if( ptr ) capi.sqlite3_close_v2(ptr);
throw e;
+ }finally{
+ capi.wasm.scopedAllocPop(stack);
}
- finally{capi.wasm.scopedAllocPop(stack);}
this.filename = fn;
__ptrMap.set(this, ptr);
__stmtMap.set(this, Object.create(null));
@@ -173,7 +198,7 @@
};
/**
- Expects to be passed (arguments) from DB.exec() and
+ Expects to be passed the `arguments` object from DB.exec() and
DB.execMulti(). Does the argument processing/validation, throws
on error, and returns a new object on success:
@@ -300,26 +325,24 @@
}
},
/**
- Similar to this.filename but will return NULL for
- special names like ":memory:". Not of much use until
- we have filesystem support. Throws if the DB has
- been closed. If passed an argument it then it will return
- the filename of the ATTACHEd db with that name, else it assumes
- a name of `main`.
+ Similar to this.filename but will return NULL for special names
+ like ":memory:". Not of much use until we have filesystem
+ support. Throws if the DB has been closed. If passed an
+ argument it then it will return the filename of the ATTACHEd db
+ with that name, else it assumes a name of `main`.
*/
- fileName: function(dbName){
- return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName||"main");
+ fileName: function(dbName='main'){
+ return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName);
},
/**
Returns true if this db instance has a name which resolves to a
file. If the name is "" or ":memory:", it resolves to false.
Note that it is not aware of the peculiarities of URI-style
names and a URI-style name for a ":memory:" db will fool it.
+ Returns false if this db is closed.
*/
hasFilename: function(){
- const fn = this.filename;
- if(!fn || ':memory'===fn) return false;
- return true;
+ return this.filename && ':memory'!==this.filename;
},
/**
Returns the name of the given 0-based db number, as documented
@@ -451,14 +474,14 @@
statement in the SQL which has any bindable
parameters. (Empty statements are skipped entirely.)
- - .callback = a function which gets called for each row of
- the FIRST statement in the SQL which has result
- _columns_, but only if that statement has any result
- _rows_. The second argument passed to the callback is
- always the current Stmt object (so that the caller may
- collect column names, or similar). The first argument
- passed to the callback defaults to the current Stmt
- object but may be changed with ...
+ - .callback = a function which gets called for each row of the
+ FIRST statement in the SQL which has result _columns_, but only
+ if that statement has any result _rows_. The callback's "this"
+ is the options object. The second argument passed to the
+ callback is always the current Stmt object (so that the caller
+ may collect column names, or similar). The first argument
+ passed to the callback defaults to the current Stmt object but
+ may be changed with ...
- .rowMode = either a string describing what type of argument
should be passed as the first argument to the callback or an
@@ -479,12 +502,13 @@
the FIRST first statement which has result _columns_ is
appended to the array in the format specified for the `rowMode`
option, with the exception that the only legal values for
- `rowMode` in this case are 'array' or 'object', neither of
- which is the default. It is legal to use both `resultRows` and
- `callback`, but `resultRows` is likely much simpler to use for
- small data sets and can be used over a WebWorker-style message
- interface. execMulti() throws if `resultRows` is set and
- `rowMode` is 'stmt' (which is the default!).
+ `rowMode` in this case are 'array', 'object', or an integer,
+ none of which are the default for `rowMode`. It is legal to use
+ both `resultRows` and `callback`, but `resultRows` is likely
+ much simpler to use for small data sets and can be used over a
+ WebWorker-style message interface. execMulti() throws if
+ `resultRows` is set and `rowMode` is 'stmt' (which is the
+ default!).
- saveSql = an optional array. If set, the SQL of each
executed statement is appended to this array before the
@@ -575,8 +599,8 @@
while(stmt.step()){
stmt._isLocked = true;
const row = arg.cbArg(stmt);
- if(callback) callback(row, stmt);
if(resultRows) resultRows.push(row);
+ if(callback) callback(row, stmt);
stmt._isLocked = false;
}
rowMode = undefined;
@@ -737,7 +761,7 @@
capi.sqlite3_result_null(pCx);
break;
}else if(util.isBindableTypedArray(val)){
- const pBlob = capi.wasm.mallocFromTypedArray(val);
+ const pBlob = capi.wasm.allocFromTypedArray(val);
capi.sqlite3_result_blob(pCx, pBlob, val.byteLength,
capi.SQLITE_TRANSIENT);
capi.wasm.dealloc(pBlob);
@@ -820,6 +844,49 @@
},
/**
+ Starts a transaction, calls the given callback, and then either
+ rolls back or commits the savepoint, depending on whether the
+ callback throws. The callback is passed this db object as its
+ only argument. On success, returns the result of the
+ callback. Throws on error.
+
+ Note that transactions may not be nested, so this will throw if
+ it is called recursively. For nested transactions, use the
+ savepoint() method or manually manage SAVEPOINTs using exec().
+ */
+ transaction: function(callback){
+ affirmDbOpen(this).exec("BEGIN");
+ try {
+ const rc = callback(this);
+ this.exec("COMMIT");
+ return rc;
+ }catch(e){
+ this.exec("ROLLBACK");
+ throw e;
+ }
+ },
+
+ /**
+ This works similarly to transaction() but uses sqlite3's SAVEPOINT
+ feature. This function starts a savepoint (with an unspecified name)
+ and calls the given callback function, passing it this db object.
+ If the callback returns, the savepoint is released (committed). If
+ the callback throws, the savepoint is rolled back. If it does not
+ throw, it returns the result of the callback.
+ */
+ savepoint: function(callback){
+ affirmDbOpen(this).exec("SAVEPOINT oo1");
+ try {
+ const rc = callback(this);
+ this.exec("RELEASE oo1");
+ return rc;
+ }catch(e){
+ this.execMulti("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1");
+ throw e;
+ }
+ },
+
+ /**
This function currently does nothing and always throws. It
WILL BE REMOVED pending other refactoring, to eliminate a hard
dependency on Emscripten. This feature will be moved into a
@@ -1028,7 +1095,7 @@
capi.wasm.scopedAllocPop(stack);
}
}else{
- const pBlob = capi.wasm.mallocFromTypedArray(val);
+ const pBlob = capi.wasm.allocFromTypedArray(val);
try{
rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
capi.SQLITE_TRANSIENT);
@@ -1042,7 +1109,7 @@
console.warn("Unsupported bind() argument type:",val);
toss3("Unsupported bind() argument type: "+(typeof val));
}
- if(rc) checkDbRc(stmt.db.pointer, rc);
+ if(rc) DB.checkRc(stmt.db.pointer, rc);
return stmt;
};
@@ -1059,6 +1126,7 @@
delete __stmtMap.get(this.db)[this.pointer];
capi.sqlite3_finalize(this.pointer);
__ptrMap.delete(this);
+ delete this._mayGet;
delete this.columnCount;
delete this.parameterCount;
delete this.db;
@@ -1228,9 +1296,10 @@
return this;
},
/**
- Steps the statement one time. If the result indicates that
- a row of data is available, true is returned. If no row of
- data is available, false is returned. Throws on error.
+ Steps the statement one time. If the result indicates that a
+ row of data is available, a truthy value is returned.
+ If no row of data is available, a falsy
+ value is returned. Throws on error.
*/
step: function(){
affirmUnlocked(this, 'step()');
@@ -1242,8 +1311,51 @@
this._mayGet = false;
console.warn("sqlite3_step() rc=",rc,"SQL =",
capi.sqlite3_sql(this.pointer));
- checkDbRc(this.db.pointer, rc);
- };
+ DB.checkRc(this.db.pointer, rc);
+ }
+ },
+ /**
+ Functions exactly like step() except that...
+
+ 1) On success, it calls this.reset() and returns this object.
+ 2) On error, it throws and does not call reset().
+
+ This is intended to simplify constructs like:
+
+ ```
+ for(...) {
+ stmt.bind(...).stepReset();
+ }
+ ```
+
+ Note that the reset() call makes it illegal to call this.get()
+ after the step.
+ */
+ stepReset: function(){
+ this.step();
+ return this.reset();
+ },
+ /**
+ Functions like step() except that
+ it finalizes this statement immediately after stepping unless
+ the step cannot be performed because the statement is
+ locked. Throws on error, but any error other than the
+ statement-is-locked case will also trigger finalization of this
+ statement.
+
+ On success, it returns true if the step indicated that a row of
+ data was available, else it returns false.
+
+ This is intended to simplify use cases such as:
+
+ ```
+ aDb.prepare("insert in foo(a) values(?)").bind(123).stepFinalize();
+ ```
+ */
+ stepFinalize: function(){
+ const rc = this.step();
+ this.finalize();
+ return rc;
},
/**
Fetches the value from the given 0-based column index of
@@ -1347,7 +1459,7 @@
default: toss3("Don't know how to translate",
"type of result column #"+ndx+".");
}
- abort("Not reached.");
+ toss3("Not reached.");
},
/** Equivalent to get(ndx) but coerces the result to an
integer. */
@@ -1434,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 60ed61477..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()
@@ -173,36 +247,6 @@ 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:
@@ -365,6 +409,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 +458,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 +471,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);
/**
@@ -554,7 +627,8 @@ self.sqlite3ApiBootstrap = function(config){
["sqlite3_value_text", "string", "*"],
["sqlite3_value_type", "int", "*"],
["sqlite3_vfs_find", "*", "string"],
- ["sqlite3_vfs_register", "int", "*", "int"]
+ ["sqlite3_vfs_register", "int", "*", "int"],
+ ["sqlite3_wasm_vfs_unlink", "int", "string"]
]/*capi.wasm.bindingSignatures*/;
if(false && capi.wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){
@@ -576,18 +650,98 @@ self.sqlite3ApiBootstrap = function(config){
["sqlite3_total_changes64", "i64", ["sqlite3*"]]
];
+ /** State for sqlite3_web_persistent_dir(). */
+ let __persistentDir;
+ /**
+ An experiment. Do not use.
+
+ 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).
+
+ 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 case cannot currently (with
+ WASMFS/OPFS) happen, but it is conceivably possible in future
+ environments or non-browser runtimes (none of which are yet
+ supported targets).
+ */
+ capi.sqlite3_web_persistent_dir = function(){
+ if(undefined !== __persistentDir) return __persistentDir;
+ // If we have no OPFS, there is no persistent dir
+ const pdir = config.persistentDirName;
+ if(!pdir
+ || !self.FileSystemHandle
+ || !self.FileSystemDirectoryHandle
+ || !self.FileSystemFileHandle){
+ return __persistentDir = "";
+ }
+ try{
+ 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
+ default, but we do so here for simplicy's sake for the
+ 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 = sqlite3.capi.sqlite3_vfs_find("unix-none");
+ if(pVfs){
+ capi.sqlite3_vfs_register(pVfs,1);
+ console.warn("Installed 'unix-none' as the default sqlite3 VFS.");
+ }
+ return __persistentDir = pdir;
+ }else{
+ return __persistentDir = "";
+ }
+ }catch(e){
+ // sqlite3_wasm_init_opfs() is not available
+ return __persistentDir = "";
+ }
+ }.bind(capi);
+
+ /**
+ Returns true if sqlite3.capi.sqlite3_web_persistent_dir() is a
+ non-empty string and the given name has that string as its
+ prefix, else returns false.
+ */
+ capi.sqlite3_web_filename_is_persistent = function(name){
+ const p = this.sqlite3_web_persistent_dir();
+ return (p && name) ? name.startsWith(p) : false;
+ }.bind(capi);
+
/* 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
};
+ 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-worker.js b/ext/wasm/api/sqlite3-api-worker1.js
index 95b27b21e..a7b759851 100644
--- a/ext/wasm/api/sqlite3-api-worker.js
+++ b/ext/wasm/api/sqlite3-api-worker1.js
@@ -10,26 +10,41 @@
***********************************************************************
- This file implements a Worker-based wrapper around SQLite3 OO API
- #1.
+ This file implements the initializer for the sqlite3 "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.
+*/
+
+/**
+ This function 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 initWorkerAPI(). If this function
+ worker API requires calling initWorker1API(). If this function
is called from a non-worker thread then it throws an exception.
- When initialized, it installs message listeners to receive messages
- from the main thread and then it posts a message in the form:
+ When initialized, it installs message listeners to receive Worker
+ messages and then it posts a message in the form:
```
- {type:'sqlite3-api',data:'worker-ready'}
+ {type:'sqlite3-api',data:'worker1-ready'}
```
- This file requires that the core C-style sqlite3 API and OO API #1
- have been loaded and that self.sqlite3 contains both,
- as documented for those APIs.
+ 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.
*/
-self.sqlite3.initWorkerAPI = function(){
+self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+sqlite3.initWorker1API = function(){
'use strict';
/**
UNDER CONSTRUCTION
@@ -39,39 +54,12 @@ self.sqlite3.initWorkerAPI = function(){
cannot pass callback functions between the window thread and a
worker thread, so we have to receive all db results via
asynchronous message-passing. That requires an asychronous API
- with a distinctly different shape that the main OO API.
-
- Certain important considerations here include:
-
- - Support only one db connection or multiple? The former is far
- easier, but there's always going to be a user out there who wants
- to juggle six database handles at once. Do we add that complexity
- or tell such users to write their own code using the provided
- lower-level APIs?
-
- - Fetching multiple results: do we pass them on as a series of
- messages, with start/end messages on either end, or do we collect
- all results and bundle them back in a single message? The former
- is, generically speaking, more memory-efficient but the latter
- far easier to implement in this environment. The latter is
- untennable for large data sets. Despite a web page hypothetically
- being a relatively limited environment, there will always be
- those users who feel that they should/need to be able to work
- with multi-hundred-meg (or larger) blobs, and passing around
- arrays of those may quickly exhaust the JS engine's memory.
-
- TODOs include, but are not limited to:
-
- - The ability to manage multiple DB handles. This can
- potentially be done via a simple mapping of DB.filename or
- DB.pointer (`sqlite3*` handle) to DB objects. The open()
- interface would need to provide an ID (probably DB.pointer) back
- to the user which can optionally be passed as an argument to
- the other APIs (they'd default to the first-opened DB, for
- ease of use). Client-side usability of this feature would
- benefit from making another wrapper class (or a singleton)
- available to the main thread, with that object proxying all(?)
- communication with the worker.
+ with a distinctly different shape than OO API #1.
+
+ TODOs include, but are not necessarily limited to:
+
+ - Support for handling multiple DBs via this interface is under
+ development.
- Revisit how virtual files are managed. We currently delete DBs
from the virtual filesystem when we close them, for the sake of
@@ -79,6 +67,8 @@ self.sqlite3.initWorkerAPI = function(){
require that we give up that habit. Similarly, fully supporting
ATTACH, where a user can upload multiple DBs and ATTACH them,
also requires the that we manage the VFS entries better.
+ Related: we most definitely do not want to delete persistent DBs
+ (e.g. stored on OPFS) when they're closed.
*/
const toss = (...args)=>{throw new Error(args.join(' '))};
if('function' !== typeof importScripts){
@@ -116,8 +106,7 @@ self.sqlite3.initWorkerAPI = function(){
// same filename and close/reopen it (or just pass it back as is?).
if(!arg && this.defaultDb) return this.defaultDb;
//???if(this.defaultDb) this.defaultDb.close();
- let db;
- db = (Array.isArray(arg) ? new DB(...arg) : new DB(arg));
+ const db = (Array.isArray(arg) ? new DB(...arg) : new DB(arg));
this.dbs[getDbId(db)] = db;
if(!this.defaultDb) this.defaultDb = db;
return db;
@@ -125,13 +114,17 @@ self.sqlite3.initWorkerAPI = function(){
close: function(db,alsoUnlink){
if(db){
delete this.dbs[getDbId(db)];
- db.close(alsoUnlink);
+ const filename = db.fileName();
+ db.close();
if(db===this.defaultDb) this.defaultDb = undefined;
+ if(alsoUnlink && filename){
+ sqlite3.capi.sqlite3_wasm_vfs_unlink(filename);
+ }
}
},
post: function(type,data,xferList){
if(xferList){
- self.postMessage({type, data},xferList);
+ self.postMessage( {type, data}, xferList );
xferList.length = 0;
}else{
self.postMessage({type, data});
@@ -173,6 +166,68 @@ self.sqlite3.initWorkerAPI = function(){
const wMsgHandler = {
xfer: [/*Temp holder for "transferable" postMessage() state.*/],
/**
+ Proxy for the DB constructor. Expects to be passed a single
+ object or a falsy value to use defaults. The object may
+ have a filename property to name the db file (see the DB
+ constructor for peculiarities and transformations) and/or a
+ buffer property (a Uint8Array holding a complete database
+ file's contents). The response is an object:
+
+ {
+ filename: db filename (possibly differing from the input),
+
+ dbId: an opaque ID value which must be passed 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 first-opened db.
+
+ messageId: if the client-sent message included this field,
+ it is mirrored in the response.
+ }
+ */
+ open: function(ev){
+ const args = [], data = (ev.data || {});
+ if(data.simulateError){ // undocumented internal testing option
+ toss("Throwing because of simulateError flag.");
+ }
+ if(data.filename) args.push(data.filename);
+ const db = wState.open(args);
+ return {
+ filename: db.filename,
+ dbId: getDbId(db)
+ };
+ },
+ /**
+ Proxy for DB.close(). If ev.data may either be a boolean or
+ an object with an `unlink` property. If that value is
+ truthy then the db file (if the db is currently open) will
+ be unlinked from the virtual filesystem, else it will be
+ kept intact. The response object is:
+
+ {
+ filename: db filename _if_ the db is opened when this
+ is called, else the undefined value,
+ unlink: boolean. If true, unlink() (delete) the db file
+ after closing int. Any error while deleting it is
+ ignored.
+ }
+
+ It does not error if the given db is already closed or no db is
+ provided. It is simply does nothing useful in that case.
+ */
+ close: function(ev){
+ const db = getMsgDb(ev,false);
+ const response = {
+ filename: db && db.filename,
+ dbId: db ? getDbId(db) : undefined
+ };
+ if(db){
+ wState.close(db, !!((ev.data && 'object'===typeof ev.data)
+ ? ev.data.unlink : false));
+ }
+ return response;
+ },
+ /**
Proxy for DB.exec() which expects a single argument of type
string (SQL to execute) or an options object in the form
expected by exec(). The notable differences from exec()
@@ -266,10 +321,16 @@ self.sqlite3.initWorkerAPI = function(){
exports of ":memory:" and "" (temp file) DBs. The latter is
ostensibly easy because the file is (potentially) on disk, but
the former does not have a structure which maps directly to a
- db file image.
+ db file image. We can VACUUM INTO a :memory:/temp db into a
+ file for that purpose, though.
*/
export: function(ev){
toss("export() requires reimplementing for portability reasons.");
+ /**
+ We need to reimplement this to use the Emscripten FS
+ interface. That part used to be in the OO#1 API but that
+ dependency was removed from that level of the API.
+ */
/**const db = getMsgDb(ev);
const response = {
buffer: db.exportBinaryImage(),
@@ -279,66 +340,6 @@ self.sqlite3.initWorkerAPI = function(){
this.xfer.push(response.buffer.buffer);
return response;**/
}/*export()*/,
- /**
- Proxy for the DB constructor. Expects to be passed a single
- object or a falsy value to use defaults. The object may
- have a filename property to name the db file (see the DB
- constructor for peculiarities and transformations) and/or a
- buffer property (a Uint8Array holding a complete database
- file's contents). The response is an object:
-
- {
- filename: db filename (possibly differing from the input),
-
- id: an opaque ID value intended for future distinction
- between multiple db handles. Messages including a specific
- ID will use the DB for that ID.
-
- }
-
- If the Worker's db is currently opened, this call closes it
- before proceeding.
- */
- open: function(ev){
- wState.close(/*true???*/);
- const args = [], data = (ev.data || {});
- if(data.simulateError){
- toss("Throwing because of open.simulateError flag.");
- }
- if(data.filename) args.push(data.filename);
- if(data.buffer){
- args.push(data.buffer);
- this.xfer.push(data.buffer.buffer);
- }
- const db = wState.open(args);
- return {
- filename: db.filename,
- dbId: getDbId(db)
- };
- },
- /**
- Proxy for DB.close(). If ev.data may either be a boolean or
- an object with an `unlink` property. If that value is
- truthy then the db file (if the db is currently open) will
- be unlinked from the virtual filesystem, else it will be
- kept intact. The response object is:
-
- {
- filename: db filename _if_ the db is opened when this
- is called, else the undefined value
- }
- */
- close: function(ev){
- const db = getMsgDb(ev,false);
- const response = {
- filename: db && db.filename
- };
- if(db){
- wState.close(db, !!((ev.data && 'object'===typeof ev.data)
- ? ev.data.unlink : ev.data));
- }
- return response;
- },
toss: function(ev){
toss("Testing worker exception");
}
@@ -352,7 +353,8 @@ self.sqlite3.initWorkerAPI = function(){
{ type: apiCommand,
dbId: optional DB ID value (else uses a default db handle)
- data: apiArguments
+ data: apiArguments,
+ messageId: optional client-specific value
}
As a rule, these commands respond with a postMessage() of their
@@ -416,5 +418,7 @@ self.sqlite3.initWorkerAPI = function(){
response.departureTime = ev.departureTime;
wState.post(evType, response, wMsgHandler.xfer);
};
- setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'worker-ready'}), 0);
-}.bind({self, sqlite3: self.sqlite3});
+ setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'worker1-ready'}), 0);
+}.bind({self, sqlite3});
+});
+
diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c
index 6a81da3e5..070282b8e 100644
--- a/ext/wasm/api/sqlite3-wasm.c
+++ b/ext/wasm/api/sqlite3-wasm.c
@@ -411,3 +411,75 @@ const char * sqlite3_wasm_enum_json(void){
#undef outf
#undef lenCheck
}
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** This function invokes the xDelete method of the default VFS,
+** passing on the given filename. If zName is NULL, no default VFS is
+** found, or it has no xDelete method, SQLITE_MISUSE is returned, else
+** the result of the xDelete() call is returned.
+*/
+int sqlite3_wasm_vfs_unlink(const char * zName){
+ int rc = SQLITE_MISUSE /* ??? */;
+ sqlite3_vfs * const pVfs = sqlite3_vfs_find(0);
+ if( zName && pVfs && pVfs->xDelete ){
+ rc = pVfs->xDelete(pVfs, zName, 1);
+ }
+ return rc;
+}
+
+#if defined(__EMSCRIPTEN__) && defined(SQLITE_WASM_OPFS)
+#include <emscripten/wasmfs.h>
+#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, specifically
+** only when building with Emscripten's WASMFS support.
+**
+** This function should only be called if the JS side detects the
+** existence of the Origin-Private FileSystem (OPFS) APIs in the
+** client. The first time it is called, this function instantiates a
+** WASMFS backend impl for OPFS. On success, subsequent calls are
+** no-ops.
+**
+** This function may be passed a "mount point" name, which must have a
+** leading "/" and is currently restricted to a single path component,
+** e.g. "/foo" is legal but "/foo/" and "/foo/bar" are not. If it is
+** NULL or empty, it defaults to "/persistent".
+**
+** Returns 0 on success, SQLITE_NOMEM if instantiation of the backend
+** object fails, SQLITE_IOERR if mkdir() of the zMountPoint dir in
+** the virtual FS fails. In builds compiled without SQLITE_WASM_OPFS
+** defined, SQLITE_NOTFOUND is returned without side effects.
+*/
+int sqlite3_wasm_init_opfs(const char *zMountPoint){
+ static backend_t pOpfs = 0;
+ if( !zMountPoint || !*zMountPoint ) zMountPoint = "/persistent";
+ if( !pOpfs ){
+ pOpfs = wasmfs_create_opfs_backend();
+ if( pOpfs ){
+ emscripten_console_log("Created WASMFS OPFS backend.");
+ }
+ }
+ /** It's not enough to instantiate the backend. We have to create a
+ mountpoint in the VFS and attach the backend to it. */
+ if( pOpfs && 0!=access(zMountPoint, F_OK) ){
+ /* mkdir() simply hangs when called from fiddle app. Cause is
+ not yet determined but the hypothesis is an init-order
+ issue. */
+ /* 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;
+}
+#else
+int sqlite3_wasm_init_opfs(void){
+ return SQLITE_NOTFOUND;
+}
+#endif /* __EMSCRIPTEN__ && SQLITE_WASM_OPFS */
diff --git a/ext/wasm/api/sqlite3-worker.js b/ext/wasm/api/sqlite3-worker.js
deleted file mode 100644
index 48797de8a..000000000
--- a/ext/wasm/api/sqlite3-worker.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- 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', data: 'worker-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.
-*/
-"use strict";
-importScripts('sqlite3.js');
-sqlite3InitModule().then((EmscriptenModule)=>EmscriptenModule.sqlite3.initWorkerAPI());