From 96b6371d709a91015204b14e4b83e61a209bbde2 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 20 Oct 2022 21:28:31 +0000 Subject: [PATCH] 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 --- ext/wasm/api/sqlite3-api-oo1.js | 22 +++--- ext/wasm/api/sqlite3-api-prologue.js | 61 +++++++++++----- ext/wasm/api/sqlite3-api-worker1.js | 2 +- ext/wasm/api/sqlite3-wasm.c | 1 + ext/wasm/tester1.js | 100 +++++++++++++++++++++++---- manifest | 20 +++--- manifest.uuid | 2 +- 7 files changed, 155 insertions(+), 53 deletions(-) diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index c2e7eb14f1..d101bc17f3 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -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); diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index ca1542e2b2..da7d0740fa 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -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... */ diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js index fa6a438462..dea06341a0 100644 --- a/ext/wasm/api/sqlite3-api-worker1.js +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -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. diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index 1decf0593f..8b767948e4 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -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 diff --git a/ext/wasm/tester1.js b/ext/wasm/tester1.js index c8808c9137..0d4dd3bcfc 100644 --- a/ext/wasm/tester1.js +++ b/ext/wasm/tester1.js @@ -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)', diff --git a/manifest b/manifest index 1e1b0ee252..40a970ee62 100644 --- a/manifest +++ b/manifest @@ -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. diff --git a/manifest.uuid b/manifest.uuid index 0796abfc38..a3b802fb1b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9bf26e2aa3579f354ed2d314e1bf3e3ef117cbd71500ef5f76caa1de5cce1edc \ No newline at end of file +9d034ef5e1bab7c9651c2450dc85765fa6365d3f1414c711550de858ff8b3ece \ No newline at end of file