Add more JS tests. Flesh out the aggregate UDF tests to use sqlite3_aggregate_context() so that they can each be used multiple times in the same statement. Add sqlite3_js_aggregate_context() convenience helper.

FossilOrigin-Name: 9d034ef5e1bab7c9651c2450dc85765fa6365d3f1414c711550de858ff8b3ece
This commit is contained in:
stephan 2022-10-20 21:28:31 +00:00
parent 875b95d4f5
commit 96b6371d70
7 changed files with 155 additions and 53 deletions

View File

@ -149,9 +149,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
oflags |= capi.SQLITE_OPEN_EXRESCODE;
const scope = wasm.scopedAllocPush();
const stack = wasm.pstack.pointer;
try {
const pPtr = wasm.allocPtr() /* output (sqlite3**) arg */;
const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
const pVfsName = vfsName ? (
('number'===typeof vfsName ? vfsName : wasm.scopedAllocCString(vfsName))
): 0;
@ -163,21 +163,19 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
__dbTraceToConsole, 0);
}
// Check for per-VFS post-open SQL...
wasm.setPtrValue(pPtr, 0);
if(0===capi.sqlite3_file_control(
pDb, "main", capi.SQLITE_FCNTL_VFS_POINTER, pPtr
)){
const postInitSql = __vfsPostOpenSql[wasm.getPtrValue(pPtr)];
if(postInitSql){
rc = capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0);
checkSqlite3Rc(pDb, rc);
}
const pVfs = capi.sqlite3_js_db_vfs(pDb);
//console.warn("Opened db",fn,"with vfs",vfsName,pVfs);
if(!pVfs) toss3("Internal error: cannot get VFS for new db handle.");
const postInitSql = __vfsPostOpenSql[pVfs];
if(postInitSql){
rc = capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0);
checkSqlite3Rc(pDb, rc);
}
}catch( e ){
if( pDb ) capi.sqlite3_close_v2(pDb);
throw e;
}finally{
wasm.scopedAllocPop(scope);
wasm.pstack.restore(stack);
}
this.filename = fnJs;
__ptrMap.set(this, pDb);

View File

