1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Change DB.exec() rowMode default from 'stmt' to 'array', per /chat discussion. Add DB.exec() rowMode option for fetching a specific column by name. Add result column names to worker1 exec() callback interface, as there's otherwise no way to get that info from a worker.

FossilOrigin-Name: 1bb37e5c477b9eb098362f74a45a55be23d450fe45cdff58c1cbff08b5b3998f
This commit is contained in:
stephan
2022-08-24 20:57:37 +00:00
parent 3734401a95
commit 407f75378e
6 changed files with 139 additions and 67 deletions

View File

@ -236,14 +236,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
} }
if(out.opt.callback || out.opt.resultRows){ if(out.opt.callback || out.opt.resultRows){
switch((undefined===out.opt.rowMode) switch((undefined===out.opt.rowMode)
? 'stmt' : out.opt.rowMode) { ? 'array' : out.opt.rowMode) {
case 'object': out.cbArg = (stmt)=>stmt.get({}); break; case 'object': out.cbArg = (stmt)=>stmt.get({}); break;
case 'array': out.cbArg = (stmt)=>stmt.get([]); break; case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
case 'stmt': case 'stmt':
if(Array.isArray(out.opt.resultRows)){ if(Array.isArray(out.opt.resultRows)){
toss3("Invalid rowMode for resultRows array: must", toss3("Invalid rowMode for a resultRows array: must",
"be one of 'array', 'object',", "be one of 'array', 'object',",
"or a result column number."); "a result column number, or column name reference.");
} }
out.cbArg = (stmt)=>stmt; out.cbArg = (stmt)=>stmt;
break; break;
@ -251,22 +251,16 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(util.isInt32(out.opt.rowMode)){ if(util.isInt32(out.opt.rowMode)){
out.cbArg = (stmt)=>stmt.get(out.opt.rowMode); out.cbArg = (stmt)=>stmt.get(out.opt.rowMode);
break; break;
} }else if('string'===typeof out.opt.rowMode && out.opt.rowMode.length>1){
/* /* "$X", ":X", and "@X" fetch column named "X" (case-sensitive!) */
TODO?: how can we define rowMode such that it uses const prefix = out.opt.rowMode[0];
rowMode of 'object' and returns a given named field from if(':'===prefix || '@'===prefix || '$'===prefix){
the object. Something like: out.cbArg = function(stmt){
return stmt.get(this.obj)[this.colName];
if(?what goes here?){ }.bind({obj:{}, colName: out.opt.rowMode.substr(1)})
out.cbArg = function f(stmt){return stmt.get(this.obj)[this.colName]}
.bind({obj:{}, colName: ???what goes here???}});
break; 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);
} }
} }
@ -449,7 +443,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(rowTarget) rowTarget.push(row); if(rowTarget) rowTarget.push(row);
if(opt.callback){ if(opt.callback){
stmt._isLocked = true; stmt._isLocked = true;
opt.callback(row, stmt); opt.callback(row,stmt);
stmt._isLocked = false; stmt._isLocked = false;
} }
} }
@ -494,23 +488,40 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if that statement has any result _rows_. The callback's "this" if that statement has any result _rows_. The callback's "this"
is the options object. The second argument passed to the is the options object. The second argument passed to the
callback is always the current Stmt object (so that the caller callback is always the current Stmt object (so that the caller
may collect column names, or similar). The first argument may collect column names, or similar). The 2nd argument to the
passed to the callback defaults to the current Stmt object but callback is always the Stmt instance, as it's needed if the
may be changed with ... caller wants to fetch the column names or some such. 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 - .rowMode = may take one of several forms:
should be passed as the first argument to the callback or an
integer representing a result column index. A `rowMode` of A) If `rowMode` is an integer, only the single value from that
'object' causes the results of `stmt.get({})` to be passed to result column (0-based) will be passed on.
the `callback` and/or appended to `resultRows`. A value of
'array' causes the results of `stmt.get([])` to be passed to B) A string describing what type of argument should be passed
passed on. A value of 'stmt' is equivalent to the default, as the first argument to the callback:
passing the current Stmt to the callback (noting that it's
always passed as the 2nd argument), but this mode will trigger B.1) 'array' (the default) causes the results of
an exception if `resultRows` is an array. If `rowMode` is an `stmt.get([])` to be passed to passed on and/or appended to
integer, only the single value from that result column will be `resultRows`.
passed on. Any other value for the option triggers an
exception. B.2) 'object' causes the results of `stmt.get({})` to be
passed to the `callback` and/or appended to `resultRows`.
B.3) 'stmt' causes the current Stmt to be passed to the
callback, but this mode will trigger an exception if
`resultRows` is an array because appending the statement to
the array would be unhelpful.
C) A string with a minimum length of 2 and leading character of
':', '$', or '@' will fetch the row as an object, extract that
one field, and pass that field's value to the callback. Note
that these keys are case-sensitive so must match the case used
in the SQL. e.g. `"select a A from t"` with a `rowMode` of '$A'
would work but '$a' would not (it would result in `undefined`).
Any other `rowMode` value triggers an exception.
- .resultRows: if this is an array, it functions similarly to - .resultRows: if this is an array, it functions similarly to
the `callback` option: each row of the result set (if any) of the `callback` option: each row of the result set (if any) of
@ -584,7 +595,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false); else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false);
wasm.setMemValue(pSql + sqlByteLen, 0/*NUL terminator*/); wasm.setMemValue(pSql + sqlByteLen, 0/*NUL terminator*/);
while(wasm.getMemValue(pSql, 'i8') while(wasm.getMemValue(pSql, 'i8')
/* Maintenance reminder: ^^^^ _must_ be i8 or else we /* Maintenance reminder:^^^ _must_ be 'i8' or else we
will very likely cause an endless loop. What that's will very likely cause an endless loop. What that's
doing is checking for a terminating NUL byte. If we doing is checking for a terminating NUL byte. If we
use i32 or similar then we read 4 bytes, read stuff use i32 or similar then we read 4 bytes, read stuff
@ -615,7 +626,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
stmt._isLocked = true; stmt._isLocked = true;
const row = arg.cbArg(stmt); const row = arg.cbArg(stmt);
if(resultRows) resultRows.push(row); if(resultRows) resultRows.push(row);
if(callback) callback(row, stmt); if(callback) callback(row,stmt);
stmt._isLocked = false; stmt._isLocked = false;
} }
rowMode = undefined; rowMode = undefined;

View File

@ -249,7 +249,11 @@ 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, rowNumber: 1-based-#, row: theRow}) postMessage({type: thatKeyType,
rowNumber: 1-based-#,
row: theRow,
columnNames: anArray
})
And, at the end of the result set (whether or not any result And, at the end of the result set (whether or not any result
rows were produced), it will post an identical message with rows were produced), it will post an identical message with
@ -280,12 +284,7 @@ sqlite3.initWorker1API = function(){
const rc = ( 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===rc.rowMode){ if('stmt'===rc.rowMode){
/* Since the default rowMode of 'stmt' is not useful
for the Worker interface, we'll default to
something else. */
rc.rowMode = 'array';
}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.");
} }
@ -294,25 +293,40 @@ sqlite3.initWorker1API = function(){
// Part of a copy-avoidance optimization for blobs // Part of a copy-avoidance optimization for blobs
db._blobXfer = wState.xfer; db._blobXfer = wState.xfer;
} }
const callbackMsgType = rc.callback; const theCallback = rc.callback;
let rowNumber = 0; let rowNumber = 0;
if('string' === typeof callbackMsgType){ const hadColNames = !!rc.columnNames;
if('string' === typeof theCallback){
if(!hadColNames) rc.columnNames = [];
/* 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. */
rc.callback = rc.callback = function(row,stmt){
(row)=>wState.post({type: callbackMsgType, rowNumber:++rowNumber, row:row}, wState.xfer); wState.post({
type: theCallback,
columnNames: rc.columnNames,
rowNumber: ++rowNumber,
row: row
}, wState.xfer);
}
} }
try { try {
db.exec(rc); db.exec(rc);
if(rc.callback instanceof Function){ if(rc.callback instanceof Function){
rc.callback = callbackMsgType; rc.callback = theCallback;
wState.post({type: callbackMsgType, rowNumber: null, row: undefined}); /* Post a sentinel message to tell the client that the end
of the result set has been reached (possibly with zero
rows). */
wState.post({
type: theCallback,
columnNames: rc.columnNames,
rowNumber: null /*null to distinguish from "property not set"*/,
row: undefined /*undefined because null is a legal row value
for some rowType values, but undefined is not*/
});
} }
}finally{ }finally{
delete db._blobXfer; delete db._blobXfer;
if(rc.callback){ if(rc.callback) rc.callback = theCallback;
rc.callback = callbackMsgType;
}
} }
return rc; return rc;
}/*exec()*/, }/*exec()*/,

