1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +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:
stephan
2022-08-24 18:39:46 +00:00
parent 9c765e7945
commit 3734401a95
7 changed files with 264 additions and 142 deletions

View File

@ -252,6 +252,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
out.cbArg = (stmt)=>stmt.get(out.opt.rowMode); out.cbArg = (stmt)=>stmt.get(out.opt.rowMode);
break; 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); toss3("Invalid rowMode:",out.opt.rowMode);
} }
} }

View File

@ -42,25 +42,21 @@
initialization is complete, as the initialization is synchronous. initialization is complete, as the initialization is synchronous.
In some contexts, however, listening for the above message is In some contexts, however, listening for the above message is
a better fit. 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){ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
sqlite3.initWorker1API = function(){ sqlite3.initWorker1API = function(){
'use strict'; '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(' '))}; const toss = (...args)=>{throw new Error(args.join(' '))};
if('function' !== typeof importScripts){ if('function' !== typeof importScripts){
toss("Cannot initalize the sqlite3 worker API in the main thread."); 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 = { const wState = {
defaultDb: undefined, defaultDb: undefined,
idSeq: 0, idSeq: 0,
idMap: new WeakMap, idMap: new WeakMap,
xfer: [/*Temp holder for "transferable" postMessage() state.*/],
open: function(opt){ open: function(opt){
const db = new DB(opt.filename); const db = new DB(opt.filename);
this.dbs[getDbId(db)] = db; 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){ post: function(msg,xferList){
if(xferList){ if(xferList && xferList.length){
self.postMessage( msg, xferList ); self.postMessage( msg, Array.from(xferList) );
xferList.length = 0; xferList.length = 0;
}else{ }else{
self.postMessage(msg); self.postMessage(msg);
@ -146,32 +149,40 @@ sqlite3.initWorker1API = function(){
message type key. The onmessage() dispatcher attempts to message type key. The onmessage() dispatcher attempts to
dispatch all inbound messages to a method of this object, dispatch all inbound messages to a method of this object,
passing it the event.data part of the inbound event object. All 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 state, which the dispatcher may amend. All methods must throw
on error. on error.
*/ */
const wMsgHandler = { const wMsgHandler = {
xfer: [/*Temp holder for "transferable" postMessage() state.*/],
/** /**
Proxy for the DB constructor. Expects to be passed a single Proxy for the DB constructor. Expects to be passed a single
object or a falsy value to use defaults. The object may have a object or a falsy value to use defaults:
filename property to name the db file (see the DB constructor
for peculiarities and transformations). The response is an
object:
{ {
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 dbId: an opaque ID value which must be passed in the message
envelope to other calls in this API to tell them which envelope to other calls in this API to tell them which db to
db to use. If it is not provided to future calls, they use. If it is not provided to future calls, they will default
will default to operating on the first-opened db. to operating on the first-opened db.
persistent: prepend sqlite3.capi.sqlite3_web_persistent_dir() persistent: true if the given filename resides in the
to the given filename so that it is stored known-persistent storage, else false. This determination is
in persistent storage _if_ the environment supports it. independent of the `persistent` input argument.
If persistent storage is not supported, the filename
is used as-is.
} }
*/ */
open: function(ev){ open: function(ev){
@ -179,28 +190,32 @@ sqlite3.initWorker1API = function(){
if(args.simulateError){ // undocumented internal testing option if(args.simulateError){ // undocumented internal testing option
toss("Throwing because of simulateError flag."); toss("Throwing because of simulateError flag.");
} }
if(args.persistent && args.filename){ const rc = Object.create(null);
oargs.filename = sqlite3.capi.sqlite3_web_persistent_dir() + args.filename; const pDir = sqlite3.capi.sqlite3_web_persistent_dir();
}else if('' === args.filename){ if(!args.filename || ':memory:'===args.filename){
oargs.filename = args.filename; oargs.filename = args.filename || '';
}else if(pDir){
oargs.filename = pDir + ('/'===args.filename[0] ? args.filename : ('/'+args.filename));
}else{ }else{
oargs.filename = args.filename || ':memory:'; oargs.filename = args.filename;
} }
const db = wState.open(oargs); const db = wState.open(oargs);
return { rc.filename = db.filename;
filename: db.filename, rc.persistent = !!pDir && db.filename.startsWith(pDir);
dbId: getDbId(db) rc.dbId = getDbId(db);
}; return rc;
}, },
/** /**
Proxy for DB.close(). ev.args may be elided or an object with 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 an `unlink` property. If that value is truthy then the db file
(if the db is currently open) will be unlinked from the virtual (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 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 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 db = getMsgDb(ev,false);
const response = { const response = {
filename: db && db.filename, filename: db && db.filename,
dbId: db ? getDbId(db) : undefined dbId: db && getDbId(db)
}; };
if(db){ if(db){
wState.close(db, ((ev.args && 'object'===typeof ev.args) wState.close(db, ((ev.args && 'object'===typeof ev.args)
@ -220,7 +235,7 @@ sqlite3.initWorker1API = function(){
return response; 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 string (SQL to execute) or an options object in the form
expected by exec(). The notable differences from exec() expected by exec(). The notable differences from exec()
include: include:
@ -234,12 +249,21 @@ sqlite3.initWorker1API = function(){
message type key, in which case a callback function will be message type key, in which case a callback function will be
applied which posts each row result via: 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 And, at the end of the result set (whether or not any result
result rows were produced), it will post an identical rows were produced), it will post an identical message with
message with row:null to alert the caller than the result (row=undefined, rowNumber=null) to alert the caller than the
set is completed. 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 The callback proxy must not recurse into this interface, or
results are undefined. (It hypothetically cannot recurse results are undefined. (It hypothetically cannot recurse
@ -250,52 +274,73 @@ sqlite3.initWorker1API = function(){
The response is the input options object (or a synthesized The response is the input options object (or a synthesized
one if passed only a string), noting that one if passed only a string), noting that
options.resultRows and options.columnNames may be populated options.resultRows and options.columnNames may be populated
by the call to exec(). by the call to db.exec().
This opens/creates the Worker's db if needed.
*/ */
exec: function(ev){ exec: function(ev){
const opt = ( const rc = (
'string'===typeof ev.args 'string'===typeof ev.args
) ? {sql: ev.args} : (ev.args || Object.create(null)); ) ? {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 /* Since the default rowMode of 'stmt' is not useful
for the Worker interface, we'll default to for the Worker interface, we'll default to
something else. */ something else. */
opt.rowMode = 'array'; rc.rowMode = 'array';
}else if('stmt'===opt.rowMode){ }else if('stmt'===rc.rowMode){
toss("Invalid rowMode for exec(): stmt mode", toss("Invalid rowMode for 'exec': stmt mode",
"does not work in the Worker API."); "does not work in the Worker API.");
} }
const db = getMsgDb(ev); 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 // 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){ if('string' === typeof callbackMsgType){
/* Treat this as a worker message type and post each /* Treat this as a worker message type and post each
row as a message of that type. */ row as a message of that type. */
const that = this; rc.callback =
opt.callback = (row)=>wState.post({type: callbackMsgType, rowNumber:++rowNumber, row:row}, wState.xfer);
(row)=>wState.post({type: callbackMsgType, row:row}, this.xfer);
} }
try { try {
db.exec(opt); db.exec(rc);
if(opt.callback instanceof Function){ if(rc.callback instanceof Function){
opt.callback = callbackMsgType; rc.callback = callbackMsgType;
wState.post({type: callbackMsgType, row: null}); wState.post({type: callbackMsgType, rowNumber: null, row: undefined});
} }
}/*catch(e){ }finally{
console.warn("Worker is propagating:",e);throw e;
}*/finally{
delete db._blobXfer; delete db._blobXfer;
if(opt.callback){ if(rc.callback){
opt.callback = callbackMsgType; rc.callback = callbackMsgType;
} }
} }
return opt; return rc;
}/*exec()*/, }/*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 TO(RE)DO, once we can abstract away access to the
JS environment's virtual filesystem. Currently this JS environment's virtual filesystem. Currently this
@ -329,7 +374,7 @@ sqlite3.initWorker1API = function(){
filename: db.filename, filename: db.filename,
mimetype: 'application/x-sqlite3' mimetype: 'application/x-sqlite3'
}; };
this.xfer.push(response.buffer.buffer); wState.xfer.push(response.buffer.buffer);
return response;**/ return response;**/
}/*export()*/, }/*export()*/,
toss: function(ev){ toss: function(ev){
@ -344,22 +389,32 @@ sqlite3.initWorker1API = function(){
form: form:
{ type: apiCommand, { type: apiCommand,
dbId: optional DB ID value (else uses a default db handle),
args: apiArguments, args: apiArguments,
dbId: optional DB ID value (else uses a default db handle),
messageId: optional client-specific value messageId: optional client-specific value
} }
As a rule, these commands respond with a postMessage() of their As a rule, these commands respond with a postMessage() of their
own in the form: own. The responses always have a `type` property equal to the
input message's type and an object-format `result` part. If
TODO: refactoring is underway. the inbound object has a `messageId` property, that property is
The responses always have 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 always mirrored in the result object, for use in client-side
dispatching of these asynchronous results. Exceptions thrown dispatching of these asynchronous results. For example:
during processing result in an `error`-type event with a payload
in the form: {
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', { type: 'error',
dbId: DB handle ID, dbId: DB handle ID,
@ -413,10 +468,15 @@ sqlite3.initWorker1API = function(){
workerReceivedTime: arrivalTime, workerReceivedTime: arrivalTime,
workerRespondTime: performance.now(), workerRespondTime: performance.now(),
departureTime: ev.departureTime, departureTime: ev.departureTime,
// TODO: move the timing bits into...
//timing:{
// departure: ev.departureTime,
// workerReceived: arrivalTime,
// workerResponse: performance.now();
//},
result: result result: result
}, wMsgHandler.xfer); }, wState.xfer);
}; };
self.postMessage({type:'sqlite3-api',result:'worker1-ready'}); self.postMessage({type:'sqlite3-api',result:'worker1-ready'});
}.bind({self, sqlite3}); }.bind({self, sqlite3});
}); });

View File

@ -27,7 +27,7 @@
manipulated via a Promise-based interface and returns a factory manipulated via a Promise-based interface and returns a factory
function which returns Promises for communicating with the worker. function which returns Promises for communicating with the worker.
This proxy has an _almost_ identical interface to the normal 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: It requires a configuration object with the following properties:
@ -127,26 +127,34 @@
- exec's {callback: STRING} option does not work via this - exec's {callback: STRING} option does not work via this
interface (it triggers an exception), but {callback: function} interface (it triggers an exception), but {callback: function}
does and works exactly like the STRING form does in the Worker: 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 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 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 the end of the result set has been reached. Note that the rows
arrive via worker-posted messages, with all the implications arrive via worker-posted messages, with all the implications
of that. 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){ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
// Inspired by: https://stackoverflow.com/a/52439530 // Inspired by: https://stackoverflow.com/a/52439530
let idNumber = 0;
const handlerMap = Object.create(null); const handlerMap = Object.create(null);
const noop = function(){}; const noop = function(){};
const err = config.onerror || noop; const err = config.onerror || noop;
const debug = config.debug || noop; const debug = config.debug || noop;
const idTypeMap = config.generateMessageId ? undefined : Object.create(null);
const genMsgId = config.generateMessageId || function(msg){ 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(' '))}; const toss = (...args)=>{throw new Error(args.join(' '))};
if('function'===typeof config.worker) config.worker = config.worker(); 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 */; msgHandler = handlerMap[ev.type] /* check for exec per-row callback */;
if(msgHandler && msgHandler.onrow){ if(msgHandler && msgHandler.onrow){
msgHandler.onrow(ev.row); msgHandler.onrow(ev);
return; return;
} }
if(config.onunhandled) config.onunhandled(arguments[0]); if(config.onunhandled) config.onunhandled(arguments[0]);
@ -183,13 +191,10 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
default: default:
break; break;
} }
try { try {msgHandler.resolve(ev)}
msgHandler.resolve(ev); catch(e){msgHandler.reject(e)}
}catch(e){
msgHandler.reject(e);
}
}/*worker.onmessage()*/; }/*worker.onmessage()*/;
return function(/*(msgType, msgArgs) || (msg)*/){ return function(/*(msgType, msgArgs) || (msgEnvelope)*/){
let msg; let msg;
if(1===arguments.length){ if(1===arguments.length){
msg = arguments[0]; msg = arguments[0];
@ -206,15 +211,29 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
msg.departureTime = performance.now(); msg.departureTime = performance.now();
const proxy = Object.create(null); const proxy = Object.create(null);
proxy.message = msg; 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('exec'===msg.type && msg.args){
if('function'===typeof msg.args.callback){ if('function'===typeof msg.args.callback){
cbId = genMsgId(msg)+':row'; rowCallbackId = msg.messageId+':row';
proxy.onrow = msg.args.callback; proxy.onrow = msg.args.callback;
msg.args.callback = cbId; msg.args.callback = rowCallbackId;
handlerMap[cbId] = proxy; handlerMap[rowCallbackId] = proxy;
}else if('string' === typeof msg.args.callback){ }else if('string' === typeof msg.args.callback){
toss("exec callback may not be a string when using the Promise interface."); 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); //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); debug("Posting",msg.type,"message to Worker dbId="+(config.dbId||'default')+':',msg);
config.worker.postMessage(msg); config.worker.postMessage(msg);
}); });
if(cbId) p = p.finally(()=>delete handlerMap[cbId]); if(rowCallbackId) p = p.finally(()=>delete handlerMap[rowCallbackId]);
return p; return p;
}; };
}/*sqlite3Worker1Promiser()*/; }/*sqlite3Worker1Promiser()*/;

