1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-27 20:41:58 +03:00

Consolidate oo1.DB.exec() and oo1.DB.execMulti() into oo1.DB.exec(). This is a bit less efficient but certainly easier for a client to deal with and lightens the maintenance burden.

FossilOrigin-Name: 7eff7213dff553b76d7ce45063e3c4a19544716611a0b609593d704076b38d0b
This commit is contained in:
stephan
2022-08-25 13:27:52 +00:00
parent 9afff9f3c5
commit 335ad5264f
6 changed files with 117 additions and 185 deletions

View File

@ -198,9 +198,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
};
/**
Expects to be passed the `arguments` object from DB.exec() and
DB.execMulti(). Does the argument processing/validation, throws
on error, and returns a new object on success:
Expects to be passed the `arguments` object from DB.exec(). Does
the argument processing/validation, throws on error, and returns
a new object on success:
{ sql: the SQL, opt: optionsObj, cbArg: function}
@ -237,11 +237,11 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(out.opt.callback || out.opt.resultRows){
switch((undefined===out.opt.rowMode)
? 'array' : out.opt.rowMode) {
case 'object': out.cbArg = (stmt)=>stmt.get({}); break;
case 'object': out.cbArg = (stmt)=>stmt.get(Object.create(null)); break;
case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
case 'stmt':
if(Array.isArray(out.opt.resultRows)){
toss3("Invalid rowMode for a resultRows array: must",
toss3("exec(): invalid rowMode for a resultRows array: must",
"be one of 'array', 'object',",
"a result column number, or column name reference.");
}
@ -256,8 +256,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const prefix = out.opt.rowMode[0];
if(':'===prefix || '@'===prefix || '$'===prefix){
out.cbArg = function(stmt){
return stmt.get(this.obj)[this.colName];
}.bind({obj:{}, colName: out.opt.rowMode.substr(1)})
const rc = stmt.get(this.obj)[this.colName];
return (undefined===rc) ? toss3("exec(): unknown result column:",this.colName) : rc;
}.bind({
obj:Object.create(null),
colName: out.opt.rowMode.substr(1)
});
break;
}
}
@ -394,70 +398,6 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
__stmtMap.get(this)[pStmt] = stmt;
return stmt;
},
/**
This function works like execMulti(), and takes most of the
same arguments, but is more efficient (performs much less
work) when the input SQL is only a single statement. If
passed a multi-statement SQL, it only processes the first
one.
This function supports the following additional options not
supported by execMulti():
- .multi: if true, this function acts as a proxy for
execMulti() and behaves identically to that function.
- .columnNames: if this is an array and the query has
result columns, the array is passed to
Stmt.getColumnNames() to append the column names to it
(regardless of whether the query produces any result
rows). If the query has no result columns, this value is
unchanged. (TODO: support this in execMulti() as well.)
The following options to execMulti() are _not_ supported by
this method (they are simply ignored):
- .saveSql
*/
exec: function(/*(sql [,optionsObj]) or (optionsObj)*/){
affirmDbOpen(this);
const arg = parseExecArgs(arguments);
if(!arg.sql) return this;
else if(arg.opt.multi){
return this.execMulti(arg, undefined, BindTypes);
}
const opt = arg.opt;
let stmt, rowTarget;
try {
if(Array.isArray(opt.resultRows)){
rowTarget = opt.resultRows;
}
stmt = this.prepare(arg.sql);
if(stmt.columnCount && Array.isArray(opt.columnNames)){
stmt.getColumnNames(opt.columnNames);
}
if(opt.bind) stmt.bind(opt.bind);
if(opt.callback || rowTarget){
while(stmt.step()){
const row = arg.cbArg(stmt);
if(rowTarget) rowTarget.push(row);
if(opt.callback){
stmt._isLocked = true;
opt.callback(row,stmt);
stmt._isLocked = false;
}
}
}else{
stmt.step();
}
}finally{
if(stmt){
delete stmt._isLocked;
stmt.finalize();
}
}
return this;
}/*exec()*/,
/**
Executes one or more SQL statements in the form of a single
string. Its arguments must be either (sql,optionsObject) or
@ -473,109 +413,115 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
properties:
- .sql = the SQL to run (unless it's provided as the first
argument). This must be of type string, Uint8Array, or an
array of strings (in which case they're concatenated
together as-is, with no separator between elements,
before evaluation).
argument). This must be of type string, Uint8Array, or an array
of strings. In the latter case they're concatenated together
as-is, _with no separator_ between elements, before evaluation.
The array form is often simpler for long hand-written queries.
- .bind = a single value valid as an argument for
Stmt.bind(). This is ONLY applied to the FIRST non-empty
statement in the SQL which has any bindable
parameters. (Empty statements are skipped entirely.)
Stmt.bind(). This is _only_ applied to the _first_ non-empty
statement in the SQL which has any bindable parameters. (Empty
statements are skipped entirely.)
- .saveSql = an optional array. If set, the SQL of each
executed statement is appended to this array before the
statement is executed (but after it is prepared - we
don't have the string until after that). Empty SQL
statements are elided.
==================================================================
The following options apply _only_ to the _first_ statement
which has a non-zero result column count, regardless of whether
the statement actually produces any result rows.
==================================================================
- .callback = a function which gets called for each row of the
FIRST statement in the SQL which has result _columns_, but only
if that statement has any result _rows_. The callback's "this"
is the options object. The second argument passed to the
callback is always the current Stmt object (so that the caller
may collect column names, or similar). The 2nd argument to the
callback is always the Stmt instance, as it's needed if the
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 ...
result set, but only if that statement has any result
_rows_. The callback's "this" is the options object. The second
argument passed to the callback is always the current Stmt
object (so that the caller may collect column names, or
similar). The 2nd argument to the callback is always the Stmt
instance, as it's needed if the caller wants to fetch the
column names or some such (noting that they could also be
fetched via `this.columnNames`, if the client provides the
`columnNames` option).
- .rowMode = may take one of several forms:
ACHTUNG: The callback MUST NOT modify the Stmt object. Calling
any of the Stmt.get() variants, Stmt.getColumnName(), or
similar, is legal, but calling step() or finalize() is
not. Routines which are illegal in this context will trigger an
exception.
A) If `rowMode` is an integer, only the single value from that
result column (0-based) will be passed on.
The first argument passed to the callback defaults to an array of
values from the current result row but may be changed with ...
B) A string describing what type of argument should be passed
- .rowMode = specifies the type of he callback's first argument.
It may be any of...
A) A string describing what type of argument should be passed
as the first argument to the callback:
B.1) 'array' (the default) causes the results of
A.1) 'array' (the default) causes the results of
`stmt.get([])` to be passed to passed on and/or appended to
`resultRows`.
B.2) 'object' causes the results of `stmt.get({})` to be
passed to the `callback` and/or appended to `resultRows`.
A.2) 'object' causes the results of
`stmt.get(Object.create(null))` to be passed to the
`callback` and/or appended to `resultRows`. Achtung: an SQL
result may have multiple columns with identical names. In
that case, the right-most column will be the one set in this
object!
B.3) 'stmt' causes the current Stmt to be passed to the
A.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.
B) An integer, indicating a zero-based column in the result
row. Only that one single value will be passed on.
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`).
would work but '$a' would not. A reference to a column not in
the result set will trigger an exception on the first row (as
the check is not performed until rows are fetched).
Any other `rowMode` value triggers an exception.
- .resultRows: if this is an array, it functions similarly to
the `callback` option: each row of the result set (if any) of
the FIRST first statement which has result _columns_ is
appended to the array in the format specified for the `rowMode`
option, with the exception that the only legal values for
`rowMode` in this case are 'array', 'object', or an integer,
none of which are the default for `rowMode`. It is legal to use
both `resultRows` and `callback`, but `resultRows` is likely
much simpler to use for small data sets and can be used over a
WebWorker-style message interface. execMulti() throws if
`resultRows` is set and `rowMode` is 'stmt' (which is the
default!).
the `callback` option: each row of the result set (if any),
with the exception that the `rowMode` 'stmt' is not legal. It
is legal to use both `resultRows` and `callback`, but
`resultRows` is likely much simpler to use for small data sets
and can be used over a WebWorker-style message interface.
exec() throws if `resultRows` is set and `rowMode` is 'stmt'.
- saveSql = an optional array. If set, the SQL of each
executed statement is appended to this array before the
statement is executed (but after it is prepared - we
don't have the string until after that). Empty SQL
statements are elided.
See also the exec() method, which is a close cousin of this
one.
ACHTUNG #1: The callback MUST NOT modify the Stmt
object. Calling any of the Stmt.get() variants,
Stmt.getColumnName(), or similar, is legal, but calling
step() or finalize() is not. Routines which are illegal
in this context will trigger an exception.
ACHTUNG #2: The semantics of the `bind` and `callback`
options may well change or those options may be removed
altogether for this function (but retained for exec()).
Generally speaking, neither bind parameters nor a callback
are generically useful when executing multi-statement SQL.
- .columnNames: if this is an array, the column names of the
result set are stored in this array before the callback (if
any) is triggered (regardless of whether the query produces any
result rows). If no statement has result columns, this value is
unchanged. Achtung: an SQL result may have multiple columns with
identical names.
*/
execMulti: function(/*(sql [,obj]) || (obj)*/){
exec: function(/*(sql [,obj]) || (obj)*/){
affirmDbOpen(this);
const wasm = capi.wasm;
const arg = (BindTypes===arguments[2]
/* ^^^ Being passed on from exec() */
? arguments[0] : parseExecArgs(arguments));
if(!arg.sql) return this;
const arg = parseExecArgs(arguments);
if(!arg.sql){
return (''===arg.sql) ? this : toss3("exec() requires an SQL string.");
}
const opt = arg.opt;
const callback = opt.callback;
const resultRows = (Array.isArray(opt.resultRows)
let resultRows = (Array.isArray(opt.resultRows)
? opt.resultRows : undefined);
if(resultRows && 'stmt'===opt.rowMode){
toss3("rowMode 'stmt' is not valid in combination",
"with a resultRows array.");
}
let rowMode = (((callback||resultRows) && (undefined!==opt.rowMode))
? opt.rowMode : undefined);
let stmt;
let bind = opt.bind;
let doneFirstQuery = false/*true once we handle a result-returning query*/;
const stack = wasm.scopedAllocPush();
try{
const isTA = util.isSQLableTypedArray(arg.sql)
@ -604,8 +550,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
re-preparing an empty statement. */ ){
wasm.setMemValue(ppStmt, 0, wasm.ptrIR);
wasm.setMemValue(pzTail, 0, wasm.ptrIR);
DB.checkRc(this, capi.sqlite3_prepare_v2(
this.pointer, pSql, sqlByteLen, ppStmt, pzTail
DB.checkRc(this, capi.sqlite3_prepare_v3(
this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail
));
const pStmt = wasm.getMemValue(ppStmt, wasm.ptrIR);
pSql = wasm.getMemValue(pzTail, wasm.ptrIR);
@ -619,17 +565,20 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
stmt.bind(bind);
bind = null;
}
if(stmt.columnCount && undefined!==rowMode){
if(!doneFirstQuery && stmt.columnCount){
/* Only forward SELECT results for the FIRST query
in the SQL which potentially has them. */
doneFirstQuery = true;
if(Array.isArray(opt.columnNames)){
stmt.getColumnNames(opt.columnNames);
}
while(stmt.step()){
stmt._isLocked = true;
const row = arg.cbArg(stmt);
if(resultRows) resultRows.push(row);
if(callback) callback(row,stmt);
if(callback) callback.apply(opt,[row,stmt]);
stmt._isLocked = false;
}
rowMode = undefined;
}else{
// Do we need to while(stmt.step()){} here?
stmt.step();
@ -637,10 +586,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
stmt.finalize();
stmt = null;
}
}catch(e){
console.warn("DB.execMulti() is propagating exception",opt,e);
}/*catch(e){
console.warn("DB.exec() is propagating exception",opt,e);
throw e;
}finally{
}*/finally{
if(stmt){
delete stmt._isLocked;
stmt.finalize();
@ -648,7 +597,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
wasm.scopedAllocPop(stack);
}
return this;
}/*execMulti()*/,
}/*exec()*/,
/**
Creates a new scalar UDF (User-Defined Function) which is
accessible via SQL code. This function may be called in any
@ -907,7 +856,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
this.exec("RELEASE oo1");
return rc;
}catch(e){
this.execMulti("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1");
this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1");
throw e;
}
},

View File

@ -589,7 +589,7 @@ sqlite3.initWorker1API = function(){
};
if(err.stack){
result.stack = ('string'===typeof err.stack)
? err.stack.split('\n') : err.stack;
? err.stack.split(/\n\s*/) : err.stack;
}
if(0) console.warn("Worker is propagating an exception to main thread.",
"Reporting it _here_ for the stack trace:",err,result);

View File

@ -144,23 +144,6 @@
});
log("Result column names:",columnNames);
/**
Main differences between exec() and execMulti():
- execMulti() traverses all statements in the input SQL
- exec() supports a couple options not supported by execMulti(),
and vice versa.
- execMulti() result callback/array only activates for the
first statement which has result columns. It is arguable
whether it should support a callback at all, and that
feature may be removed.
- execMulti() column-bind data only activates for the first statement
with bindable columns. This feature is arguable and may be removed.
*/
if(0){
warn("UDF will throw because of incorrect arg count...");
db.exec("select twice(1,2,3)");
@ -187,7 +170,7 @@
log("In savepoint: count(*) from t =",db.selectValue("select count(*) from t"));
D.savepoint(function(DD){
const rows = [];
D.execMulti({
D.exec({
sql: ["insert into t(a,b) values(99,100);",
"select count(*) from t"],
rowMode: 0,

View File

@ -70,7 +70,7 @@
(ev.workerRespondTime - ev.workerReceivedTime),"ms.",
"Round-trip event time =",
(performance.now() - ev.departureTime),"ms.",
(evd.errorClass ? ev.message : ""), evd
(evd.errorClass ? ev.message : "")//, JSON.stringify(evd)
);
};
@ -149,10 +149,9 @@
throw new Error("This is not supposed to be reached.");
};
runOneTest('exec',{
sql: ["create table t(a,b)",
sql: ["create table t(a,b);",
"insert into t(a,b) values(1,2),(3,4),(5,6)"
].join(';'),
multi: true,
],
resultRows: [], columnNames: []
}, function(ev){
ev = ev.result;
@ -161,7 +160,7 @@
});
runOneTest('exec',{
sql: 'select a a, b b from t order by a',
resultRows: [], columnNames: [],
resultRows: [], columnNames: [], saveSql:[]
}, function(ev){
ev = ev.result;
T.assert(3===ev.resultRows.length)
@ -170,6 +169,7 @@
.assert(2===ev.columnNames.length)
.assert('b'===ev.columnNames[1]);
});
//if(1){ error("Returning prematurely for testing."); return; }
runOneTest('exec',{
sql: 'select a a, b b from t order by a',
resultRows: [], columnNames: [],
@ -200,12 +200,12 @@
dbMsgHandler.resultRowTest1.counter = 0;
});
runOneTest('exec',{
multi: true,
sql:[
'pragma foreign_keys=0;',
"pragma foreign_keys=0;",
// ^^^ arbitrary query with no result columns
'select a, b from t order by a desc; select a from t;'
// multi-exec only honors results from the first
"select a, b from t order by a desc;",
"select a from t;"
// multi-statement exec only honors results from the first
// statement with result columns (regardless of whether)
// it has any rows).
],

View File

@ -1,5 +1,5 @@
C Refactor\sand\sexpand\sthe\sworker1\sdocs,\sconsolidating\sthem\sinto\sthe\stop\sof\stheir\sfile\sinstead\sof\sscattered\saround\sthe\sinternals.\sAccommodate\san\sAPI\schange\sfrom\syesterday\sin\sdemo-oo1.js.
D 2022-08-25T11:39:12.535
C Consolidate\soo1.DB.exec()\sand\soo1.DB.execMulti()\sinto\soo1.DB.exec().\sThis\sis\sa\sbit\sless\sefficient\sbut\scertainly\seasier\sfor\sa\sclient\sto\sdeal\swith\sand\slightens\sthe\smaintenance\sburden.
D 2022-08-25T13:27:52.079
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
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/sqlite3-api-cleanup.js 1a12e64060c2cb0defd34656a76a9b1d7ed58459c290249bb31567c806fd44de
F ext/wasm/api/sqlite3-api-glue.js 67ca83974410961953eeaa1dfed3518530d68381729ed1d27f95122f5baeabd3
F ext/wasm/api/sqlite3-api-oo1.js a207d53bcc11955cc8f844aa59b30caaba8f57573222f62010628cfa4b1f4444
F ext/wasm/api/sqlite3-api-oo1.js 329f07524e4a5d12960a0d17845443a4495b3e34b53b0182b1b944c626864974
F ext/wasm/api/sqlite3-api-opfs.js 011799db398157cbd254264b6ebae00d7234b93d0e9e810345f213a5774993c0
F ext/wasm/api/sqlite3-api-prologue.js 2d5c5d3355f55eefe51922cec5bfedbec0f8300db98a17685ab7a34a03953c7a
F ext/wasm/api/sqlite3-api-worker1.js 38db6c3d77798a0ef8be78ae6d421ef144e2e9602cdabdd7a93c7fcb7a2d449f
F ext/wasm/api/sqlite3-api-worker1.js 73579555563b789785ae83724014eaf31811073aad9be6596c8336ffb51edd71
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 0d81282eaeff2a6e9fc5c28a388c5c5b45cf25a9393992fa511ac009b27df982
F ext/wasm/common/SqliteTestUtil.js eb96275bed43fdb364b7d65bcded0ca5e22aaacff120d593d1385f852f486247
@ -494,7 +494,7 @@ F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d695
F ext/wasm/common/testing.css 572cf1ffae0b6eb7ca63684d3392bf350217a07b90e7a896e4fa850700c989b0
F ext/wasm/common/whwasmutil.js 41b8e097e0a9cb07c24c0ede3c81b72470a63f4a4efb07f75586dc131569f5ae
F ext/wasm/demo-oo1.html 75646855b38405d82781246fd08c852a2b3bee05dd9f0fe10ab655a8cffb79aa
F ext/wasm/demo-oo1.js 77b837b0fe13b542cee893c8caf84009482986bd43cf775197dfeb1e62ec0a2b
F ext/wasm/demo-oo1.js aad38cb90b6fa7fd4d1184e759b25056fb4ed45c4957c458896354281259515f
F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
F ext/wasm/fiddle/fiddle-worker.js bccf46045be8824752876f3eec01c223be0616ccac184bffd0024cfe7a3262b8
F ext/wasm/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08
@ -515,7 +515,7 @@ F ext/wasm/testing-worker1-promiser.js c62b5879339eef0b21aebd9d75bc125c86530edc1
F ext/wasm/testing1.html 528001c7e32ee567abc195aa071fd9820cc3c8ffc9c8a39a75e680db05f0c409
F ext/wasm/testing1.js 2def7a86c52ff28b145cb86188d5c7a49d5993f9b78c50d140e1c31551220955
F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
F ext/wasm/testing2.js ab4ae24cd3ffe814370b35515aea426647a6f9d271c6542cf18e580470540615
F ext/wasm/testing2.js 25584bcc30f19673ce13a6f301f89f8820a59dfe044e0c4f2913941f4097fe3c
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
@ -2009,8 +2009,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 1bb37e5c477b9eb098362f74a45a55be23d450fe45cdff58c1cbff08b5b3998f
R 317eb3fcfe686c9323e52473ace36982
P 0a65747047322b7b585e281ac275e437ce3f46e1d06105c19117213929a906ad
R 14238254f8c2a9ecda72692d6551fb99
U stephan
Z 2516659ac708c3f3e5c0bf51001da2da
Z 0ceace5a4ad7c624466e4c9b471faa7f
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
0a65747047322b7b585e281ac275e437ce3f46e1d06105c19117213929a906ad
7eff7213dff553b76d7ce45063e3c4a19544716611a0b609593d704076b38d0b