View File

@ -166,16 +166,19 @@
if(undefined === f.counter) f.counter = 0; if(undefined === f.counter) f.counter = 0;
if(null === ev.rowNumber){ if(null === ev.rowNumber){
/* End of result set. */ /* End of result set. */
T.assert(undefined === ev.row); T.assert(undefined === ev.row)
.assert(2===ev.columnNames.length)
.assert('a'===ev.columnNames[0])
.assert('B'===ev.columnNames[1]);
}else{ }else{
T.assert(ev.rowNumber > 0); T.assert(ev.rowNumber > 0);
++f.counter; ++f.counter;
} }
log("exec() result row:",ev); log("exec() result row:",ev);
T.assert(null === ev.rowNumber || 'number' === typeof ev.row.b); 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 limit 3',
callback: resultRowTest1, callback: resultRowTest1,
rowMode: 'object' rowMode: 'object'
}, function(ev){ }, function(ev){
@ -183,6 +186,48 @@
resultRowTest1.counter = 0; resultRowTest1.counter = 0;
}); });
const resultRowTest2 = function f(ev){
if(null === ev.rowNumber){
/* End of result set. */
T.assert(undefined === ev.row)
.assert(1===ev.columnNames.length)
.assert('a'===ev.columnNames[0])
}else{
T.assert(ev.rowNumber > 0);
f.counter = ev.rowNumber;
}
log("exec() result row:",ev);
T.assert(null === ev.rowNumber || 'number' === typeof ev.row);
};
await wtest('exec',{
sql: 'select a a from t limit 3',
callback: resultRowTest2,
rowMode: 0
}, function(ev){
T.assert(3===resultRowTest2.counter);
});
const resultRowTest3 = function f(ev){
if(null === ev.rowNumber){
T.assert(3===ev.columnNames.length)
.assert('foo'===ev.columnNames[0])
.assert('bar'===ev.columnNames[1])
.assert('baz'===ev.columnNames[2]);
}else{
f.counter = ev.rowNumber;
T.assert('number' === typeof ev.row);
}
};
await wtest('exec',{
sql: "select 'foo' foo, a bar, 'baz' baz from t limit 2",
callback: resultRowTest3,
columnNames: [],
rowMode: ':bar'
}, function(ev){
log("exec() result row:",ev);
T.assert(2===resultRowTest3.counter);
});
await wtest('exec',{ await wtest('exec',{
multi: true, multi: true,
sql:[ sql:[
@ -220,7 +265,7 @@
await wtest('close', (ev)=>{ await wtest('close', (ev)=>{
T.assert(undefined === ev.result.filename); T.assert(undefined === ev.result.filename);
}).finally(()=>log("That's all, folks!")); }).finally(()=>logHtml('',"That's all, folks!"));
}/*runTests2()*/; }/*runTests2()*/;

View File

@ -117,7 +117,9 @@
if(undefined === f.counter) f.counter = 0; if(undefined === f.counter) f.counter = 0;
if(null === ev.rowNumber){ if(null === ev.rowNumber){
/* End of result set. */ /* End of result set. */
T.assert(undefined === ev.row); T.assert(undefined === ev.row)
.assert(Array.isArray(ev.columnNames))
.assert(ev.columnNames.length);
}else{ }else{
T.assert(ev.rowNumber > 0); T.assert(ev.rowNumber > 0);
++f.counter; ++f.counter;

View File

@ -1,5 +1,5 @@
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). C Change\sDB.exec()\srowMode\sdefault\sfrom\s'stmt'\sto\s'array',\sper\s/chat\sdiscussion.\sAdd\sDB.exec()\srowMode\soption\sfor\sfetching\sa\sspecific\scolumn\sby\sname.\sAdd\sresult\scolumn\snames\sto\sworker1\sexec()\scallback\sinterface,\sas\sthere's\sotherwise\sno\sway\sto\sget\sthat\sinfo\sfrom\sa\sworker.
D 2022-08-24T18:39:46.246 D 2022-08-24T20:57:37.430
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 5ce93b89165e1eb6ef26ba67ae9d3c25379df74eea82edb7b46255f86db21cfc F ext/wasm/api/sqlite3-api-oo1.js 324b2f6817ff3711b59bd9505157f7a91fe319249d3b8b525c8254427c10504a
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 f7372b84b6d71ebdc0d2a9e7944ce571b4f18e0dd4c1be78282c68b4582558ca F ext/wasm/api/sqlite3-api-worker1.js 11a9e8b22147d948e338b25d21697178b4414dc0578fc9613aa5fc4bfe62f208
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
@ -511,11 +511,11 @@ F ext/wasm/scratchpad-opfs-worker2.js 5f2237427ac537b8580b1c659ff14ad2621d169404
F ext/wasm/sqlite3-worker1-promiser.js 92b8da5f38439ffec459a8215775d30fa498bc0f1ab929ff341fc3dd479660b9 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 81d81eda77c9d4a3e43cfeee91df6c3b039782cc998020d72fe1fdf91790242d F ext/wasm/testing-worker1-promiser.js f4b0895b612606d04ae371d03a9ffe9ffa94a2a840da6e92742b2adf86f0783c
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 04a4194188d54856027eb4cad7239223a8f7a60e64b0aac81fc1a5a70363b98e F ext/wasm/testing2.js ab4ae24cd3ffe814370b35515aea426647a6f9d271c6542cf18e580470540615
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 7467ac88801224089b51c6ba7924f93283dd87beca602a186c83632df26cfc85 P 509f8839201ec1ea4863bd31493e6c29a0721ca6340755bb96656b828758fea7
R 4d53a1266e528a954f73a792c3648256 R 4d9ca8433788ec1804950abdc80ba467
U stephan U stephan
Z cffa6daad399e2d401f0fd69f4f04b4f Z aac659391ac05e725777e807ed0e05f4
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
509f8839201ec1ea4863bd31493e6c29a0721ca6340755bb96656b828758fea7 1bb37e5c477b9eb098362f74a45a55be23d450fe45cdff58c1cbff08b5b3998f