View File

@ -60,24 +60,41 @@
delete self.sqlite3Worker1Promiser; delete self.sqlite3Worker1Promiser;
const wtest = async function(msgType, msgArgs, callback){ 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}); const p = workerPromise({type: msgType, args:msgArgs});
return callback ? p.then(callback).finally(testCount) : p; return callback ? p.then(callback).finally(testCount) : p;
}; };
const runTests = async function(){ const runTests = async function(){
const dbFilename = '/testing2.sqlite3'; const dbFilename = '/testing2.sqlite3';
logHtml('',
"Sending 'open' message and waiting for its response before continuing.");
startTime = performance.now(); 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', { await wtest('open', {
filename: dbFilename, filename: dbFilename,
persistent: true, persistent: sqConfig.persistenceEnabled,
simulateError: 0 /* if true, fail the 'open' */, simulateError: 0 /* if true, fail the 'open' */,
}, function(ev){ }, function(ev){
log("then open result",ev); const r = ev.result;
T.assert(1 && (dbFilename===ev.result.filename log("then open result",r);
|| (sqlite3TestModule.sqlite3ApiConfig.persistentDirName T.assert(r.persistent === sqConfig.persistenceEnabled)
+ dbFilename)==ev.result.filename)) .assert(r.persistent
? (dbFilename!==r.filename)
: (dbFilename==r.filename))
.assert(ev.dbId) .assert(ev.dbId)
.assert(ev.messageId) .assert(ev.messageId)
.assert(promiserConfig.dbId === ev.dbId); .assert(promiserConfig.dbId === ev.dbId);
@ -145,11 +162,17 @@
.assert(3 === ev.resultRows[1][0]); .assert(3 === ev.resultRows[1][0]);
}); });
const resultRowTest1 = function f(row){ const resultRowTest1 = function f(ev){
if(undefined === f.counter) f.counter = 0; if(undefined === f.counter) f.counter = 0;
if(row) ++f.counter; if(null === ev.rowNumber){
//log("exec() result row:",row); /* End of result set. */
T.assert(null===row || 'number' === typeof row.b); 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',{ await wtest('exec',{
sql: 'select a a, b b from t order by a', sql: 'select a a, b b from t order by a',
@ -195,10 +218,9 @@
T.assert('string' === typeof ev.result.filename); T.assert('string' === typeof ev.result.filename);
}); });
await wtest('close').then((ev)=>{ await wtest('close', (ev)=>{
T.assert(undefined === ev.result.filename); T.assert(undefined === ev.result.filename);
log("That's all, folks!"); }).finally(()=>log("That's all, folks!"));
});
}/*runTests2()*/; }/*runTests2()*/;

