aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api/sqlite3-api-worker1.js
diff options
context:
space:
mode:
authorstephan <stephan@noemail.net>2022-08-25 11:39:12 +0000
committerstephan <stephan@noemail.net>2022-08-25 11:39:12 +0000
commit9afff9f3c539516c29ce0752b5b84adf8f6ebc19 (patch)
tree3d10d4a2121882d143ac56235f4e148f9abba1db /ext/wasm/api/sqlite3-api-worker1.js
parent407f75378e2bfebfd21ca56b6986154f0c35d1ac (diff)
downloadsqlite-9afff9f3c539516c29ce0752b5b84adf8f6ebc19.tar.gz
sqlite-9afff9f3c539516c29ce0752b5b84adf8f6ebc19.zip
Refactor and expand the worker1 docs, consolidating them into the top of their file instead of scattered around the internals. Accommodate an API change from yesterday in demo-oo1.js.
FossilOrigin-Name: 0a65747047322b7b585e281ac275e437ce3f46e1d06105c19117213929a906ad
Diffstat (limited to 'ext/wasm/api/sqlite3-api-worker1.js')
-rw-r--r--ext/wasm/api/sqlite3-api-worker1.js453
1 files changed, 289 insertions, 164 deletions
diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js
index 90c8f0de1..f882f6d76 100644
--- a/ext/wasm/api/sqlite3-api-worker1.js
+++ b/ext/wasm/api/sqlite3-api-worker1.js
@@ -22,19 +22,20 @@
*/
/**
- This function implements a Worker-based wrapper around SQLite3 OO
- API #1, colloquially known as "Worker API #1".
+ sqlite3.initWorker1API() implements a Worker-based wrapper around
+ SQLite3 OO API #1, colloquially known as "Worker API #1".
In order to permit this API to be loaded in worker threads without
automatically registering onmessage handlers, initializing the
- worker API requires calling initWorker1API(). If this function
- is called from a non-worker thread then it throws an exception.
+ worker API requires calling initWorker1API(). If this function is
+ called from a non-worker thread then it throws an exception. It
+ must only be called once per Worker.
When initialized, it installs message listeners to receive Worker
messages and then it posts a message in the form:
```
- {type:'sqlite3-api',result:'worker1-ready'}
+ {type:'sqlite3-api', result:'worker1-ready'}
```
to let the client know that it has been initialized. Clients may
@@ -50,8 +51,275 @@
Promise-based wrapper for this API (`sqlite3-worker1-promiser.js`)
is more comfortable to use in that regard.
+ The documentation for the input and output worker messages for
+ this API follows...
- TODO: hoist the message API docs from deep in this code to here.
+ ====================================================================
+ Common message format...
+
+ Each message posted to the worker has an operation-independent
+ envelope and operation-dependent arguments:
+
+ ```
+ {
+ type: string, // one of: 'open', 'close', 'exec', 'config-get'
+
+ messageId: OPTIONAL arbitrary value. The worker will copy it as-is
+ into response messages to assist in client-side dispatching.
+
+ dbId: a db identifier string (returned by 'open') which tells the
+ operation which database instance to work on. If not provided, the
+ first-opened db is used. This is an "opaque" value, with no
+ inherently useful syntax or information. Its value is subject to
+ change with any given build of this API and cannot be used as a
+ basis for anything useful beyond its one intended purpose.
+
+ args: ...operation-dependent arguments...
+
+ // the framework may add other properties for testing or debugging
+ // purposes.
+
+ }
+ ```
+
+ Response messages, posted back to the main thread, look like:
+
+ ```
+ {
+ type: string. Same as above except for error responses, which have the type
+ 'error',
+
+ messageId: same value, if any, provided by the inbound message
+
+ dbId: the id of the db which was operated on, if any, as returned
+ by the corresponding 'open' operation.
+
+ result: ...operation-dependent result...
+
+ }
+ ```
+
+ ====================================================================
+ Error responses
+
+ Errors are reported messages in an operation-independent format:
+
+ ```
+ {
+ type: 'error',
+
+ messageId: ...as above...,
+
+ dbId: ...as above...
+
+ result: {
+
+ operation: type of the triggering operation: 'open', 'close', ...
+
+ message: ...error message text...
+
+ errorClass: string. The ErrorClass.name property from the thrown exception.
+
+ input: the message object which triggered the error.
+
+ stack: _if available_, a stack trace array.
+
+ }
+
+ }
+ ```
+
+
+ ====================================================================
+ "config-get"
+
+ This operation fetches the serializable parts of the sqlite3 API
+ configuration.
+
+ Message format:
+
+ ```
+ {
+ type: "config-get",
+ messageId: ...as above...,
+ args: currently ignored and may be elided.
+ }
+ ```
+
+ Response:
+
+ ```
+ {
+ type: 'config',
+ messageId: ...as above...,
+ result: {
+
+ persistentDirName: path prefix, if any, of persistent storage.
+ An empty string denotes that no persistent storage is available.
+
+ bigIntEnabled: bool. True if BigInt support is enabled.
+
+ persistenceEnabled: true if persistent storage is enabled in the
+ current environment. Only files stored under persistentDirName
+ will persist, however.
+
+ }
+ }
+ ```
+
+
+ ====================================================================
+ "open" a database
+
+ Message format:
+
+ ```
+ {
+ type: "open",
+ messageId: ...as above...,
+ args:{
+
+ filename [=":memory:" or "" (unspecified)]: the db filename.
+ See the sqlite3.oo1.DB constructor for peculiarities and transformations,
+
+ persistent [=false]: if true and filename is not one of ("",
+ ":memory:"), prepend sqlite3.capi.sqlite3_web_persistent_dir()
+ to the given filename so that it is stored in persistent storage
+ _if_ the environment supports it. If persistent storage is not
+ supported, the filename is used as-is.
+
+ }
+ }
+ ```
+
+ Response:
+
+ ```
+ {
+ type: 'open',
+ messageId: ...as above...,
+ result: {
+ filename: db filename, possibly differing from the input.
+
+ dbId: an opaque ID value which must be passed in the message
+ envelope to other calls in this API to tell them which db to
+ use. If it is not provided to future calls, they will default to
+ operating on the first-opened db. This property is, for API
+ consistency's sake, also part of the contaning message envelope.
+ Only the `open` operation includes it in the `result` property.
+
+ persistent: true if the given filename resides in the
+ known-persistent storage, else false. This determination is
+ independent of the `persistent` input argument.
+ }
+ }
+ ```
+
+ ====================================================================
+ "close" a database
+
+ Message format:
+
+ ```
+ {
+ type: "close",
+ messageId: ...as above...
+ dbId: ...as above...
+ args: OPTIONAL: {
+
+ unlink: if truthy, the associated db will be unlinked (removed)
+ from the virtual filesystems. Failure to unlink is silently
+ ignored.
+
+ }
+ }
+ ```
+
+ If the dbId does not refer to an opened ID, this is a no-op. The
+ inability to close a db (because it's not opened) or delete its
+ file does not trigger an error.
+
+ Response:
+
+ ```
+ {
+ type: 'close',
+ messageId: ...as above...,
+ result: {
+
+ filename: filename of closed db, or undefined if no db was closed
+
+ }
+ }
+ ```
+
+ ====================================================================
+ "exec" SQL
+
+ All SQL execution is processed through the exec operation. It offers
+ most of the features of the oo1.DB.exec() method, with a few limitations
+ imposed by the state having to cross thread boundaries.
+
+ Message format:
+
+ ```
+ {
+ type: "exec",
+ messageId: ...as above...
+ dbId: ...as above...
+ args: string (SQL) or {... see below ...}
+ }
+ ```
+
+ Response:
+
+ ```
+ {
+ type: 'exec',
+ messageId: ...as above...,
+ dbId: ...as above...
+ result: {
+ input arguments, possibly modified. See below.
+ }
+ }
+ ```
+
+ The arguments are in the same form accepted by oo1.DB.exec(), with
+ the exceptions noted below.
+
+ A function-type args.callback property cannot cross
+ the window/Worker boundary, so is not useful here. If
+ args.callback is a string then it is assumed to be a
+ message type key, in which case a callback function will be
+ applied which posts each row result via:
+
+ postMessage({type: thatKeyType,
+ rowNumber: 1-based-#,
+ row: theRow,
+ columnNames: anArray
+ })
+
+ And, at the end of the result set (whether or not any result rows
+ were produced), it will post an identical message with
+ (row=undefined, rowNumber=null) to alert the caller than the result
+ set is completed. Note that a row value of `null` is a legal row
+ result for certain arg.rowMode values.
+
+ (Design note: we don't use (row=undefined, rowNumber=undefined) to
+ indicate end-of-results because fetching those would be
+ indistinguishable from fetching from an empty object unless the
+ client used hasOwnProperty() (or similar) to distinguish "missing
+ property" from "property with the undefined value". Similarly,
+ `null` is a legal value for `row` in some case , whereas the db
+ layer won't emit a result value of `undefined`.)
+
+ The callback proxy must not recurse into this interface. An exec()
+ call will type up the Worker thread, causing any recursion attempt
+ to wait until the first exec() is completed.
+
+ The response is the input options object (or a synthesized one if
+ passed only a string), noting that options.resultRows and
+ options.columnNames may be populated by the call to db.exec().
*/
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
@@ -85,10 +353,15 @@ sqlite3.initWorker1API = function(){
Internal helper for managing Worker-level state.
*/
const wState = {
+ /** First-opened db is the default for future operations when no
+ dbId is provided by the client. */
defaultDb: undefined,
+ /** Sequence number of dbId generation. */
idSeq: 0,
+ /** Map of DB instances to dbId. */
idMap: new WeakMap,
- xfer: [/*Temp holder for "transferable" postMessage() state.*/],
+ /** Temp holder for "transferable" postMessage() state. */
+ xfer: [],
open: function(opt){
const db = new DB(opt.filename);
this.dbs[getDbId(db)] = db;
@@ -122,6 +395,8 @@ sqlite3.initWorker1API = function(){
},
/** Map of DB IDs to DBs. */
dbs: Object.create(null),
+ /** Fetch the DB for the given id. Throw if require=true and the
+ id is not valid, else return the db or undefined. */
getDb: function(id,require=true){
return this.dbs[id]
|| (require ? toss("Unknown (or closed) DB ID:",id) : undefined);
@@ -154,37 +429,6 @@ sqlite3.initWorker1API = function(){
on error.
*/
const wMsgHandler = {
- /**
- Proxy for the DB constructor. Expects to be passed a single
- object or a falsy value to use defaults:
-
- {
- filename [=":memory:" or "" (unspecified)]: the db filename.
- See the sqlite3.oo1.DB constructor for peculiarities and transformations,
-
- persistent [=false]: if true and filename is not one of ("",
- ":memory:"), prepend
- sqlite3.capi.sqlite3_web_persistent_dir() to the given
- filename so that it is stored in persistent storage _if_ the
- environment supports it. If persistent storage is not
- supported, the filename is used as-is.
- }
-
- The response object looks like:
-
- {
- filename: db filename, possibly differing from the input.
-
- dbId: an opaque ID value which must be passed in the message
- envelope to other calls in this API to tell them which db to
- use. If it is not provided to future calls, they will default
- to operating on the first-opened db.
-
- persistent: true if the given filename resides in the
- known-persistent storage, else false. This determination is
- independent of the `persistent` input argument.
- }
- */
open: function(ev){
const oargs = Object.create(null), args = (ev.args || Object.create(null));
if(args.simulateError){ // undocumented internal testing option
@@ -205,28 +449,11 @@ sqlite3.initWorker1API = function(){
rc.dbId = getDbId(db);
return rc;
},
- /**
- Proxy for DB.close(). ev.args may be elided 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, noting that unlink
- failure is ignored. The result object is:
-
- {
- filename: db filename _if_ the db is opened when this
- is called, else the undefined value
-
- dbId: the ID of the closed b, or undefined if none is closed
- }
- 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)
+ filename: db && db.filename
};
if(db){
wState.close(db, ((ev.args && 'object'===typeof ev.args)
@@ -234,52 +461,7 @@ sqlite3.initWorker1API = function(){
}
return response;
},
- /**
- Proxy for oo1.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()
- include:
-
- - The default value for options.rowMode is 'array' because
- the normal default cannot cross the window/Worker boundary.
-
- - A function-type options.callback property cannot cross
- the window/Worker boundary, so is not useful here. If
- options.callback is a string then it is assumed to be a
- message type key, in which case a callback function will be
- applied which posts each row result via:
-
- postMessage({type: thatKeyType,
- rowNumber: 1-based-#,
- row: theRow,
- columnNames: anArray
- })
-
- And, at the end of the result set (whether or not any result
- rows were produced), it will post an identical message with
- (row=undefined, rowNumber=null) to alert the caller than the
- result set is completed. Note that a row value of `null` is
- a legal row result for certain `rowMode` values.
-
- (Design note: we don't use (row=undefined, rowNumber=undefined)
- to indicate end-of-results because fetching those would be
- indistinguishable from fetching from an empty object unless the
- client used hasOwnProperty() (or similar) to distinguish
- "missing property" from "property with the undefined value".
- Similarly, `null` is a legal value for `row` in some case ,
- whereas the db layer won't emit a result value of `undefined`.)
-
- The callback proxy must not recurse into this interface, or
- results are undefined. (It hypothetically cannot recurse
- because an exec() call will be tying up the Worker thread,
- causing any recursion attempt to wait until the first
- exec() is completed.)
-
- The response is the input options object (or a synthesized
- one if passed only a string), noting that
- options.resultRows and options.columnNames may be populated
- by the call to db.exec().
- */
+
exec: function(ev){
const rc = (
'string'===typeof ev.args
@@ -287,6 +469,8 @@ sqlite3.initWorker1API = function(){
if('stmt'===rc.rowMode){
toss("Invalid rowMode for 'exec': stmt mode",
"does not work in the Worker API.");
+ }else if(!rc.sql){
+ toss("'exec' requires input SQL.");
}
const db = getMsgDb(ev);
if(rc.callback || Array.isArray(rc.resultRows)){
@@ -330,21 +514,7 @@ sqlite3.initWorker1API = function(){
}
return rc;
}/*exec()*/,
- /**
- Returns a JSON-friendly form of a _subset_ of sqlite3.config,
- sans any parts which cannot be serialized. Because we cannot,
- from here, distingush whether or not certain objects can be
- serialized, this routine selectively copies certain properties
- rather than trying JSON.stringify() and seeing what happens
- (the results are horrid if the config object contains an
- Emscripten module object).
-
- In addition to the "real" config properties, it sythesizes
- the following:
-
- - persistenceEnabled: true if persistent dir support is available,
- else false.
- */
+
'config-get': function(){
const rc = Object.create(null), src = sqlite3.config;
[
@@ -355,6 +525,7 @@ sqlite3.initWorker1API = function(){
rc.persistenceEnabled = !!sqlite3.capi.sqlite3_web_persistent_dir();
return rc;
},
+
/**
TO(RE)DO, once we can abstract away access to the
JS environment's virtual filesystem. Currently this
@@ -391,58 +562,12 @@ sqlite3.initWorker1API = function(){
wState.xfer.push(response.buffer.buffer);
return response;**/
}/*export()*/,
+
toss: function(ev){
toss("Testing worker exception");
}
}/*wMsgHandler*/;
- /**
- UNDER CONSTRUCTION!
-
- A subset of the DB API is accessible via Worker messages in the
- form:
-
- { type: apiCommand,
- args: apiArguments,
- dbId: optional DB ID value (else uses a default db handle),
- messageId: optional client-specific value
- }
-
- As a rule, these commands respond with a postMessage() of their
- own. The responses always have a `type` property equal to the
- input message's type and an object-format `result` part. If
- the inbound object has a `messageId` property, that property is
- always mirrored in the result object, for use in client-side
- dispatching of these asynchronous results. For example:
-
- {
- type: 'open',
- messageId: ...copied from inbound message...,
- dbId: ID of db which was opened,
- result: {
- dbId: repeat of ^^^, for API consistency's sake,
- filename: ...,
- persistent: false
- },
- ...possibly other framework-internal/testing/debugging info...
- }
-
- Exceptions thrown during processing result in an `error`-type
- event with a payload in the form:
-
- { type: 'error',
- dbId: DB handle ID,
- [messageId: if set in the inbound message],
- result: {
- operation: "inbound message's 'type' value",
- message: error string,
- errorClass: class name of the error type,
- input: ev.data
- }
- }
-
- The individual APIs are documented in the wMsgHandler object.
- */
self.onmessage = function(ev){
ev = ev.data;
let result, dbId = ev.dbId, evType = ev.type;