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

Add oo1.DB.exec() 'returnValue' option, which specifies what exec() should return. Defaults to the db object and enables direct return of the result rows array or a list of the individual SQL statements. Other code-adjacent internal cleanups.

FossilOrigin-Name: 69d36a6aa5e2cd79d26c0fd3e0d20fe8838fd1be97db07725233bfff1dfe6643
This commit is contained in:
stephan
2022-10-31 11:09:14 +00:00
parent 549907fd67
commit 1acfe91582
5 changed files with 229 additions and 159 deletions

View File

@ -365,7 +365,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
current Stmt and returns the callback argument of the type current Stmt and returns the callback argument of the type
indicated by the input arguments. indicated by the input arguments.
*/ */
const parseExecArgs = function(args){ const parseExecArgs = function(db, args){
const out = Object.create(null); const out = Object.create(null);
out.opt = Object.create(null); out.opt = Object.create(null);
switch(args.length){ switch(args.length){
@ -385,20 +385,34 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
break; break;
default: toss3("Invalid argument count for exec()."); default: toss3("Invalid argument count for exec().");
}; };
if(util.isSQLableTypedArray(out.sql)){ out.sql = util.flexibleString(out.sql);
out.sql = util.typedArrayToString(out.sql); if('string'!==typeof out.sql){
}else if(Array.isArray(out.sql)){
out.sql = out.sql.join('');
}else if('string'!==typeof out.sql){
toss3("Missing SQL argument or unsupported SQL value type."); toss3("Missing SQL argument or unsupported SQL value type.");
} }
if(out.opt.callback || out.opt.resultRows){ const opt = out.opt;
switch((undefined===out.opt.rowMode) switch(opt.returnValue){
? 'array' : out.opt.rowMode) { case 'resultRows':
if(!opt.resultRows) opt.resultRows = [];
out.returnVal = ()=>opt.resultRows;
break;
case 'saveSql':
if(!opt.saveSql) opt.saveSql = [];
out.returnVal = ()=>opt.saveSql;
break;
case undefined:
case 'this':
break;
default:
toss3("Invalid returnValue value:",opt.returnValue);
}
if(!out.returnVal) out.returnVal = ()=>db;
if(opt.callback || opt.resultRows){
switch((undefined===opt.rowMode)
? 'array' : opt.rowMode) {
case 'object': out.cbArg = (stmt)=>stmt.get(Object.create(null)); break; case 'object': out.cbArg = (stmt)=>stmt.get(Object.create(null)); 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(opt.resultRows)){
toss3("exec(): invalid rowMode for a resultRows array: must", toss3("exec(): invalid rowMode for a resultRows array: must",
"be one of 'array', 'object',", "be one of 'array', 'object',",
"a result column number, or column name reference."); "a result column number, or column name reference.");
@ -406,32 +420,32 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
out.cbArg = (stmt)=>stmt; out.cbArg = (stmt)=>stmt;
break; break;
default: default:
if(util.isInt32(out.opt.rowMode)){ if(util.isInt32(opt.rowMode)){
out.cbArg = (stmt)=>stmt.get(out.opt.rowMode); out.cbArg = (stmt)=>stmt.get(opt.rowMode);
break; break;
}else if('string'===typeof out.opt.rowMode && out.opt.rowMode.length>1){ }else if('string'===typeof opt.rowMode && opt.rowMode.length>1){
/* "$X", ":X", and "@X" fetch column named "X" (case-sensitive!) */ /* "$X", ":X", and "@X" fetch column named "X" (case-sensitive!) */
const prefix = out.opt.rowMode[0]; const prefix = opt.rowMode[0];
if(':'===prefix || '@'===prefix || '$'===prefix){ if(':'===prefix || '@'===prefix || '$'===prefix){
out.cbArg = function(stmt){ out.cbArg = function(stmt){
const rc = stmt.get(this.obj)[this.colName]; const rc = stmt.get(this.obj)[this.colName];
return (undefined===rc) ? toss3("exec(): unknown result column:",this.colName) : rc; return (undefined===rc) ? toss3("exec(): unknown result column:",this.colName) : rc;
}.bind({ }.bind({
obj:Object.create(null), obj:Object.create(null),
colName: out.opt.rowMode.substr(1) colName: opt.rowMode.substr(1)
}); });
break; break;
} }
} }
toss3("Invalid rowMode:",out.opt.rowMode); toss3("Invalid rowMode:",opt.rowMode);
} }
} }
return out; return out;
}; };
/** /**
Internal impl of the DB.selectRowArray() and Internal impl of the DB.selectArray() and
selectRowObject() methods. selectObject() methods.
*/ */
const __selectFirstRow = (db, sql, bind, getArg)=>{ const __selectFirstRow = (db, sql, bind, getArg)=>{
let stmt, rc; let stmt, rc;
@ -588,9 +602,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/** /**
Executes one or more SQL statements in the form of a single Executes one or more SQL statements in the form of a single
string. Its arguments must be either (sql,optionsObject) or string. Its arguments must be either (sql,optionsObject) or
(optionsObject). In the latter case, optionsObject.sql (optionsObject). In the latter case, optionsObject.sql must
must contain the SQL to execute. Returns this contain the SQL to execute. By default it returns this object
object. Throws on error. but that can be changed via the `returnValue` option as
described below. Throws on error.
If no SQL is provided, or a non-string is provided, an If no SQL is provided, or a non-string is provided, an
exception is triggered. Empty SQL, on the other hand, is exception is triggered. Empty SQL, on the other hand, is
@ -599,21 +614,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
The optional options object may contain any of the following The optional options object may contain any of the following
properties: properties:
- `.sql` = the SQL to run (unless it's provided as the first - `sql` = the SQL to run (unless it's provided as the first
argument). This must be of type string, Uint8Array, or an array argument). This must be of type string, Uint8Array, or an array
of strings. In the latter case they're concatenated together of strings. In the latter case they're concatenated together
as-is, _with no separator_ between elements, before evaluation. as-is, _with no separator_ between elements, before evaluation.
The array form is often simpler for long hand-written queries. The array form is often simpler for long hand-written queries.
- `.bind` = a single value valid as an argument for - `bind` = a single value valid as an argument for
Stmt.bind(). This is _only_ applied to the _first_ non-empty Stmt.bind(). This is _only_ applied to the _first_ non-empty
statement in the SQL which has any bindable parameters. (Empty statement in the SQL which has any bindable parameters. (Empty
statements are skipped entirely.) statements are skipped entirely.)
- `.saveSql` = an optional array. If set, the SQL of each - `saveSql` = an optional array. If set, the SQL of each
executed statement is appended to this array before the executed statement is appended to this array before the
statement is executed (but after it is prepared - we don't have statement is executed (but after it is prepared - we don't have
the string until after that). Empty SQL statements are elided. the string until after that). Empty SQL statements are elided
but can have odd effects in the output. e.g. SQL of: `"select
1; -- empty\n; select 2"` will result in an array containing
`["select 1;", "--empty \n; select 2"]`. That's simply how
sqlite3 records the SQL for the 2nd statement.
================================================================== ==================================================================
The following options apply _only_ to the _first_ statement The following options apply _only_ to the _first_ statement
@ -621,14 +640,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
the statement actually produces any result rows. the statement actually produces any result rows.
================================================================== ==================================================================
- `.columnNames`: if this is an array, the column names of the - `columnNames`: if this is an array, the column names of the
result set are stored in this array before the callback (if result set are stored in this array before the callback (if
any) is triggered (regardless of whether the query produces any any) is triggered (regardless of whether the query produces any
result rows). If no statement has result columns, this value is result rows). If no statement has result columns, this value is
unchanged. Achtung: an SQL result may have multiple columns unchanged. Achtung: an SQL result may have multiple columns
with identical names. with identical names.
- `.callback` = a function which gets called for each row of - `callback` = a function which gets called for each row of
the result set, but only if that statement has any result the result set, but only if that statement has any result
_rows_. The callback's "this" is the options object, noting _rows_. The callback's "this" is the options object, noting
that this function synthesizes one if the caller does not pass that this function synthesizes one if the caller does not pass
@ -647,7 +666,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
The first argument passed to the callback defaults to an array of The first argument passed to the callback defaults to an array of
values from the current result row but may be changed with ... values from the current result row but may be changed with ...
- `.rowMode` = specifies the type of he callback's first argument. - `rowMode` = specifies the type of he callback's first argument.
It may be any of... It may be any of...
A) A string describing what type of argument should be passed A) A string describing what type of argument should be passed
@ -655,7 +674,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
A.1) `'array'` (the default) causes the results of A.1) `'array'` (the default) causes the results of
`stmt.get([])` to be passed to the `callback` and/or appended `stmt.get([])` to be passed to the `callback` and/or appended
to `resultRows`. to `resultRows`
A.2) `'object'` causes the results of A.2) `'object'` causes the results of
`stmt.get(Object.create(null))` to be passed to the `stmt.get(Object.create(null))` to be passed to the
@ -687,7 +706,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Any other `rowMode` value triggers an exception. 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), the `callback` option: each row of the result set (if any),
with the exception that the `rowMode` 'stmt' is not legal. It with the exception that the `rowMode` 'stmt' is not legal. It
is legal to use both `resultRows` and `callback`, but is legal to use both `resultRows` and `callback`, but
@ -695,28 +714,44 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
and can be used over a WebWorker-style message interface. and can be used over a WebWorker-style message interface.
exec() throws if `resultRows` is set and `rowMode` is 'stmt'. exec() throws if `resultRows` is set and `rowMode` is 'stmt'.
- `returnValue`: is a string specifying what this function
should return:
A) The default value is `"this"`, meaning that the
DB object itself should be returned.
B) `"resultRows"` means to return the value of the
`resultRows` option. If `resultRows` is not set, this
function behaves as if it were set to an empty array.
C) `"saveSql"` means to return the value of the
`saveSql` option. If `saveSql` is not set, this
function behaves as if it were set to an empty array.
Potential TODOs: Potential TODOs:
- `.bind`: permit an array of arrays/objects to bind. The first - `bind`: permit an array of arrays/objects to bind. The first
sub-array would act on the first statement which has bindable sub-array would act on the first statement which has bindable
parameters (as it does now). The 2nd would act on the next such parameters (as it does now). The 2nd would act on the next such
statement, etc. statement, etc.
- `.callback` and `.resultRows`: permit an array entries with - `callback` and `resultRows`: permit an array entries with
semantics similar to those described for `.bind` above. semantics similar to those described for `bind` above.
*/ */
exec: function(/*(sql [,obj]) || (obj)*/){ exec: function(/*(sql [,obj]) || (obj)*/){
affirmDbOpen(this); affirmDbOpen(this);
const arg = parseExecArgs(arguments); const arg = parseExecArgs(this, arguments);
if(!arg.sql){ if(!arg.sql){
return (''===arg.sql) ? this : toss3("exec() requires an SQL string."); return (''===arg.sql) ? this : toss3("exec() requires an SQL string.");
} }
const opt = arg.opt; const opt = arg.opt;
const callback = opt.callback; const callback = opt.callback;
let resultRows = (Array.isArray(opt.resultRows) const returnValue = opt.returnValue || 'this';
? opt.resultRows : undefined); const resultRows = (Array.isArray(opt.resultRows)
? opt.resultRows : (
'resultRows'===returnValue ? [] : undefined
));
let stmt; let stmt;
let bind = opt.bind; let bind = opt.bind;
let evalFirstResult = !!(arg.cbArg || opt.columnNames) /* true to evaluate the first result-returning query */; let evalFirstResult = !!(arg.cbArg || opt.columnNames) /* true to evaluate the first result-returning query */;
@ -774,7 +809,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.apply(opt,[row,stmt]); if(callback) callback.call(opt, row, stmt);
stmt._isLocked = false; stmt._isLocked = false;
} }
}else{ }else{
@ -793,7 +828,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
} }
wasm.scopedAllocPop(stack); wasm.scopedAllocPop(stack);
} }
return this; return arg.returnVal();
}/*exec()*/, }/*exec()*/,
/** /**
Creates a new scalar UDF (User-Defined Function) which is Creates a new scalar UDF (User-Defined Function) which is

View File

@ -162,12 +162,64 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
} }
}); });
/** Throws a new Error, the message of which is the concatenation /**
all args with a space between each. */ An Error subclass specifically for reporting DB-level errors and
const toss = (...args)=>{throw new Error(args.join(' '))}; enabling clients to unambiguously identify such exceptions.
The C-level APIs never throw, but some of the higher-level
C-style APIs do and the object-oriented APIs use exceptions
exclusively to report errors.
*/
class SQLite3Error extends Error {
/**
Constructs this object with a message equal to all arguments
concatenated with a space between each one. As a special case,
if it's passed only a single integer argument, the string form
of that argument is the result of
sqlite3.capi.sqlite3_js_rc_str() or (if that returns falsy), a
synthesized string which contains that integer.
*/
constructor(...args){
if(1===args.length && 'number'===typeof args[0] && args[0]===(args[0] | 0)){
super((capi.sqlite3_js_rc_str && capi.sqlite3_js_rc_str(args[0]))
|| ("Unknown result code #"+args[0]));
}else{
super(args.join(' '));
}
this.name = 'SQLite3Error';
}
};
/**
The main sqlite3 binding API gets installed into this object,
mimicking the C API as closely as we can. The numerous members
names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as
possible, identically to the C-native counterparts, as documented at:
https://www.sqlite.org/c3ref/intro.html
A very few exceptions require an additional level of proxy
function or may otherwise require special attention in the WASM
environment, and all such cases are document here. Those not
documented otherwise are installed as 1-to-1 proxies for their
C-side counterparts.
*/
const capi = Object.create(null);
/**
Functionally equivalent to the SQLite3Error constructor but may
be used as part of an expression, e.g.:
```
return someFunction(x) || SQLite3Error.toss(...);
```
*/
SQLite3Error.toss = (...args)=>{
throw new SQLite3Error(...args);
};
const toss3 = SQLite3Error.toss;
if(config.wasmfsOpfsDir && !/^\/[^/]+$/.test(config.wasmfsOpfsDir)){ if(config.wasmfsOpfsDir && !/^\/[^/]+$/.test(config.wasmfsOpfsDir)){
toss("config.wasmfsOpfsDir must be falsy or in the form '/dir-name'."); toss3("config.wasmfsOpfsDir must be falsy or in the form '/dir-name'.");
} }
/** /**
@ -267,7 +319,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
that v is not a supported TypedArray value. */ that v is not a supported TypedArray value. */
const affirmBindableTypedArray = (v)=>{ const affirmBindableTypedArray = (v)=>{
return isBindableTypedArray(v) return isBindableTypedArray(v)
|| toss("Value is not of a supported TypedArray type."); || toss3("Value is not of a supported TypedArray type.");
}; };
const utf8Decoder = new TextDecoder('utf-8'); const utf8Decoder = new TextDecoder('utf-8');
@ -318,21 +370,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
throw new WasmAllocError(...args); throw new WasmAllocError(...args);
}; };
/** Object.assign(capi, {
The main sqlite3 binding API gets installed into this object,
mimicking the C API as closely as we can. The numerous members
names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as
possible, identically to the C-native counterparts, as documented at:
https://www.sqlite.org/c3ref/intro.html
A very few exceptions require an additional level of proxy
function or may otherwise require special attention in the WASM
environment, and all such cases are document here. Those not
documented here are installed as 1-to-1 proxies for their C-side
counterparts.
*/
const capi = {
/** /**
sqlite3_create_function_v2() differs from its native sqlite3_create_function_v2() differs from its native
counterpart only in the following ways: counterpart only in the following ways:
@ -557,7 +595,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
values. values.
*/ */
sqlite3_randomness: (n, outPtr)=>{/*installed later*/}, sqlite3_randomness: (n, outPtr)=>{/*installed later*/},
}/*capi*/; }/*capi*/);
/** /**
Various internal-use utilities are added here as needed. They Various internal-use utilities are added here as needed. They
@ -617,7 +655,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
The symbols exported by the WASM environment. The symbols exported by the WASM environment.
*/ */
exports: config.exports exports: config.exports
|| toss("Missing API config.exports (WASM module exports)."), || toss3("Missing API config.exports (WASM module exports)."),
/** /**
When Emscripten compiles with `-sIMPORT_MEMORY`, it When Emscripten compiles with `-sIMPORT_MEMORY`, it
@ -626,7 +664,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
available via this.exports.memory. available via this.exports.memory.
*/ */
memory: config.memory || config.exports['memory'] memory: config.memory || config.exports['memory']
|| toss("API config object requires a WebAssembly.Memory object", || toss3("API config object requires a WebAssembly.Memory object",
"in either config.exports.memory (exported)", "in either config.exports.memory (exported)",
"or config.memory (imported)."), "or config.memory (imported)."),
@ -688,7 +726,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
keyDealloc = config.deallocExportName || 'free'; keyDealloc = config.deallocExportName || 'free';
for(const key of [keyAlloc, keyDealloc]){ for(const key of [keyAlloc, keyDealloc]){
const f = wasm.exports[key]; const f = wasm.exports[key];
if(!(f instanceof Function)) toss("Missing required exports[",key,"] function."); if(!(f instanceof Function)) toss3("Missing required exports[",key,"] function.");
} }
wasm.alloc = function(n){ wasm.alloc = function(n){
@ -1057,43 +1095,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
} }
})/*wasm.pstack properties*/; })/*wasm.pstack properties*/;
/**
An Error subclass specifically for reporting DB-level errors and
enabling clients to unambiguously identify such exceptions.
The C-level APIs never throw, but some of the higher-level
C-style APIs do and the object-oriented APIs use exceptions
exclusively to report errors.
*/
class SQLite3Error extends Error {
/**
Constructs this object with a message equal to all arguments
concatenated with a space between each one. As a special case,
if it's passed only a single integer argument, the string form
of that argument is the result of
sqlite3.capi.sqlite3_js_rc_str() or (if that returns falsy), a
synthesized string which contains that integer.
*/
constructor(...args){
if(1===args.length && 'number'===typeof args[0] && args[0]===(args[0] | 0)){
super(capi.sqlite3_js_rc_str(args[0]) || ("Unknown result code #"+args[0]));
}else{
super(args.join(' '));
}
this.name = 'SQLite3Error';
}
};
/**
Functionally equivalent to the SQLite3Error constructor but may
be used as part of an expression, e.g.:
```
return someFunction(x) || SQLite3Error.toss(...);
```
*/
SQLite3Error.toss = (...args)=>{
throw new SQLite3Error(...args);
};
capi.sqlite3_randomness = (...args)=>{ capi.sqlite3_randomness = (...args)=>{
if(1===args.length && util.isTypedArray(args[0]) if(1===args.length && util.isTypedArray(args[0])
&& 1===args[0].BYTES_PER_ELEMENT){ && 1===args[0].BYTES_PER_ELEMENT){
@ -1245,8 +1246,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
error it throws with a description of the problem. error it throws with a description of the problem.
*/ */
capi.sqlite3_js_db_export = function(pDb){ capi.sqlite3_js_db_export = function(pDb){
if(!pDb) toss('Invalid sqlite3* argument.'); if(!pDb) toss3('Invalid sqlite3* argument.');
if(!wasm.bigIntEnabled) toss('BigInt64 support is not enabled.'); if(!wasm.bigIntEnabled) toss3('BigInt64 support is not enabled.');
const stack = wasm.pstack.pointer; const stack = wasm.pstack.pointer;
let pOut; let pOut;
try{ try{
@ -1263,7 +1264,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
pDb, ppOut, pSize, 0 pDb, ppOut, pSize, 0
); );
if(rc){ if(rc){
toss("Database serialization failed with code", toss3("Database serialization failed with code",
sqlite3.capi.sqlite3_js_rc_str(rc)); sqlite3.capi.sqlite3_js_rc_str(rc));
} }
pOut = wasm.getPtrValue(ppOut); pOut = wasm.getPtrValue(ppOut);

View File

@ -1220,8 +1220,9 @@
.t('Table t', function(sqlite3){ .t('Table t', function(sqlite3){
const db = this.db; const db = this.db;
let list = []; let list = [];
db.exec({ let rc = db.exec({
sql:['CREATE TABLE t(a,b);', sql:['CREATE TABLE t(a,b);',
// ^^^ using TEMP TABLE breaks the db export test
"INSERT INTO t(a,b) VALUES(1,2),(3,4),", "INSERT INTO t(a,b) VALUES(1,2),(3,4),",
"(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for
off-by-one bug in string-to-WASM conversion*/], off-by-one bug in string-to-WASM conversion*/],
@ -1229,7 +1230,8 @@
bind: [5,6] bind: [5,6]
}); });
//debug("Exec'd SQL:", list); //debug("Exec'd SQL:", list);
T.assert(2 === list.length) T.assert(rc === db)
.assert(2 === list.length)
.assert('string'===typeof list[1]) .assert('string'===typeof list[1])
.assert(4===db.changes()); .assert(4===db.changes());
if(wasm.bigIntEnabled){ if(wasm.bigIntEnabled){
@ -1502,19 +1504,22 @@
xValue: xValueFinal xValue: xValueFinal
}); });
db.exec([ db.exec([
"CREATE TABLE twin(x, y); INSERT INTO twin VALUES", "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
"('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
]); ]);
let count = 0; let rc = db.exec({
db.exec({ returnValue: 'resultRows',
sql:[ sql:[
"SELECT x, winsumint(y) OVER (", "SELECT x, winsumint(y) OVER (",
"ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
") AS sum_y ", ") AS sum_y ",
"FROM twin ORDER BY x;", "FROM twin ORDER BY x;"
"DROP TABLE twin;" ]
], });
callback: function(row){ T.assert(Array.isArray(rc))
.assert(5 === rc.length);
let count = 0;
for(const row of rc){
switch(++count){ switch(++count){
case 1: T.assert('a'===row[0] && 9===row[1]); break; case 1: T.assert('a'===row[0] && 9===row[1]); break;
case 2: T.assert('b'===row[0] && 12===row[1]); break; case 2: T.assert('b'===row[0] && 12===row[1]); break;
@ -1524,8 +1529,36 @@
default: toss("Too many rows to window function."); default: toss("Too many rows to window function.");
} }
} }
const resultRows = [];
rc = db.exec({
resultRows,
returnValue: 'resultRows',
sql:[
"SELECT x, winsumint(y) OVER (",
"ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
") AS sum_y ",
"FROM twin ORDER BY x;"
]
}); });
T.assert(5 === count); T.assert(rc === resultRows)
.assert(5 === rc.length);
rc = db.exec({
returnValue: 'saveSql',
sql: "select 1; select 2; -- empty\n; select 3"
});
T.assert(Array.isArray(rc))
.assert(3===rc.length)
.assert('select 1;' === rc[0])
.assert('select 2;' === rc[1])
.assert('-- empty\n; select 3' === rc[2]
/* Strange but true. */);
T.mustThrowMatching(()=>{
db.exec({sql:'', returnValue: 'nope'});
}, /^Invalid returnValue/);
db.exec("DROP TABLE twin");
} }
}/*window UDFs*/) }/*window UDFs*/)
@ -1536,7 +1569,7 @@
db.exec({ db.exec({
sql:new TextEncoder('utf-8').encode([ sql:new TextEncoder('utf-8').encode([
// ^^^ testing string-vs-typedarray handling in exec() // ^^^ testing string-vs-typedarray handling in exec()
"attach 'session' as foo;" /* name 'session' is magic for kvvfs! */, "attach 'session' as foo;",
"create table foo.bar(a);", "create table foo.bar(a);",
"insert into foo.bar(a) values(1),(2),(3);", "insert into foo.bar(a) values(1),(2),(3);",
"select a from foo.bar order by a;" "select a from foo.bar order by a;"
@ -1665,20 +1698,16 @@
;/* end of oo1 checks */ ;/* end of oo1 checks */
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
T.g('kvvfs (Worker thread only)', isWorker) T.g('kvvfs')
.t({ .t('kvvfs sanity checks', function(sqlite3){
name: 'kvvfs is disabled', if(isWorker()){
test: ()=>{
T.assert( T.assert(
!capi.sqlite3_vfs_find('kvvfs'), !capi.sqlite3_vfs_find('kvvfs'),
"Expecting kvvfs to be unregistered." "Expecting kvvfs to be unregistered."
); );
log("kvvfs is (correctly) unavailable in a Worker.");
return;
} }
});
T.g('kvvfs (UI thread only)', isUIThread)
.t({
name: 'kvvfs sanity checks',
test: function(sqlite3){
const filename = 'session'; const filename = 'session';
const pVfs = capi.sqlite3_vfs_find('kvvfs'); const pVfs = capi.sqlite3_vfs_find('kvvfs');
T.assert(pVfs); T.assert(pVfs);
@ -1686,6 +1715,7 @@
const unlink = ()=>JDb.clearStorage(filename); const unlink = ()=>JDb.clearStorage(filename);
unlink(); unlink();
let db = new JDb(filename); let db = new JDb(filename);
try {
db.exec([ db.exec([
'create table kvvfs(a);', 'create table kvvfs(a);',
'insert into kvvfs(a) values(1),(2),(3)' 'insert into kvvfs(a) values(1),(2),(3)'
@ -1695,6 +1725,7 @@
db = new JDb(filename); db = new JDb(filename);
db.exec('insert into kvvfs(a) values(4),(5),(6)'); db.exec('insert into kvvfs(a) values(4),(5),(6)');
T.assert(6 === db.selectValue('select count(*) from kvvfs')); T.assert(6 === db.selectValue('select count(*) from kvvfs'));
}finally{
db.close(); db.close();
unlink(); unlink();
} }
@ -1713,6 +1744,7 @@
const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn); const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn);
unlink(); unlink();
let db = new sqlite3.opfs.OpfsDb(filename); let db = new sqlite3.opfs.OpfsDb(filename);
try {
db.exec([ db.exec([
'create table p(a);', 'create table p(a);',
'insert into p(a) values(1),(2),(3)' 'insert into p(a) values(1),(2),(3)'
@ -1722,9 +1754,11 @@
db = new sqlite3.opfs.OpfsDb(filename); db = new sqlite3.opfs.OpfsDb(filename);
db.exec('insert into p(a) values(4),(5),(6)'); db.exec('insert into p(a) values(4),(5),(6)');
T.assert(6 === db.selectValue('select count(*) from p')); T.assert(6 === db.selectValue('select count(*) from p'));
}finally{
db.close(); db.close();
unlink(); unlink();
} }
}
}/*OPFS sanity checks*/) }/*OPFS sanity checks*/)
;/* end OPFS tests */ ;/* end OPFS tests */

View File

@ -1,5 +1,5 @@
C Avoid\sa\ssegfault\sthat\scould\soccur\swhen\srunning\sthe\srecover\sAPI\son\sa\sdatabase\shandle\swith\smemory-mapping\senabled. C Add\soo1.DB.exec()\s'returnValue'\soption,\swhich\sspecifies\swhat\sexec()\sshould\sreturn.\sDefaults\sto\sthe\sdb\sobject\sand\senables\sdirect\sreturn\sof\sthe\sresult\srows\sarray\sor\sa\slist\sof\sthe\sindividual\sSQL\sstatements.\sOther\scode-adjacent\sinternal\scleanups.
D 2022-10-31T10:53:23.735 D 2022-10-31T11:09:14.111
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
@ -500,9 +500,9 @@ F ext/wasm/api/post-js-header.js d6ab3dfef4a06960d28a7eaa338d4e2a1a5981e9b387181
F ext/wasm/api/pre-js.js 287e462f969342b032c03900e668099fa1471d852df7a472de5bc349161d9c04 F ext/wasm/api/pre-js.js 287e462f969342b032c03900e668099fa1471d852df7a472de5bc349161d9c04
F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34 F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34
F ext/wasm/api/sqlite3-api-glue.js b87543534821ecfa56fc0d0cd153a115fa974e70d6217964baf6e93ef8d25fb1 F ext/wasm/api/sqlite3-api-glue.js b87543534821ecfa56fc0d0cd153a115fa974e70d6217964baf6e93ef8d25fb1
F ext/wasm/api/sqlite3-api-oo1.js a17e2624967073f86cc50f4c1e30f8822ef631dc20dfc73b1143847b9e9723fe F ext/wasm/api/sqlite3-api-oo1.js 4028bc2bac7e3ae2d23b7c99828155b4a06da006b51dc2a929bc0db26337370d
F ext/wasm/api/sqlite3-api-opfs.js c67cbe0b1451ec43bc6b3199e13453e1ca56d718a75c0498253b0d479c336256 F ext/wasm/api/sqlite3-api-opfs.js c67cbe0b1451ec43bc6b3199e13453e1ca56d718a75c0498253b0d479c336256
F ext/wasm/api/sqlite3-api-prologue.js a218dda5e5ced8894f65760131371e4cabd31062af58803af8952cc00ea778d2 F ext/wasm/api/sqlite3-api-prologue.js 873986ca150c79510f647b910f8349bc71b14db21e444cab3b9fad9c4f39ffc7
F ext/wasm/api/sqlite3-api-worker1.js efdca1b42299d80b54f366d15a8fc5343f3b3e9e3647e5c1fd6f3ee1015e501b F ext/wasm/api/sqlite3-api-worker1.js efdca1b42299d80b54f366d15a8fc5343f3b3e9e3647e5c1fd6f3ee1015e501b
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3 F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js 29f6f5c314c2956e77573c6ab975c2455a0839721ed44f38004382c2a1463ab5 F ext/wasm/api/sqlite3-opfs-async-proxy.js 29f6f5c314c2956e77573c6ab975c2455a0839721ed44f38004382c2a1463ab5
@ -549,7 +549,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 48fc59110e8775bb43c9be25b6d634fc07ebadab7da8fbd44889e8129c6e2548 F ext/wasm/test-opfs-vfs.js 48fc59110e8775bb43c9be25b6d634fc07ebadab7da8fbd44889e8129c6e2548
F ext/wasm/tester1-worker.html d02b9d38876b023854cf8955e77a40912f7e516956b4dbe1ec7f215faac273ee F ext/wasm/tester1-worker.html d02b9d38876b023854cf8955e77a40912f7e516956b4dbe1ec7f215faac273ee
F ext/wasm/tester1.html c6c47e5a8071eb09cb1301104435c8e44fbb5719c92411f5b2384a461f9793c5 F ext/wasm/tester1.html c6c47e5a8071eb09cb1301104435c8e44fbb5719c92411f5b2384a461f9793c5
F ext/wasm/tester1.js 08ccc16972562ff604b7bda387b56d6eea380aaf968697fc8e2a9bb3c6ba2dda F ext/wasm/tester1.js 2427ac48e255f658ad81163b5dc6372a8609ab6ab60e295e371d1e5fe9a495ab
F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5 F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5
F ext/wasm/wasmfs.make fb2d3c4a298b12cf1ec994ad1d0f1d027ae297449b364cde43d2eb807d68048f F ext/wasm/wasmfs.make fb2d3c4a298b12cf1ec994ad1d0f1d027ae297449b364cde43d2eb807d68048f
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
@ -2054,8 +2054,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 cb12ac5de17e677f089d7b0b46803efbd9a9178972ffb0454a8b557b06633658 P e02c697281a777c33070168af784b2d291397e488244a217620897f40aed7158
R 46019e96ebee7f43e27063daa1a45bd1 R 26339704ab975fcc1e6818338a87c2a1
U dan U stephan
Z 31c01b86489219dc9c143f294c0c509f Z 2aa3561d4216c084633673f53cc5adc1
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
e02c697281a777c33070168af784b2d291397e488244a217620897f40aed7158 69d36a6aa5e2cd79d26c0fd3e0d20fe8838fd1be97db07725233bfff1dfe6643