4 The author disclaims copyright to this source code. In place of a
5 legal notice, here is a blessing:
7 * May you do good and not evil.
8 * May you find forgiveness for yourself and forgive others.
9 * May you share freely, never taking more than you give.
11 ***********************************************************************
13 Main functional and regression tests for the sqlite3 WASM API.
15 This mini-framework works like so:
17 This script adds a series of test groups, each of which contains an
18 arbitrary number of tests, into a queue. After loading of the
19 sqlite3 WASM/JS module is complete, that queue is processed. If any
20 given test fails, the whole thing fails. This script is built such
21 that it can run from the main UI thread or worker thread. Test
22 groups and individual tests can be assigned a predicate function
23 which determines whether to run them or not, and this is
24 specifically intended to be used to toggle certain tests on or off
25 for the main/worker threads or the availability (or not) of
26 optional features such as int64 support.
28 Each test group defines a single state object which gets applied as
29 the test functions' `this` for all tests in that group. Test
30 functions can use that to, e.g., set up a db in an early test and
31 close it in a later test. Each test gets passed the sqlite3
32 namespace object as its only argument.
35 This file is intended to be processed by c-pp to inject (or not)
36 code specific to ES6 modules which is illegal in non-module code.
38 Non-ES6 module build and ES6 module for the main-thread:
40 ./c-pp -f tester1.c-pp.js -o tester1.js
42 ES6 worker module build:
44 ./c-pp -f tester1.c-pp.js -o tester1-esm.js -Dtarget=es6-module
46 //#if target=es6-module
47 import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs';
48 globalThis.sqlite3InitModule = sqlite3InitModule;
54 Set up our output channel differently depending
55 on whether we are running in a worker thread or
59 /* Predicate for tests/groups. */
60 const isUIThread = ()=>(globalThis.window===self && globalThis.document);
61 /* Predicate for tests/groups. */
62 const isWorker = ()=>!isUIThread();
63 /* Predicate for tests/groups. */
64 const testIsTodo = ()=>false;
65 const haveWasmCTests = ()=>{
66 return !!wasm.exports.sqlite3__wasm_test_intptr;
69 return globalThis.FileSystemHandle
70 && globalThis.FileSystemDirectoryHandle
71 && globalThis.FileSystemFileHandle
72 && globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle
73 && navigator?.storage?.getDirectory;
77 const mapToString = (v)=>{
79 case 'number': case 'string': case 'boolean':
80 case 'undefined': case 'bigint':
84 if(null===v) return 'null';
85 if(v instanceof Error){
92 return JSON.stringify(v,undefined,2);
94 const normalizeArgs = (args)=>args.map(mapToString);
96 console.log("Running in the UI thread.");
97 const logTarget = document.querySelector('#test-output');
98 logClass = function(cssClass,...args){
99 const ln = document.createElement('div');
101 for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){
105 ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
106 logTarget.append(ln);
108 const cbReverse = document.querySelector('#cb-log-reverse');
109 //cbReverse.setAttribute('checked','checked');
110 const cbReverseKey = 'tester1:cb-log-reverse';
111 const cbReverseIt = ()=>{
112 logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse');
113 //localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0);
115 cbReverse.addEventListener('change', cbReverseIt, true);
116 /*if(localStorage.getItem(cbReverseKey)){
117 cbReverse.checked = !!(+localStorage.getItem(cbReverseKey));
120 }else{ /* Worker thread */
121 console.log("Running in a Worker thread.");
122 logClass = function(cssClass,...args){
125 payload:{cssClass, args: normalizeArgs(args)}
130 const reportFinalTestStatus = function(pass){
132 let e = document.querySelector('#color-target');
133 e.classList.add(pass ? 'tests-pass' : 'tests-fail');
134 e = document.querySelector('title');
135 e.innerText = (pass ? 'PASS' : 'FAIL') + ': ' + e.innerText;
137 postMessage({type:'test-result', payload:{pass}});
140 const log = (...args)=>{
141 //console.log(...args);
142 logClass('',...args);
144 const warn = (...args)=>{
145 console.warn(...args);
146 logClass('warning',...args);
148 const error = (...args)=>{
149 console.error(...args);
150 logClass('error',...args);
153 const toss = (...args)=>{
155 throw new Error(args.join(' '));
157 const tossQuietly = (...args)=>{
158 throw new Error(args.join(' '));
161 const roundMs = (ms)=>Math.round(ms*100)/100;
164 Helpers for writing sqlite3-specific tests.
167 /** Running total of the number of tests run via
171 If expr is a function, it is called and its result
172 is returned, coerced to a bool, else expr, coerced to
175 toBool: function(expr){
176 return (expr instanceof Function) ? !!expr() : !!expr;
178 /** Throws if expr is false. If expr is a function, it is called
179 and its result is evaluated. If passed multiple arguments,
180 those after the first are a message string which get applied
181 as an exception message if the assertion fails. The message
182 arguments are concatenated together with a space between each.
184 assert: function f(expr, ...msg){
186 if(!this.toBool(expr)){
187 throw new Error(msg.length ? msg.join(' ') : "Assertion failed.");
191 /** Calls f() and squelches any exception it throws. If it
192 does not throw, this function throws. */
193 mustThrow: function(f, msg){
196 try{ f(); } catch(e){err=e;}
197 if(!err) throw new Error(msg || "Expected exception.");
201 Works like mustThrow() but expects filter to be a regex,
202 function, or string to match/filter the resulting exception
203 against. If f() does not throw, this test fails and an Error is
204 thrown. If filter is a regex, the test passes if
205 filter.test(error.message) passes. If it's a function, the test
206 passes if filter(error) returns truthy. If it's a string, the
207 test passes if the filter matches the exception message
208 precisely. In all other cases the test fails, throwing an
211 If it throws, msg is used as the error report unless it's falsy,
212 in which case a default is used.
214 mustThrowMatching: function(f, filter, msg){
217 try{ f(); } catch(e){err=e;}
218 if(!err) throw new Error(msg || "Expected exception.");
220 if(filter instanceof RegExp) pass = filter.test(err.message);
221 else if(filter instanceof Function) pass = filter(err);
222 else if('string' === typeof filter) pass = (err.message === filter);
224 throw new Error(msg || ("Filter rejected this exception: "+err.message));
228 /** Throws if expr is truthy or expr is a function and expr()
230 throwIf: function(expr, msg){
232 if(this.toBool(expr)) throw new Error(msg || "throwIf() failed");
235 /** Throws if expr is falsy or expr is a function and expr()
237 throwUnless: function(expr, msg){
239 if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed");
242 eqApprox: (v1,v2,factor=0.05)=>(v1>=(v2-factor) && v1<=(v2+factor)),
243 TestGroup: (function(){
244 let groupCounter = 0;
245 const TestGroup = function(name, predicate){
246 this.number = ++groupCounter;
248 this.predicate = predicate;
251 TestGroup.prototype = {
252 addTest: function(testObj){
253 this.tests.push(testObj);
256 run: async function(sqlite3){
257 logClass('group-start',"Group #"+this.number+':',this.name);
259 const p = this.predicate(sqlite3);
260 if(!p || 'string'===typeof p){
261 logClass(['warning','skipping-group'],
262 "SKIPPING group:", p ? p : "predicate says to" );
266 const assertCount = TestUtil.counter;
267 const groupState = Object.create(null);
269 let runtime = 0, i = 0;
270 for(const t of this.tests){
272 const n = this.number+"."+i;
273 logClass('one-test-line', n+":", t.name);
275 const p = t.predicate(sqlite3);
276 if(!p || 'string'===typeof p){
277 logClass(['warning','skipping-test'],
278 "SKIPPING:", p ? p : "predicate says to" );
279 skipped.push( n+': '+t.name );
283 const tc = TestUtil.counter, now = performance.now();
284 let rc = t.test.call(groupState, sqlite3);
285 /*if(rc instanceof Promise){
287 error("Test failure:",e);
292 const then = performance.now();
293 runtime += then - now;
294 logClass(['faded','one-test-summary'],
295 TestUtil.counter - tc, 'assertion(s) in',
296 roundMs(then-now),'ms');
298 logClass(['green','group-end'],
300 (TestUtil.counter - assertCount),
301 "assertion(s) in",roundMs(runtime),"ms");
302 if(0 && skipped.length){
303 logClass('warning',"SKIPPED test(s) in group",this.number+":",skipped);
310 currentTestGroup: undefined,
311 addGroup: function(name, predicate){
312 this.testGroups.push( this.currentTestGroup =
313 new this.TestGroup(name, predicate) );
316 addTest: function(name, callback){
318 if(1===arguments.length){
319 this.currentTestGroup.addTest(arguments[0]);
321 this.currentTestGroup.addTest({
322 name, predicate, test: callback
327 runTests: async function(sqlite3){
328 return new Promise(async function(pok,pnok){
331 for(let g of this.testGroups){
332 const now = performance.now();
333 await g.run(sqlite3);
334 runtime += performance.now() - now;
336 logClass(['strong','green','full-test-summary'],
337 "Done running tests.",TestUtil.counter,"assertions in",
338 roundMs(runtime),'ms');
340 reportFinalTestStatus(true);
344 reportFinalTestStatus(false);
352 let capi, wasm/*assigned after module init*/;
353 const sahPoolConfig = {
354 name: 'opfs-sahpool-tester1',
358 ////////////////////////////////////////////////////////////////////////
359 // End of infrastructure setup. Now define the tests...
360 ////////////////////////////////////////////////////////////////////////
362 ////////////////////////////////////////////////////////////////////
363 T.g('Basic sanity checks')
365 name:'sqlite3_config()',
366 test:function(sqlite3){
368 'SQLITE_CONFIG_GETMALLOC', 'SQLITE_CONFIG_URI'
370 T.assert(capi[k] > 0);
372 T.assert(capi.SQLITE_MISUSE===capi.sqlite3_config(
373 capi.SQLITE_CONFIG_URI, 1
374 ), "MISUSE because the library has already been initialized.");
375 T.assert(capi.SQLITE_MISUSE === capi.sqlite3_config(
377 capi.SQLITE_CONFIG_GETMALLOC
379 T.assert(capi.SQLITE_NOTFOUND === capi.sqlite3_config(
380 // unhandled-in-JS config option
381 capi.SQLITE_CONFIG_GETMALLOC, 1
384 log("We cannot _fully_ test sqlite3_config() after the library",
385 "has been initialized (which it necessarily has been to",
386 "set up various bindings) and we cannot shut it down ",
387 "without losing the VFS registrations.");
388 T.assert(0 === capi.sqlite3_config(
389 capi.SQLITE_CONFIG_URI, 1
393 })/*sqlite3_config()*/
395 ////////////////////////////////////////////////////////////////////
397 name: "JS wasm-side allocator",
398 test: function(sqlite3){
399 if(sqlite3.config.useStdAlloc){
400 warn("Using system allocator. This violates the docs and",
401 "may cause grief with certain APIs",
402 "(e.g. sqlite3_deserialize()).");
403 T.assert(wasm.alloc.impl === wasm.exports.malloc)
404 .assert(wasm.dealloc === wasm.exports.free)
405 .assert(wasm.realloc.impl === wasm.exports.realloc);
407 T.assert(wasm.alloc.impl === wasm.exports.sqlite3_malloc)
408 .assert(wasm.dealloc === wasm.exports.sqlite3_free)
409 .assert(wasm.realloc.impl === wasm.exports.sqlite3_realloc);
413 .t('Namespace object checks', function(sqlite3){
414 const wasmCtypes = wasm.ctype;
415 T.assert(wasmCtypes.structs[0].name==='sqlite3_vfs').
416 assert(wasmCtypes.structs[0].members.szOsFile.sizeof>=4).
417 assert(wasmCtypes.structs[1/*sqlite3_io_methods*/
418 ].members.xFileSize.offset>0);
419 [ /* Spot-check a handful of constants to make sure they got installed... */
420 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8',
421 'SQLITE_STATIC', 'SQLITE_DIRECTONLY',
422 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE'
423 ].forEach((k)=>T.assert('number' === typeof capi[k]));
424 [/* Spot-check a few of the WASM API methods. */
425 'alloc', 'dealloc', 'installFunction'
426 ].forEach((k)=>T.assert(wasm[k] instanceof Function));
428 T.assert(capi.sqlite3_errstr(capi.SQLITE_IOERR_ACCESS).indexOf("I/O")>=0).
429 assert(capi.sqlite3_errstr(capi.SQLITE_CORRUPT).indexOf('malformed')>0).
430 assert(capi.sqlite3_errstr(capi.SQLITE_OK) === 'not an error');
433 throw new sqlite3.WasmAllocError;
435 T.assert(e instanceof Error)
436 .assert(e instanceof sqlite3.WasmAllocError)
437 .assert("Allocation failed." === e.message);
440 throw new sqlite3.WasmAllocError("test",{
444 T.assert(3 === e.cause)
445 .assert("test" === e.message);
447 try {throw new sqlite3.WasmAllocError("test","ing",".")}
448 catch(e){T.assert("test ing ." === e.message)}
450 try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) }
452 T.assert('SQLITE_SCHEMA' === e.message)
453 .assert(capi.SQLITE_SCHEMA === e.resultCode);
455 try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) }
457 T.assert('SQLITE_CORRUPT' === e.message)
458 .assert(capi.SQLITE_CORRUPT === e.resultCode)
459 .assert(true===e.cause);
461 try{ sqlite3.SQLite3Error.toss("resultCode check") }
463 T.assert(capi.SQLITE_ERROR === e.resultCode)
464 .assert('resultCode check' === e.message);
467 ////////////////////////////////////////////////////////////////////
468 .t('strglob/strlike', function(sqlite3){
469 T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
470 assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
471 assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
472 assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
475 ////////////////////////////////////////////////////////////////////
476 ;/*end of basic sanity checks*/
478 ////////////////////////////////////////////////////////////////////
479 T.g('C/WASM Utilities')
480 .t('sqlite3.wasm namespace', function(sqlite3){
481 // TODO: break this into smaller individual test functions.
483 const chr = (x)=>x.charCodeAt(0);
484 //log("heap getters...");
486 const li = [8, 16, 32];
487 if(w.bigIntEnabled) li.push(64);
490 const s = w.heapForSize(n,false);
491 T.assert(bpe===s.BYTES_PER_ELEMENT).
492 assert(w.heapForSize(s.constructor) === s);
493 const u = w.heapForSize(n,true);
494 T.assert(bpe===u.BYTES_PER_ELEMENT).
496 assert(w.heapForSize(u.constructor) === u);
500 // alloc(), realloc(), allocFromTypedArray()
503 let m2 = w.realloc(m, 16);
504 T.assert(m === m2/* because of alignment */);
505 T.assert(0 === w.realloc(m, 0));
508 // Check allocation limits and allocator's responses...
509 T.assert('number' === typeof sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE);
510 if(!sqlite3.config.useStdAlloc){
511 const tooMuch = sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE + 1,
512 isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
513 T.mustThrowMatching(()=>w.alloc(tooMuch), isAllocErr)
514 .assert(0 === w.alloc.impl(tooMuch))
515 .mustThrowMatching(()=>w.realloc(0, tooMuch), isAllocErr)
516 .assert(0 === w.realloc.impl(0, tooMuch));
519 // Check allocFromTypedArray()...
520 const byteList = [11,22,33]
521 const u = new Uint8Array(byteList);
522 m = w.allocFromTypedArray(u);
523 for(let i = 0; i < u.length; ++i){
524 T.assert(u[i] === byteList[i])
525 .assert(u[i] === w.peek8(m + i));
528 m = w.allocFromTypedArray(u.buffer);
529 for(let i = 0; i < u.length; ++i){
530 T.assert(u[i] === byteList[i])
531 .assert(u[i] === w.peek8(m + i));
536 ()=>w.allocFromTypedArray(1),
537 'Value is not of a supported TypedArray type.'
541 { // Test peekXYZ()/pokeXYZ()...
542 const m = w.alloc(8);
543 T.assert( 17 === w.poke8(m,17).peek8(m) )
544 .assert( 31987 === w.poke16(m,31987).peek16(m) )
545 .assert( 345678 === w.poke32(m,345678).peek32(m) )
547 T.eqApprox( 345678.9, w.poke32f(m,345678.9).peek32f(m) )
549 T.eqApprox( 4567890123.4, w.poke64f(m, 4567890123.4).peek64f(m) )
553 BigInt(Number.MAX_SAFE_INTEGER) ===
554 w.poke64(m, Number.MAX_SAFE_INTEGER).peek64(m)
562 const ip = w.isPtr32;
566 .assert(!ip(0xffffffff))
567 .assert(ip(0x7fffffff))
569 .assert(!ip(null)/*might change: under consideration*/)
573 //log("jstrlen()...");
575 T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc"));
578 //log("jstrcpy()...");
581 let ua = new Uint8Array(8), rc,
582 refill = ()=>ua.fill(fillChar);
584 rc = w.jstrcpy("hello", ua);
585 T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]);
588 rc = w.jstrcpy("HELLO", ua, 0, -1, false);
589 T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]);
591 rc = w.jstrcpy("the end", ua, 4);
592 //log("rc,ua",rc,ua);
593 T.assert(4===rc).assert(0===ua[7]).
594 assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
596 rc = w.jstrcpy("the end", ua, 4, -1, false);
597 T.assert(4===rc).assert(chr(' ')===ua[7]).
598 assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
600 rc = w.jstrcpy("", ua, 0, 1, true);
601 //log("rc,ua",rc,ua);
602 T.assert(1===rc).assert(0===ua[0]);
604 rc = w.jstrcpy("x", ua, 0, 1, true);
605 //log("rc,ua",rc,ua);
606 T.assert(1===rc).assert(0===ua[0]);
608 rc = w.jstrcpy('äbä', ua, 0, 1, true);
609 T.assert(1===rc, 'Must not write partial multi-byte char.')
612 rc = w.jstrcpy('äbä', ua, 0, 2, true);
613 T.assert(1===rc, 'Must not write partial multi-byte char.')
616 rc = w.jstrcpy('äbä', ua, 0, 2, false);
617 T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]);
620 //log("cstrncpy()...");
622 const scope = w.scopedAllocPush();
624 let cStr = w.scopedAllocCString("hello");
625 const n = w.cstrlen(cStr);
626 let cpy = w.scopedAlloc(n+10);
627 let rc = w.cstrncpy(cpy, cStr, n+10);
628 T.assert(n+1 === rc).
629 assert("hello" === w.cstrToJs(cpy)).
630 assert(chr('o') === w.peek8(cpy+n-1)).
631 assert(0 === w.peek8(cpy+n));
632 let cStr2 = w.scopedAllocCString("HI!!!");
633 rc = w.cstrncpy(cpy, cStr2, 3);
635 assert("HI!lo" === w.cstrToJs(cpy)).
636 assert(chr('!') === w.peek8(cpy+2)).
637 assert(chr('l') === w.peek8(cpy+3));
639 w.scopedAllocPop(scope);
643 //log("jstrToUintArray()...");
645 let a = w.jstrToUintArray("hello", false);
646 T.assert(5===a.byteLength).assert(chr('o')===a[4]);
647 a = w.jstrToUintArray("hello", true);
648 T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]);
649 a = w.jstrToUintArray("äbä", false);
650 T.assert(5===a.byteLength).assert(chr('b')===a[2]);
651 a = w.jstrToUintArray("äbä", true);
652 T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]);
655 //log("allocCString()...");
657 const jstr = "hällo, world!";
658 const [cstr, n] = w.allocCString(jstr, true);
660 .assert(0===w.peek8(cstr+n))
661 .assert(chr('!')===w.peek8(cstr+n-1));
665 //log("scopedAlloc() and friends...");
667 const alloc = w.alloc, dealloc = w.dealloc;
668 w.alloc = w.dealloc = null;
669 T.assert(!w.scopedAlloc.level)
670 .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
671 .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
673 T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
675 T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/)
676 .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
677 .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/);
678 const asc = w.scopedAllocPush();
681 const p1 = w.scopedAlloc(16),
682 p2 = w.scopedAlloc(16);
683 T.assert(1===w.scopedAlloc.level)
684 .assert(Number.isFinite(p1))
685 .assert(Number.isFinite(p2))
686 .assert(asc[0] === p1)
687 .assert(asc[1]===p2);
688 asc2 = w.scopedAllocPush();
689 const p3 = w.scopedAlloc(16);
690 T.assert(2===w.scopedAlloc.level)
691 .assert(Number.isFinite(p3))
692 .assert(2===asc.length)
693 .assert(p3===asc2[0]);
695 const [z1, z2, z3] = w.scopedAllocPtr(3);
696 T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2)
697 .assert(0===w.peek32(z1), 'allocPtr() must zero the targets')
698 .assert(0===w.peek32(z3));
700 // Pop them in "incorrect" order to make sure they behave:
701 w.scopedAllocPop(asc);
702 T.assert(0===asc.length);
703 T.mustThrowMatching(()=>w.scopedAllocPop(asc),
704 /^Invalid state object/);
706 T.assert(2===asc2.length,'Should be p3 and z1');
707 w.scopedAllocPop(asc2);
708 T.assert(0===asc2.length);
709 T.mustThrowMatching(()=>w.scopedAllocPop(asc2),
710 /^Invalid state object/);
713 T.assert(0===w.scopedAlloc.level);
714 w.scopedAllocCall(function(){
715 T.assert(1===w.scopedAlloc.level);
716 const [cstr, n] = w.scopedAllocCString("hello, world", true);
718 .assert(0===w.peek8(cstr+n))
719 .assert(chr('d')===w.peek8(cstr+n-1));
725 const pJson = w.xCall('sqlite3__wasm_enum_json');
726 T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
731 T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'),
733 assert(w.xWrap.resultAdapter('i32') instanceof Function).
734 assert(w.xWrap.argAdapter('i32') instanceof Function);
735 let fw = w.xWrap('sqlite3_libversion','utf8');
736 T.mustThrowMatching(()=>fw(1), /requires 0 arg/);
738 T.assert('string'===typeof rc).assert(rc.length>5);
739 rc = w.xCallWrapped('sqlite3__wasm_enum_json','*');
740 T.assert(rc>0 && Number.isFinite(rc));
741 rc = w.xCallWrapped('sqlite3__wasm_enum_json','utf8');
742 T.assert('string'===typeof rc).assert(rc.length>300);
745 { // 'string:static' argAdapter() sanity checks...
746 let argAd = w.xWrap.argAdapter('string:static');
747 let p0 = argAd('foo'), p1 = argAd('bar');
748 T.assert(w.isPtr(p0) && w.isPtr(p1))
750 .assert(p0 === argAd('foo'))
751 .assert(p1 === argAd('bar'));
754 // 'string:flexible' argAdapter() sanity checks...
755 w.scopedAllocCall(()=>{
756 const argAd = w.xWrap.argAdapter('string:flexible');
757 const cj = (v)=>w.cstrToJs(argAd(v));
758 T.assert('Hi' === cj('Hi'))
759 .assert('hi' === cj(['h','i']))
760 .assert('HI' === cj(new Uint8Array([72, 73])));
765 const fsum3 = (x,y,z)=>x+y+z;
766 fw = w.jsFuncToWasm('i(iii)', fsum3);
767 T.assert(fw instanceof Function)
768 .assert( fsum3 !== fw )
769 .assert( 3 === fw.length )
770 .assert( 6 === fw(1,2,3) );
771 T.mustThrowMatching( ()=>w.jsFuncToWasm('x()', function(){}),
772 'Invalid signature letter: x');
775 // xWrap(Function,...)
779 const fmy = function fmy(i,s,d){
780 if(fmy.debug) log("fmy(",...arguments,")");
782 .assert( w.isPtr(s) )
783 .assert( w.cstrToJs(s) === 'a string' )
784 .assert( T.eqApprox(1.2, d) );
785 return w.allocCString("hi");
788 const xwArgs = ['string:dealloc', ['i32', 'string', 'f64']];
789 fw = w.xWrap(fmy, ...xwArgs);
790 const fmyArgs = [3, 'a string', 1.2];
791 let rc = fw(...fmyArgs);
792 T.assert( 'hi' === rc );
794 /* Retain this as a "reminder to self"...
796 This extra level of indirection does not work: the
797 string argument is ending up as a null in fmy() but
798 the numeric arguments are making their ways through
800 What's happening is: installFunction() is creating a
801 WASM-compatible function instance. When we pass a JS string
802 into there it's getting coerced into `null` before being passed
803 on to the lower-level wrapper.
806 fp = wasm.installFunction('i(isd)', fw);
807 fw = w.functionEntry(fp);
810 T.assert( 'hi' === rc );
811 // Similarly, this does not work:
812 //let fpw = w.xWrap(fp, null, [null,null,null]);
813 //rc = fpw(...fmyArgs);
815 //T.assert( 'hi' === rc );
818 wasm.uninstallFunction(fp);
822 if(haveWasmCTests()){
823 if(!sqlite3.config.useStdAlloc){
824 fw = w.xWrap('sqlite3__wasm_test_str_hello', 'utf8:dealloc',['i32']);
826 T.assert('hello'===rc);
832 w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v));
833 w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v));
834 fw = w.xWrap('sqlite3__wasm_test_int64_times2','thrice','twice');
838 w.scopedAllocCall(function(){
839 const pI1 = w.scopedAlloc(8), pI2 = pI1+4;
840 w.pokePtr([pI1, pI2], 0);
841 const f = w.xWrap('sqlite3__wasm_test_int64_minmax',undefined,['i64*','i64*']);
842 const [r1, r2] = w.peek64([pI1, pI2]);
843 T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
850 ////////////////////////////////////////////////////////////////////
851 .t('sqlite3.StructBinder (jaccwabyt🐇)', function(sqlite3){
852 const S = sqlite3, W = S.wasm;
853 const MyStructDef = {
856 p4: {offset: 0, sizeof: 4, signature: "i"},
857 pP: {offset: 4, sizeof: 4, signature: "P"},
858 ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true},
859 cstr: {offset: 12, sizeof: 4, signature: "s"}
863 const m = MyStructDef;
864 m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"};
865 m.sizeof += m.members.p8.sizeof;
867 const StructType = S.StructBinder.StructType;
868 const K = S.StructBinder('my_struct',MyStructDef);
869 T.mustThrowMatching(()=>K(), /via 'new'/).
870 mustThrowMatching(()=>new K('hi'), /^Invalid pointer/);
871 const k1 = new K(), k2 = new K();
873 T.assert(k1.constructor === K).
875 assert(k1 instanceof K).
876 assert(K.prototype.lookupMember('p4').key === '$p4').
877 assert(K.prototype.lookupMember('$p4').name === 'p4').
878 mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/).
879 assert(undefined === K.prototype.lookupMember('nope',false)).
880 assert(k1 instanceof StructType).
881 assert(StructType.isA(k1)).
882 mustThrowMatching(()=>k1.$ro = 1, /read-only/);
883 Object.keys(MyStructDef.members).forEach(function(key){
884 key = K.memberKey(key);
885 T.assert(0 == k1[key],
886 "Expecting allocation to zero the memory "+
887 "for "+key+" but got: "+k1[key]+
888 " from "+k1.memoryDump());
890 T.assert('number' === typeof k1.pointer).
891 mustThrowMatching(()=>k1.pointer = 1, /pointer/);
892 k1.$p4 = 1; k1.$pP = 2;
893 T.assert(1 === k1.$p4).assert(2 === k1.$pP);
894 if(MyStructDef.members.$p8){
895 k1.$p8 = 1/*must not throw despite not being a BigInt*/;
896 k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2);
897 T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8);
899 T.assert(!k1.ondispose);
900 k1.setMemberCString('cstr', "A C-string.");
901 T.assert(Array.isArray(k1.ondispose)).
902 assert(k1.ondispose[0] === k1.$cstr).
903 assert('number' === typeof k1.$cstr).
904 assert('A C-string.' === k1.memberToJsString('cstr'));
906 T.assert(k1.$pP === k2.pointer);
907 k1.$pP = null/*null is special-cased to 0.*/;
908 T.assert(0===k1.$pP);
909 let ptr = k1.pointer;
911 T.assert(undefined === k1.pointer).
912 mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
918 if(!W.bigIntEnabled){
919 log("Skipping WasmTestStruct tests: BigInt not enabled.");
924 W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0];
925 const autoResolvePtr = true /* EXPERIMENTAL */;
927 WTStructDesc.members.ppV.signature = 'P';
929 const WTStruct = S.StructBinder(WTStructDesc);
930 //log(WTStruct.structName, WTStruct.structInfo);
931 const wts = new WTStruct();
932 //log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype));
934 T.assert(wts.constructor === WTStruct).
935 assert(WTStruct.memberKeys().indexOf('$ppV')>=0).
936 assert(wts.memberKeys().indexOf('$v8')>=0).
938 assert(WTStruct.isA(wts)).
939 assert(wts instanceof WTStruct).
940 assert(wts instanceof StructType).
941 assert(StructType.isA(wts)).
942 assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
943 assert(0===wts.$ppV).assert(0===wts.$xFunc);
945 W.xGet('sqlite3__wasm_test_struct'/*name gets mangled in -O3 builds!*/);
947 //log("wts.pointer =",wts.pointer);
948 const wtsFunc = function(arg){
949 /*log("This from a JS function called from C, "+
950 "which itself was called from JS. arg =",arg);*/
953 tossQuietly("Testing exception propagation.");
956 wts.$v4 = 10; wts.$v8 = 20;
957 wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc'))
958 T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8)
959 .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc)
960 .assert(0 === wts.$cstr)
961 .assert(wts.memberIsString('$cstr'))
962 .assert(!wts.memberIsString('$v4'))
963 .assert(null === wts.memberToJsString('$cstr'))
964 .assert(W.functionEntry(wts.$xFunc) instanceof Function);
965 /* It might seem silly to assert that the values match
966 what we just set, but recall that all of those property
967 reads and writes are, via property interceptors,
968 actually marshaling their data to/from a raw memory
969 buffer, so merely reading them back is actually part of
970 testing the struct-wrapping API. */
972 testFunc(wts.pointer);
973 //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV);
974 T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8)
975 .assert(wts.$ppV === wts.pointer)
976 .assert('string' === typeof wts.memberToJsString('cstr'))
977 .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr'))
978 .mustThrowMatching(()=>wts.memberToJsString('xFunc'),
979 /Invalid member type signature for C-string/)
981 testFunc(wts.pointer);
982 T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8)
983 .assert(wts.$ppV === wts.pointer);
984 /** The 3rd call to wtsFunc throw from JS, which is called
985 from C, which is called from JS. Let's ensure that
986 that exception propagates back here... */
987 T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/);
988 W.uninstallFunction(wts.$xFunc);
992 //WTStruct.debugFlags(0x03);
994 T.assert(wts.pointer === wts.$ppV)
995 wts.setMemberCString('cstr', "A C-string.");
996 T.assert(Array.isArray(wts.ondispose)).
997 assert(wts.ondispose[0] === wts.$cstr).
998 assert('A C-string.' === wts.memberToJsString('cstr'));
999 const ptr = wts.pointer;
1001 T.assert(ptr).assert(undefined === wts.pointer);
1006 if(1){ // ondispose of other struct instances
1007 const s1 = new WTStruct, s2 = new WTStruct, s3 = new WTStruct;
1008 T.assert(s1.lookupMember instanceof Function)
1009 .assert(s1.addOnDispose instanceof Function);
1010 s1.addOnDispose(s2,"testing variadic args");
1011 T.assert(2===s1.ondispose.length);
1012 s2.addOnDispose(s3);
1014 T.assert(!s2.pointer,"Expecting s2 to be ondispose'd by s1.");
1015 T.assert(!s3.pointer,"Expecting s3 to be ondispose'd by s2.");
1019 ////////////////////////////////////////////////////////////////////
1020 .t('sqlite3.wasm.pstack', function(sqlite3){
1021 const P = wasm.pstack;
1022 const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
1023 const stack = P.pointer;
1024 T.assert(0===stack % 8 /* must be 8-byte aligned */);
1026 const remaining = P.remaining;
1027 T.assert(P.quota >= 4096)
1028 .assert(remaining === P.quota)
1029 .mustThrowMatching(()=>P.alloc(0), isAllocErr)
1030 .mustThrowMatching(()=>P.alloc(-1), isAllocErr)
1033 (e)=>e instanceof sqlite3.WasmAllocError
1036 let p1 = P.alloc(12);
1037 T.assert(p1 === stack - 16/*8-byte aligned*/)
1038 .assert(P.pointer === p1);
1039 let p2 = P.alloc(7);
1040 T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/)
1041 .mustThrowMatching(()=>P.alloc(remaining), isAllocErr)
1042 .assert(24 === stack - p2)
1043 .assert(P.pointer === p2);
1044 let n = remaining - (stack - p2);
1045 let p3 = P.alloc(n);
1046 T.assert(p3 === stack-remaining)
1047 .mustThrowMatching(()=>P.alloc(1), isAllocErr);
1052 T.assert(P.pointer === stack);
1054 const [p1, p2, p3] = P.allocChunks(3,'i32');
1055 T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/)
1056 .assert(p2 === p1 + 4)
1057 .assert(p3 === p2 + 4);
1058 T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16),
1059 (e)=>e instanceof sqlite3.WasmAllocError)
1064 T.assert(P.pointer === stack);
1066 let [p1, p2, p3] = P.allocPtr(3,false);
1067 let sPos = stack-16/*always rounded to multiple of 8*/;
1068 T.assert(P.pointer === sPos)
1069 .assert(p2 === p1 + 4)
1070 .assert(p3 === p2 + 4);
1071 [p1, p2, p3] = P.allocPtr(3);
1072 T.assert(P.pointer === sPos-24/*3 x 8 bytes*/)
1073 .assert(p2 === p1 + 8)
1074 .assert(p3 === p2 + 8);
1076 T.assert('number'===typeof p1);
1081 ////////////////////////////////////////////////////////////////////
1082 ;/*end of C/WASM utils checks*/
1084 T.g('sqlite3_randomness()')
1085 .t('To memory buffer', function(sqlite3){
1086 const stack = wasm.pstack.pointer;
1089 const p = wasm.pstack.alloc(n);
1090 T.assert(0===wasm.peek8(p))
1091 .assert(0===wasm.peek8(p+n-1));
1092 T.assert(undefined === capi.sqlite3_randomness(n - 10, p));
1094 const heap = wasm.heap8u();
1095 for(j = 0; j < 10 && 0===check; ++j){
1096 check += heap[p + j];
1098 T.assert(check > 0);
1100 // Ensure that the trailing bytes were not modified...
1101 for(j = n - 10; j < n && 0===check; ++j){
1102 check += heap[p + j];
1104 T.assert(0===check);
1106 wasm.pstack.restore(stack);
1109 .t('To byte array', function(sqlite3){
1110 const ta = new Uint8Array(117);
1112 for(i=0; i<ta.byteLength && 0===n; ++i){
1116 .assert(ta === capi.sqlite3_randomness(ta));
1117 for(i=ta.byteLength-10; i<ta.byteLength && 0===n; ++i){
1121 const t0 = new Uint8Array(0);
1122 T.assert(t0 === capi.sqlite3_randomness(t0),
1123 "0-length array is a special case");
1125 ;/*end sqlite3_randomness() checks*/
1127 ////////////////////////////////////////////////////////////////////////
1129 .t('Create db', function(sqlite3){
1130 const dbFile = '/tester1.db';
1131 sqlite3.util.sqlite3__wasm_vfs_unlink(0, dbFile);
1132 const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c');
1137 //console.debug("db.onclose.before dropping modules");
1138 //sqlite3.capi.sqlite3_drop_modules(db.pointer, 0);
1141 before: function(db){
1142 while(this.disposeBefore.length){
1143 const v = this.disposeBefore.shift();
1144 console.debug("db.onclose.before cleaning up:",v);
1145 if(wasm.isPtr(v)) wasm.dealloc(v);
1146 else if(v instanceof sqlite3.StructBinder.StructType){
1148 }else if(v instanceof Function){
1149 try{ v(db) } catch(e){
1150 console.warn("beforeDispose() callback threw:",e);
1156 while(this.disposeAfter.length){
1157 const v = this.disposeAfter.shift();
1158 console.debug("db.onclose.after cleaning up:",v);
1159 if(wasm.isPtr(v)) wasm.dealloc(v);
1160 else if(v instanceof sqlite3.StructBinder.StructType){
1162 }else if(v instanceof Function){
1163 try{v()} catch(e){/*ignored*/}
1169 T.assert(wasm.isPtr(db.pointer))
1170 .mustThrowMatching(()=>db.pointer=1, /read-only/)
1171 .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
1172 .assert('main'===db.dbName(0))
1173 .assert('string' === typeof db.dbVfsName())
1174 .assert(db.pointer === wasm.xWrap.testConvertArg('sqlite3*',db));
1175 // Custom db error message handling via sqlite3_prepare_v2/v3()
1176 let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null);
1177 T.assert(capi.SQLITE_MISUSE === rc)
1178 .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL"))
1179 .assert(dbFile === db.dbFilename())
1180 .assert(!db.dbFilename('nope'));
1181 //Sanity check DB.checkRc()...
1185 T.assert(ex instanceof sqlite3.SQLite3Error)
1186 .assert(capi.SQLITE_MISUSE===ex.resultCode)
1187 .assert(0===ex.message.indexOf("SQLITE_MISUSE: sqlite3 result code"))
1188 .assert(ex.message.indexOf("Invalid SQL")>0);
1189 T.assert(db === db.checkRc(0))
1190 .assert(db === sqlite3.oo1.DB.checkRc(db,0))
1191 .assert(null === sqlite3.oo1.DB.checkRc(null,0));
1193 this.progressHandlerCount = 0;
1194 capi.sqlite3_progress_handler(db, 5, (p)=>{
1195 ++this.progressHandlerCount;
1199 ////////////////////////////////////////////////////////////////////
1200 .t('sqlite3_db_config() and sqlite3_db_status()', function(sqlite3){
1201 let rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, 0, 0);
1203 rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAX+1, 0);
1204 T.assert(capi.SQLITE_MISUSE === rc);
1205 rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAINDBNAME, "main");
1207 const stack = wasm.pstack.pointer;
1209 const [pCur, pHi] = wasm.pstack.allocChunks(2,'i64');
1210 wasm.poke32([pCur, pHi], 0);
1211 let [vCur, vHi] = wasm.peek32(pCur, pHi);
1212 T.assert(0===vCur).assert(0===vHi);
1213 rc = capi.sqlite3_status(capi.SQLITE_STATUS_MEMORY_USED,
1215 [vCur, vHi] = wasm.peek32(pCur, pHi);
1216 //console.warn("i32 vCur,vHi",vCur,vHi);
1217 T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur);
1218 if(wasm.bigIntEnabled){
1219 // Again in 64-bit. Recall that pCur and pHi are allocated
1220 // large enough to account for this re-use.
1221 wasm.poke64([pCur, pHi], 0);
1222 rc = capi.sqlite3_status64(capi.SQLITE_STATUS_MEMORY_USED,
1224 [vCur, vHi] = wasm.peek64([pCur, pHi]);
1225 //console.warn("i64 vCur,vHi",vCur,vHi);
1226 T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur);
1229 wasm.pstack.restore(stack);
1233 ////////////////////////////////////////////////////////////////////
1234 .t('DB.Stmt', function(sqlite3){
1235 let st = this.db.prepare(
1236 new TextEncoder('utf-8').encode("select 3 as a")
1238 //debug("statement =",st);
1239 this.progressHandlerCount = 0;
1242 T.assert(wasm.isPtr(st.pointer))
1243 .mustThrowMatching(()=>st.pointer=1, /read-only/)
1244 .assert(1===this.db.openStatementCount())
1246 capi.sqlite3_stmt_status(
1247 st, capi.SQLITE_STMTSTATUS_RUN, 0
1249 .assert(!st._mayGet)
1250 .assert('a' === st.getColumnName(0))
1251 .mustThrowMatching(()=>st.columnCount=2,
1252 /columnCount property is read-only/)
1253 .assert(1===st.columnCount)
1254 .assert(0===st.parameterCount)
1255 .mustThrow(()=>st.bind(1,null))
1256 .assert(true===st.step())
1257 .assert(3 === st.get(0))
1258 .mustThrow(()=>st.get(1))
1259 .mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER))
1260 .assert(3 === st.get(0,capi.SQLITE_INTEGER))
1261 .assert(3 === st.getInt(0))
1262 .assert('3' === st.get(0,capi.SQLITE_TEXT))
1263 .assert('3' === st.getString(0))
1264 .assert(3.0 === st.get(0,capi.SQLITE_FLOAT))
1265 .assert(3.0 === st.getFloat(0))
1266 .assert(3 === st.get({}).a)
1267 .assert(3 === st.get([])[0])
1268 .assert(3 === st.getJSON(0))
1269 .assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array)
1270 .assert(1===st.get(0,capi.SQLITE_BLOB).length)
1271 .assert(st.getBlob(0) instanceof Uint8Array)
1272 .assert('3'.charCodeAt(0) === st.getBlob(0)[0])
1274 .assert(false===st.step())
1275 .assert(!st._mayGet)
1277 capi.sqlite3_stmt_status(
1278 st, capi.SQLITE_STMTSTATUS_RUN, 0
1281 T.assert(this.progressHandlerCount > 0,
1282 "Expecting progress callback.").
1283 assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
1284 assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
1285 assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
1286 assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
1290 T.assert(!st.pointer)
1291 .assert(0===this.db.openStatementCount())
1294 T.mustThrowMatching(()=>new sqlite3.oo1.Stmt("hi"), function(err){
1295 return (err instanceof sqlite3.SQLite3Error)
1296 && capi.SQLITE_MISUSE === err.resultCode
1297 && 0 < err.message.indexOf("Do not call the Stmt constructor directly.")
1301 ////////////////////////////////////////////////////////////////////////
1302 .t('sqlite3_js_...()', function(){
1305 const vfsList = capi.sqlite3_js_vfs_list();
1306 T.assert(vfsList.length>1);
1307 wasm.scopedAllocCall(()=>{
1308 const vfsArg = (v)=>wasm.xWrap.testConvertArg('sqlite3_vfs*',v);
1309 for(const v of vfsList){
1310 T.assert('string' === typeof v);
1311 const pVfs = capi.sqlite3_vfs_find(v);
1312 T.assert(wasm.isPtr(pVfs))
1313 .assert(pVfs===vfsArg(v));
1314 const vfs = new capi.sqlite3_vfs(pVfs);
1315 try { T.assert(vfsArg(vfs)===pVfs) }
1316 finally{ vfs.dispose() }
1321 Trivia: the magic db name ":memory:" does not actually use the
1322 "memdb" VFS unless "memdb" is _explicitly_ provided as the VFS
1323 name. Instead, it uses the default VFS with an in-memory btree.
1324 Thus this.db's VFS may not be memdb even though it's an in-memory
1327 const pVfsMem = capi.sqlite3_vfs_find('memdb'),
1328 pVfsDflt = capi.sqlite3_vfs_find(0),
1329 pVfsDb = capi.sqlite3_js_db_vfs(db.pointer);
1330 T.assert(pVfsMem > 0)
1331 .assert(pVfsDflt > 0)
1333 .assert(pVfsMem !== pVfsDflt
1334 /* memdb lives on top of the default vfs */)
1335 .assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem)
1337 /*const vMem = new capi.sqlite3_vfs(pVfsMem),
1338 vDflt = new capi.sqlite3_vfs(pVfsDflt),
1339 vDb = new capi.sqlite3_vfs(pVfsDb);*/
1340 const duv = capi.sqlite3_js_db_uses_vfs;
1341 T.assert(pVfsDflt === duv(db.pointer, 0)
1342 || pVfsMem === duv(db.pointer,0))
1343 .assert(!duv(db.pointer, "foo"))
1345 }/*sqlite3_js_...()*/)
1347 ////////////////////////////////////////////////////////////////////
1348 .t('Table t', function(sqlite3){
1351 this.progressHandlerCount = 0;
1353 sql:['CREATE TABLE t(a,b);',
1354 // ^^^ using TEMP TABLE breaks the db export test
1355 "INSERT INTO t(a,b) VALUES(1,2),(3,4),",
1356 "(?,?)"/*intentionally missing semicolon to test for
1357 off-by-one bug in string-to-WASM conversion*/],
1361 //debug("Exec'd SQL:", list);
1363 .assert(2 === list.length)
1364 .assert('string'===typeof list[1])
1365 .assert(3===db.changes())
1366 .assert(this.progressHandlerCount > 0,
1367 "Expecting progress callback.")
1368 if(wasm.bigIntEnabled){
1369 T.assert(3n===db.changes(false,true));
1372 sql: "INSERT INTO t(a,b) values('blob',X'6869') RETURNING 13",
1375 T.assert(Array.isArray(rc))
1376 .assert(1===rc.length)
1377 .assert(13 === rc[0])
1378 .assert(1===db.changes());
1380 let vals = db.selectValues('select a from t order by a limit 2');
1381 T.assert( 2 === vals.length )
1382 .assert( 1===vals[0] && 3===vals[1] );
1383 vals = db.selectValues('select a from t order by a limit $L',
1384 {$L:2}, capi.SQLITE_TEXT);
1385 T.assert( 2 === vals.length )
1386 .assert( '1'===vals[0] && '3'===vals[1] );
1389 let blob = db.selectValue("select b from t where a='blob'");
1390 T.assert(blob instanceof Uint8Array).
1391 assert(0x68===blob[0] && 0x69===blob[1]);
1393 let counter = 0, colNames = [];
1395 db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{
1398 columnNames: colNames,
1399 _myState: 3 /* Accessible from the callback */,
1400 callback: function(row,stmt){
1404 /* Recall that "this" is the options object. */
1406 this.columnNames===colNames
1408 this.columnNames[0]==='a' && this.columnNames[1]==='b'
1410 (row.a%2 && row.a<6) || 'blob'===row.a
1414 T.assert(2 === colNames.length)
1415 .assert('a' === colNames[0])
1416 .assert(4 === counter)
1417 .assert(4 === list.length);
1420 /* Ensure that columnNames is populated for empty result sets. */
1421 sql: "SELECT a a, b B FROM t WHERE 0",
1422 columnNames: colNames
1424 T.assert(2===colNames.length)
1425 .assert('a'===colNames[0] && 'B'===colNames[1]);
1427 db.exec("SELECT a a, b b FROM t",{
1429 callback: function(row,stmt){
1431 T.assert(Array.isArray(row))
1432 .assert((0===row[1]%2 && row[1]<7)
1433 || (row[1] instanceof Uint8Array));
1436 T.assert(8 === counter);
1437 T.assert(Number.MIN_SAFE_INTEGER ===
1438 db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)).
1439 assert(Number.MAX_SAFE_INTEGER ===
1440 db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER));
1443 sql: "SELECT a FROM t",
1444 callback: ()=>(1===++counter),
1447 .assert(2===counter,
1448 "Expecting exec step() loop to stop if callback returns false.");
1449 /** If exec() is passed neither callback nor returnValue but
1450 is passed an explicit rowMode then the default returnValue
1451 is the whole result set, as if an empty resultRows option
1454 sql: "SELECT -1 UNION ALL SELECT -2 UNION ALL SELECT -3 ORDER BY 1 DESC",
1457 T.assert(Array.isArray(rv)).assert(3===rv.length)
1458 .assert(-1===rv[0]).assert(-3===rv[2]);
1459 rv = db.exec("SELECT 1 WHERE 0",{rowMode: 0});
1460 T.assert(Array.isArray(rv)).assert(0===rv.length);
1461 if(wasm.bigIntEnabled && haveWasmCTests()){
1462 const mI = wasm.xCall('sqlite3__wasm_test_int64_max');
1463 const b = BigInt(Number.MAX_SAFE_INTEGER * 2);
1464 T.assert(b === db.selectValue("SELECT "+b)).
1465 assert(b === db.selectValue("SELECT ?", b)).
1466 assert(mI == db.selectValue("SELECT $x", {$x:mI}));
1468 /* Curiously, the JS spec seems to be off by one with the definitions
1469 of MIN/MAX_SAFE_INTEGER:
1471 https://github.com/emscripten-core/emscripten/issues/17391 */
1472 T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))).
1473 mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1)));
1476 let st = db.prepare("update t set b=:b where a='blob'");
1478 T.assert(0===st.columnCount);
1479 const ndx = st.getParamIndex(':b');
1481 st.bindAsBlob(ndx, "ima blob")
1482 /*step() skipped intentionally*/.reset(true);
1484 T.assert(0===st.finalize())
1485 .assert(undefined===st.finalize());
1489 db.prepare("/*empty SQL*/");
1490 toss("Must not be reached.");
1492 T.assert(e instanceof sqlite3.SQLite3Error)
1493 .assert(0==e.message.indexOf('Cannot prepare empty'));
1498 // Check for https://sqlite.org/forum/forumpost/895425b49a
1499 sql: "pragma table_info('t')",
1501 callback: function(row){
1503 T.assert(row.name==='a' || row.name==='b');
1506 T.assert(2===counter);
1509 ////////////////////////////////////////////////////////////////////
1511 name: "sqlite3_set_authorizer()",
1512 test:function(sqlite3){
1513 T.assert(capi.SQLITE_IGNORE>0)
1514 .assert(capi.SQLITE_DENY>0);
1516 const ssa = capi.sqlite3_set_authorizer;
1517 const n = db.selectValue('select count(*) from t');
1520 let rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
1522 return capi.SQLITE_IGNORE;
1526 undefined === db.selectValue('select count(*) from t')
1527 /* Note that the count() never runs, so we get undefined
1530 .assert(authCount>0);
1532 db.exec("update t set a=-9999");
1533 T.assert(authCount>0);
1534 /* Reminder: we don't use DELETE because, from the C API docs:
1536 "If the action code is [SQLITE_DELETE] and the callback
1537 returns [SQLITE_IGNORE] then the [DELETE] operation proceeds
1538 but the [truncate optimization] is disabled and all rows are
1539 deleted individually."
1541 rc = ssa(db, null, 0);
1543 T.assert(-9999 != db.selectValue('select a from t'))
1544 .assert(0===authCount);
1545 rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
1547 return capi.SQLITE_DENY;
1551 try{ db.exec("select 1 from t") }
1553 T.assert(err instanceof sqlite3.SQLite3Error)
1554 .assert(err.message.indexOf('not authorized'>0))
1555 .assert(1===authCount);
1557 rc = ssa(db, function(...args){
1559 return capi.SQLITE_OK;
1562 T.assert(n === db.selectValue('select count(*) from t'))
1563 .assert(authCount>0);
1565 rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
1567 throw new Error("Testing catching of authorizer.");
1572 try{ db.exec("select 1 from t") }
1574 T.assert(err instanceof Error)
1575 .assert(err.message.indexOf('not authorized')>0)
1576 /* Note that the thrown message is trumped/overwritten
1577 by the authorizer process. */
1578 .assert(1===authCount);
1582 T.assert(n === db.selectValue('select count(*) from t'))
1583 .assert(0===authCount);
1585 })/*sqlite3_set_authorizer()*/
1587 ////////////////////////////////////////////////////////////////////////
1588 .t("sqlite3_table_column_metadata()", function(sqlite3){
1589 const stack = wasm.pstack.pointer;
1591 const [pzDT, pzColl, pNotNull, pPK, pAuto] =
1592 wasm.pstack.allocPtr(5);
1593 const rc = capi.sqlite3_table_column_metadata(
1594 this.db, "main", "t", "rowid",
1595 pzDT, pzColl, pNotNull, pPK, pAuto
1598 .assert("INTEGER"===wasm.cstrToJs(wasm.peekPtr(pzDT)))
1599 .assert("BINARY"===wasm.cstrToJs(wasm.peekPtr(pzColl)))
1600 .assert(0===wasm.peek32(pNotNull))
1601 .assert(1===wasm.peek32(pPK))
1602 .assert(0===wasm.peek32(pAuto))
1604 wasm.pstack.restore(stack);
1608 ////////////////////////////////////////////////////////////////////////
1609 .t('selectArray/Object()', function(sqlite3){
1611 let rc = db.selectArray('select a, b from t where a=?', 5);
1612 T.assert(Array.isArray(rc))
1613 .assert(2===rc.length)
1614 .assert(5===rc[0] && 6===rc[1]);
1615 rc = db.selectArray('select a, b from t where b=-1');
1616 T.assert(undefined === rc);
1617 rc = db.selectObject('select a A, b b from t where b=?', 6);
1618 T.assert(rc && 'object'===typeof rc)
1621 rc = db.selectArray('select a, b from t where b=-1');
1622 T.assert(undefined === rc);
1624 ////////////////////////////////////////////////////////////////////////
1625 .t('selectArrays/Objects()', function(sqlite3){
1627 const sql = 'select a, b from t where a=? or b=? order by a';
1628 let rc = db.selectArrays(sql, [1, 4]);
1629 T.assert(Array.isArray(rc))
1630 .assert(2===rc.length)
1631 .assert(2===rc[0].length)
1632 .assert(1===rc[0][0])
1633 .assert(2===rc[0][1])
1634 .assert(3===rc[1][0])
1635 .assert(4===rc[1][1])
1636 rc = db.selectArrays(sql, [99,99]);
1637 T.assert(Array.isArray(rc)).assert(0===rc.length);
1638 rc = db.selectObjects(sql, [1,4]);
1639 T.assert(Array.isArray(rc))
1640 .assert(2===rc.length)
1641 .assert('object' === typeof rc[1])
1642 .assert(1===rc[0].a)
1643 .assert(2===rc[0].b)
1644 .assert(3===rc[1].a)
1645 .assert(4===rc[1].b);
1647 ////////////////////////////////////////////////////////////////////////
1648 .t('selectArray/Object/Values() via INSERT/UPDATE...RETURNING', function(sqlite3){
1649 let rc = this.db.selectObject("INSERT INTO t(a,b) VALUES(83,84) RETURNING a as AA");
1650 T.assert(83===rc.AA);
1651 rc = this.db.selectArray("UPDATE T set a=85 WHERE a=83 RETURNING b as BB");
1652 T.assert(Array.isArray(rc)).assert(84===rc[0]);
1653 //log("select * from t:",this.db.selectObjects("select * from t order by a"));
1654 rc = this.db.selectValues("UPDATE T set a=a*1 RETURNING a");
1655 T.assert(Array.isArray(rc))
1656 .assert(5 === rc.length)
1657 .assert('number'===typeof rc[0])
1658 .assert(rc[0]|0 === rc[0] /* is small integer */);
1660 ////////////////////////////////////////////////////////////////////////
1662 name: 'sqlite3_js_db_export()',
1663 predicate: ()=>true,
1664 test: function(sqlite3){
1666 const xp = capi.sqlite3_js_db_export(db.pointer);
1667 T.assert(xp instanceof Uint8Array)
1668 .assert(xp.byteLength>0)
1669 .assert(0 === xp.byteLength % 512);
1672 }/*sqlite3_js_db_export()*/)
1674 name: 'sqlite3_js_posix_create_file()',
1675 predicate: ()=>true,
1676 test: function(sqlite3){
1678 const filename = "sqlite3_js_posix_create_file.db";
1679 capi.sqlite3_js_posix_create_file(filename, this.dbExport);
1680 delete this.dbExport;
1681 const db2 = new sqlite3.oo1.DB(filename,'r');
1683 const sql = "select count(*) from t";
1684 const n = db.selectValue(sql);
1685 T.assert(n>0 && db2.selectValue(sql) === n);
1688 sqlite3.util.sqlite3__wasm_vfs_unlink(0, filename);
1691 }/*sqlite3_js_posix_create_file()*/)
1693 ////////////////////////////////////////////////////////////////////
1696 test: function(sqlite3){
1698 db.createFunction("foo",(pCx,a,b)=>a+b);
1699 T.assert(7===db.selectValue("select foo(3,4)")).
1700 assert(5===db.selectValue("select foo(3,?)",2)).
1701 assert(5===db.selectValue("select foo(?,?2)",[1,4])).
1702 assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
1703 db.createFunction("bar", {
1705 xFunc: (pCx,...args)=>{
1706 T.assert(db.pointer === capi.sqlite3_context_db_handle(pCx));
1708 for(const v of args) rc += v;
1713 xFunc: (pCx,arg)=>arg
1715 T.assert(0===db.selectValue("select bar()")).
1716 assert(1===db.selectValue("select bar(1)")).
1717 assert(3===db.selectValue("select bar(1,2)")).
1718 assert(-1===db.selectValue("select bar(1,2,-4)")).
1719 assert('hi' === db.selectValue("select asis('hi')")).
1720 assert('hi' === db.selectValue("select ?",'hi')).
1721 assert(null === db.selectValue("select null")).
1722 assert(null === db.selectValue("select asis(null)")).
1723 assert(1 === db.selectValue("select ?",1)).
1724 assert(2 === db.selectValue("select ?",[2])).
1725 assert(3 === db.selectValue("select $a",{$a:3})).
1726 assert(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
1727 assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")));
1729 let blobArg = new Uint8Array([0x68, 0x69]);
1730 let blobRc = db.selectValue(
1732 blobArg.buffer/*confirm that ArrayBuffer is handled as a Uint8Array*/
1734 T.assert(blobRc instanceof Uint8Array).
1735 assert(2 === blobRc.length).
1736 assert(0x68==blobRc[0] && 0x69==blobRc[1]);
1737 blobRc = db.selectValue("select asis(X'6869')");
1738 T.assert(blobRc instanceof Uint8Array).
1739 assert(2 === blobRc.length).
1740 assert(0x68==blobRc[0] && 0x69==blobRc[1]);
1742 blobArg = new Int8Array([0x68, 0x69]);
1743 //debug("blobArg=",blobArg);
1744 blobRc = db.selectValue("select asis(?1)", blobArg);
1745 T.assert(blobRc instanceof Uint8Array).
1746 assert(2 === blobRc.length);
1747 //debug("blobRc=",blobRc);
1748 T.assert(0x68==blobRc[0] && 0x69==blobRc[1]);
1750 let rc = sqlite3.capi.sqlite3_create_function_v2(
1751 this.db, "foo", 0, -1, 0, 0, 0, 0, 0
1754 sqlite3.capi.SQLITE_FORMAT === rc,
1755 "For invalid eTextRep argument."
1757 rc = sqlite3.capi.sqlite3_create_function_v2(this.db, "foo", 0);
1759 sqlite3.capi.SQLITE_MISUSE === rc,
1760 "For invalid arg count."
1763 /* Confirm that we can map and unmap the same function with
1764 multiple arities... */
1765 const fCounts = [0,0];
1766 const fArityCheck = function(pCx){
1767 return ++fCounts[arguments.length-1];
1769 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
1770 rc = capi.sqlite3_create_function_v2(
1771 db, "nary", 0, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0
1774 rc = capi.sqlite3_create_function_v2(
1775 db, "nary", 1, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0
1778 const sqlFArity0 = "select nary()";
1779 const sqlFArity1 = "select nary(1)";
1780 T.assert( 1 === db.selectValue(sqlFArity0) )
1781 .assert( 1 === fCounts[0] ).assert( 0 === fCounts[1] );
1782 T.assert( 1 === db.selectValue(sqlFArity1) )
1783 .assert( 1 === fCounts[0] ).assert( 1 === fCounts[1] );
1784 capi.sqlite3_create_function_v2(
1785 db, "nary", 0, capi.SQLITE_UTF8, 0, 0, 0, 0, 0
1787 T.mustThrowMatching((()=>db.selectValue(sqlFArity0)),
1788 (e)=>((e instanceof sqlite3.SQLite3Error)
1789 && e.message.indexOf("wrong number of arguments")>0),
1790 "0-arity variant was uninstalled.");
1791 T.assert( 2 === db.selectValue(sqlFArity1) )
1792 .assert( 1 === fCounts[0] ).assert( 2 === fCounts[1] );
1793 capi.sqlite3_create_function_v2(
1794 db, "nary", 1, capi.SQLITE_UTF8, 0, 0, 0, 0, 0
1796 T.mustThrowMatching((()=>db.selectValue(sqlFArity1)),
1797 (e)=>((e instanceof sqlite3.SQLite3Error)
1798 && e.message.indexOf("no such function")>0),
1799 "1-arity variant was uninstalled.");
1800 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
1804 ////////////////////////////////////////////////////////////////////
1806 name: 'Aggregate UDFs',
1807 //predicate: ()=>false,
1808 test: function(sqlite3){
1810 const sjac = capi.sqlite3_js_aggregate_context;
1814 const ac = sjac(pCtx, 4);
1815 wasm.poke32(ac, wasm.peek32(ac) + Number(n));
1818 const ac = sjac(pCtx, 0);
1819 return ac ? wasm.peek32(ac) : 0;
1822 let v = db.selectValue([
1824 "select 3 union all select 5 union all select 7",
1825 ") select summer(v), summer(v+1) from cte"
1826 /* ------------------^^^^^^^^^^^ ensures that we're handling
1827 sqlite3_aggregate_context() properly. */
1830 T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
1831 /wrong number of arguments/);
1836 xStep: (pCtx, ...args)=>{
1837 const ac = sjac(pCtx, 4);
1838 let sum = wasm.peek32(ac);
1839 for(const v of args) sum += Number(v);
1840 wasm.poke32(ac, sum);
1843 const ac = sjac(pCtx, 0);
1844 capi.sqlite3_result_int( pCtx, ac ? wasm.peek32(ac) : 0 );
1845 // xFinal() may either return its value directly or call
1846 // sqlite3_result_xyz() and return undefined. Both are
1847 // functionally equivalent.
1850 T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)'));
1851 T.mustThrowMatching(()=>{
1852 db.createFunction('nope',{
1853 xFunc: ()=>{}, xStep: ()=>{}
1855 }, /scalar or aggregate\?/);
1856 T.mustThrowMatching(()=>{
1857 db.createFunction('nope',{xStep: ()=>{}});
1858 }, /Missing xFinal/);
1859 T.mustThrowMatching(()=>{
1860 db.createFunction('nope',{xFinal: ()=>{}});
1861 }, /Missing xStep/);
1862 T.mustThrowMatching(()=>{
1863 db.createFunction('nope',{});
1864 }, /Missing function-type properties/);
1865 T.mustThrowMatching(()=>{
1866 db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'});
1867 }, /xDestroy property must be a function/);
1868 T.mustThrowMatching(()=>{
1869 db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'});
1870 }, /Invalid value for pApp/);
1872 }/*aggregate UDFs*/)
1874 ////////////////////////////////////////////////////////////////////////
1876 name: 'Aggregate UDFs (64-bit)',
1877 predicate: ()=>wasm.bigIntEnabled,
1878 //predicate: ()=>false,
1879 test: function(sqlite3){
1881 const sjac = capi.sqlite3_js_aggregate_context;
1885 const ac = sjac(pCtx, 8);
1886 wasm.poke64(ac, wasm.peek64(ac) + BigInt(n));
1889 const ac = sjac(pCtx, 0);
1890 return ac ? wasm.peek64(ac) : 0n;
1893 let v = db.selectValue([
1895 "select 9007199254740991 union all select 1 union all select 2",
1896 ") select summer64(v), summer64(v+1) from cte"
1898 T.assert(9007199254740994n===v);
1900 }/*aggregate UDFs*/)
1902 ////////////////////////////////////////////////////////////////////
1904 name: 'Window UDFs',
1905 //predicate: ()=>false,
1907 /* Example window function, table, and results taken from:
1908 https://sqlite.org/windowfunctions.html#udfwinfunc */
1910 const sjac = (cx,n=4)=>capi.sqlite3_js_aggregate_context(cx,n);
1911 const xValueFinal = (pCtx)=>{
1912 const ac = sjac(pCtx, 0);
1913 return ac ? wasm.peek32(ac) : 0;
1915 const xStepInverse = (pCtx, n)=>{
1916 const ac = sjac(pCtx);
1917 wasm.poke32(ac, wasm.peek32(ac) + Number(n));
1921 xStep: (pCtx, n)=>xStepInverse(pCtx, n),
1922 xInverse: (pCtx, n)=>xStepInverse(pCtx, -n),
1923 xFinal: xValueFinal,
1927 "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
1928 "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
1931 returnValue: 'resultRows',
1933 "SELECT x, winsumint(y) OVER (",
1934 "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
1936 "FROM twin ORDER BY x;"
1939 T.assert(Array.isArray(rc))
1940 .assert(5 === rc.length);
1942 for(const row of rc){
1944 case 1: T.assert('a'===row[0] && 9===row[1]); break;
1945 case 2: T.assert('b'===row[0] && 12===row[1]); break;
1946 case 3: T.assert('c'===row[0] && 16===row[1]); break;
1947 case 4: T.assert('d'===row[0] && 12===row[1]); break;
1948 case 5: T.assert('e'===row[0] && 9===row[1]); break;
1949 default: toss("Too many rows to window function.");
1952 const resultRows = [];
1955 returnValue: 'resultRows',
1957 "SELECT x, winsumint(y) OVER (",
1958 "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
1960 "FROM twin ORDER BY x;"
1963 T.assert(rc === resultRows)
1964 .assert(5 === rc.length);
1967 returnValue: 'saveSql',
1968 sql: "select 1; select 2; -- empty\n; select 3"
1970 T.assert(Array.isArray(rc))
1971 .assert(3===rc.length)
1972 .assert('select 1;' === rc[0])
1973 .assert('select 2;' === rc[1])
1974 .assert('-- empty\n; select 3' === rc[2]
1975 /* Strange but true. */);
1976 T.mustThrowMatching(()=>{
1977 db.exec({sql:'', returnValue: 'nope'});
1978 }, /^Invalid returnValue/);
1980 db.exec("DROP TABLE twin");
1984 ////////////////////////////////////////////////////////////////////
1985 .t("ATTACH", function(){
1987 const resultRows = [];
1989 sql:new TextEncoder('utf-8').encode([
1990 // ^^^ testing string-vs-typedarray handling in exec()
1991 "attach 'session' as foo;",
1992 "create table foo.bar(a);",
1993 "insert into foo.bar(a) values(1),(2),(3);",
1994 "select a from foo.bar order by a;"
1999 T.assert(3===resultRows.length)
2000 .assert(2===resultRows[1]);
2001 T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a'));
2003 /** Demonstrate the JS-simplified form of the sqlite3_exec() callback... */
2004 let colCount = 0, rowCount = 0;
2005 let rc = capi.sqlite3_exec(
2006 db, "select a, a*2 from foo.bar", function(aVals, aNames){
2007 //console.warn("execCallback(",arguments,")");
2008 colCount = aVals.length;
2010 T.assert(2===aVals.length)
2011 .assert(2===aNames.length)
2012 .assert(+(aVals[1]) === 2 * +(aVals[0]));
2015 T.assert(0===rc).assert(3===rowCount).assert(2===colCount);
2016 rc = capi.sqlite3_exec(
2017 db.pointer, "select a from foo.bar", ()=>{
2018 tossQuietly("Testing throwing from exec() callback.");
2021 T.assert(capi.SQLITE_ABORT === rc);
2023 /* Demonstrate how to get access to the "full" callback
2024 signature, as opposed to the simplified JS-specific one... */
2025 rowCount = colCount = 0;
2026 const pCb = wasm.installFunction('i(pipp)', function(pVoid,nCols,aVals,aCols){
2027 /* Tip: wasm.cArgvToJs() can be used to convert aVals and
2028 aCols to arrays: const vals = wasm.cArgvToJs(nCols,
2032 T.assert(2 === nCols)
2033 .assert(wasm.isPtr(pVoid))
2034 .assert(wasm.isPtr(aVals))
2035 .assert(wasm.isPtr(aCols))
2036 .assert(+wasm.cstrToJs(wasm.peekPtr(aVals + wasm.ptrSizeof))
2037 === 2 * +wasm.cstrToJs(wasm.peekPtr(aVals)));
2041 T.assert(wasm.isPtr(pCb));
2042 rc = capi.sqlite3_exec(
2043 db, new TextEncoder('utf-8').encode("select a, a*2 from foo.bar"),
2047 .assert(3===rowCount)
2048 .assert(2===colCount);
2050 wasm.uninstallFunction(pCb);
2053 // Demonstrate that an OOM result does not propagate through sqlite3_exec()...
2054 rc = capi.sqlite3_exec(
2055 db, ["select a,"," a*2 from foo.bar"], (aVals, aNames)=>{
2056 sqlite3.WasmAllocError.toss("just testing");
2059 T.assert(capi.SQLITE_ABORT === rc);
2061 db.exec("detach foo");
2062 T.mustThrow(()=>db.exec("select * from foo.bar"),
2063 "Because foo is no longer attached.");
2066 ////////////////////////////////////////////////////////////////////
2068 name: 'C-side WASM tests',
2069 predicate: ()=>(haveWasmCTests() || "Not compiled in."),
2071 const w = wasm, db = this.db;
2072 const stack = w.scopedAllocPush();
2074 const origValue = 512;
2076 ptrInt = w.scopedAlloc(4);
2077 w.poke32(ptrInt,origValue);
2078 const cf = w.xGet('sqlite3__wasm_test_intptr');
2079 const oldPtrInt = ptrInt;
2080 T.assert(origValue === w.peek32(ptrInt));
2081 const rc = cf(ptrInt);
2082 T.assert(2*origValue === rc).
2083 assert(rc === w.peek32(ptrInt)).
2084 assert(oldPtrInt === ptrInt);
2085 const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/;
2086 const o64 = 0x010203040506/*>32-bit integer*/;
2087 if(w.bigIntEnabled){
2088 w.poke64(pi64, o64);
2089 //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64);
2090 const v64 = ()=>w.peek64(pi64)
2091 T.assert(v64() == o64);
2092 //T.assert(o64 === w.peek64(pi64));
2093 const cf64w = w.xGet('sqlite3__wasm_test_int64ptr');
2095 T.assert(v64() == BigInt(2 * o64));
2097 T.assert(v64() == BigInt(4 * o64));
2099 const biTimes2 = w.xGet('sqlite3__wasm_test_int64_times2');
2100 T.assert(BigInt(2 * o64) ===
2101 biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError
2102 in the call :/ */));
2104 const pMin = w.scopedAlloc(16);
2105 const pMax = pMin + 8;
2106 const g64 = (p)=>w.peek64(p);
2107 w.poke64([pMin, pMax], 0);
2109 w.xCall('sqlite3__wasm_test_int64_min'),
2110 w.xCall('sqlite3__wasm_test_int64_max')
2112 T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)).
2113 assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER));
2114 //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]);
2115 w.xCall('sqlite3__wasm_test_int64_minmax', pMin, pMax);
2116 T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch").
2117 assert(g64(pMax) === minMaxI64[1], "int64 mismatch");
2118 //log("pMin",g64(pMin), "pMax",g64(pMax));
2119 w.poke64(pMin, minMaxI64[0]);
2120 T.assert(g64(pMin) === minMaxI64[0]).
2121 assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))).
2122 assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax)));
2123 const rxRange = /too big/;
2124 T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))},
2126 mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))},
2127 (e)=>rxRange.test(e.message));
2129 log("No BigInt support. Skipping related tests.");
2130 log("\"The problem\" here is that we can manipulate, at the byte level,",
2131 "heap memory to set 64-bit values, but we can't get those values",
2132 "back into JS because of the lack of 64-bit integer support.");
2135 const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1);
2136 //log("x=",x,"y=",y,"z=",z); // just looking at the alignment
2137 w.scopedAllocPop(stack);
2140 }/* jaccwabyt-specific tests */)
2142 ////////////////////////////////////////////////////////////////////////
2144 name: 'virtual table #1: eponymous w/ manual exception handling',
2145 predicate: ()=>!!capi.sqlite3_index_info,
2146 test: function(sqlite3){
2147 const VT = sqlite3.vtab;
2148 const tmplCols = Object.assign(Object.create(null),{
2152 The vtab demonstrated here is a JS-ification of
2153 ext/misc/templatevtab.c.
2155 const tmplMod = new sqlite3.capi.sqlite3_module();
2156 T.assert(0===tmplMod.$xUpdate);
2157 tmplMod.setupModule({
2158 catchExceptions: false,
2160 xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){
2162 const args = wasm.cArgvToJs(argc, argv);
2163 T.assert(args.length>=3)
2164 .assert(args[0] === 'testvtab')
2165 .assert(args[1] === 'main')
2166 .assert(args[2] === 'testvtab');
2167 //console.debug("xConnect() args =",args);
2168 const rc = capi.sqlite3_declare_vtab(
2169 pDb, "CREATE TABLE ignored(a,b)"
2172 const t = VT.xVtab.create(ppVtab);
2173 T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab)));
2177 if(!(e instanceof sqlite3.WasmAllocError)){
2178 wasm.dealloc(wasm.peekPtr, pzErr);
2179 wasm.pokePtr(pzErr, wasm.allocCString(e.message));
2181 return VT.xError('xConnect',e);
2184 xCreate: true /* just for testing. Will be removed afterwards. */,
2185 xDisconnect: function(pVtab){
2187 VT.xVtab.unget(pVtab).dispose();
2190 return VT.xError('xDisconnect',e);
2193 xOpen: function(pVtab, ppCursor){
2195 const t = VT.xVtab.get(pVtab),
2196 c = VT.xCursor.create(ppCursor);
2197 T.assert(t instanceof capi.sqlite3_vtab)
2198 .assert(c instanceof capi.sqlite3_vtab_cursor);
2202 return VT.xError('xOpen',e);
2205 xClose: function(pCursor){
2207 const c = VT.xCursor.unget(pCursor);
2208 T.assert(c instanceof capi.sqlite3_vtab_cursor)
2209 .assert(!VT.xCursor.get(pCursor));
2213 return VT.xError('xClose',e);
2216 xNext: function(pCursor){
2218 const c = VT.xCursor.get(pCursor);
2222 return VT.xError('xNext',e);
2225 xColumn: function(pCursor, pCtx, iCol){
2227 const c = VT.xCursor.get(pCursor);
2230 capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
2233 capi.sqlite3_result_int(pCtx, 2000 + c._rowId);
2235 default: sqlite3.SQLite3Error.toss("Invalid column id",iCol);
2239 return VT.xError('xColumn',e);
2242 xRowid: function(pCursor, ppRowid64){
2244 const c = VT.xCursor.get(pCursor);
2245 VT.xRowid(ppRowid64, c._rowId);
2248 return VT.xError('xRowid',e);
2251 xEof: function(pCursor){
2252 const c = VT.xCursor.get(pCursor),
2256 xFilter: function(pCursor, idxNum, idxCStr,
2257 argc, argv/* [sqlite3_value* ...] */){
2259 const c = VT.xCursor.get(pCursor);
2261 const list = capi.sqlite3_values_to_js(argc, argv);
2262 T.assert(argc === list.length);
2263 //log(argc,"xFilter value(s):",list);
2266 return VT.xError('xFilter',e);
2269 xBestIndex: function(pVtab, pIdxInfo){
2271 //const t = VT.xVtab.get(pVtab);
2272 const sii = capi.sqlite3_index_info;
2273 const pii = new sii(pIdxInfo);
2274 pii.$estimatedRows = 10;
2275 pii.$estimatedCost = 10.0;
2276 //log("xBestIndex $nConstraint =",pii.$nConstraint);
2277 if(pii.$nConstraint>0){
2278 // Validate nthConstraint() and nthConstraintUsage()
2279 const max = pii.$nConstraint;
2280 for(let i=0; i < max; ++i ){
2281 let v = pii.nthConstraint(i,true);
2282 T.assert(wasm.isPtr(v));
2283 v = pii.nthConstraint(i);
2284 T.assert(v instanceof sii.sqlite3_index_constraint)
2285 .assert(v.pointer >= pii.$aConstraint);
2287 v = pii.nthConstraintUsage(i,true);
2288 T.assert(wasm.isPtr(v));
2289 v = pii.nthConstraintUsage(i);
2290 T.assert(v instanceof sii.sqlite3_index_constraint_usage)
2291 .assert(v.pointer >= pii.$aConstraintUsage);
2292 v.$argvIndex = i;//just to get some values into xFilter
2296 //log("xBestIndex $nOrderBy =",pii.$nOrderBy);
2297 if(pii.$nOrderBy>0){
2298 // Validate nthOrderBy()
2299 const max = pii.$nOrderBy;
2300 for(let i=0; i < max; ++i ){
2301 let v = pii.nthOrderBy(i,true);
2302 T.assert(wasm.isPtr(v));
2303 v = pii.nthOrderBy(i);
2304 T.assert(v instanceof sii.sqlite3_index_orderby)
2305 .assert(v.pointer >= pii.$aOrderBy);
2312 return VT.xError('xBestIndex',e);
2317 this.db.onclose.disposeAfter.push(tmplMod);
2318 T.assert(0===tmplMod.$xUpdate)
2319 .assert(tmplMod.$xCreate)
2320 .assert(tmplMod.$xCreate === tmplMod.$xConnect,
2321 "setup() must make these equivalent and "+
2322 "installMethods() must avoid re-compiling identical functions");
2323 tmplMod.$xCreate = 0 /* make tmplMod eponymous-only */;
2324 let rc = capi.sqlite3_create_module(
2325 this.db, "testvtab", tmplMod, 0
2327 this.db.checkRc(rc);
2328 const list = this.db.selectArrays(
2329 "SELECT a,b FROM testvtab where a<9999 and b>1 order by a, b"
2330 /* Query is shaped so that it will ensure that some constraints
2331 end up in xBestIndex(). */
2333 T.assert(10===list.length)
2334 .assert(1000===list[0][0])
2335 .assert(2009===list[list.length-1][1]);
2337 })/*custom vtab #1*/
2339 ////////////////////////////////////////////////////////////////////////
2341 name: 'virtual table #2: non-eponymous w/ automated exception wrapping',
2342 predicate: ()=>!!capi.sqlite3_index_info,
2343 test: function(sqlite3){
2344 const VT = sqlite3.vtab;
2345 const tmplCols = Object.assign(Object.create(null),{
2349 The vtab demonstrated here is a JS-ification of
2350 ext/misc/templatevtab.c.
2352 let throwOnCreate = 1 ? 0 : capi.SQLITE_CANTOPEN
2353 /* ^^^ just for testing exception wrapping. Note that sqlite
2354 always translates errors from a vtable to a generic
2355 SQLITE_ERROR unless it's from xConnect()/xCreate() and that
2356 callback sets an error string. */;
2359 : (methodName,...args)=>console.debug('sqlite3_module::'+methodName+'():',...args);
2361 /* catchExceptions changes how the methods are wrapped */
2362 catchExceptions: true,
2365 xCreate: function(pDb, pAux, argc, argv, ppVtab, pzErr){
2366 vtabTrace("xCreate",...arguments);
2368 sqlite3.SQLite3Error.toss(
2370 "Throwing a test exception."
2373 const args = wasm.cArgvToJs(argc, argv);
2374 vtabTrace("xCreate","argv:",args);
2375 T.assert(args.length>=3);
2376 const rc = capi.sqlite3_declare_vtab(
2377 pDb, "CREATE TABLE ignored(a,b)"
2380 const t = VT.xVtab.create(ppVtab);
2381 T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab)));
2382 vtabTrace("xCreate",...arguments," ppVtab =",t.pointer);
2387 xDestroy: function(pVtab){
2388 vtabTrace("xDestroy/xDisconnect",pVtab);
2389 VT.xVtab.dispose(pVtab);
2392 xOpen: function(pVtab, ppCursor){
2393 const t = VT.xVtab.get(pVtab),
2394 c = VT.xCursor.create(ppCursor);
2395 T.assert(t instanceof capi.sqlite3_vtab)
2396 .assert(c instanceof capi.sqlite3_vtab_cursor);
2397 vtabTrace("xOpen",...arguments," cursor =",c.pointer);
2400 xClose: function(pCursor){
2401 vtabTrace("xClose",...arguments);
2402 const c = VT.xCursor.unget(pCursor);
2403 T.assert(c instanceof capi.sqlite3_vtab_cursor)
2404 .assert(!VT.xCursor.get(pCursor));
2407 xNext: function(pCursor){
2408 vtabTrace("xNext",...arguments);
2409 const c = VT.xCursor.get(pCursor);
2412 xColumn: function(pCursor, pCtx, iCol){
2413 vtabTrace("xColumn",...arguments);
2414 const c = VT.xCursor.get(pCursor);
2417 capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
2420 capi.sqlite3_result_int(pCtx, 2000 + c._rowId);
2422 default: sqlite3.SQLite3Error.toss("Invalid column id",iCol);
2425 xRowid: function(pCursor, ppRowid64){
2426 vtabTrace("xRowid",...arguments);
2427 const c = VT.xCursor.get(pCursor);
2428 VT.xRowid(ppRowid64, c._rowId);
2430 xEof: function(pCursor){
2431 vtabTrace("xEof",...arguments);
2432 return VT.xCursor.get(pCursor)._rowId>=10;
2434 xFilter: function(pCursor, idxNum, idxCStr,
2435 argc, argv/* [sqlite3_value* ...] */){
2436 vtabTrace("xFilter",...arguments);
2437 const c = VT.xCursor.get(pCursor);
2439 const list = capi.sqlite3_values_to_js(argc, argv);
2440 T.assert(argc === list.length);
2442 xBestIndex: function(pVtab, pIdxInfo){
2443 vtabTrace("xBestIndex",...arguments);
2444 //const t = VT.xVtab.get(pVtab);
2445 const pii = VT.xIndexInfo(pIdxInfo);
2446 pii.$estimatedRows = 10;
2447 pii.$estimatedCost = 10.0;
2452 const tmplMod = VT.setupModule(modConfig);
2453 T.assert(1===tmplMod.$iVersion);
2454 this.db.onclose.disposeAfter.push(tmplMod);
2455 this.db.checkRc(capi.sqlite3_create_module(
2456 this.db.pointer, modConfig.name, tmplMod.pointer, 0
2459 "create virtual table testvtab2 using ",
2461 "(arg1 blah, arg2 bloop)"
2464 /* If we DROP TABLE then xDestroy() is called. If the
2465 vtab is instead destroyed when the db is closed,
2466 xDisconnect() is called. */
2467 this.db.onclose.disposeBefore.push(function(db){
2468 console.debug("Explicitly dropping testvtab2 via disposeBefore handler...");
2470 /** DROP TABLE is the only way to get xDestroy() to be called. */
2471 "DROP TABLE testvtab2"
2475 let list = this.db.selectArrays(
2476 "SELECT a,b FROM testvtab2 where a<9999 and b>1 order by a, b"
2477 /* Query is shaped so that it will ensure that some
2478 constraints end up in xBestIndex(). */
2480 T.assert(10===list.length)
2481 .assert(1000===list[0][0])
2482 .assert(2009===list[list.length-1][1]);
2484 list = this.db.selectArrays(
2485 "SELECT a,b FROM testvtab2 where a<9999 and b>1 order by b, a limit 5"
2487 T.assert(5===list.length)
2488 .assert(1000===list[0][0])
2489 .assert(2004===list[list.length-1][1]);
2491 // Call it as a table-valued function...
2492 list = this.db.selectArrays([
2493 "SELECT a,b FROM ", modConfig.name,
2494 " where a<9999 and b>1 order by b, a limit 1"
2496 T.assert(1===list.length)
2497 .assert(1000===list[0][0])
2498 .assert(2000===list[0][1]);
2500 })/*custom vtab #2*/
2501 ////////////////////////////////////////////////////////////////////////
2502 .t('Custom collation', function(sqlite3){
2503 let collationCounter = 0;
2504 let myCmp = function(pArg,n1,p1,n2,p2){
2505 //int (*)(void*,int,const void*,int,const void*)
2507 const rc = wasm.exports.sqlite3_strnicmp(p1,p2,(n1<n2?n1:n2));
2508 return rc ? rc : (n1 - n2);
2510 let rc = capi.sqlite3_create_collation_v2(this.db, "mycollation", capi.SQLITE_UTF8,
2512 this.db.checkRc(rc);
2513 rc = this.db.selectValue("select 'hi' = 'HI' collate mycollation");
2514 T.assert(1===rc).assert(1===collationCounter);
2515 rc = this.db.selectValue("select 'hii' = 'HI' collate mycollation");
2516 T.assert(0===rc).assert(2===collationCounter);
2517 rc = this.db.selectValue("select 'hi' = 'HIi' collate mycollation");
2518 T.assert(0===rc).assert(3===collationCounter);
2519 rc = capi.sqlite3_create_collation(this.db,"hi",capi.SQLITE_UTF8/*not enough args*/);
2520 T.assert(capi.SQLITE_MISUSE === rc);
2521 rc = capi.sqlite3_create_collation_v2(this.db,"hi",capi.SQLITE_UTF8+1/*invalid encoding*/,0,0,0);
2522 T.assert(capi.SQLITE_FORMAT === rc)
2523 .mustThrowMatching(()=>this.db.checkRc(rc),
2524 /SQLITE_UTF8 is the only supported encoding./);
2526 We need to ensure that replacing that collation function does
2527 the right thing. We don't have a handle to the underlying WASM
2528 pointer from here, so cannot verify (without digging through
2529 internal state) that the old one gets uninstalled, but we can
2530 verify that a new one properly replaces it. (That said,
2531 console.warn() output has shown that the uninstallation does
2534 collationCounter = 0;
2535 myCmp = function(pArg,n1,p1,n2,p2){
2539 rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8,
2541 this.db.checkRc(rc);
2542 rc = this.db.selectValue("select 'hi' = 'HI' collate mycollation");
2543 T.assert(rc>0).assert(-1===collationCounter);
2544 rc = this.db.selectValue("select 'a' = 'b' collate mycollation");
2545 T.assert(rc>0).assert(-2===collationCounter);
2546 rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8,
2548 this.db.checkRc(rc);
2551 this.db.selectValue("select 'a' = 'b' collate mycollation");
2553 /* Why is e.resultCode not automatically an extended result
2554 code? The DB() class enables those automatically. */
2555 rc = sqlite3.capi.sqlite3_extended_errcode(this.db);
2557 T.assert(capi.SQLITE_ERROR_MISSING_COLLSEQ === rc);
2558 })/*custom collation*/
2560 ////////////////////////////////////////////////////////////////////////
2561 .t('Close db', function(){
2562 T.assert(this.db).assert(wasm.isPtr(this.db.pointer));
2563 //wasm.sqlite3__wasm_db_reset(this.db); // will leak virtual tables!
2565 T.assert(!this.db.pointer);
2567 ;/* end of oo1 checks */
2569 ////////////////////////////////////////////////////////////////////////
2572 name: 'kvvfs is disabled in worker',
2573 predicate: ()=>(isWorker() || "test is only valid in a Worker"),
2574 test: function(sqlite3){
2576 !capi.sqlite3_vfs_find('kvvfs'),
2577 "Expecting kvvfs to be unregistered."
2582 name: 'kvvfs in main thread',
2583 predicate: ()=>(isUIThread()
2584 || "local/sessionStorage are unavailable in a Worker"),
2585 test: function(sqlite3){
2586 const filename = this.kvvfsDbFile = 'session';
2587 const pVfs = capi.sqlite3_vfs_find('kvvfs');
2589 const JDb = this.JDb = sqlite3.oo1.JsStorageDb;
2590 const unlink = this.kvvfsUnlink = ()=>{JDb.clearStorage(filename)};
2592 let db = new JDb(filename);
2595 'create table kvvfs(a);',
2596 'insert into kvvfs(a) values(1),(2),(3)'
2598 T.assert(3 === db.selectValue('select count(*) from kvvfs'));
2600 db = new JDb(filename);
2601 db.exec('insert into kvvfs(a) values(4),(5),(6)');
2602 T.assert(6 === db.selectValue('select count(*) from kvvfs'));
2607 }/*kvvfs sanity checks*/)
2608 ;/* end kvvfs tests */
2610 ////////////////////////////////////////////////////////////////////////
2613 name: "sqlite3_commit/rollback/update_hook()",
2614 predicate: ()=>wasm.bigIntEnabled || "Update hook requires int64",
2615 test: function(sqlite3){
2616 let countCommit = 0, countRollback = 0;;
2617 const db = new sqlite3.oo1.DB(':memory:',1 ? 'c' : 'ct');
2618 let rc = capi.sqlite3_commit_hook(db, (p)=>{
2620 return (1 === p) ? 0 : capi.SQLITE_ERROR;
2622 T.assert( 0 === rc /*void pointer*/ );
2625 T.assert( 0!=capi.sqlite3_get_autocommit(db) );
2626 db.exec("BEGIN; SELECT 1; COMMIT");
2627 T.assert(0 === countCommit,
2628 "No-op transactions (mostly) do not trigger commit hook.");
2629 db.exec("BEGIN EXCLUSIVE; SELECT 1; COMMIT");
2630 T.assert(1 === countCommit,
2631 "But EXCLUSIVE transactions do.");
2632 db.transaction((d)=>{
2633 T.assert( 0==capi.sqlite3_get_autocommit(db) );
2634 d.exec("create table t(a)");
2636 T.assert(2 === countCommit);
2639 rc = capi.sqlite3_rollback_hook(db, (p)=>{
2641 T.assert( 2 === p );
2643 T.assert( 0 === rc /*void pointer*/ );
2644 T.mustThrowMatching(()=>{
2645 db.transaction('drop table t',()=>{})
2647 return (capi.SQLITE_MISUSE === e.resultCode)
2648 && ( e.message.indexOf('Invalid argument') > 0 );
2650 T.assert(0 === countRollback, "Transaction was not started.");
2651 T.mustThrowMatching(()=>{
2652 db.transaction('immediate', ()=>{
2653 sqlite3.SQLite3Error.toss(capi.SQLITE_FULL,'testing rollback hook');
2656 return capi.SQLITE_FULL === e.resultCode
2658 T.assert(1 === countRollback);
2661 const countUpdate = Object.create(null);
2662 capi.sqlite3_update_hook(db, (p,op,dbName,tbl,rowid)=>{
2663 T.assert('main' === dbName.toLowerCase())
2664 .assert('t' === tbl.toLowerCase())
2666 .assert('bigint' === typeof rowid);
2668 case capi.SQLITE_INSERT:
2669 case capi.SQLITE_UPDATE:
2670 case capi.SQLITE_DELETE:
2671 countUpdate[op] = (countUpdate[op]||0) + 1;
2673 default: toss("Unexpected hook operator:",op);
2676 db.transaction((d)=>{
2678 "insert into t(a) values(1);",
2679 "update t set a=2;",
2680 "update t set a=3;",
2681 "delete from t where a=3"
2682 // update hook is not called for an unqualified DELETE
2685 T.assert(1 === countRollback)
2686 .assert(3 === countCommit)
2687 .assert(1 === countUpdate[capi.SQLITE_INSERT])
2688 .assert(2 === countUpdate[capi.SQLITE_UPDATE])
2689 .assert(1 === countUpdate[capi.SQLITE_DELETE]);
2690 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
2691 T.assert(1 === capi.sqlite3_commit_hook(db, 0, 0));
2692 T.assert(2 === capi.sqlite3_rollback_hook(db, 0, 0));
2693 T.assert(3 === capi.sqlite3_update_hook(db, 0, 0));
2694 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
2697 })/* commit/rollback/update hooks */
2699 name: "sqlite3_preupdate_hook()",
2700 predicate: ()=>wasm.bigIntEnabled || "Pre-update hook requires int64",
2701 test: function(sqlite3){
2702 const db = new sqlite3.oo1.DB(':memory:', 1 ? 'c' : 'ct');
2703 const countHook = Object.create(null);
2704 let rc = capi.sqlite3_preupdate_hook(
2705 db, function(p, pDb, op, zDb, zTbl, iKey1, iKey2){
2707 .assert(db.pointer === pDb)
2708 .assert(1 === capi.sqlite3_preupdate_count(pDb))
2709 .assert( 0 > capi.sqlite3_preupdate_blobwrite(pDb) );
2710 countHook[op] = (countHook[op]||0) + 1;
2712 case capi.SQLITE_INSERT:
2713 case capi.SQLITE_UPDATE:
2714 T.assert('number' === typeof capi.sqlite3_preupdate_new_js(pDb, 0));
2716 case capi.SQLITE_DELETE:
2717 T.assert('number' === typeof capi.sqlite3_preupdate_old_js(pDb, 0));
2719 default: toss("Unexpected hook operator:",op);
2724 db.transaction((d)=>{
2726 "create table t(a);",
2727 "insert into t(a) values(1);",
2728 "update t set a=2;",
2729 "update t set a=3;",
2730 "delete from t where a=3"
2733 T.assert(1 === countHook[capi.SQLITE_INSERT])
2734 .assert(2 === countHook[capi.SQLITE_UPDATE])
2735 .assert(1 === countHook[capi.SQLITE_DELETE]);
2736 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
2738 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
2740 })/*pre-update hooks*/
2741 ;/*end hook API tests*/
2743 ////////////////////////////////////////////////////////////////////////
2744 T.g('Auto-extension API')
2746 name: "Auto-extension sanity checks.",
2747 test: function(sqlite3){
2749 const fp = wasm.installFunction('i(ppp)', function(pDb,pzErr,pApi){
2753 (new sqlite3.oo1.DB()).close();
2754 T.assert( 0===counter );
2755 capi.sqlite3_auto_extension(fp);
2756 (new sqlite3.oo1.DB()).close();
2757 T.assert( 1===counter );
2758 (new sqlite3.oo1.DB()).close();
2759 T.assert( 2===counter );
2760 capi.sqlite3_cancel_auto_extension(fp);
2761 wasm.uninstallFunction(fp);
2762 (new sqlite3.oo1.DB()).close();
2763 T.assert( 2===counter );
2767 ////////////////////////////////////////////////////////////////////////
2770 name: 'Session API sanity checks',
2771 predicate: ()=>!!capi.sqlite3changegroup_add,
2772 test: function(sqlite3){
2773 warn("The session API tests could use some expansion.");
2774 const db1 = new sqlite3.oo1.DB(), db2 = new sqlite3.oo1.DB();
2776 "create table t(rowid INTEGER PRIMARY KEY,a,b); ",
2777 "insert into t(rowid,a,b) values",
2784 T.assert(3 === db1.selectValue("select count(*) from t"))
2785 .assert('b3' === db1.selectValue('select b from t where rowid=3'));
2786 const stackPtr = wasm.pstack.pointer;
2788 let ppOut = wasm.pstack.allocPtr();
2789 let rc = capi.sqlite3session_create(db1, "main", ppOut);
2791 let pSession = wasm.peekPtr(ppOut);
2792 T.assert(pSession && wasm.isPtr(pSession));
2793 capi.sqlite3session_table_filter(pSession, (pCtx, tbl)=>{
2794 T.assert('t' === tbl).assert( 99 === pCtx );
2798 "update t set b='bTwo' where rowid=2;",
2799 "update t set a='aThree' where rowid=3;",
2800 "delete from t where rowid=1;",
2801 "insert into t(rowid,a,b) values(4,'a4','b4')"
2803 T.assert('bTwo' === db1.selectValue("select b from t where rowid=2"))
2804 .assert(undefined === db1.selectValue('select a from t where rowid=1'))
2805 .assert('b4' === db1.selectValue('select b from t where rowid=4'))
2806 .assert(3 === db1.selectValue('select count(*) from t'));
2808 const testSessionEnable = false;
2809 if(testSessionEnable){
2810 rc = capi.sqlite3session_enable(pSession, 0);
2811 T.assert( 0 === rc )
2812 .assert( 0 === capi.sqlite3session_enable(pSession, -1) );
2813 db1.exec("delete from t where rowid=2;");
2814 rc = capi.sqlite3session_enable(pSession, 1);
2816 .assert( capi.sqlite3session_enable(pSession, -1) > 0 )
2817 .assert(undefined === db1.selectValue('select a from t where rowid=2'));
2819 warn("sqlite3session_enable() tests are currently disabled.");
2821 let db1Count = db1.selectValue("select count(*) from t");
2822 T.assert( db1Count === (testSessionEnable ? 2 : 3) );
2824 /* Capture changeset and destroy session. */
2825 let pnChanges = wasm.pstack.alloc('i32'),
2826 ppChanges = wasm.pstack.allocPtr();
2827 rc = capi.sqlite3session_changeset(pSession, pnChanges, ppChanges);
2828 T.assert( 0 === rc );
2829 capi.sqlite3session_delete(pSession);
2831 const pChanges = wasm.peekPtr(ppChanges),
2832 nChanges = wasm.peek32(pnChanges);
2833 T.assert( pChanges && wasm.isPtr( pChanges ) )
2834 .assert( nChanges > 0 );
2836 /* Revert db1 via an inverted changeset, but keep pChanges
2837 and nChanges for application to db2. */
2838 rc = capi.sqlite3changeset_invert( nChanges, pChanges, pnChanges, ppChanges );
2839 T.assert( 0 === rc );
2840 rc = capi.sqlite3changeset_apply(
2841 db1, wasm.peek32(pnChanges), wasm.peekPtr(ppChanges), 0, (pCtx, eConflict, pIter)=>{
2845 T.assert( 0 === rc );
2846 wasm.dealloc( wasm.peekPtr(ppChanges) );
2847 pnChanges = ppChanges = 0;
2848 T.assert('b2' === db1.selectValue("select b from t where rowid=2"))
2849 .assert('a1' === db1.selectValue('select a from t where rowid=1'))
2850 .assert(undefined === db1.selectValue('select b from t where rowid=4'));
2851 db1Count = db1.selectValue("select count(*) from t");
2852 T.assert(3 === db1Count);
2854 /* Apply pre-reverted changeset (pChanges, nChanges) to
2856 rc = capi.sqlite3changeset_apply(
2857 db2, nChanges, pChanges, 0, (pCtx, eConflict, pIter)=>{
2861 wasm.dealloc( pChanges );
2862 T.assert( 0 === rc )
2863 .assert( 'b4' === db2.selectValue('select b from t where rowid=4') )
2864 .assert( 'aThree' === db2.selectValue('select a from t where rowid=3') )
2865 .assert( undefined === db2.selectValue('select b from t where rowid=1') );
2866 if(testSessionEnable){
2867 T.assert( (undefined === db2.selectValue('select b from t where rowid=2')),
2868 "But... the session was disabled when rowid=2 was deleted?" );
2869 log("rowids from db2.t:",db2.selectValues('select rowid from t order by rowid'));
2870 T.assert( 3 === db2.selectValue('select count(*) from t') );
2872 T.assert( 'bTwo' === db2.selectValue('select b from t where rowid=2') )
2873 .assert( 3 === db2.selectValue('select count(*) from t') );
2876 wasm.pstack.restore(stackPtr);
2881 })/*session API sanity tests*/
2882 ;/*end of session API group*/;
2884 ////////////////////////////////////////////////////////////////////////
2885 T.g('OPFS: Origin-Private File System',
2886 (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs")
2887 || 'requires "opfs" VFS'))
2889 name: 'OPFS db sanity checks',
2890 test: async function(sqlite3){
2891 const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db';
2892 const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs');
2894 const unlink = this.opfsUnlink =
2895 (fn=filename)=>{sqlite3.util.sqlite3__wasm_vfs_unlink(pVfs,fn)};
2897 let db = new sqlite3.oo1.OpfsDb(filename);
2900 'create table p(a);',
2901 'insert into p(a) values(1),(2),(3)'
2903 T.assert(3 === db.selectValue('select count(*) from p'));
2905 db = new sqlite3.oo1.OpfsDb(filename);
2906 db.exec('insert into p(a) values(4),(5),(6)');
2907 T.assert(6 === db.selectValue('select count(*) from p'));
2908 this.opfsDbExport = capi.sqlite3_js_db_export(db);
2909 T.assert(this.opfsDbExport instanceof Uint8Array)
2910 .assert(this.opfsDbExport.byteLength>0
2911 && 0===this.opfsDbExport.byteLength % 512);
2917 }/*OPFS db sanity checks*/)
2919 name: 'OPFS import',
2920 test: async function(sqlite3){
2923 const exp = this.opfsDbExport;
2924 const filename = this.opfsDbFile;
2925 delete this.opfsDbExport;
2926 this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp);
2927 db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
2928 T.assert(6 === db.selectValue('select count(*) from p')).
2929 assert( this.opfsImportSize == exp.byteLength );
2931 this.opfsUnlink(filename);
2932 T.assert(!(await sqlite3.opfs.entryExists(filename)));
2933 // Try again with a function as an input source:
2935 const blockSize = 512, end = exp.byteLength;
2936 const reader = async function(){
2937 if(cursor >= exp.byteLength){
2940 const rv = exp.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
2941 cursor += blockSize;
2944 this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, reader);
2945 db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
2946 T.assert(6 === db.selectValue('select count(*) from p')).
2947 assert( this.opfsImportSize == exp.byteLength );
2952 }/*OPFS export/import*/)
2954 name: '(Internal-use) OPFS utility APIs',
2955 test: async function(sqlite3){
2956 const filename = this.opfsDbFile;
2957 const pVfs = this.opfsVfs;
2958 const unlink = this.opfsUnlink;
2959 T.assert(filename && pVfs && !!unlink);
2960 delete this.opfsDbFile;
2961 delete this.opfsVfs;
2962 delete this.opfsUnlink;
2963 /**************************************************************
2964 ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended
2965 for client-side use. It is only for this project's own
2966 internal use. Its APIs are subject to change or removal at
2968 ***************************************************************/
2969 const opfs = sqlite3.opfs;
2970 const fSize = this.opfsImportSize;
2971 delete this.opfsImportSize;
2974 T.assert(await opfs.entryExists(filename));
2975 const [dirHandle, filenamePart] = await opfs.getDirForFilename(filename, false);
2976 const fh = await dirHandle.getFileHandle(filenamePart);
2977 sh = await fh.createSyncAccessHandle();
2978 T.assert(fSize === await sh.getSize());
2982 T.assert(!(await opfs.entryExists(filename)));
2984 if(sh) await sh.close();
2988 // Some sanity checks of the opfs utility functions...
2989 const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
2990 const aDir = testDir+'/test/dir';
2991 T.assert(await opfs.mkdir(aDir), "mkdir failed")
2992 .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
2993 .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
2994 .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
2995 .assert(!(await opfs.unlink(testDir+'/test/dir')),
2996 "delete 2b should have failed (dir already deleted)")
2997 .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
2998 .assert(!(await opfs.entryExists(testDir)),
2999 "entryExists(",testDir,") should have failed");
3001 }/*OPFS util sanity checks*/)
3002 ;/* end OPFS tests */
3004 ////////////////////////////////////////////////////////////////////////
3005 T.g('OPFS SyncAccessHandle Pool VFS',
3006 (sqlite3)=>(hasOpfs() || "requires OPFS APIs"))
3008 name: 'SAH sanity checks',
3009 test: async function(sqlite3){
3010 T.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
3011 .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) < 0)
3012 const inst = sqlite3.installOpfsSAHPoolVfs,
3014 error("Cannot load SAH pool VFS.",
3015 "This might not be a problem,",
3016 "depending on the environment.");
3020 // Ensure that two immediately-consecutive installations
3021 // resolve to the same Promise instead of triggering
3023 const P1 = inst(sahPoolConfig).then(u=>u1 = u).catch(catcher),
3024 P2 = inst(sahPoolConfig).then(u=>u2 = u).catch(catcher);
3025 await Promise.all([P1, P2]);
3026 if(!(await P1)) return;
3028 .assert(sahPoolConfig.name === u1.vfsName)
3029 .assert(sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
3030 .assert(u1.getCapacity() >= sahPoolConfig.initialCapacity
3031 /* If a test fails before we get to nuke the VFS, we
3032 can have more than the initial capacity on the next
3034 .assert(u1.getCapacity() + 2 === (await u2.addCapacity(2)))
3035 .assert(2 === (await u2.reduceCapacity(2)))
3036 .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) >= 0);
3038 T.assert(0 === u1.getFileCount());
3039 const dbName = '/foo.db';
3040 let db = new u1.OpfsSAHPoolDb(dbName);
3041 T.assert(db instanceof sqlite3.oo1.DB)
3042 .assert(1 === u1.getFileCount());
3044 'create table t(a);',
3045 'insert into t(a) values(1),(2),(3)'
3047 T.assert(1 === u1.getFileCount());
3048 T.assert(3 === db.selectValue('select count(*) from t'));
3050 T.assert(1 === u1.getFileCount());
3051 db = new u2.OpfsSAHPoolDb(dbName);
3052 T.assert(1 === u1.getFileCount());
3054 const fileNames = u1.getFileNames();
3055 T.assert(1 === fileNames.length)
3056 .assert(dbName === fileNames[0])
3057 .assert(1 === u1.getFileCount())
3059 if(1){ // test exportFile() and importDb()
3060 const dbytes = u1.exportFile(dbName);
3061 T.assert(dbytes.length >= 4096);
3062 const dbName2 = '/exported.db';
3063 let nWrote = u1.importDb(dbName2, dbytes);
3064 T.assert( 2 == u1.getFileCount() )
3065 .assert( dbytes.byteLength == nWrote );
3066 let db2 = new u1.OpfsSAHPoolDb(dbName2);
3067 T.assert(db2 instanceof sqlite3.oo1.DB)
3068 .assert(3 === db2.selectValue('select count(*) from t'));
3070 T.assert(true === u1.unlink(dbName2))
3071 .assert(false === u1.unlink(dbName2))
3072 .assert(1 === u1.getFileCount())
3073 .assert(1 === u1.getFileNames().length);
3074 // Try again with a function as an input source:
3076 const blockSize = 1024, end = dbytes.byteLength;
3077 const reader = async function(){
3078 if(cursor >= dbytes.byteLength){
3081 const rv = dbytes.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
3082 cursor += blockSize;
3085 nWrote = await u1.importDb(dbName2, reader);
3086 T.assert( 2 == u1.getFileCount() );
3087 db2 = new u1.OpfsSAHPoolDb(dbName2);
3088 T.assert(db2 instanceof sqlite3.oo1.DB)
3089 .assert(3 === db2.selectValue('select count(*) from t'));
3091 T.assert(true === u1.unlink(dbName2))
3092 .assert(dbytes.byteLength == nWrote);
3095 T.assert(true === u1.unlink(dbName))
3096 .assert(false === u1.unlink(dbName))
3097 .assert(0 === u1.getFileCount())
3098 .assert(0 === u1.getFileNames().length);
3100 // Demonstrate that two SAH pools can coexist so long as
3101 // they have different names.
3102 const conf2 = JSON.parse(JSON.stringify(sahPoolConfig));
3103 conf2.name += '-test2';
3104 const POther = await inst(conf2);
3105 //log("Installed second SAH instance as",conf2.name);
3106 T.assert(0 === POther.getFileCount())
3107 .assert(true === await POther.removeVfs());
3110 /* Enable this block to inspect vfs's contents via the dev
3111 console or OPFS Explorer browser extension. The
3112 following bits will remove them. */
3115 T.assert(true === await u2.removeVfs())
3116 .assert(false === await u1.removeVfs())
3117 .assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name));
3120 conf2.$testThrowInInit = new Error("Testing throwing during init.");
3121 conf2.name = sahPoolConfig.name+'-err';
3122 const P3 = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e);
3123 T.assert(P3 === conf2.$testThrowInInit)
3124 .assert(cErr === P3)
3125 .assert(undefined === u3)
3126 .assert(!sqlite3.capi.sqlite3_vfs_find(conf2.name));
3128 }/*OPFS SAH Pool sanity checks*/)
3130 ////////////////////////////////////////////////////////////////////////
3133 name: 'Delete via bound parameter in subquery',
3134 test: function(sqlite3){
3135 // Testing https://sqlite.org/forum/forumpost/40ce55bdf5
3136 // with the exception that that post uses "external content"
3137 // for the FTS index.
3138 const db = new sqlite3.oo1.DB();//(':memory:','wt');
3140 "create virtual table f using fts5 (path);",
3141 "insert into f(path) values('abc'),('def'),('ghi');"
3143 const fetchEm = ()=> db.exec({
3144 sql: "SELECT * FROM f order by path",
3147 const dump = function(lbl){
3149 log((lbl ? (lbl+' results') : ''),rc);
3151 //dump('Full fts table');
3153 T.assert(3===rc.length);
3155 delete from f where rowid in (
3156 select rowid from f where path = :path
3158 {bind: {":path": "def"}}
3160 //dump('After deleting one entry via subquery');
3162 T.assert(2===rc.length)
3163 .assert('abcghi'===rc.join(''));
3168 ;/*end of Bug Reports group*/;
3170 ////////////////////////////////////////////////////////////////////////
3171 log("Loading and initializing sqlite3 WASM module...");
3173 globalThis.sqlite3ApiConfig = {
3180 //#ifnot target=es6-module
3181 if(!globalThis.sqlite3InitModule && !isUIThread()){
3182 /* Vanilla worker, as opposed to an ES6 module worker */
3184 If sqlite3.js is in a directory other than this script, in order
3185 to get sqlite3.js to resolve sqlite3.wasm properly, we have to
3186 explicitly tell it where sqlite3.js is being loaded from. We do
3187 that by passing the `sqlite3.dir=theDirName` URL argument to
3188 _this_ script. That URL argument will be seen by the JS/WASM
3189 loader and it will adjust the sqlite3.wasm path accordingly. If
3190 sqlite3.js/.wasm are in the same directory as this script then
3193 URL arguments passed as part of the filename via importScripts()
3194 are simply lost, and such scripts see the globalThis.location of
3197 let sqlite3Js = 'sqlite3.js';
3198 const urlParams = new URL(globalThis.location.href).searchParams;
3199 if(urlParams.has('sqlite3.dir')){
3200 sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
3202 importScripts(sqlite3Js);
3205 globalThis.sqlite3InitModule.__isUnderTest =
3206 true /* disables certain API-internal cleanup so that we can
3207 test internal APIs from here */;
3208 globalThis.sqlite3InitModule({
3211 }).then(async function(sqlite3){
3212 TestUtil.assert(!!sqlite3.util);
3213 log("Done initializing WASM/JS bits. Running tests...");
3214 sqlite3.config.warn("Installing sqlite3 bits as global S for local dev/test purposes.");
3215 globalThis.S = sqlite3;
3216 /*await sqlite3.installOpfsSAHPoolVfs(sahPoolConfig)
3217 .then((u)=>log("Loaded",u.vfsName,"VFS"))
3219 log("Cannot install OpfsSAHPool.",e);
3221 capi = sqlite3.capi;
3222 wasm = sqlite3.wasm;
3223 log("sqlite3 version:",capi.sqlite3_libversion(),
3224 capi.sqlite3_sourceid());
3225 if(wasm.bigIntEnabled){
3226 log("BigInt/int64 support is enabled.");
3228 logClass('warning',"BigInt/int64 support is disabled.");
3230 if(haveWasmCTests()){
3231 log("sqlite3__wasm_test_...() APIs are available.");
3233 logClass('warning',"sqlite3__wasm_test_...() APIs unavailable.");
3235 log("registered vfs list =",capi.sqlite3_js_vfs_list().join(', '));
3236 TestUtil.runTests(sqlite3);