@ -280,6 +280,14 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
this.name = 'WasmAllocError';
}
};
/**
Functionally equivalent to the WasmAllocError constructor but may
be used as part of an expression, e.g.:
```
return someAllocatingFunction(x) || WasmAllocError.toss(...);
```
*/
WasmAllocError.toss = (...args)=>{
throw new WasmAllocError(args.join(' '));
};
@ -508,6 +516,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
"flexible-string" argument converter.
*/
sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/,
/**
Various internal-use utilities are added here as needed. They
are bound to an object only so that we have access to them in
@ -1024,6 +1033,14 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
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.join(' '));
};
@ -1096,8 +1113,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
pointer. The 3rd argument specifies the database name of the
given database connection to check, defaulting to the main db.
The 2nd and 3rd arguments may either be a JS string or a C-string
allocated from the wasm environment.
The 2nd and 3rd arguments may either be a JS string or a WASM
C-string. If the 2nd argument is a NULL WASM pointer, the default
VFS is assumed. If the 3rd is a NULL WASM pointer, "main" is
assumed.
The truthy value it returns is a pointer to the `sqlite3_vfs`
object.
@ -1112,17 +1131,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
const pK = capi.sqlite3_vfs_find(vfsName);
if(!pK) return false;
else if(!pDb){
return capi.sqlite3_vfs_find(0)===pK ? pK : false;
}
const ppVfs = wasm.allocPtr();
try{
return (
(0===capi.sqlite3_file_control(
pDb, dbName, capi.SQLITE_FCNTL_VFS_POINTER, ppVfs
)) && (wasm.getPtrValue(ppVfs) === pK)
) ? pK : false;
}finally{
wasm.dealloc(ppVfs);
return pK===capi.sqlite3_vfs_find(0) ? pK : false;
}else{
return pK===capi.sqlite3_js_db_vfs(pDb) ? pK : false;
}
}catch(e){
/* Ignore - probably bad args to a wasm-bound function. */
@ -1179,14 +1190,32 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
? wasm.heap8u().slice(pOut, pOut + Number(nOut))
: new Uint8Array();
return rc;
}catch(e){
console.error('internal error?',e);
throw w;
}finally{
if(pOut) wasm.exports.sqlite3_free(pOut);
wasm.pstack.restore(stack);
}
};
/**
Given a `sqlite3*` and a database name (JS string or WASM
C-string pointer, which may be 0), returns a pointer to the
sqlite3_vfs responsible for it. If the given db name is null/0,
or not provided, then "main" is assumed.
*/
capi.sqlite3_js_db_vfs =
(dbPointer, dbName=0)=>wasm.sqlite3_wasm_db_vfs(dbPointer, dbName);
/**
A thin wrapper around capi.sqlite3_aggregate_context() which
behaves the same except that it throws a WasmAllocError if that
function returns 0.
*/
capi.sqlite3_js_aggregate_context = (pCtx, n)=>{
return capi.sqlite3_aggregate_context(pCtx, n)
|| WasmAllocError.toss(
"Cannot allocate",n,"bytes for sqlite3_aggregate_context()"
);
};
if( capi.util.isMainWindow() ){
/* Features specific to the main window thread... */

View File

@ -231,7 +231,7 @@
If the `dbId` does not refer to an opened ID, this is a no-op. If
the `args` object contains a truthy `unlink` value then the database
will unlinked (deleted) after closing it. The inability to close a
will be unlinked (deleted) after closing it. The inability to close a
db (because it's not opened) or delete its file does not trigger an
error.

View File

@ -48,6 +48,7 @@
** want undefined. Please keep these alphabetized.
*/
#undef SQLITE_OMIT_DESERIALIZE
#undef SQLITE_OMIT_MEMORYDB
/*
** Define any SQLITE_... config defaults we want if they aren't

View File

@ -1083,6 +1083,47 @@
.assert(0===this.db.openStatementCount());
})
////////////////////////////////////////////////////////////////////////
.t('sqlite3_js_...()', function(){
const db = this.db;
if(1){
const vfsList = capi.sqlite3_js_vfs_list();
T.assert(vfsList.length>1);
T.assert('string'===typeof vfsList[0]);
for(const v of vfsList){
T.assert('string' === typeof v)
.assert(capi.sqlite3_vfs_find(v) > 0);
}
}
/**
Trivia: the magic db name ":memory:" does not actually use the
"memdb" VFS unless "memdb" is _explicitly_ provided as the VFS
name. Instead, it uses the default VFS with an in-memory btree.
Thus this.db's VFS may not be memdb even though it's an in-memory
db.
*/
const pVfsMem = capi.sqlite3_vfs_find('memdb'),
pVfsDflt = capi.sqlite3_vfs_find(0),
pVfsDb = capi.sqlite3_js_db_vfs(db.pointer);
T.assert(pVfsMem > 0)
.assert(pVfsDflt > 0)
.assert(pVfsDb > 0)
.assert(pVfsMem !== pVfsDflt
/* memdb lives on top of the default vfs */)
.assert(':memory:' === db.filename)
.assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem)
;
/*const vMem = new capi.sqlite3_vfs(pVfsMem),
vDflt = new capi.sqlite3_vfs(pVfsDflt),
vDb = new capi.sqlite3_vfs(pVfsDb);*/
const duv = capi.sqlite3_js_db_uses_vfs;
T.assert(pVfsDflt === duv(db.pointer, 0)
|| pVfsMem === duv(db.pointer,0))
.assert(!duv(db.pointer, "foo"))
;
}/*sqlite3_js_...()*/)
////////////////////////////////////////////////////////////////////
.t('Table t', function(sqlite3){
const db = this.db;
@ -1168,6 +1209,14 @@
.assert(0==e.message.indexOf('Cannot prepare empty'));
}
})
////////////////////////////////////////////////////////////////////////
.t('sqlite3_js_db_export()', function(){
const db = this.db;
const xp = capi.sqlite3_js_db_export(db.pointer);
T.assert(xp instanceof Uint8Array)
.assert(xp.byteLength>0)
.assert(0 === xp.byteLength % 512);
}/*sqlite3_js_db_export()*/)
////////////////////////////////////////////////////////////////////
.t('Scalar UDFs', function(sqlite3){
@ -1228,39 +1277,40 @@
name: 'Aggregate UDFs',
test: function(sqlite3){
const db = this.db;
const aggState = {summer: 0, summerN: 0};
const sjac = capi.sqlite3_js_aggregate_context;
db.createFunction({
name: 'summer',
xStep: function(pCtx, n){
aggState.summer += n;
const pAgg = sjac(pCtx, 4);
wasm.setMemValue(pAgg, wasm.getMemValue(pAgg,'i32') + Number(n), 'i32');
},
xFinal: function(pCtx){
const rc = aggState.summer;
aggState.summer = 0;
return rc;
}
xFinal: (pCtx)=>wasm.getMemValue(sjac(pCtx, 4),'i32')
});
let v = db.selectValue([
"with cte(v) as (",
"select 3 union all select 5 union all select 7",
") select summer(v) from cte"
") select summer(v), summer(v+1) from cte"
/* ------------------^^^^^^^^^^^ ensures that we're handling
sqlite3_aggregate_context() properly. */
]);
T.assert(15===v);
T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
/wrong number of arguments/);
db.createFunction({
name: 'summerN',
arity: -1,
xStep: function(pCtx, ...args){
for(const v of args) aggState.summerN += v;
const pAgg = sjac(pCtx, 4);
let sum = wasm.getMemValue(pAgg, 'i32');
for(const v of args) sum += Number(v);
wasm.setMemValue(pAgg, sum, 'i32');
},
xFinal: function(pCtx){
const rc = aggState.summerN;
aggState.summerN = 0;
return rc;
return wasm.getMemValue(sjac(pCtx, 4),'i32')
}
});
T.assert(18===db.selectValue('select summerN(1,8,9)'));
T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)'));
T.mustThrowMatching(()=>{
db.createFunction('nope',{
xFunc: ()=>{}, xStep: ()=>{}
@ -1284,6 +1334,30 @@
}
}/*aggregate UDFs*/)
////////////////////////////////////////////////////////////////////////
.t({
name: 'Aggregate UDFs (64-bit)',
predicate: ()=>wasm.bigIntEnabled,
test: function(sqlite3){
const db = this.db;
const sjac = capi.sqlite3_js_aggregate_context;
db.createFunction({
name: 'summer64',
xStep: function(pCtx, n){
const pAgg = sjac(pCtx, 8);
wasm.setMemValue(pAgg, wasm.getMemValue(pAgg,'i64') + BigInt(n), 'i64');
},
xFinal: (pCtx)=>wasm.getMemValue(sjac(pCtx, 8),'i64')
});
let v = db.selectValue([
"with cte(v) as (",
"select 3 union all select 5 union all select 7",
") select summer64(v*10), summer64(v+1) from cte"
]);
T.assert(150n===BigInt(v));
}
}/*aggregate UDFs*/)
////////////////////////////////////////////////////////////////////
.t({
name: 'Window UDFs (tests are TODO)',

View File

@ -1,5 +1,5 @@
C Apply\smagic.txt\scorrection\sand\saddition\sreported\sin\s[forum\spost\s2d2366a04a0385|forum:2d2366a04a0385].
D 2022-10-20T18:58:14.289
C Add\smore\sJS\stests.\sFlesh\sout\sthe\saggregate\sUDF\stests\sto\suse\ssqlite3_aggregate_context()\sso\sthat\sthey\scan\seach\sbe\sused\smultiple\stimes\sin\sthe\ssame\sstatement.\sAdd\ssqlite3_js_aggregate_context()\sconvenience\shelper.
D 2022-10-20T21:28:31.440
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -485,14 +485,14 @@ F ext/wasm/api/post-js-header.js 2e5c886398013ba2af88028ecbced1e4b22dc96a86467f1
F ext/wasm/api/pre-js.js 151e0616614a49f3db19ed544fa13b38c87c108959fbcd4029ea8399a562d94f
F ext/wasm/api/sqlite3-api-cleanup.js 4d07a7524dc9b7b050acfde57163e839243ad2383bd7ee0de0178b1b3e988588
F ext/wasm/api/sqlite3-api-glue.js 0b5240bd325d2561f269cd0d82bf686336526e5e276251c2241adfbda802abf8
F ext/wasm/api/sqlite3-api-oo1.js dc9b6a61649ad32836044de388c5248790239d62ced4e1116023135fcb0fc68b
F ext/wasm/api/sqlite3-api-oo1.js 0e278d131dad72e9eb348a3dda6a4ff734a9e08925b4ed7e6e5a688d2edaf525
F ext/wasm/api/sqlite3-api-opfs.js 22d60ba956e873b65e2e0591e239178082bd53a6d563c3c58db7dc03e562e8f7
F ext/wasm/api/sqlite3-api-prologue.js 1366d538a7b388c299a389f441a79cf0b18af50208343545bd318936b6232acd
F ext/wasm/api/sqlite3-api-worker1.js cb07b321164483524a27cf2207d4358b905703c410fcd8256e0acca5ab2fffb2
F ext/wasm/api/sqlite3-api-prologue.js bb7a98a8c62545bf07b5fdee831c0d40c86f98c0094b00d8497a9de8976a0544
F ext/wasm/api/sqlite3-api-worker1.js a7f38f03275d6c27ab2aef3e83215d3c97ce09c43e6904df47c3764d9d4572b4
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js 206ce6bbc3c30ad51a37d9c25e3a2712e70b586e0f9a2cf8cb0b9619017c2671
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c e1fcda97775dd149b4a2e0a4f16a22cede96daa5a91795ba214bdb3c2e680f4a
F ext/wasm/api/sqlite3-wasm.c dde771a10e6a8d01ae516d32abc4cdd2d542cb3339e35c5b4be8ebbc1dc1ccc1
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
F ext/wasm/api/sqlite3-worker1.js dbe54b69c1520a2d25eae148cd2750ded2dd7f219ea4ee46f83e0a851dca5974
F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
@ -533,7 +533,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 48fc59110e8775bb43c9be25b6d634fc07ebadab7da8fbd44889e8129c6e2548
F ext/wasm/tester1-worker.html 048c341f124fdb61ca14dfd1bd1f78742490f208aa3bb1e84399f83f1e7e6a74
F ext/wasm/tester1.html 37ccc958fa0d95074af2d72b7241c8e2d982bbec6dda4dc790241af3d933c3b6
F ext/wasm/tester1.js 44d71175e2941bf1d7c27afa0c395fe81c83cbd74cd10e34e0688dd833042f1e
F ext/wasm/tester1.js 6e1c8e4d48add1d49fb7901dfee04fdf6cdd5bc39adcbf15c097f61b581a893c
F ext/wasm/version-info.c 5fa356d38859d71a0369b5c37e1935def7413fcc8a4e349a39d9052c1d0479f4
F ext/wasm/wasmfs.make ee0004813e16c283ff633e08b482008d56adf9b7d42f6c5612f7ab002b924f69
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
@ -2036,8 +2036,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 ac9af71b72a749b0a040273a88480d56f49570b569389a4ea20cc055f494d8ff
R b44d84c26c2f9b06f7e9b798ca72a049
P 9bf26e2aa3579f354ed2d314e1bf3e3ef117cbd71500ef5f76caa1de5cce1edc
R dbe97bbb80fe6071199461fb846ef1aa
U stephan
Z e01cdee717736d0716b4f5500835f6af
Z 83bd13decfea9b8d2129079a8ff06725
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
9bf26e2aa3579f354ed2d314e1bf3e3ef117cbd71500ef5f76caa1de5cce1edc
9d034ef5e1bab7c9651c2450dc85765fa6365d3f1414c711550de858ff8b3ece