mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Expand the worker1 'exec' op handling for per-row callbacks for API-level consistency and smooth some edges between worker1 core and worker1-promiser. Add worker1 'config-get' message to fetch the serializable parts of the sqlite3.config state. Improve the 'open' op's handling of the 'persistent' option (noting that we cannot yet test that case from a worker).
FossilOrigin-Name: 509f8839201ec1ea4863bd31493e6c29a0721ca6340755bb96656b828758fea7
This commit is contained in:
@ -252,6 +252,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
out.cbArg = (stmt)=>stmt.get(out.opt.rowMode);
|
||||
break;
|
||||
}
|
||||
/*
|
||||
TODO?: how can we define rowMode such that it uses
|
||||
rowMode of 'object' and returns a given named field from
|
||||
the object. Something like:
|
||||
|
||||
if(?what goes here?){
|
||||
out.cbArg = function f(stmt){return stmt.get(this.obj)[this.colName]}
|
||||
.bind({obj:{}, colName: ???what goes here???}});
|
||||
break;
|
||||
}
|
||||
|
||||
Maybe rowMode:['colName1',... 'colNameN']? That could be
|
||||
ambiguous: might mean "return an object with just these
|
||||
columns".
|
||||
*/
|
||||
toss3("Invalid rowMode:",out.opt.rowMode);
|
||||
}
|
||||
}
|
||||
|
@ -42,25 +42,21 @@
|
||||
initialization is complete, as the initialization is synchronous.
|
||||
In some contexts, however, listening for the above message is
|
||||
a better fit.
|
||||
|
||||
Note that the worker-based interface can be slightly quirky because
|
||||
of its async nature. In particular, any number of messages may be posted
|
||||
to the worker before it starts handling any of them. If, e.g., an
|
||||
"open" operation fails, any subsequent messages will fail. The
|
||||
Promise-based wrapper for this API (`sqlite3-worker1-promiser.js`)
|
||||
is more comfortable to use in that regard.
|
||||
|
||||
|
||||
TODO: hoist the message API docs from deep in this code to here.
|
||||
|
||||
*/
|
||||
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
sqlite3.initWorker1API = function(){
|
||||
'use strict';
|
||||
/**
|
||||
UNDER CONSTRUCTION
|
||||
|
||||
We need an API which can proxy the DB API via a Worker message
|
||||
interface. The primary quirky factor in such an API is that we
|
||||
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 than OO API #1.
|
||||
|
||||
TODOs include, but are not necessarily limited to:
|
||||
|
||||
- Support for handling multiple DBs via this interface is under
|
||||
development.
|
||||
*/
|
||||
const toss = (...args)=>{throw new Error(args.join(' '))};
|
||||
if('function' !== typeof importScripts){
|
||||
toss("Cannot initalize the sqlite3 worker API in the main thread.");
|
||||
@ -86,12 +82,13 @@ sqlite3.initWorker1API = function(){
|
||||
};
|
||||
|
||||
/**
|
||||
Helper for managing Worker-level state.
|
||||
Internal helper for managing Worker-level state.
|
||||
*/
|
||||
const wState = {
|
||||
defaultDb: undefined,
|
||||
idSeq: 0,
|
||||
idMap: new WeakMap,
|
||||
xfer: [/*Temp holder for "transferable" postMessage() state.*/],
|
||||
open: function(opt){
|
||||
const db = new DB(opt.filename);
|
||||
this.dbs[getDbId(db)] = db;
|
||||
@ -109,9 +106,15 @@ sqlite3.initWorker1API = function(){
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
Posts the given worker message value. If xferList is provided,
|
||||
it must be an array, in which case a copy of it passed as
|
||||
postMessage()'s second argument and xferList.length is set to
|
||||
0.
|
||||
*/
|
||||
post: function(msg,xferList){
|
||||
if(xferList){
|
||||
self.postMessage( msg, xferList );
|
||||
if(xferList && xferList.length){
|
||||
self.postMessage( msg, Array.from(xferList) );
|
||||
xferList.length = 0;
|
||||
}else{
|
||||
self.postMessage(msg);
|
||||
@ -146,32 +149,40 @@ sqlite3.initWorker1API = function(){
|
||||
message type key. The onmessage() dispatcher attempts to
|
||||
dispatch all inbound messages to a method of this object,
|
||||
passing it the event.data part of the inbound event object. All
|
||||
methods must return a plain Object containing any response
|
||||
methods must return a plain Object containing any result
|
||||
state, which the dispatcher may amend. All methods must throw
|
||||
on error.
|
||||
*/
|
||||
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). The response is an
|
||||
object:
|
||||
object or a falsy value to use defaults:
|
||||
|
||||
{
|
||||
filename: db filename (possibly differing from the input),
|
||||
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.
|
||||
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: 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.
|
||||
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){
|
||||
@ -179,28 +190,32 @@ sqlite3.initWorker1API = function(){
|
||||
if(args.simulateError){ // undocumented internal testing option
|
||||
toss("Throwing because of simulateError flag.");
|
||||
}
|
||||
if(args.persistent && args.filename){
|
||||
oargs.filename = sqlite3.capi.sqlite3_web_persistent_dir() + args.filename;
|
||||
}else if('' === args.filename){
|
||||
oargs.filename = args.filename;
|
||||
const rc = Object.create(null);
|
||||
const pDir = sqlite3.capi.sqlite3_web_persistent_dir();
|
||||
if(!args.filename || ':memory:'===args.filename){
|
||||
oargs.filename = args.filename || '';
|
||||
}else if(pDir){
|
||||
oargs.filename = pDir + ('/'===args.filename[0] ? args.filename : ('/'+args.filename));
|
||||
}else{
|
||||
oargs.filename = args.filename || ':memory:';
|
||||
}
|
||||
oargs.filename = args.filename;
|
||||
}
|
||||
const db = wState.open(oargs);
|
||||
return {
|
||||
filename: db.filename,
|
||||
dbId: getDbId(db)
|
||||
};
|
||||
rc.filename = db.filename;
|
||||
rc.persistent = !!pDir && db.filename.startsWith(pDir);
|
||||
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. The result object is:
|
||||
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
|
||||
is called, else the undefined value
|
||||
|
||||
dbId: the ID of the closed b, or undefined if none is closed
|
||||
}
|
||||
|
||||
@ -211,7 +226,7 @@ sqlite3.initWorker1API = function(){
|
||||
const db = getMsgDb(ev,false);
|
||||
const response = {
|
||||
filename: db && db.filename,
|
||||
dbId: db ? getDbId(db) : undefined
|
||||
dbId: db && getDbId(db)
|
||||
};
|
||||
if(db){
|
||||
wState.close(db, ((ev.args && 'object'===typeof ev.args)
|
||||
@ -220,7 +235,7 @@ sqlite3.initWorker1API = function(){
|
||||
return response;
|
||||
},
|
||||
/**
|
||||
Proxy for DB.exec() which expects a single argument of type
|
||||
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:
|
||||
@ -234,12 +249,21 @@ sqlite3.initWorker1API = function(){
|
||||
message type key, in which case a callback function will be
|
||||
applied which posts each row result via:
|
||||
|
||||
postMessage({type: thatKeyType, row: theRow})
|
||||
postMessage({type: thatKeyType, rowNumber: 1-based-#, row: theRow})
|
||||
|
||||
And, at the end of the result set (whether or not any
|
||||
result rows were produced), it will post an identical
|
||||
message with row:null to alert the caller than the result
|
||||
set is completed.
|
||||
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
|
||||
@ -250,52 +274,73 @@ sqlite3.initWorker1API = function(){
|
||||
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 exec().
|
||||
|
||||
This opens/creates the Worker's db if needed.
|
||||
by the call to db.exec().
|
||||
*/
|
||||
exec: function(ev){
|
||||
const opt = (
|
||||
const rc = (
|
||||
'string'===typeof ev.args
|
||||
) ? {sql: ev.args} : (ev.args || Object.create(null));
|
||||
if(undefined===opt.rowMode){
|
||||
if(undefined===rc.rowMode){
|
||||
/* Since the default rowMode of 'stmt' is not useful
|
||||
for the Worker interface, we'll default to
|
||||
something else. */
|
||||
opt.rowMode = 'array';
|
||||
}else if('stmt'===opt.rowMode){
|
||||
toss("Invalid rowMode for exec(): stmt mode",
|
||||
rc.rowMode = 'array';
|
||||
}else if('stmt'===rc.rowMode){
|
||||
toss("Invalid rowMode for 'exec': stmt mode",
|
||||
"does not work in the Worker API.");
|
||||
}
|
||||
const db = getMsgDb(ev);
|
||||
if(opt.callback || Array.isArray(opt.resultRows)){
|
||||
if(rc.callback || Array.isArray(rc.resultRows)){
|
||||
// Part of a copy-avoidance optimization for blobs
|
||||
db._blobXfer = this.xfer;
|
||||
db._blobXfer = wState.xfer;
|
||||
}
|
||||
const callbackMsgType = opt.callback;
|
||||
const callbackMsgType = rc.callback;
|
||||
let rowNumber = 0;
|
||||
if('string' === typeof callbackMsgType){
|
||||
/* Treat this as a worker message type and post each
|
||||
row as a message of that type. */
|
||||
const that = this;
|
||||
opt.callback =
|
||||
(row)=>wState.post({type: callbackMsgType, row:row}, this.xfer);
|
||||
rc.callback =
|
||||
(row)=>wState.post({type: callbackMsgType, rowNumber:++rowNumber, row:row}, wState.xfer);
|
||||
}
|
||||
try {
|
||||
db.exec(opt);
|
||||
if(opt.callback instanceof Function){
|
||||
opt.callback = callbackMsgType;
|
||||
wState.post({type: callbackMsgType, row: null});
|
||||
db.exec(rc);
|
||||
if(rc.callback instanceof Function){
|
||||
rc.callback = callbackMsgType;
|
||||
wState.post({type: callbackMsgType, rowNumber: null, row: undefined});
|
||||
}
|
||||
}/*catch(e){
|
||||
console.warn("Worker is propagating:",e);throw e;
|
||||
}*/finally{
|
||||
delete db._blobXfer;
|
||||
if(opt.callback){
|
||||
opt.callback = callbackMsgType;
|
||||
}
|
||||
}
|
||||
return opt;
|
||||
}finally{
|
||||
delete db._blobXfer;
|
||||
if(rc.callback){
|
||||
rc.callback = callbackMsgType;
|
||||
}
|
||||
}
|
||||
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;
|
||||
[
|
||||
'persistentDirName', 'bigIntEnabled'
|
||||
].forEach(function(k){
|
||||
if(Object.getOwnPropertyDescriptor(src, k)) rc[k] = src[k];
|
||||
});
|
||||
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
|
||||
@ -329,7 +374,7 @@ sqlite3.initWorker1API = function(){
|
||||
filename: db.filename,
|
||||
mimetype: 'application/x-sqlite3'
|
||||
};
|
||||
this.xfer.push(response.buffer.buffer);
|
||||
wState.xfer.push(response.buffer.buffer);
|
||||
return response;**/
|
||||
}/*export()*/,
|
||||
toss: function(ev){
|
||||
@ -344,22 +389,32 @@ sqlite3.initWorker1API = function(){
|
||||
form:
|
||||
|
||||
{ type: apiCommand,
|
||||
dbId: optional DB ID value (else uses a default db handle),
|
||||
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 in the form:
|
||||
|
||||
TODO: refactoring is underway.
|
||||
|
||||
The responses always have an object-format `result` part. If the
|
||||
inbound object has a `messageId` property, that property is
|
||||
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. Exceptions thrown
|
||||
during processing result in an `error`-type event with a payload
|
||||
in the form:
|
||||
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,
|
||||
@ -413,10 +468,15 @@ sqlite3.initWorker1API = function(){
|
||||
workerReceivedTime: arrivalTime,
|
||||
workerRespondTime: performance.now(),
|
||||
departureTime: ev.departureTime,
|
||||
// TODO: move the timing bits into...
|
||||
//timing:{
|
||||
// departure: ev.departureTime,
|
||||
// workerReceived: arrivalTime,
|
||||
// workerResponse: performance.now();
|
||||
//},
|
||||
result: result
|
||||
}, wMsgHandler.xfer);
|
||||
}, wState.xfer);
|
||||
};
|
||||
self.postMessage({type:'sqlite3-api',result:'worker1-ready'});
|
||||
}.bind({self, sqlite3});
|
||||
});
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
manipulated via a Promise-based interface and returns a factory
|
||||
function which returns Promises for communicating with the worker.
|
||||
This proxy has an _almost_ identical interface to the normal
|
||||
worker API, with any exceptions noted below.
|
||||
worker API, with any exceptions documented below.
|
||||
|
||||
It requires a configuration object with the following properties:
|
||||
|
||||
@ -127,26 +127,34 @@
|
||||
- exec's {callback: STRING} option does not work via this
|
||||
interface (it triggers an exception), but {callback: function}
|
||||
does and works exactly like the STRING form does in the Worker:
|
||||
the callback is called one time for each row of the result set
|
||||
and once more, at the end, passed only `null`, to indicate that
|
||||
the callback is called one time for each row of the result set,
|
||||
passed the same worker message format as the worker API emits:
|
||||
|
||||
{type:typeString, row:VALUE, rowNumber:1-based-#}
|
||||
|
||||
Where `typeString` is an internally-synthesized message type string
|
||||
used temporarily for worker message dispatching. It can be ignored
|
||||
by all client code except that which tests this API. The `row`
|
||||
property contains the row result in the form implied by the
|
||||
`rowMode` option (defaulting to `'array'`). The `rowNumber` is a
|
||||
1-based integer value incremented by 1 on each call into th
|
||||
callback.
|
||||
|
||||
At the end of the result set, the same event is fired with
|
||||
(row=undefined, rowNumber=null) to indicate that
|
||||
the end of the result set has been reached. Note that the rows
|
||||
arrive via worker-posted messages, with all the implications
|
||||
of that.
|
||||
|
||||
|
||||
TODO?: a config option which causes it to queue up events to fire
|
||||
one at a time and flush the event queue on the first error. The
|
||||
main use for this is test runs which must fail at the first error.
|
||||
*/
|
||||
self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
|
||||
// Inspired by: https://stackoverflow.com/a/52439530
|
||||
let idNumber = 0;
|
||||
const handlerMap = Object.create(null);
|
||||
const noop = function(){};
|
||||
const err = config.onerror || noop;
|
||||
const debug = config.debug || noop;
|
||||
const idTypeMap = config.generateMessageId ? undefined : Object.create(null);
|
||||
const genMsgId = config.generateMessageId || function(msg){
|
||||
return msg.type+'#'+(++idNumber);
|
||||
return msg.type+'#'+(idTypeMap[msg.type] = (idTypeMap[msg.type]||0) + 1);
|
||||
};
|
||||
const toss = (...args)=>{throw new Error(args.join(' '))};
|
||||
if('function'===typeof config.worker) config.worker = config.worker();
|
||||
@ -162,7 +170,7 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
|
||||
}
|
||||
msgHandler = handlerMap[ev.type] /* check for exec per-row callback */;
|
||||
if(msgHandler && msgHandler.onrow){
|
||||
msgHandler.onrow(ev.row);
|
||||
msgHandler.onrow(ev);
|
||||
return;
|
||||
}
|
||||
if(config.onunhandled) config.onunhandled(arguments[0]);
|
||||
@ -183,13 +191,10 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
|
||||
default:
|
||||
break;
|
||||
}
|
||||
try {
|
||||
msgHandler.resolve(ev);
|
||||
}catch(e){
|
||||
msgHandler.reject(e);
|
||||
}
|
||||
try {msgHandler.resolve(ev)}
|
||||
catch(e){msgHandler.reject(e)}
|
||||
}/*worker.onmessage()*/;
|
||||
return function(/*(msgType, msgArgs) || (msg)*/){
|
||||
return function(/*(msgType, msgArgs) || (msgEnvelope)*/){
|
||||
let msg;
|
||||
if(1===arguments.length){
|
||||
msg = arguments[0];
|
||||
@ -206,15 +211,29 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
|
||||
msg.departureTime = performance.now();
|
||||
const proxy = Object.create(null);
|
||||
proxy.message = msg;
|
||||
let cbId /* message handler ID for exec on-row callback proxy */;
|
||||
let rowCallbackId /* message handler ID for exec on-row callback proxy */;
|
||||
if('exec'===msg.type && msg.args){
|
||||
if('function'===typeof msg.args.callback){
|
||||
cbId = genMsgId(msg)+':row';
|
||||
rowCallbackId = msg.messageId+':row';
|
||||
proxy.onrow = msg.args.callback;
|
||||
msg.args.callback = cbId;
|
||||
handlerMap[cbId] = proxy;
|
||||
msg.args.callback = rowCallbackId;
|
||||
handlerMap[rowCallbackId] = proxy;
|
||||
}else if('string' === typeof msg.args.callback){
|
||||
toss("exec callback may not be a string when using the Promise interface.");
|
||||
/**
|
||||
Design note: the reason for this limitation is that this
|
||||
API takes over worker.onmessage() and the client has no way
|
||||
of adding their own message-type handlers to it. Per-row
|
||||
callbacks are implemented as short-lived message.type
|
||||
mappings for worker.onmessage().
|
||||
|
||||
We "could" work around this by providing a new
|
||||
config.fallbackMessageHandler (or some such) which contains
|
||||
a map of event type names to callbacks. Seems like overkill
|
||||
for now, seeing as the client can pass callback functions
|
||||
to this interface (whereas the string-form "callback" is
|
||||
needed for the over-the-Worker interface).
|
||||
*/
|
||||
}
|
||||
}
|
||||
//debug("requestWork", msg);
|
||||
@ -225,7 +244,7 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
|
||||
debug("Posting",msg.type,"message to Worker dbId="+(config.dbId||'default')+':',msg);
|
||||
config.worker.postMessage(msg);
|
||||
});
|
||||
if(cbId) p = p.finally(()=>delete handlerMap[cbId]);
|
||||
if(rowCallbackId) p = p.finally(()=>delete handlerMap[rowCallbackId]);
|
||||
return p;
|
||||
};
|
||||
}/*sqlite3Worker1Promiser()*/;
|
||||
|
@ -60,24 +60,41 @@
|
||||
delete self.sqlite3Worker1Promiser;
|
||||
|
||||
const wtest = async function(msgType, msgArgs, callback){
|
||||
if(2===arguments.length && 'function'===typeof msgArgs){
|
||||
callback = msgArgs;
|
||||
msgArgs = undefined;
|
||||
}
|
||||
const p = workerPromise({type: msgType, args:msgArgs});
|
||||
return callback ? p.then(callback).finally(testCount) : p;
|
||||
};
|
||||
|
||||
const runTests = async function(){
|
||||
const dbFilename = '/testing2.sqlite3';
|
||||
logHtml('',
|
||||
"Sending 'open' message and waiting for its response before continuing.");
|
||||
startTime = performance.now();
|
||||
|
||||
let sqConfig;
|
||||
await wtest('config-get', (ev)=>{
|
||||
const r = ev.result;
|
||||
log('sqlite3.config subset:', r);
|
||||
T.assert('boolean' === typeof r.bigIntEnabled)
|
||||
.assert('string'===typeof r.persistentDirName)
|
||||
.assert('boolean' === typeof r.persistenceEnabled);
|
||||
sqConfig = r;
|
||||
});
|
||||
logHtml('',
|
||||
"Sending 'open' message and waiting for its response before continuing...");
|
||||
|
||||
await wtest('open', {
|
||||
filename: dbFilename,
|
||||
persistent: true,
|
||||
persistent: sqConfig.persistenceEnabled,
|
||||
simulateError: 0 /* if true, fail the 'open' */,
|
||||
}, function(ev){
|
||||
log("then open result",ev);
|
||||
T.assert(1 && (dbFilename===ev.result.filename
|
||||
|| (sqlite3TestModule.sqlite3ApiConfig.persistentDirName
|
||||
+ dbFilename)==ev.result.filename))
|
||||
const r = ev.result;
|
||||
log("then open result",r);
|
||||
T.assert(r.persistent === sqConfig.persistenceEnabled)
|
||||
.assert(r.persistent
|
||||
? (dbFilename!==r.filename)
|
||||
: (dbFilename==r.filename))
|
||||
.assert(ev.dbId)
|
||||
.assert(ev.messageId)
|
||||
.assert(promiserConfig.dbId === ev.dbId);
|
||||
@ -145,11 +162,17 @@
|
||||
.assert(3 === ev.resultRows[1][0]);
|
||||
});
|
||||
|
||||
const resultRowTest1 = function f(row){
|
||||
const resultRowTest1 = function f(ev){
|
||||
if(undefined === f.counter) f.counter = 0;
|
||||
if(row) ++f.counter;
|
||||
//log("exec() result row:",row);
|
||||
T.assert(null===row || 'number' === typeof row.b);
|
||||
if(null === ev.rowNumber){
|
||||
/* End of result set. */
|
||||
T.assert(undefined === ev.row);
|
||||
}else{
|
||||
T.assert(ev.rowNumber > 0);
|
||||
++f.counter;
|
||||
}
|
||||
log("exec() result row:",ev);
|
||||
T.assert(null === ev.rowNumber || 'number' === typeof ev.row.b);
|
||||
};
|
||||
await wtest('exec',{
|
||||
sql: 'select a a, b b from t order by a',
|
||||
@ -195,10 +218,9 @@
|
||||
T.assert('string' === typeof ev.result.filename);
|
||||
});
|
||||
|
||||
await wtest('close').then((ev)=>{
|
||||
await wtest('close', (ev)=>{
|
||||
T.assert(undefined === ev.result.filename);
|
||||
log("That's all, folks!");
|
||||
});
|
||||
}).finally(()=>log("That's all, folks!"));
|
||||
}/*runTests2()*/;
|
||||
|
||||
|
||||
|
@ -115,9 +115,15 @@
|
||||
},
|
||||
resultRowTest1: function f(ev){
|
||||
if(undefined === f.counter) f.counter = 0;
|
||||
if(ev.row) ++f.counter;
|
||||
//log("exec() result row:",ev.row);
|
||||
T.assert(null===ev.row || 'number' === typeof ev.row.b);
|
||||
if(null === ev.rowNumber){
|
||||
/* End of result set. */
|
||||
T.assert(undefined === ev.row);
|
||||
}else{
|
||||
T.assert(ev.rowNumber > 0);
|
||||
++f.counter;
|
||||
}
|
||||
//log("exec() result row:",ev);
|
||||
T.assert(null === ev.rowNumber || 'number' === typeof ev.row.b);
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user