diff options
Diffstat (limited to 'ext/wasm/api')
-rw-r--r-- | ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api | 2 | ||||
-rw-r--r-- | ext/wasm/api/README.md | 8 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-cleanup.js | 68 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-glue.js | 36 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-oo1.js | 229 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-opfs.js | 9 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-prologue.js | 260 | ||||
-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.c | 72 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-worker.js | 31 |
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()); |