View File

@ -115,9 +115,15 @@
}, },
resultRowTest1: function f(ev){ resultRowTest1: function f(ev){
if(undefined === f.counter) f.counter = 0; if(undefined === f.counter) f.counter = 0;
if(ev.row) ++f.counter; if(null === ev.rowNumber){
//log("exec() result row:",ev.row); /* End of result set. */
T.assert(null===ev.row || 'number' === typeof ev.row.b); 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);
} }
}; };

View File

@ -1,5 +1,5 @@
C js:\sresolve\sthe\smysterious\s"extra"\sunhandled\sexception\snotification,\scaused\sby\sinadvertently\sforking\sone\spromise\sinto\stwo\sseparate\sones\s(failing\sto\sproperly\sreassign\sa\sthen()\sresult).\sFix\sa\stypo\sin\snew\sWorker\s1\scode\swhich\scaused\sthe\sDB(filename)\sname\sto\sbe\sincorrect. C Expand\sthe\sworker1\s'exec'\sop\shandling\sfor\sper-row\scallbacks\sfor\sAPI-level\sconsistency\sand\ssmooth\ssome\sedges\sbetween\sworker1\score\sand\sworker1-promiser.\sAdd\sworker1\s'config-get'\smessage\sto\sfetch\sthe\sserializable\sparts\sof\sthe\ssqlite3.config\sstate.\sImprove\sthe\s'open'\sop's\shandling\sof\sthe\s'persistent'\soption\s(noting\sthat\swe\scannot\syet\stest\sthat\scase\sfrom\sa\sworker).
D 2022-08-24T14:50:10.920 D 2022-08-24T18:39:46.246
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -483,10 +483,10 @@ F ext/wasm/api/post-js-footer.js b64319261d920211b8700004d08b956a6c285f3b0bba814
F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a4a06cb776c003880b F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a4a06cb776c003880b
F ext/wasm/api/sqlite3-api-cleanup.js 1a12e64060c2cb0defd34656a76a9b1d7ed58459c290249bb31567c806fd44de F ext/wasm/api/sqlite3-api-cleanup.js 1a12e64060c2cb0defd34656a76a9b1d7ed58459c290249bb31567c806fd44de
F ext/wasm/api/sqlite3-api-glue.js 67ca83974410961953eeaa1dfed3518530d68381729ed1d27f95122f5baeabd3 F ext/wasm/api/sqlite3-api-glue.js 67ca83974410961953eeaa1dfed3518530d68381729ed1d27f95122f5baeabd3
F ext/wasm/api/sqlite3-api-oo1.js f6dcaac3270182471f97efcfda25bd4a4ac1777b8ec52ebd1c6846721160e54c F ext/wasm/api/sqlite3-api-oo1.js 5ce93b89165e1eb6ef26ba67ae9d3c25379df74eea82edb7b46255f86db21cfc
F ext/wasm/api/sqlite3-api-opfs.js 011799db398157cbd254264b6ebae00d7234b93d0e9e810345f213a5774993c0 F ext/wasm/api/sqlite3-api-opfs.js 011799db398157cbd254264b6ebae00d7234b93d0e9e810345f213a5774993c0
F ext/wasm/api/sqlite3-api-prologue.js 2d5c5d3355f55eefe51922cec5bfedbec0f8300db98a17685ab7a34a03953c7a F ext/wasm/api/sqlite3-api-prologue.js 2d5c5d3355f55eefe51922cec5bfedbec0f8300db98a17685ab7a34a03953c7a
F ext/wasm/api/sqlite3-api-worker1.js b23f66ef5afd350a17fbadb795007098e518a40e5c7c439cd83ef34aa55a45af F ext/wasm/api/sqlite3-api-worker1.js f7372b84b6d71ebdc0d2a9e7944ce571b4f18e0dd4c1be78282c68b4582558ca
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 0d81282eaeff2a6e9fc5c28a388c5c5b45cf25a9393992fa511ac009b27df982 F ext/wasm/api/sqlite3-wasm.c 0d81282eaeff2a6e9fc5c28a388c5c5b45cf25a9393992fa511ac009b27df982
F ext/wasm/common/SqliteTestUtil.js eb96275bed43fdb364b7d65bcded0ca5e22aaacff120d593d1385f852f486247 F ext/wasm/common/SqliteTestUtil.js eb96275bed43fdb364b7d65bcded0ca5e22aaacff120d593d1385f852f486247
@ -508,14 +508,14 @@ F ext/wasm/scratchpad-opfs-main.js 69e960e9161f6412fd0c30f355d4112f1894d6609eb43
F ext/wasm/scratchpad-opfs-worker.html 66c1d15d678f3bd306373d76b61c6c8aef988f61f4a8dd40185d452f9c6d2bf5 F ext/wasm/scratchpad-opfs-worker.html 66c1d15d678f3bd306373d76b61c6c8aef988f61f4a8dd40185d452f9c6d2bf5
F ext/wasm/scratchpad-opfs-worker.js 3ec2868c669713145c76eb5877c64a1b20741f741817b87c907a154b676283a9 F ext/wasm/scratchpad-opfs-worker.js 3ec2868c669713145c76eb5877c64a1b20741f741817b87c907a154b676283a9
F ext/wasm/scratchpad-opfs-worker2.js 5f2237427ac537b8580b1c659ff14ad2621d1694043eaaf41ae18dbfef2e48c0 F ext/wasm/scratchpad-opfs-worker2.js 5f2237427ac537b8580b1c659ff14ad2621d1694043eaaf41ae18dbfef2e48c0
F ext/wasm/sqlite3-worker1-promiser.js 9638b0ced7f02806c3220b616f08729dde9eb13fb56e125cd4759f40bfa81210 F ext/wasm/sqlite3-worker1-promiser.js 92b8da5f38439ffec459a8215775d30fa498bc0f1ab929ff341fc3dd479660b9
F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416458af84f340a9e F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416458af84f340a9e
F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893 F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893
F ext/wasm/testing-worker1-promiser.js 931d909c769c57292f1cafdf10c7dab402d17cd16a6d0ec32089f67b559b058f F ext/wasm/testing-worker1-promiser.js 81d81eda77c9d4a3e43cfeee91df6c3b039782cc998020d72fe1fdf91790242d
F ext/wasm/testing1.html 528001c7e32ee567abc195aa071fd9820cc3c8ffc9c8a39a75e680db05f0c409 F ext/wasm/testing1.html 528001c7e32ee567abc195aa071fd9820cc3c8ffc9c8a39a75e680db05f0c409
F ext/wasm/testing1.js 2def7a86c52ff28b145cb86188d5c7a49d5993f9b78c50d140e1c31551220955 F ext/wasm/testing1.js 2def7a86c52ff28b145cb86188d5c7a49d5993f9b78c50d140e1c31551220955
F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3 F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
F ext/wasm/testing2.js f01e8d7b32f6d4790f8352bf8dc17d2b860afa58f6c8ea1f824ef1c0d2068138 F ext/wasm/testing2.js 04a4194188d54856027eb4cad7239223a8f7a60e64b0aac81fc1a5a70363b98e
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
@ -2009,8 +2009,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P b030f321bd5a38cdd5d6f6735f201afa62d30d2b0ba02e67f055b4895553a878 P 7467ac88801224089b51c6ba7924f93283dd87beca602a186c83632df26cfc85
R a11ec2b23afafa9cff8838dfde907f0c R 4d53a1266e528a954f73a792c3648256
U stephan U stephan
Z 64f88270da37ddd3ce54e6e07b8ab1af Z cffa6daad399e2d401f0fd69f4f04b4f
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
7467ac88801224089b51c6ba7924f93283dd87beca602a186c83632df26cfc85 509f8839201ec1ea4863bd31493e6c29a0721ca6340755bb96656b828758fea7