diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index 0d2416a282..c2b0601374 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -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; } }, diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js index f882f6d76a..6ca337df1d 100644 --- a/ext/wasm/api/sqlite3-api-worker1.js +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -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); diff --git a/ext/wasm/demo-oo1.js b/ext/wasm/demo-oo1.js index 333d3c204c..353ed50a64 100644 --- a/ext/wasm/demo-oo1.js +++ b/ext/wasm/demo-oo1.js @@ -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, diff --git a/ext/wasm/testing2.js b/ext/wasm/testing2.js index 8d9e66dc1f..0a31c470a6 100644 --- a/ext/wasm/testing2.js +++ b/ext/wasm/testing2.js @@ -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). ], diff --git a/manifest b/manifest index feef45cc58..3f6cea0347 100644 --- a/manifest +++ b/manifest @@ -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. diff --git a/manifest.uuid b/manifest.uuid index 31227d768b..7080e62c57 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0a65747047322b7b585e281ac275e437ce3f46e1d06105c19117213929a906ad \ No newline at end of file +7eff7213dff553b76d7ce45063e3c4a19544716611a0b609593d704076b38d0b \ No newline at end of file