Snapshot of upstream SQLite 3.46.1
[sqlcipher.git] / ext / wasm / tester1.c-pp.js
blobfe5bdc83726129fbd656528a5720e08e251318c5
1 /*
2 2022-10-12
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;
49 //#else
50 'use strict';
51 //#endif
52 (function(self){
53 /**
54 Set up our output channel differently depending
55 on whether we are running in a worker thread or
56 the main (UI) thread.
58 let logClass;
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;
68 const hasOpfs = ()=>{
69 return globalThis.FileSystemHandle
70 && globalThis.FileSystemDirectoryHandle
71 && globalThis.FileSystemFileHandle
72 && globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle
73 && navigator?.storage?.getDirectory;
77 const mapToString = (v)=>{
78 switch(typeof v){
79 case 'number': case 'string': case 'boolean':
80 case 'undefined': case 'bigint':
81 return ''+v;
82 default: break;
84 if(null===v) return 'null';
85 if(v instanceof Error){
86 v = {
87 message: v.message,
88 stack: v.stack,
89 errorClass: v.name
92 return JSON.stringify(v,undefined,2);
94 const normalizeArgs = (args)=>args.map(mapToString);
95 if( isUIThread() ){
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');
100 if(cssClass){
101 for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){
102 ln.classList.add(c);
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));
119 cbReverseIt();
120 }else{ /* Worker thread */
121 console.log("Running in a Worker thread.");
122 logClass = function(cssClass,...args){
123 postMessage({
124 type:'log',
125 payload:{cssClass, args: normalizeArgs(args)}
130 const reportFinalTestStatus = function(pass){
131 if(isUIThread()){
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;
136 }else{
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)=>{
154 error(...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.
166 const TestUtil = {
167 /** Running total of the number of tests run via
168 this API. */
169 counter: 0,
171 If expr is a function, it is called and its result
172 is returned, coerced to a bool, else expr, coerced to
173 a bool, is returned.
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){
185 ++this.counter;
186 if(!this.toBool(expr)){
187 throw new Error(msg.length ? msg.join(' ') : "Assertion failed.");
189 return this;
191 /** Calls f() and squelches any exception it throws. If it
192 does not throw, this function throws. */
193 mustThrow: function(f, msg){
194 ++this.counter;
195 let err;
196 try{ f(); } catch(e){err=e;}
197 if(!err) throw new Error(msg || "Expected exception.");
198 return this;
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
209 Error.
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){
215 ++this.counter;
216 let err;
217 try{ f(); } catch(e){err=e;}
218 if(!err) throw new Error(msg || "Expected exception.");
219 let pass = false;
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);
223 if(!pass){
224 throw new Error(msg || ("Filter rejected this exception: "+err.message));
226 return this;
228 /** Throws if expr is truthy or expr is a function and expr()
229 returns truthy. */
230 throwIf: function(expr, msg){
231 ++this.counter;
232 if(this.toBool(expr)) throw new Error(msg || "throwIf() failed");
233 return this;
235 /** Throws if expr is falsy or expr is a function and expr()
236 returns falsy. */
237 throwUnless: function(expr, msg){
238 ++this.counter;
239 if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed");
240 return this;
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;
247 this.name = name;
248 this.predicate = predicate;
249 this.tests = [];
251 TestGroup.prototype = {
252 addTest: function(testObj){
253 this.tests.push(testObj);
254 return this;
256 run: async function(sqlite3){
257 logClass('group-start',"Group #"+this.number+':',this.name);
258 if(this.predicate){
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" );
263 return;
266 const assertCount = TestUtil.counter;
267 const groupState = Object.create(null);
268 const skipped = [];
269 let runtime = 0, i = 0;
270 for(const t of this.tests){
271 ++i;
272 const n = this.number+"."+i;
273 logClass('one-test-line', n+":", t.name);
274 if(t.predicate){
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 );
280 continue;
283 const tc = TestUtil.counter, now = performance.now();
284 let rc = t.test.call(groupState, sqlite3);
285 /*if(rc instanceof Promise){
286 rc = rc.catch((e)=>{
287 error("Test failure:",e);
288 throw e;
291 await rc;
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'],
299 "#"+this.number+":",
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);
307 return TestGroup;
308 })()/*TestGroup*/,
309 testGroups: [],
310 currentTestGroup: undefined,
311 addGroup: function(name, predicate){
312 this.testGroups.push( this.currentTestGroup =
313 new this.TestGroup(name, predicate) );
314 return this;
316 addTest: function(name, callback){
317 let predicate;
318 if(1===arguments.length){
319 this.currentTestGroup.addTest(arguments[0]);
320 }else{
321 this.currentTestGroup.addTest({
322 name, predicate, test: callback
325 return this;
327 runTests: async function(sqlite3){
328 return new Promise(async function(pok,pnok){
329 try {
330 let runtime = 0;
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');
339 pok();
340 reportFinalTestStatus(true);
341 }catch(e){
342 error(e);
343 pnok(e);
344 reportFinalTestStatus(false);
346 }.bind(this));
348 }/*TestUtil*/;
349 const T = TestUtil;
350 T.g = T.addGroup;
351 T.t = T.addTest;
352 let capi, wasm/*assigned after module init*/;
353 const sahPoolConfig = {
354 name: 'opfs-sahpool-tester1',
355 clearOnInit: true,
356 initialCapacity: 6
358 ////////////////////////////////////////////////////////////////////////
359 // End of infrastructure setup. Now define the tests...
360 ////////////////////////////////////////////////////////////////////////
362 ////////////////////////////////////////////////////////////////////
363 T.g('Basic sanity checks')
364 .t({
365 name:'sqlite3_config()',
366 test:function(sqlite3){
367 for(const k of [
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(
376 // not enough args
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
383 if(0){
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 ////////////////////////////////////////////////////////////////////
396 .t({
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);
406 }else{
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');
432 try {
433 throw new sqlite3.WasmAllocError;
434 }catch(e){
435 T.assert(e instanceof Error)
436 .assert(e instanceof sqlite3.WasmAllocError)
437 .assert("Allocation failed." === e.message);
439 try {
440 throw new sqlite3.WasmAllocError("test",{
441 cause: 3
443 }catch(e){
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) }
451 catch(e){
452 T.assert('SQLITE_SCHEMA' === e.message)
453 .assert(capi.SQLITE_SCHEMA === e.resultCode);
455 try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) }
456 catch(e){
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") }
462 catch(e){
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.
482 const w = wasm;
483 const chr = (x)=>x.charCodeAt(0);
484 //log("heap getters...");
486 const li = [8, 16, 32];
487 if(w.bigIntEnabled) li.push(64);
488 for(const n of li){
489 const bpe = n/8;
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).
495 assert(s!==u).
496 assert(w.heapForSize(u.constructor) === u);
500 // alloc(), realloc(), allocFromTypedArray()
502 let m = w.alloc(14);
503 let m2 = w.realloc(m, 16);
504 T.assert(m === m2/* because of alignment */);
505 T.assert(0 === w.realloc(m, 0));
506 m = m2 = 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));
527 w.dealloc(m);
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));
534 w.dealloc(m);
535 T.mustThrowMatching(
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) )
546 .assert(
547 T.eqApprox( 345678.9, w.poke32f(m,345678.9).peek32f(m) )
548 ).assert(
549 T.eqApprox( 4567890123.4, w.poke64f(m, 4567890123.4).peek64f(m) )
551 if(w.bigIntEnabled){
552 T.assert(
553 BigInt(Number.MAX_SAFE_INTEGER) ===
554 w.poke64(m, Number.MAX_SAFE_INTEGER).peek64(m)
557 w.dealloc(m);
560 // isPtr32()
562 const ip = w.isPtr32;
563 T.assert(ip(0))
564 .assert(!ip(-1))
565 .assert(!ip(1.1))
566 .assert(!ip(0xffffffff))
567 .assert(ip(0x7fffffff))
568 .assert(!ip())
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()...");
580 const fillChar = 10;
581 let ua = new Uint8Array(8), rc,
582 refill = ()=>ua.fill(fillChar);
583 refill();
584 rc = w.jstrcpy("hello", ua);
585 T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]);
586 refill();
587 ua[5] = chr('!');
588 rc = w.jstrcpy("HELLO", ua, 0, -1, false);
589 T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]);
590 refill();
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]);
595 refill();
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]);
599 refill();
600 rc = w.jstrcpy("", ua, 0, 1, true);
601 //log("rc,ua",rc,ua);
602 T.assert(1===rc).assert(0===ua[0]);
603 refill();
604 rc = w.jstrcpy("x", ua, 0, 1, true);
605 //log("rc,ua",rc,ua);
606 T.assert(1===rc).assert(0===ua[0]);
607 refill();
608 rc = w.jstrcpy('äbä', ua, 0, 1, true);
609 T.assert(1===rc, 'Must not write partial multi-byte char.')
610 .assert(0===ua[0]);
611 refill();
612 rc = w.jstrcpy('äbä', ua, 0, 2, true);
613 T.assert(1===rc, 'Must not write partial multi-byte char.')
614 .assert(0===ua[0]);
615 refill();
616 rc = w.jstrcpy('äbä', ua, 0, 2, false);
617 T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]);
618 }/*jstrcpy()*/
620 //log("cstrncpy()...");
622 const scope = w.scopedAllocPush();
623 try {
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);
634 T.assert(3===rc).
635 assert("HI!lo" === w.cstrToJs(cpy)).
636 assert(chr('!') === w.peek8(cpy+2)).
637 assert(chr('l') === w.peek8(cpy+3));
638 }finally{
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);
659 T.assert(14 === n)
660 .assert(0===w.peek8(cstr+n))
661 .assert(chr('!')===w.peek8(cstr+n-1));
662 w.dealloc(cstr);
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/);
672 w.alloc = alloc;
673 T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
674 w.dealloc = dealloc;
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();
679 let asc2;
680 try {
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));
699 }finally{
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/);
705 if(asc2){
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);
717 T.assert(12 === n)
718 .assert(0===w.peek8(cstr+n))
719 .assert(chr('d')===w.peek8(cstr+n-1));
721 }/*scopedAlloc()*/
723 //log("xCall()...");
725 const pJson = w.xCall('sqlite3__wasm_enum_json');
726 T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
729 //log("xWrap()...");
731 T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'),
732 /requires 0 arg/).
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/);
737 let rc = fw();
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))
749 .assert(p0 !== 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])));
763 // jsFuncToWasm()
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,...)
777 let fp;
778 try {
779 const fmy = function fmy(i,s,d){
780 if(fmy.debug) log("fmy(",...arguments,")");
781 T.assert( 3 === i )
782 .assert( w.isPtr(s) )
783 .assert( w.cstrToJs(s) === 'a string' )
784 .assert( T.eqApprox(1.2, d) );
785 return w.allocCString("hi");
787 fmy.debug = false;
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 );
793 if(0){
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.
805 fmy.debug = true;
806 fp = wasm.installFunction('i(isd)', fw);
807 fw = w.functionEntry(fp);
808 rc = fw(...fmyArgs);
809 log("rc =",rc);
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);
814 //log("rc =",rc);
815 //T.assert( 'hi' === rc );
817 }finally{
818 wasm.uninstallFunction(fp);
822 if(haveWasmCTests()){
823 if(!sqlite3.config.useStdAlloc){
824 fw = w.xWrap('sqlite3__wasm_test_str_hello', 'utf8:dealloc',['i32']);
825 rc = fw(0);
826 T.assert('hello'===rc);
827 rc = fw(1);
828 T.assert(null===rc);
831 if(w.bigIntEnabled){
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');
835 rc = fw(1);
836 T.assert(12n===rc);
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));
847 }/*xWrap()*/
848 }/*WhWasmUtil*/)
850 ////////////////////////////////////////////////////////////////////
851 .t('sqlite3.StructBinder (jaccwabyt🐇)', function(sqlite3){
852 const S = sqlite3, W = S.wasm;
853 const MyStructDef = {
854 sizeof: 16,
855 members: {
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"}
862 if(W.bigIntEnabled){
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();
872 try {
873 T.assert(k1.constructor === K).
874 assert(K.isA(k1)).
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'));
905 k1.$pP = k2;
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;
910 k1.dispose();
911 T.assert(undefined === k1.pointer).
912 mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
913 }finally{
914 k1.dispose();
915 k2.dispose();
918 if(!W.bigIntEnabled){
919 log("Skipping WasmTestStruct tests: BigInt not enabled.");
920 return;
923 const WTStructDesc =
924 W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0];
925 const autoResolvePtr = true /* EXPERIMENTAL */;
926 if(autoResolvePtr){
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));
933 try{
934 T.assert(wts.constructor === WTStruct).
935 assert(WTStruct.memberKeys().indexOf('$ppV')>=0).
936 assert(wts.memberKeys().indexOf('$v8')>=0).
937 assert(!K.isA(wts)).
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);
944 const testFunc =
945 W.xGet('sqlite3__wasm_test_struct'/*name gets mangled in -O3 builds!*/);
946 let counter = 0;
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);*/
951 ++counter;
952 if(3===counter){
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);
989 wts.$xFunc = 0;
990 wts.$ppV = 0;
991 T.assert(!wts.$ppV);
992 //WTStruct.debugFlags(0x03);
993 wts.$ppV = wts;
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;
1000 wts.dispose();
1001 T.assert(ptr).assert(undefined === wts.pointer);
1002 }finally{
1003 wts.dispose();
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);
1013 s1.dispose();
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.");
1017 }/*StructBinder*/)
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 */);
1025 try{
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)
1031 .mustThrowMatching(
1032 ()=>P.alloc('i33'),
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);
1048 }finally{
1049 P.restore(stack);
1052 T.assert(P.pointer === stack);
1053 try {
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)
1060 }finally{
1061 P.restore(stack);
1064 T.assert(P.pointer === stack);
1065 try {
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);
1075 p1 = P.allocPtr();
1076 T.assert('number'===typeof p1);
1077 }finally{
1078 P.restore(stack);
1080 }/*pstack tests*/)
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;
1087 try{
1088 const n = 520;
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));
1093 let j, check = 0;
1094 const heap = wasm.heap8u();
1095 for(j = 0; j < 10 && 0===check; ++j){
1096 check += heap[p + j];
1098 T.assert(check > 0);
1099 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);
1105 }finally{
1106 wasm.pstack.restore(stack);
1109 .t('To byte array', function(sqlite3){
1110 const ta = new Uint8Array(117);
1111 let i, n = 0;
1112 for(i=0; i<ta.byteLength && 0===n; ++i){
1113 n += ta[i];
1115 T.assert(0===n)
1116 .assert(ta === capi.sqlite3_randomness(ta));
1117 for(i=ta.byteLength-10; i<ta.byteLength && 0===n; ++i){
1118 n += ta[i];
1120 T.assert(n>0);
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 ////////////////////////////////////////////////////////////////////////
1128 T.g('sqlite3.oo1')
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');
1133 db.onclose = {
1134 disposeAfter: [],
1135 disposeBefore: [
1136 (db)=>{
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){
1147 v.dispose();
1148 }else if(v instanceof Function){
1149 try{ v(db) } catch(e){
1150 console.warn("beforeDispose() callback threw:",e);
1155 after: function(){
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){
1161 v.dispose();
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()...
1182 let ex;
1183 try{db.checkRc(rc)}
1184 catch(e){ex = e}
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;
1196 return 0;
1197 }, 0);
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);
1202 T.assert(0===rc);
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");
1206 T.assert(0 === rc);
1207 const stack = wasm.pstack.pointer;
1208 try {
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,
1214 pCur, pHi, 0);
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,
1223 pCur, pHi, 0);
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);
1228 }finally{
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;
1240 let rc;
1241 try {
1242 T.assert(wasm.isPtr(st.pointer))
1243 .mustThrowMatching(()=>st.pointer=1, /read-only/)
1244 .assert(1===this.db.openStatementCount())
1245 .assert(
1246 capi.sqlite3_stmt_status(
1247 st, capi.SQLITE_STMTSTATUS_RUN, 0
1248 ) === 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])
1273 .assert(st._mayGet)
1274 .assert(false===st.step())
1275 .assert(!st._mayGet)
1276 .assert(
1277 capi.sqlite3_stmt_status(
1278 st, capi.SQLITE_STMTSTATUS_RUN, 0
1279 ) > 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));
1287 }finally{
1288 rc = st.finalize();
1290 T.assert(!st.pointer)
1291 .assert(0===this.db.openStatementCount())
1292 .assert(0===rc);
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(){
1303 const db = this.db;
1304 if(1){
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)
1332 .assert(pVfsDb > 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){
1349 const db = this.db;
1350 let list = [];
1351 this.progressHandlerCount = 0;
1352 let rc = db.exec({
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*/],
1358 saveSql: list,
1359 bind: [5,6]
1361 //debug("Exec'd SQL:", list);
1362 T.assert(rc === db)
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));
1371 rc = db.exec({
1372 sql: "INSERT INTO t(a,b) values('blob',X'6869') RETURNING 13",
1373 rowMode: 0
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] );
1387 vals = undefined;
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]);
1392 blob = null;
1393 let counter = 0, colNames = [];
1394 list.length = 0;
1395 db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{
1396 rowMode: 'object',
1397 resultRows: list,
1398 columnNames: colNames,
1399 _myState: 3 /* Accessible from the callback */,
1400 callback: function(row,stmt){
1401 ++counter;
1402 T.assert(
1403 3 === this._myState
1404 /* Recall that "this" is the options object. */
1405 ).assert(
1406 this.columnNames===colNames
1407 ).assert(
1408 this.columnNames[0]==='a' && this.columnNames[1]==='b'
1409 ).assert(
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);
1418 colNames = [];
1419 db.exec({
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]);
1426 list.length = 0;
1427 db.exec("SELECT a a, b b FROM t",{
1428 rowMode: 'array',
1429 callback: function(row,stmt){
1430 ++counter;
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));
1441 counter = 0;
1442 let rv = db.exec({
1443 sql: "SELECT a FROM t",
1444 callback: ()=>(1===++counter),
1446 T.assert(db === rv)
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
1452 had been passed. */
1453 rv = db.exec({
1454 sql: "SELECT -1 UNION ALL SELECT -2 UNION ALL SELECT -3 ORDER BY 1 DESC",
1455 rowMode: 0
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}));
1467 }else{
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'");
1477 try {
1478 T.assert(0===st.columnCount);
1479 const ndx = st.getParamIndex(':b');
1480 T.assert(1===ndx);
1481 st.bindAsBlob(ndx, "ima blob")
1482 /*step() skipped intentionally*/.reset(true);
1483 } finally {
1484 T.assert(0===st.finalize())
1485 .assert(undefined===st.finalize());
1488 try {
1489 db.prepare("/*empty SQL*/");
1490 toss("Must not be reached.");
1491 }catch(e){
1492 T.assert(e instanceof sqlite3.SQLite3Error)
1493 .assert(0==e.message.indexOf('Cannot prepare empty'));
1496 counter = 0;
1497 db.exec({
1498 // Check for https://sqlite.org/forum/forumpost/895425b49a
1499 sql: "pragma table_info('t')",
1500 rowMode: 'object',
1501 callback: function(row){
1502 ++counter;
1503 T.assert(row.name==='a' || row.name==='b');
1506 T.assert(2===counter);
1507 })/*setup table T*/
1509 ////////////////////////////////////////////////////////////////////
1510 .t({
1511 name: "sqlite3_set_authorizer()",
1512 test:function(sqlite3){
1513 T.assert(capi.SQLITE_IGNORE>0)
1514 .assert(capi.SQLITE_DENY>0);
1515 const db = this.db;
1516 const ssa = capi.sqlite3_set_authorizer;
1517 const n = db.selectValue('select count(*) from t');
1518 T.assert(n>0);
1519 let authCount = 0;
1520 let rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
1521 ++authCount;
1522 return capi.SQLITE_IGNORE;
1523 }, 0);
1524 T.assert(0===rc)
1525 .assert(
1526 undefined === db.selectValue('select count(*) from t')
1527 /* Note that the count() never runs, so we get undefined
1528 instead of 0. */
1530 .assert(authCount>0);
1531 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);
1542 authCount = 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){
1546 ++authCount;
1547 return capi.SQLITE_DENY;
1548 }, 0);
1549 T.assert(0===rc);
1550 let err;
1551 try{ db.exec("select 1 from t") }
1552 catch(e){ err = e }
1553 T.assert(err instanceof sqlite3.SQLite3Error)
1554 .assert(err.message.indexOf('not authorized'>0))
1555 .assert(1===authCount);
1556 authCount = 0;
1557 rc = ssa(db, function(...args){
1558 ++authCount;
1559 return capi.SQLITE_OK;
1560 }, 0);
1561 T.assert(0===rc);
1562 T.assert(n === db.selectValue('select count(*) from t'))
1563 .assert(authCount>0);
1564 authCount = 0;
1565 rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
1566 ++authCount;
1567 throw new Error("Testing catching of authorizer.");
1568 }, 0);
1569 T.assert(0===rc);
1570 authCount = 0;
1571 err = undefined;
1572 try{ db.exec("select 1 from t") }
1573 catch(e){err = e}
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);
1579 rc = ssa(db, 0, 0);
1580 authCount = 0;
1581 T.assert(0===rc);
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;
1590 try{
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
1597 T.assert(0===rc)
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))
1603 }finally{
1604 wasm.pstack.restore(stack);
1608 ////////////////////////////////////////////////////////////////////////
1609 .t('selectArray/Object()', function(sqlite3){
1610 const db = this.db;
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)
1619 .assert(5===rc.A)
1620 .assert(6===rc.b);
1621 rc = db.selectArray('select a, b from t where b=-1');
1622 T.assert(undefined === rc);
1624 ////////////////////////////////////////////////////////////////////////
1625 .t('selectArrays/Objects()', function(sqlite3){
1626 const db = this.db;
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 ////////////////////////////////////////////////////////////////////////
1661 .t({
1662 name: 'sqlite3_js_db_export()',
1663 predicate: ()=>true,
1664 test: function(sqlite3){
1665 const db = this.db;
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);
1670 this.dbExport = xp;
1672 }/*sqlite3_js_db_export()*/)
1673 .t({
1674 name: 'sqlite3_js_posix_create_file()',
1675 predicate: ()=>true,
1676 test: function(sqlite3){
1677 const db = this.db;
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');
1682 try {
1683 const sql = "select count(*) from t";
1684 const n = db.selectValue(sql);
1685 T.assert(n>0 && db2.selectValue(sql) === n);
1686 }finally{
1687 db2.close();
1688 sqlite3.util.sqlite3__wasm_vfs_unlink(0, filename);
1691 }/*sqlite3_js_posix_create_file()*/)
1693 ////////////////////////////////////////////////////////////////////
1694 .t({
1695 name:'Scalar UDFs',
1696 test: function(sqlite3){
1697 const db = this.db;
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", {
1704 arity: -1,
1705 xFunc: (pCx,...args)=>{
1706 T.assert(db.pointer === capi.sqlite3_context_db_handle(pCx));
1707 let rc = 0;
1708 for(const v of args) rc += v;
1709 return rc;
1711 }).createFunction({
1712 name: "asis",
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(
1731 "select asis(?1)",
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
1753 T.assert(
1754 sqlite3.capi.SQLITE_FORMAT === rc,
1755 "For invalid eTextRep argument."
1757 rc = sqlite3.capi.sqlite3_create_function_v2(this.db, "foo", 0);
1758 T.assert(
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
1773 T.assert( 0===rc );
1774 rc = capi.sqlite3_create_function_v2(
1775 db, "nary", 1, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0
1777 T.assert( 0===rc );
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 ////////////////////////////////////////////////////////////////////
1805 .t({
1806 name: 'Aggregate UDFs',
1807 //predicate: ()=>false,
1808 test: function(sqlite3){
1809 const db = this.db;
1810 const sjac = capi.sqlite3_js_aggregate_context;
1811 db.createFunction({
1812 name: 'summer',
1813 xStep: (pCtx, n)=>{
1814 const ac = sjac(pCtx, 4);
1815 wasm.poke32(ac, wasm.peek32(ac) + Number(n));
1817 xFinal: (pCtx)=>{
1818 const ac = sjac(pCtx, 0);
1819 return ac ? wasm.peek32(ac) : 0;
1822 let v = db.selectValue([
1823 "with cte(v) as (",
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. */
1829 T.assert(15===v);
1830 T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
1831 /wrong number of arguments/);
1833 db.createFunction({
1834 name: 'summerN',
1835 arity: -1,
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);
1842 xFinal: (pCtx)=>{
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 ////////////////////////////////////////////////////////////////////////
1875 .t({
1876 name: 'Aggregate UDFs (64-bit)',
1877 predicate: ()=>wasm.bigIntEnabled,
1878 //predicate: ()=>false,
1879 test: function(sqlite3){
1880 const db = this.db;
1881 const sjac = capi.sqlite3_js_aggregate_context;
1882 db.createFunction({
1883 name: 'summer64',
1884 xStep: (pCtx, n)=>{
1885 const ac = sjac(pCtx, 8);
1886 wasm.poke64(ac, wasm.peek64(ac) + BigInt(n));
1888 xFinal: (pCtx)=>{
1889 const ac = sjac(pCtx, 0);
1890 return ac ? wasm.peek64(ac) : 0n;
1893 let v = db.selectValue([
1894 "with cte(v) as (",
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 ////////////////////////////////////////////////////////////////////
1903 .t({
1904 name: 'Window UDFs',
1905 //predicate: ()=>false,
1906 test: function(){
1907 /* Example window function, table, and results taken from:
1908 https://sqlite.org/windowfunctions.html#udfwinfunc */
1909 const db = this.db;
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));
1919 db.createFunction({
1920 name: 'winsumint',
1921 xStep: (pCtx, n)=>xStepInverse(pCtx, n),
1922 xInverse: (pCtx, n)=>xStepInverse(pCtx, -n),
1923 xFinal: xValueFinal,
1924 xValue: xValueFinal
1926 db.exec([
1927 "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
1928 "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
1930 let rc = db.exec({
1931 returnValue: 'resultRows',
1932 sql:[
1933 "SELECT x, winsumint(y) OVER (",
1934 "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
1935 ") AS sum_y ",
1936 "FROM twin ORDER BY x;"
1939 T.assert(Array.isArray(rc))
1940 .assert(5 === rc.length);
1941 let count = 0;
1942 for(const row of rc){
1943 switch(++count){
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 = [];
1953 rc = db.exec({
1954 resultRows,
1955 returnValue: 'resultRows',
1956 sql:[
1957 "SELECT x, winsumint(y) OVER (",
1958 "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
1959 ") AS sum_y ",
1960 "FROM twin ORDER BY x;"
1963 T.assert(rc === resultRows)
1964 .assert(5 === rc.length);
1966 rc = db.exec({
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");
1982 }/*window UDFs*/)
1984 ////////////////////////////////////////////////////////////////////
1985 .t("ATTACH", function(){
1986 const db = this.db;
1987 const resultRows = [];
1988 db.exec({
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;"
1995 ].join('')),
1996 rowMode: 0,
1997 resultRows
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;
2009 ++rowCount;
2010 T.assert(2===aVals.length)
2011 .assert(2===aNames.length)
2012 .assert(+(aVals[1]) === 2 * +(aVals[0]));
2013 }, 0, 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.");
2019 }, 0, 0
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,
2029 aVals); */
2030 ++rowCount;
2031 colCount = 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)));
2038 return 0;
2040 try {
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"),
2044 pCb, 0, 0
2046 T.assert(0===rc)
2047 .assert(3===rowCount)
2048 .assert(2===colCount);
2049 }finally{
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");
2057 }, 0, 0
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 ////////////////////////////////////////////////////////////////////
2067 .t({
2068 name: 'C-side WASM tests',
2069 predicate: ()=>(haveWasmCTests() || "Not compiled in."),
2070 test: function(){
2071 const w = wasm, db = this.db;
2072 const stack = w.scopedAllocPush();
2073 let ptrInt;
2074 const origValue = 512;
2075 try{
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');
2094 cf64w(pi64);
2095 T.assert(v64() == BigInt(2 * o64));
2096 cf64w(pi64);
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);
2108 const minMaxI64 = [
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))},
2125 rxRange).
2126 mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))},
2127 (e)=>rxRange.test(e.message));
2128 }else{
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.");
2134 }finally{
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 ////////////////////////////////////////////////////////////////////////
2143 .t({
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),{
2149 A: 0, B: 1
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,
2159 methods: {
2160 xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){
2161 try{
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)"
2171 if(0===rc){
2172 const t = VT.xVtab.create(ppVtab);
2173 T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab)));
2175 return rc;
2176 }catch(e){
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){
2186 try {
2187 VT.xVtab.unget(pVtab).dispose();
2188 return 0;
2189 }catch(e){
2190 return VT.xError('xDisconnect',e);
2193 xOpen: function(pVtab, ppCursor){
2194 try{
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);
2199 c._rowId = 0;
2200 return 0;
2201 }catch(e){
2202 return VT.xError('xOpen',e);
2205 xClose: function(pCursor){
2206 try{
2207 const c = VT.xCursor.unget(pCursor);
2208 T.assert(c instanceof capi.sqlite3_vtab_cursor)
2209 .assert(!VT.xCursor.get(pCursor));
2210 c.dispose();
2211 return 0;
2212 }catch(e){
2213 return VT.xError('xClose',e);
2216 xNext: function(pCursor){
2217 try{
2218 const c = VT.xCursor.get(pCursor);
2219 ++c._rowId;
2220 return 0;
2221 }catch(e){
2222 return VT.xError('xNext',e);
2225 xColumn: function(pCursor, pCtx, iCol){
2226 try{
2227 const c = VT.xCursor.get(pCursor);
2228 switch(iCol){
2229 case tmplCols.A:
2230 capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
2231 break;
2232 case tmplCols.B:
2233 capi.sqlite3_result_int(pCtx, 2000 + c._rowId);
2234 break;
2235 default: sqlite3.SQLite3Error.toss("Invalid column id",iCol);
2237 return 0;
2238 }catch(e){
2239 return VT.xError('xColumn',e);
2242 xRowid: function(pCursor, ppRowid64){
2243 try{
2244 const c = VT.xCursor.get(pCursor);
2245 VT.xRowid(ppRowid64, c._rowId);
2246 return 0;
2247 }catch(e){
2248 return VT.xError('xRowid',e);
2251 xEof: function(pCursor){
2252 const c = VT.xCursor.get(pCursor),
2253 rc = c._rowId>=10;
2254 return rc;
2256 xFilter: function(pCursor, idxNum, idxCStr,
2257 argc, argv/* [sqlite3_value* ...] */){
2258 try{
2259 const c = VT.xCursor.get(pCursor);
2260 c._rowId = 0;
2261 const list = capi.sqlite3_values_to_js(argc, argv);
2262 T.assert(argc === list.length);
2263 //log(argc,"xFilter value(s):",list);
2264 return 0;
2265 }catch(e){
2266 return VT.xError('xFilter',e);
2269 xBestIndex: function(pVtab, pIdxInfo){
2270 try{
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);
2286 v.dispose();
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
2293 v.dispose();
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);
2306 v.dispose();
2309 pii.dispose();
2310 return 0;
2311 }catch(e){
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 ////////////////////////////////////////////////////////////////////////
2340 .t({
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),{
2346 A: 0, B: 1
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. */;
2357 const vtabTrace = 1
2358 ? ()=>{}
2359 : (methodName,...args)=>console.debug('sqlite3_module::'+methodName+'():',...args);
2360 const modConfig = {
2361 /* catchExceptions changes how the methods are wrapped */
2362 catchExceptions: true,
2363 name: "vtab2test",
2364 methods:{
2365 xCreate: function(pDb, pAux, argc, argv, ppVtab, pzErr){
2366 vtabTrace("xCreate",...arguments);
2367 if(throwOnCreate){
2368 sqlite3.SQLite3Error.toss(
2369 throwOnCreate,
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)"
2379 if(0===rc){
2380 const t = VT.xVtab.create(ppVtab);
2381 T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab)));
2382 vtabTrace("xCreate",...arguments," ppVtab =",t.pointer);
2384 return rc;
2386 xConnect: true,
2387 xDestroy: function(pVtab){
2388 vtabTrace("xDestroy/xDisconnect",pVtab);
2389 VT.xVtab.dispose(pVtab);
2391 xDisconnect: true,
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);
2398 c._rowId = 0;
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));
2405 c.dispose();
2407 xNext: function(pCursor){
2408 vtabTrace("xNext",...arguments);
2409 const c = VT.xCursor.get(pCursor);
2410 ++c._rowId;
2412 xColumn: function(pCursor, pCtx, iCol){
2413 vtabTrace("xColumn",...arguments);
2414 const c = VT.xCursor.get(pCursor);
2415 switch(iCol){
2416 case tmplCols.A:
2417 capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
2418 break;
2419 case tmplCols.B:
2420 capi.sqlite3_result_int(pCtx, 2000 + c._rowId);
2421 break;
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);
2438 c._rowId = 0;
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;
2448 pii.dispose();
2450 }/*methods*/
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
2458 this.db.exec([
2459 "create virtual table testvtab2 using ",
2460 modConfig.name,
2461 "(arg1 blah, arg2 bloop)"
2463 if(0){
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...");
2469 db.exec(
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*)
2506 ++collationCounter;
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,
2511 0, myCmp, 0);
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
2532 happen.)
2534 collationCounter = 0;
2535 myCmp = function(pArg,n1,p1,n2,p2){
2536 --collationCounter;
2537 return 0;
2539 rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8,
2540 0, myCmp, 0);
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,
2547 0, null, 0);
2548 this.db.checkRc(rc);
2549 rc = 0;
2550 try {
2551 this.db.selectValue("select 'a' = 'b' collate mycollation");
2552 }catch(e){
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!
2564 this.db.close();
2565 T.assert(!this.db.pointer);
2567 ;/* end of oo1 checks */
2569 ////////////////////////////////////////////////////////////////////////
2570 T.g('kvvfs')
2571 .t({
2572 name: 'kvvfs is disabled in worker',
2573 predicate: ()=>(isWorker() || "test is only valid in a Worker"),
2574 test: function(sqlite3){
2575 T.assert(
2576 !capi.sqlite3_vfs_find('kvvfs'),
2577 "Expecting kvvfs to be unregistered."
2581 .t({
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');
2588 T.assert(pVfs);
2589 const JDb = this.JDb = sqlite3.oo1.JsStorageDb;
2590 const unlink = this.kvvfsUnlink = ()=>JDb.clearStorage(this.kvvfsDbFile);
2591 unlink();
2592 let db = new JDb(filename);
2593 try {
2594 db.exec([
2595 'create table kvvfs(a);',
2596 'insert into kvvfs(a) values(1),(2),(3)'
2598 T.assert(3 === db.selectValue('select count(*) from kvvfs'));
2599 db.close();
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'));
2603 }finally{
2604 db.close();
2607 }/*kvvfs sanity checks*/)
2608 //#if enable-see
2609 .t({
2610 name: 'kvvfs with SEE encryption',
2611 predicate: ()=>(isUIThread()
2612 || "Only available in main thread."),
2613 test: function(sqlite3){
2614 this.kvvfsUnlink();
2615 let db;
2616 const encOpt1 = 1
2617 ? {textkey: 'foo'}
2618 : {key: 'foo'};
2619 const encOpt2 = encOpt1.textkey
2620 ? encOpt1
2621 : {hexkey: new Uint8Array([0x66,0x6f,0x6f]/*==>"foo"*/)}
2622 try{
2623 db = new this.JDb({
2624 filename: this.kvvfsDbFile,
2625 ...encOpt1
2627 db.exec([
2628 "create table t(a,b);",
2629 "insert into t(a,b) values(1,2),(3,4)"
2631 db.close();
2632 let err;
2633 try{
2634 db = new this.JDb({
2635 filename: this.kvvfsDbFile,
2636 flags: 'ct'
2638 T.assert(db) /* opening is fine, but... */;
2639 db.exec("select 1 from sqlite_schema");
2640 console.warn("sessionStorage =",sessionStorage);
2641 }catch(e){
2642 err = e;
2643 }finally{
2644 db.close();
2646 T.assert(err,"Expecting an exception")
2647 .assert(sqlite3.capi.SQLITE_NOTADB==err.resultCode,
2648 "Expecting NOTADB");
2649 db = new sqlite3.oo1.DB({
2650 filename: this.kvvfsDbFile,
2651 vfs: 'kvvfs',
2652 ...encOpt2
2654 T.assert( 4===db.selectValue('select sum(a) from t') );
2655 }finally{
2656 if( db ) db.close();
2657 this.kvvfsUnlink();
2660 })/*kvvfs with SEE*/
2661 //#endif enable-see
2662 ;/* end kvvfs tests */
2664 ////////////////////////////////////////////////////////////////////////
2665 T.g('Hook APIs')
2666 .t({
2667 name: "sqlite3_commit/rollback/update_hook()",
2668 predicate: ()=>wasm.bigIntEnabled || "Update hook requires int64",
2669 test: function(sqlite3){
2670 let countCommit = 0, countRollback = 0;;
2671 const db = new sqlite3.oo1.DB(':memory:',1 ? 'c' : 'ct');
2672 let rc = capi.sqlite3_commit_hook(db, (p)=>{
2673 ++countCommit;
2674 return (1 === p) ? 0 : capi.SQLITE_ERROR;
2675 }, 1);
2676 T.assert( 0 === rc /*void pointer*/ );
2678 // Commit hook...
2679 T.assert( 0!=capi.sqlite3_get_autocommit(db) );
2680 db.exec("BEGIN; SELECT 1; COMMIT");
2681 T.assert(0 === countCommit,
2682 "No-op transactions (mostly) do not trigger commit hook.");
2683 db.exec("BEGIN EXCLUSIVE; SELECT 1; COMMIT");
2684 T.assert(1 === countCommit,
2685 "But EXCLUSIVE transactions do.");
2686 db.transaction((d)=>{
2687 T.assert( 0==capi.sqlite3_get_autocommit(db) );
2688 d.exec("create table t(a)");
2690 T.assert(2 === countCommit);
2692 // Rollback hook:
2693 rc = capi.sqlite3_rollback_hook(db, (p)=>{
2694 ++countRollback;
2695 T.assert( 2 === p );
2696 }, 2);
2697 T.assert( 0 === rc /*void pointer*/ );
2698 T.mustThrowMatching(()=>{
2699 db.transaction('drop table t',()=>{})
2700 }, (e)=>{
2701 return (capi.SQLITE_MISUSE === e.resultCode)
2702 && ( e.message.indexOf('Invalid argument') > 0 );
2704 T.assert(0 === countRollback, "Transaction was not started.");
2705 T.mustThrowMatching(()=>{
2706 db.transaction('immediate', ()=>{
2707 sqlite3.SQLite3Error.toss(capi.SQLITE_FULL,'testing rollback hook');
2709 }, (e)=>{
2710 return capi.SQLITE_FULL === e.resultCode
2712 T.assert(1 === countRollback);
2714 // Update hook...
2715 const countUpdate = Object.create(null);
2716 capi.sqlite3_update_hook(db, (p,op,dbName,tbl,rowid)=>{
2717 T.assert('main' === dbName.toLowerCase())
2718 .assert('t' === tbl.toLowerCase())
2719 .assert(3===p)
2720 .assert('bigint' === typeof rowid);
2721 switch(op){
2722 case capi.SQLITE_INSERT:
2723 case capi.SQLITE_UPDATE:
2724 case capi.SQLITE_DELETE:
2725 countUpdate[op] = (countUpdate[op]||0) + 1;
2726 break;
2727 default: toss("Unexpected hook operator:",op);
2729 }, 3);
2730 db.transaction((d)=>{
2731 d.exec([
2732 "insert into t(a) values(1);",
2733 "update t set a=2;",
2734 "update t set a=3;",
2735 "delete from t where a=3"
2736 // update hook is not called for an unqualified DELETE
2739 T.assert(1 === countRollback)
2740 .assert(3 === countCommit)
2741 .assert(1 === countUpdate[capi.SQLITE_INSERT])
2742 .assert(2 === countUpdate[capi.SQLITE_UPDATE])
2743 .assert(1 === countUpdate[capi.SQLITE_DELETE]);
2744 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
2745 T.assert(1 === capi.sqlite3_commit_hook(db, 0, 0));
2746 T.assert(2 === capi.sqlite3_rollback_hook(db, 0, 0));
2747 T.assert(3 === capi.sqlite3_update_hook(db, 0, 0));
2748 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
2749 db.close();
2751 })/* commit/rollback/update hooks */
2752 .t({
2753 name: "sqlite3_preupdate_hook()",
2754 predicate: ()=>wasm.bigIntEnabled || "Pre-update hook requires int64",
2755 test: function(sqlite3){
2756 const db = new sqlite3.oo1.DB(':memory:', 1 ? 'c' : 'ct');
2757 const countHook = Object.create(null);
2758 let rc = capi.sqlite3_preupdate_hook(
2759 db, function(p, pDb, op, zDb, zTbl, iKey1, iKey2){
2760 T.assert(9 === p)
2761 .assert(db.pointer === pDb)
2762 .assert(1 === capi.sqlite3_preupdate_count(pDb))
2763 .assert( 0 > capi.sqlite3_preupdate_blobwrite(pDb) );
2764 countHook[op] = (countHook[op]||0) + 1;
2765 switch(op){
2766 case capi.SQLITE_INSERT:
2767 case capi.SQLITE_UPDATE:
2768 T.assert('number' === typeof capi.sqlite3_preupdate_new_js(pDb, 0));
2769 break;
2770 case capi.SQLITE_DELETE:
2771 T.assert('number' === typeof capi.sqlite3_preupdate_old_js(pDb, 0));
2772 break;
2773 default: toss("Unexpected hook operator:",op);
2778 db.transaction((d)=>{
2779 d.exec([
2780 "create table t(a);",
2781 "insert into t(a) values(1);",
2782 "update t set a=2;",
2783 "update t set a=3;",
2784 "delete from t where a=3"
2787 T.assert(1 === countHook[capi.SQLITE_INSERT])
2788 .assert(2 === countHook[capi.SQLITE_UPDATE])
2789 .assert(1 === countHook[capi.SQLITE_DELETE]);
2790 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
2791 db.close();
2792 //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
2794 })/*pre-update hooks*/
2795 ;/*end hook API tests*/
2797 ////////////////////////////////////////////////////////////////////////
2798 T.g('Auto-extension API')
2799 .t({
2800 name: "Auto-extension sanity checks.",
2801 test: function(sqlite3){
2802 let counter = 0;
2803 const fp = wasm.installFunction('i(ppp)', function(pDb,pzErr,pApi){
2804 ++counter;
2805 return 0;
2807 (new sqlite3.oo1.DB()).close();
2808 T.assert( 0===counter );
2809 capi.sqlite3_auto_extension(fp);
2810 (new sqlite3.oo1.DB()).close();
2811 T.assert( 1===counter );
2812 (new sqlite3.oo1.DB()).close();
2813 T.assert( 2===counter );
2814 capi.sqlite3_cancel_auto_extension(fp);
2815 wasm.uninstallFunction(fp);
2816 (new sqlite3.oo1.DB()).close();
2817 T.assert( 2===counter );
2821 ////////////////////////////////////////////////////////////////////////
2822 T.g('Session API')
2823 .t({
2824 name: 'Session API sanity checks',
2825 predicate: ()=>!!capi.sqlite3changegroup_add,
2826 test: function(sqlite3){
2827 warn("The session API tests could use some expansion.");
2828 const db1 = new sqlite3.oo1.DB(), db2 = new sqlite3.oo1.DB();
2829 const sqlInit = [
2830 "create table t(rowid INTEGER PRIMARY KEY,a,b); ",
2831 "insert into t(rowid,a,b) values",
2832 "(1,'a1','b1'),",
2833 "(2,'a2','b2'),",
2834 "(3,'a3','b3');"
2835 ].join('');
2836 db1.exec(sqlInit);
2837 db2.exec(sqlInit);
2838 T.assert(3 === db1.selectValue("select count(*) from t"))
2839 .assert('b3' === db1.selectValue('select b from t where rowid=3'));
2840 const stackPtr = wasm.pstack.pointer;
2841 try{
2842 let ppOut = wasm.pstack.allocPtr();
2843 let rc = capi.sqlite3session_create(db1, "main", ppOut);
2844 T.assert(0===rc);
2845 let pSession = wasm.peekPtr(ppOut);
2846 T.assert(pSession && wasm.isPtr(pSession));
2847 capi.sqlite3session_table_filter(pSession, (pCtx, tbl)=>{
2848 T.assert('t' === tbl).assert( 99 === pCtx );
2849 return 1;
2850 }, 99);
2851 db1.exec([
2852 "update t set b='bTwo' where rowid=2;",
2853 "update t set a='aThree' where rowid=3;",
2854 "delete from t where rowid=1;",
2855 "insert into t(rowid,a,b) values(4,'a4','b4')"
2857 T.assert('bTwo' === db1.selectValue("select b from t where rowid=2"))
2858 .assert(undefined === db1.selectValue('select a from t where rowid=1'))
2859 .assert('b4' === db1.selectValue('select b from t where rowid=4'))
2860 .assert(3 === db1.selectValue('select count(*) from t'));
2862 const testSessionEnable = false;
2863 if(testSessionEnable){
2864 rc = capi.sqlite3session_enable(pSession, 0);
2865 T.assert( 0 === rc )
2866 .assert( 0 === capi.sqlite3session_enable(pSession, -1) );
2867 db1.exec("delete from t where rowid=2;");
2868 rc = capi.sqlite3session_enable(pSession, 1);
2869 T.assert( rc > 0 )
2870 .assert( capi.sqlite3session_enable(pSession, -1) > 0 )
2871 .assert(undefined === db1.selectValue('select a from t where rowid=2'));
2872 }else{
2873 warn("sqlite3session_enable() tests are currently disabled.");
2875 let db1Count = db1.selectValue("select count(*) from t");
2876 T.assert( db1Count === (testSessionEnable ? 2 : 3) );
2878 /* Capture changeset and destroy session. */
2879 let pnChanges = wasm.pstack.alloc('i32'),
2880 ppChanges = wasm.pstack.allocPtr();
2881 rc = capi.sqlite3session_changeset(pSession, pnChanges, ppChanges);
2882 T.assert( 0 === rc );
2883 capi.sqlite3session_delete(pSession);
2884 pSession = 0;
2885 const pChanges = wasm.peekPtr(ppChanges),
2886 nChanges = wasm.peek32(pnChanges);
2887 T.assert( pChanges && wasm.isPtr( pChanges ) )
2888 .assert( nChanges > 0 );
2890 /* Revert db1 via an inverted changeset, but keep pChanges
2891 and nChanges for application to db2. */
2892 rc = capi.sqlite3changeset_invert( nChanges, pChanges, pnChanges, ppChanges );
2893 T.assert( 0 === rc );
2894 rc = capi.sqlite3changeset_apply(
2895 db1, wasm.peek32(pnChanges), wasm.peekPtr(ppChanges), 0, (pCtx, eConflict, pIter)=>{
2896 return 1;
2897 }, 0
2899 T.assert( 0 === rc );
2900 wasm.dealloc( wasm.peekPtr(ppChanges) );
2901 pnChanges = ppChanges = 0;
2902 T.assert('b2' === db1.selectValue("select b from t where rowid=2"))
2903 .assert('a1' === db1.selectValue('select a from t where rowid=1'))
2904 .assert(undefined === db1.selectValue('select b from t where rowid=4'));
2905 db1Count = db1.selectValue("select count(*) from t");
2906 T.assert(3 === db1Count);
2908 /* Apply pre-reverted changeset (pChanges, nChanges) to
2909 db2... */
2910 rc = capi.sqlite3changeset_apply(
2911 db2, nChanges, pChanges, 0, (pCtx, eConflict, pIter)=>{
2912 return pCtx ? 1 : 0
2913 }, 1
2915 wasm.dealloc( pChanges );
2916 T.assert( 0 === rc )
2917 .assert( 'b4' === db2.selectValue('select b from t where rowid=4') )
2918 .assert( 'aThree' === db2.selectValue('select a from t where rowid=3') )
2919 .assert( undefined === db2.selectValue('select b from t where rowid=1') );
2920 if(testSessionEnable){
2921 T.assert( (undefined === db2.selectValue('select b from t where rowid=2')),
2922 "But... the session was disabled when rowid=2 was deleted?" );
2923 log("rowids from db2.t:",db2.selectValues('select rowid from t order by rowid'));
2924 T.assert( 3 === db2.selectValue('select count(*) from t') );
2925 }else{
2926 T.assert( 'bTwo' === db2.selectValue('select b from t where rowid=2') )
2927 .assert( 3 === db2.selectValue('select count(*) from t') );
2929 }finally{
2930 wasm.pstack.restore(stackPtr);
2931 db1.close();
2932 db2.close();
2935 })/*session API sanity tests*/
2936 ;/*end of session API group*/;
2938 ////////////////////////////////////////////////////////////////////////
2939 T.g('OPFS: Origin-Private File System',
2940 (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs")
2941 || 'requires "opfs" VFS'))
2942 .t({
2943 name: 'OPFS db sanity checks',
2944 test: async function(sqlite3){
2945 T.assert(capi.sqlite3_vfs_find('opfs'));
2946 const opfs = sqlite3.opfs;
2947 const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db';
2948 const fileUri = 'file://'+filename+'?delete-before-open=1';
2949 const initSql = [
2950 'create table p(a);',
2951 'insert into p(a) values(1),(2),(3)'
2953 let db = new sqlite3.oo1.OpfsDb(fileUri);
2954 try {
2955 db.exec(initSql);
2956 T.assert(3 === db.selectValue('select count(*) from p'));
2957 db.close();
2958 db = new sqlite3.oo1.OpfsDb(filename);
2959 db.exec('insert into p(a) values(4),(5),(6)');
2960 T.assert(6 === db.selectValue('select count(*) from p'));
2961 this.opfsDbExport = capi.sqlite3_js_db_export(db);
2962 T.assert(this.opfsDbExport instanceof Uint8Array)
2963 .assert(this.opfsDbExport.byteLength>0
2964 && 0===this.opfsDbExport.byteLength % 512);
2965 }finally{
2966 db.close();
2968 T.assert(await opfs.entryExists(filename));
2969 try {
2970 db = new sqlite3.oo1.OpfsDb(fileUri);
2971 db.exec(initSql) /* will throw if delete-before-open did not work */;
2972 T.assert(3 === db.selectValue('select count(*) from p'));
2973 }finally{
2974 if(db) db.close();
2977 }/*OPFS db sanity checks*/)
2978 .t({
2979 name: 'OPFS import',
2980 test: async function(sqlite3){
2981 let db;
2982 const filename = this.opfsDbFile;
2983 try {
2984 const exp = this.opfsDbExport;
2985 delete this.opfsDbExport;
2986 this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp);
2987 db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
2988 T.assert(6 === db.selectValue('select count(*) from p')).
2989 assert( this.opfsImportSize == exp.byteLength );
2990 db.close();
2991 const unlink = this.opfsUnlink =
2992 (fn=filename)=>sqlite3.util.sqlite3__wasm_vfs_unlink("opfs",fn);
2993 this.opfsUnlink(filename);
2994 T.assert(!(await sqlite3.opfs.entryExists(filename)));
2995 // Try again with a function as an input source:
2996 let cursor = 0;
2997 const blockSize = 512, end = exp.byteLength;
2998 const reader = async function(){
2999 if(cursor >= exp.byteLength){
3000 return undefined;
3002 const rv = exp.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
3003 cursor += blockSize;
3004 return rv;
3006 this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, reader);
3007 db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
3008 T.assert(6 === db.selectValue('select count(*) from p')).
3009 assert( this.opfsImportSize == exp.byteLength );
3010 }finally{
3011 if(db) db.close();
3014 }/*OPFS export/import*/)
3015 .t({
3016 name: '(Internal-use) OPFS utility APIs',
3017 test: async function(sqlite3){
3018 const filename = this.opfsDbFile;
3019 const unlink = this.opfsUnlink;
3020 T.assert(filename && !!unlink);
3021 delete this.opfsDbFile;
3022 delete this.opfsUnlink;
3023 /**************************************************************
3024 ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended
3025 for client-side use. It is only for this project's own
3026 internal use. Its APIs are subject to change or removal at
3027 any time.
3028 ***************************************************************/
3029 const opfs = sqlite3.opfs;
3030 const fSize = this.opfsImportSize;
3031 delete this.opfsImportSize;
3032 let sh;
3033 try{
3034 T.assert(await opfs.entryExists(filename));
3035 const [dirHandle, filenamePart] = await opfs.getDirForFilename(filename, false);
3036 const fh = await dirHandle.getFileHandle(filenamePart);
3037 sh = await fh.createSyncAccessHandle();
3038 T.assert(fSize === await sh.getSize());
3039 await sh.close();
3040 sh = undefined;
3041 unlink();
3042 T.assert(!(await opfs.entryExists(filename)));
3043 }finally{
3044 if(sh) await sh.close();
3045 unlink();
3048 // Some sanity checks of the opfs utility functions...
3049 const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
3050 const aDir = testDir+'/test/dir';
3051 T.assert(await opfs.mkdir(aDir), "mkdir failed")
3052 .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
3053 .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
3054 .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
3055 .assert(!(await opfs.unlink(testDir+'/test/dir')),
3056 "delete 2b should have failed (dir already deleted)")
3057 .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
3058 .assert(!(await opfs.entryExists(testDir)),
3059 "entryExists(",testDir,") should have failed");
3061 }/*OPFS util sanity checks*/)
3062 ;/* end OPFS tests */
3064 ////////////////////////////////////////////////////////////////////////
3065 T.g('OPFS SyncAccessHandle Pool VFS',
3066 (sqlite3)=>(hasOpfs() || "requires OPFS APIs"))
3067 .t({
3068 name: 'SAH sanity checks',
3069 test: async function(sqlite3){
3070 T.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
3071 .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) < 0)
3072 const inst = sqlite3.installOpfsSAHPoolVfs,
3073 catcher = (e)=>{
3074 error("Cannot load SAH pool VFS.",
3075 "This might not be a problem,",
3076 "depending on the environment.");
3077 return false;
3079 let u1, u2;
3080 // Ensure that two immediately-consecutive installations
3081 // resolve to the same Promise instead of triggering
3082 // a locking error.
3083 const P1 = inst(sahPoolConfig).then(u=>u1 = u).catch(catcher),
3084 P2 = inst(sahPoolConfig).then(u=>u2 = u).catch(catcher);
3085 await Promise.all([P1, P2]);
3086 if(!(await P1)) return;
3087 T.assert(u1 === u2)
3088 .assert(sahPoolConfig.name === u1.vfsName)
3089 .assert(sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
3090 .assert(u1.getCapacity() >= sahPoolConfig.initialCapacity
3091 /* If a test fails before we get to nuke the VFS, we
3092 can have more than the initial capacity on the next
3093 run. */)
3094 .assert(u1.getCapacity() + 2 === (await u2.addCapacity(2)))
3095 .assert(2 === (await u2.reduceCapacity(2)))
3096 .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) >= 0);
3098 T.assert(0 === u1.getFileCount());
3099 const dbName = '/foo.db';
3100 let db = new u1.OpfsSAHPoolDb(dbName);
3101 T.assert(db instanceof sqlite3.oo1.DB)
3102 .assert(1 === u1.getFileCount());
3103 db.exec([
3104 'create table t(a);',
3105 'insert into t(a) values(1),(2),(3)'
3107 T.assert(1 === u1.getFileCount());
3108 T.assert(3 === db.selectValue('select count(*) from t'));
3109 db.close();
3110 T.assert(1 === u1.getFileCount());
3111 db = new u2.OpfsSAHPoolDb(dbName);
3112 T.assert(1 === u1.getFileCount());
3113 db.close();
3114 const fileNames = u1.getFileNames();
3115 T.assert(1 === fileNames.length)
3116 .assert(dbName === fileNames[0])
3117 .assert(1 === u1.getFileCount())
3119 if(1){ // test exportFile() and importDb()
3120 const dbytes = u1.exportFile(dbName);
3121 T.assert(dbytes.length >= 4096);
3122 const dbName2 = '/exported.db';
3123 let nWrote = u1.importDb(dbName2, dbytes);
3124 T.assert( 2 == u1.getFileCount() )
3125 .assert( dbytes.byteLength == nWrote );
3126 let db2 = new u1.OpfsSAHPoolDb(dbName2);
3127 T.assert(db2 instanceof sqlite3.oo1.DB)
3128 .assert(3 === db2.selectValue('select count(*) from t'));
3129 db2.close();
3130 T.assert(true === u1.unlink(dbName2))
3131 .assert(false === u1.unlink(dbName2))
3132 .assert(1 === u1.getFileCount())
3133 .assert(1 === u1.getFileNames().length);
3134 // Try again with a function as an input source:
3135 let cursor = 0;
3136 const blockSize = 1024, end = dbytes.byteLength;
3137 const reader = async function(){
3138 if(cursor >= dbytes.byteLength){
3139 return undefined;
3141 const rv = dbytes.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
3142 cursor += blockSize;
3143 return rv;
3145 nWrote = await u1.importDb(dbName2, reader);
3146 T.assert( 2 == u1.getFileCount() );
3147 db2 = new u1.OpfsSAHPoolDb(dbName2);
3148 T.assert(db2 instanceof sqlite3.oo1.DB)
3149 .assert(3 === db2.selectValue('select count(*) from t'));
3150 db2.close();
3151 T.assert(true === u1.unlink(dbName2))
3152 .assert(dbytes.byteLength == nWrote);
3155 T.assert(true === u1.unlink(dbName))
3156 .assert(false === u1.unlink(dbName))
3157 .assert(0 === u1.getFileCount())
3158 .assert(0 === u1.getFileNames().length);
3160 // Demonstrate that two SAH pools can coexist so long as
3161 // they have different names.
3162 const conf2 = JSON.parse(JSON.stringify(sahPoolConfig));
3163 conf2.name += '-test2';
3164 const POther = await inst(conf2);
3165 //log("Installed second SAH instance as",conf2.name);
3166 T.assert(0 === POther.getFileCount())
3167 .assert(true === await POther.removeVfs());
3169 if(0){
3170 /* Enable this block to inspect vfs's contents via the dev
3171 console or OPFS Explorer browser extension. The
3172 following bits will remove them. */
3173 return;
3175 T.assert(true === await u2.removeVfs())
3176 .assert(false === await u1.removeVfs())
3177 .assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name));
3179 let cErr, u3;
3180 conf2.$testThrowInInit = new Error("Testing throwing during init.");
3181 conf2.name = sahPoolConfig.name+'-err';
3182 const P3 = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e);
3183 T.assert(P3 === conf2.$testThrowInInit)
3184 .assert(cErr === P3)
3185 .assert(undefined === u3)
3186 .assert(!sqlite3.capi.sqlite3_vfs_find(conf2.name));
3188 }/*OPFS SAH Pool sanity checks*/)
3190 ////////////////////////////////////////////////////////////////////////
3191 T.g('Bug Reports')
3192 .t({
3193 name: 'Delete via bound parameter in subquery',
3194 test: function(sqlite3){
3195 // Testing https://sqlite.org/forum/forumpost/40ce55bdf5
3196 // with the exception that that post uses "external content"
3197 // for the FTS index.
3198 const db = new sqlite3.oo1.DB();//(':memory:','wt');
3199 db.exec([
3200 "create virtual table f using fts5 (path);",
3201 "insert into f(path) values('abc'),('def'),('ghi');"
3203 const fetchEm = ()=> db.exec({
3204 sql: "SELECT * FROM f order by path",
3205 rowMode: 'array'
3207 const dump = function(lbl){
3208 let rc = fetchEm();
3209 log((lbl ? (lbl+' results') : ''),rc);
3211 //dump('Full fts table');
3212 let rc = fetchEm();
3213 T.assert(3===rc.length);
3214 db.exec(`
3215 delete from f where rowid in (
3216 select rowid from f where path = :path
3218 {bind: {":path": "def"}}
3220 //dump('After deleting one entry via subquery');
3221 rc = fetchEm();
3222 T.assert(2===rc.length)
3223 .assert('abcghi'===rc.join(''));
3224 //log('rc =',rc);
3225 db.close();
3228 ;/*end of Bug Reports group*/;
3230 ////////////////////////////////////////////////////////////////////////
3231 log("Loading and initializing sqlite3 WASM module...");
3232 if(0){
3233 globalThis.sqlite3ApiConfig = {
3234 debug: ()=>{},
3235 log: ()=>{},
3236 warn: ()=>{},
3237 error: ()=>{}
3240 //#ifnot target=es6-module
3241 if(!globalThis.sqlite3InitModule && !isUIThread()){
3242 /* Vanilla worker, as opposed to an ES6 module worker */
3244 If sqlite3.js is in a directory other than this script, in order
3245 to get sqlite3.js to resolve sqlite3.wasm properly, we have to
3246 explicitly tell it where sqlite3.js is being loaded from. We do
3247 that by passing the `sqlite3.dir=theDirName` URL argument to
3248 _this_ script. That URL argument will be seen by the JS/WASM
3249 loader and it will adjust the sqlite3.wasm path accordingly. If
3250 sqlite3.js/.wasm are in the same directory as this script then
3251 that's not needed.
3253 URL arguments passed as part of the filename via importScripts()
3254 are simply lost, and such scripts see the globalThis.location of
3255 _this_ script.
3257 let sqlite3Js = 'sqlite3.js';
3258 const urlParams = new URL(globalThis.location.href).searchParams;
3259 if(urlParams.has('sqlite3.dir')){
3260 sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
3262 importScripts(sqlite3Js);
3264 //#endif
3265 globalThis.sqlite3InitModule.__isUnderTest =
3266 true /* disables certain API-internal cleanup so that we can
3267 test internal APIs from here */;
3268 globalThis.sqlite3InitModule({
3269 print: log,
3270 printErr: error
3271 }).then(async function(sqlite3){
3272 TestUtil.assert(!!sqlite3.util);
3273 log("Done initializing WASM/JS bits. Running tests...");
3274 sqlite3.config.warn("Installing sqlite3 bits as global S for local dev/test purposes.");
3275 globalThis.S = sqlite3;
3276 /*await sqlite3.installOpfsSAHPoolVfs(sahPoolConfig)
3277 .then((u)=>log("Loaded",u.vfsName,"VFS"))
3278 .catch(e=>{
3279 log("Cannot install OpfsSAHPool.",e);
3280 });*/
3281 capi = sqlite3.capi;
3282 wasm = sqlite3.wasm;
3283 log("sqlite3 version:",capi.sqlite3_libversion(),
3284 capi.sqlite3_sourceid());
3285 if(wasm.bigIntEnabled){
3286 log("BigInt/int64 support is enabled.");
3287 }else{
3288 logClass('warning',"BigInt/int64 support is disabled.");
3290 if(haveWasmCTests()){
3291 log("sqlite3__wasm_test_...() APIs are available.");
3292 }else{
3293 logClass('warning',"sqlite3__wasm_test_...() APIs unavailable.");
3295 log("registered vfs list =",capi.sqlite3_js_vfs_list().join(', '));
3296 TestUtil.runTests(sqlite3);
3298 })(self);