Add tests for the new code on this branch.
[sqlite.git] / ext / wasm / tester1.c-pp.js
blobf8a0225234d39a3df485037f9baafe695b62e1a2
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.
57   */
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;
67   };
68   const hasOpfs = ()=>{
69     return globalThis.FileSystemHandle
70       && globalThis.FileSystemDirectoryHandle
71       && globalThis.FileSystemFileHandle
72       && globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle
73       && navigator?.storage?.getDirectory;
74   };
76   {
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;
83       }
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
90         };
91       }
92       return JSON.stringify(v,undefined,2);
93     };
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);
103           }
104         }
105         ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
106         logTarget.append(ln);
107       };
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);
114       };
115       cbReverse.addEventListener('change', cbReverseIt, true);
116       /*if(localStorage.getItem(cbReverseKey)){
117         cbReverse.checked = !!(+localStorage.getItem(cbReverseKey));
118       }*/
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)}
126         });
127       };
128     }
129   }
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}});
138     }
139   };
140   const log = (...args)=>{
141     //console.log(...args);
142     logClass('',...args);
143   }
144   const warn = (...args)=>{
145     console.warn(...args);
146     logClass('warning',...args);
147   }
148   const error = (...args)=>{
149     console.error(...args);
150     logClass('error',...args);
151   };
153   const toss = (...args)=>{
154     error(...args);
155     throw new Error(args.join(' '));
156   };
157   const tossQuietly = (...args)=>{
158     throw new Error(args.join(' '));
159   };
161   const roundMs = (ms)=>Math.round(ms*100)/100;
163   /**
164      Helpers for writing sqlite3-specific tests.
165   */
166   const TestUtil = {
167     /** Running total of the number of tests run via
168         this API. */
169     counter: 0,
170     /**
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.
174     */
175     toBool: function(expr){
176       return (expr instanceof Function) ? !!expr() : !!expr;
177     },
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.
183     */
184     assert: function f(expr, ...msg){
185       ++this.counter;
186       if(!this.toBool(expr)){
187         throw new Error(msg.length ? msg.join(' ') : "Assertion failed.");
188       }
189       return this;
190     },
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;
199     },
200     /**
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.
213     */
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));
225       }
226       return this;
227     },
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;
234     },
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;
241     },
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 = [];
250       };
251       TestGroup.prototype = {
252         addTest: function(testObj){
253           this.tests.push(testObj);
254           return this;
255         },
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;
264             }
265           }
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;
281               }
282             }
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;
289               });
290             }*/
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');
297           }
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);
304           }
305         }
306       };
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;
315     },
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
323         });
324       }
325       return this;
326     },
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;
335           }
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);
345         }
346       }.bind(this));
347     }
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
357   };
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'
369         ]){
370           T.assert(capi[k] > 0);
371         }
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
378         ));
379         T.assert(capi.SQLITE_NOTFOUND === capi.sqlite3_config(
380           // unhandled-in-JS config option
381           capi.SQLITE_CONFIG_GETMALLOC, 1
382         ));
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
390           ));
391         }
392       }
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);
410         }
411       }
412     })
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);
438       }
439       try {
440         throw new sqlite3.WasmAllocError("test",{
441           cause: 3
442         });
443       }catch(e){
444         T.assert(3 === e.cause)
445           .assert("test" === e.message);
446       }
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);
454       }
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);
460       }
461       try{ sqlite3.SQLite3Error.toss("resultCode check") }
462       catch(e){
463         T.assert(capi.SQLITE_ERROR === e.resultCode)
464           .assert('resultCode check' === e.message);        
465       }
466     })
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));
473     })
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...");
485       {
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);
497         }
498       }
500       // alloc(), realloc(), allocFromTypedArray()
501       {
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));
517         }
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));
526         }
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));
532         }
534         w.dealloc(m);
535         T.mustThrowMatching(
536           ()=>w.allocFromTypedArray(1),
537           'Value is not of a supported TypedArray type.'
538         );
539       }
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) )
550           );
551         if(w.bigIntEnabled){
552           T.assert(
553             BigInt(Number.MAX_SAFE_INTEGER) ===
554               w.poke64(m, Number.MAX_SAFE_INTEGER).peek64(m)
555           );
556         }
557         w.dealloc(m);
558       }
560       // isPtr32()
561       {
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*/)
570         ;
571       }
573       //log("jstrlen()...");
574       {
575         T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc"));
576       }
578       //log("jstrcpy()...");
579       {
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()...");
621       {
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);
640         }
641       }
643       //log("jstrToUintArray()...");
644       {
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]);
653       }
655       //log("allocCString()...");
656       {
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);
663       }
665       //log("scopedAlloc() and friends...");
666       {
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/);
711           }
712         }
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));
720         });
721       }/*scopedAlloc()*/
723       //log("xCall()...");
724       {
725         const pJson = w.xCall('sqlite3__wasm_enum_json');
726         T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
727       }
729       //log("xWrap()...");
730       {
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'));
752         }
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])));
761         });
763         // jsFuncToWasm()
764         {
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');
773         }
775         // xWrap(Function,...)
776         {
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");
786             };
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.
804               */
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 );
816             }
817           }finally{
818             wasm.uninstallFunction(fp);
819           }
820         }
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);
829           }
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));
844             });
845           }
846         }
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"}
860         }
861       };
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;
866       }
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());
889         });
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);
898         }
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();
916       }
918       if(!W.bigIntEnabled){
919         log("Skipping WasmTestStruct tests: BigInt not enabled.");
920         return;
921       }
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';
928       }
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.");
954           }
955         }
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/)
980         ;
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();
1004       }
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.");
1016       }
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
1034           );
1035         ;
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);
1050       }
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);
1062       }
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);
1079       }
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];
1097         }
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];
1103         }
1104         T.assert(0===check);
1105       }finally{
1106         wasm.pstack.restore(stack);
1107       }
1108     })
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];
1114       }
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];
1119       }
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");
1124     })
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);
1139           }
1140         ],
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);
1151               }
1152             }
1153           }
1154         },
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*/}
1164             }
1165           }
1166         }
1167       };
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);
1198     })
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);
1227         }
1228       }finally{
1229         wasm.pstack.restore(stack);
1230       }
1231     })
1233   ////////////////////////////////////////////////////////////////////
1234     .t('DB.Stmt', function(sqlite3){
1235       let st = this.db.prepare(
1236         new TextEncoder('utf-8').encode("select 3 as a")
1237       );
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();
1289       }
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.")
1298       });
1299     })
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() }
1317           }
1318         });
1319       }
1320       /**
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
1325          db.
1326       */
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)
1336       ;
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"))
1344       ;
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]
1360       });
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));
1370       }
1371       rc = db.exec({
1372         sql: "INSERT INTO t(a,b) values('blob',X'6869') RETURNING 13",
1373         rowMode: 0
1374       });
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
1411           );
1412         }
1413       });
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
1423       });
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));
1434         }
1435       });
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),
1445       });
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
1456       });
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)));
1474       }
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());        
1486       }
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'));
1494       }
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');
1504         }
1505       });
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. */
1529           )
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."
1540         */
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);
1584       }
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
1596         );
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);
1605       }
1606     })
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);
1623     })
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);
1646     })
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 */);
1659     })
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;
1671       }
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);
1689         }
1690       }
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;
1710           }
1711         }).createFunction({
1712           name: "asis",
1713           xFunc: (pCx,arg)=>arg
1714         });
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*/
1733         );
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
1752         );
1753         T.assert(
1754           sqlite3.capi.SQLITE_FORMAT === rc,
1755           "For invalid eTextRep argument."
1756         );
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."
1761         );
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];
1768         };
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
1772         );
1773         T.assert( 0===rc );
1774         rc = capi.sqlite3_create_function_v2(
1775           db, "nary", 1, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0
1776         );
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
1786         );
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
1795         );
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;
1801       }
1802     })
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));
1816           },
1817           xFinal: (pCtx)=>{
1818             const ac = sjac(pCtx, 0);
1819             return ac ? wasm.peek32(ac) : 0;
1820           }
1821         });
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. */
1828         ]);
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);
1841           },
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.
1848           }
1849         });
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: ()=>{}
1854           });
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/);
1871      }
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));
1887           },
1888           xFinal: (pCtx)=>{
1889             const ac = sjac(pCtx, 0);
1890             return ac ? wasm.peek64(ac) : 0n;
1891           }
1892         });
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"
1897         ]);
1898         T.assert(9007199254740994n===v);
1899      }
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;
1914         };
1915         const xStepInverse = (pCtx, n)=>{
1916           const ac = sjac(pCtx);
1917           wasm.poke32(ac, wasm.peek32(ac) + Number(n));
1918         };
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
1925         });
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)"
1929         ]);
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;"
1937           ]
1938         });
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.");
1950           }
1951         }
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;"
1961           ]
1962         });
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"
1969         });
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");
1981       }
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
1998       });
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
2014       );
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
2020       );
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;
2039       });
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
2045         );
2046         T.assert(0===rc)
2047           .assert(3===rowCount)
2048           .assert(2===colCount);
2049       }finally{
2050         wasm.uninstallFunction(pCb);
2051       }
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
2058       );
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.");
2064     })
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')
2111             ];
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.");
2133           }
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);
2138         }
2139       }
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
2150         });
2151         /**
2152            The vtab demonstrated here is a JS-ification of
2153            ext/misc/templatevtab.c.
2154         */
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)"
2170                 );
2171                 if(0===rc){
2172                   const t = VT.xVtab.create(ppVtab);
2173                   T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab)));
2174                 }
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));
2180                 }
2181                 return VT.xError('xConnect',e);
2182               }
2183             },
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);
2191               }
2192             },
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);
2203               }
2204             },
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);
2214               }
2215             },
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);
2223               }
2224             },
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);
2236                 }
2237                 return 0;
2238               }catch(e){
2239                 return VT.xError('xColumn',e);
2240               }
2241             },
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);
2249               }
2250             },
2251             xEof: function(pCursor){
2252               const c = VT.xCursor.get(pCursor),
2253                     rc = c._rowId>=10;
2254               return rc;
2255             },
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);
2267               }
2268             },
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();
2294                   }
2295                 }
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();
2307                   }
2308                 }
2309                 pii.dispose();
2310                 return 0;
2311               }catch(e){
2312                 return VT.xError('xBestIndex',e);
2313               }
2314             }
2315           }
2316         });
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
2326         );
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(). */
2332         );
2333         T.assert(10===list.length)
2334           .assert(1000===list[0][0])
2335           .assert(2009===list[list.length-1][1]);
2336       }
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
2347         });
2348         /**
2349            The vtab demonstrated here is a JS-ification of
2350            ext/misc/templatevtab.c.
2351         */
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."
2371                 );
2372               }
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)"
2378               );
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);
2383               }
2384               return rc;
2385             },
2386             xConnect: true,
2387             xDestroy: function(pVtab){
2388               vtabTrace("xDestroy/xDisconnect",pVtab);
2389               VT.xVtab.dispose(pVtab);
2390             },
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;
2399             },
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();
2406             },
2407             xNext: function(pCursor){
2408               vtabTrace("xNext",...arguments);
2409               const c = VT.xCursor.get(pCursor);
2410               ++c._rowId;
2411             },
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);
2423               }
2424             },
2425             xRowid: function(pCursor, ppRowid64){
2426               vtabTrace("xRowid",...arguments);
2427               const c = VT.xCursor.get(pCursor);
2428               VT.xRowid(ppRowid64, c._rowId);
2429             },
2430             xEof: function(pCursor){
2431               vtabTrace("xEof",...arguments);
2432               return VT.xCursor.get(pCursor)._rowId>=10;
2433             },
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);
2441             },
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();
2449             }
2450           }/*methods*/
2451         };
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
2457         ));
2458         this.db.exec([
2459           "create virtual table testvtab2 using ",
2460           modConfig.name,
2461           "(arg1 blah, arg2 bloop)"
2462         ]);
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"
2472             );
2473           });
2474         }
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(). */
2479         );
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"
2486         );
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"
2495         ]);
2496         T.assert(1===list.length)
2497           .assert(1000===list[0][0])
2498           .assert(2000===list[0][1]);
2499       }
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);
2509       };
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./);
2525       /*
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.)
2533       */
2534       collationCounter = 0;
2535       myCmp = function(pArg,n1,p1,n2,p2){
2536         --collationCounter;
2537         return 0;
2538       };
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);
2556       }
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);
2566     })
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."
2578         );
2579       }
2580     })
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(filename)};
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)'
2597           ]);
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();
2605         }
2606       }
2607     }/*kvvfs sanity checks*/)
2608   ;/* end kvvfs tests */
2610   ////////////////////////////////////////////////////////////////////////
2611   T.g('Hook APIs')
2612     .t({
2613       name: "sqlite3_commit/rollback/update_hook()",
2614       predicate: ()=>wasm.bigIntEnabled || "Update hook requires int64",
2615       test: function(sqlite3){
2616         let countCommit = 0, countRollback = 0;;
2617         const db = new sqlite3.oo1.DB(':memory:',1 ? 'c' : 'ct');
2618         let rc = capi.sqlite3_commit_hook(db, (p)=>{
2619           ++countCommit;
2620           return (1 === p) ? 0 : capi.SQLITE_ERROR;
2621         }, 1);
2622         T.assert( 0 === rc /*void pointer*/ );
2624         // Commit hook...
2625         T.assert( 0!=capi.sqlite3_get_autocommit(db) );
2626         db.exec("BEGIN; SELECT 1; COMMIT");
2627         T.assert(0 === countCommit,
2628                  "No-op transactions (mostly) do not trigger commit hook.");
2629         db.exec("BEGIN EXCLUSIVE; SELECT 1; COMMIT");
2630         T.assert(1 === countCommit,
2631                  "But EXCLUSIVE transactions do.");
2632         db.transaction((d)=>{
2633           T.assert( 0==capi.sqlite3_get_autocommit(db) );
2634           d.exec("create table t(a)");
2635         });
2636         T.assert(2 === countCommit);
2638         // Rollback hook:
2639         rc = capi.sqlite3_rollback_hook(db, (p)=>{
2640           ++countRollback;
2641           T.assert( 2 === p );
2642         }, 2);
2643         T.assert( 0 === rc /*void pointer*/ );
2644         T.mustThrowMatching(()=>{
2645           db.transaction('drop table t',()=>{})
2646         }, (e)=>{
2647           return (capi.SQLITE_MISUSE === e.resultCode)
2648             && ( e.message.indexOf('Invalid argument') > 0 );
2649         });
2650         T.assert(0 === countRollback, "Transaction was not started.");
2651         T.mustThrowMatching(()=>{
2652           db.transaction('immediate', ()=>{
2653             sqlite3.SQLite3Error.toss(capi.SQLITE_FULL,'testing rollback hook');
2654           });
2655         }, (e)=>{
2656           return capi.SQLITE_FULL === e.resultCode
2657         });
2658         T.assert(1 === countRollback);
2660         // Update hook...
2661         const countUpdate = Object.create(null);
2662         capi.sqlite3_update_hook(db, (p,op,dbName,tbl,rowid)=>{
2663           T.assert('main' === dbName.toLowerCase())
2664             .assert('t' === tbl.toLowerCase())
2665             .assert(3===p)
2666             .assert('bigint' === typeof rowid);
2667           switch(op){
2668               case capi.SQLITE_INSERT:
2669               case capi.SQLITE_UPDATE:
2670               case capi.SQLITE_DELETE:
2671                 countUpdate[op] = (countUpdate[op]||0) + 1;
2672                 break;
2673               default: toss("Unexpected hook operator:",op);
2674           }
2675         }, 3);
2676         db.transaction((d)=>{
2677           d.exec([
2678             "insert into t(a) values(1);",
2679             "update t set a=2;",
2680             "update t set a=3;",
2681             "delete from t where a=3"
2682             // update hook is not called for an unqualified DELETE
2683           ]);
2684         });
2685         T.assert(1 === countRollback)
2686           .assert(3 === countCommit)
2687           .assert(1 === countUpdate[capi.SQLITE_INSERT])
2688           .assert(2 === countUpdate[capi.SQLITE_UPDATE])
2689           .assert(1 === countUpdate[capi.SQLITE_DELETE]);
2690         //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
2691         T.assert(1 === capi.sqlite3_commit_hook(db, 0, 0));
2692         T.assert(2 === capi.sqlite3_rollback_hook(db, 0, 0));
2693         T.assert(3 === capi.sqlite3_update_hook(db, 0, 0));
2694         //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
2695         db.close();
2696       }
2697     })/* commit/rollback/update hooks */
2698     .t({
2699       name: "sqlite3_preupdate_hook()",
2700       predicate: ()=>wasm.bigIntEnabled || "Pre-update hook requires int64",
2701       test: function(sqlite3){
2702         const db = new sqlite3.oo1.DB(':memory:', 1 ? 'c' : 'ct');
2703         const countHook = Object.create(null);
2704         let rc = capi.sqlite3_preupdate_hook(
2705           db, function(p, pDb, op, zDb, zTbl, iKey1, iKey2){
2706             T.assert(9 === p)
2707               .assert(db.pointer === pDb)
2708               .assert(1 === capi.sqlite3_preupdate_count(pDb))
2709               .assert( 0 > capi.sqlite3_preupdate_blobwrite(pDb) );
2710             countHook[op] = (countHook[op]||0) + 1;
2711             switch(op){
2712                 case capi.SQLITE_INSERT:
2713                 case capi.SQLITE_UPDATE:
2714                  T.assert('number' === typeof capi.sqlite3_preupdate_new_js(pDb, 0));
2715                   break;
2716                 case capi.SQLITE_DELETE:
2717                  T.assert('number' === typeof capi.sqlite3_preupdate_old_js(pDb, 0));
2718                   break;
2719                 default: toss("Unexpected hook operator:",op);
2720             }
2721           },
2722           9
2723         );
2724         db.transaction((d)=>{
2725           d.exec([
2726             "create table t(a);",
2727             "insert into t(a) values(1);",
2728             "update t set a=2;",
2729             "update t set a=3;",
2730             "delete from t where a=3"
2731           ]);
2732         });
2733         T.assert(1 === countHook[capi.SQLITE_INSERT])
2734           .assert(2 === countHook[capi.SQLITE_UPDATE])
2735           .assert(1 === countHook[capi.SQLITE_DELETE]);
2736         //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
2737         db.close();
2738         //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
2739       }
2740     })/*pre-update hooks*/
2741   ;/*end hook API tests*/
2743   ////////////////////////////////////////////////////////////////////////
2744   T.g('Auto-extension API')
2745     .t({
2746       name: "Auto-extension sanity checks.",
2747       test: function(sqlite3){
2748         let counter = 0;
2749         const fp = wasm.installFunction('i(ppp)', function(pDb,pzErr,pApi){
2750           ++counter;
2751           return 0;
2752         });
2753         (new sqlite3.oo1.DB()).close();
2754         T.assert( 0===counter );
2755         capi.sqlite3_auto_extension(fp);
2756         (new sqlite3.oo1.DB()).close();
2757         T.assert( 1===counter );
2758         (new sqlite3.oo1.DB()).close();
2759         T.assert( 2===counter );
2760         capi.sqlite3_cancel_auto_extension(fp);
2761         wasm.uninstallFunction(fp);
2762         (new sqlite3.oo1.DB()).close();
2763         T.assert( 2===counter );
2764       }
2765     });
2767   ////////////////////////////////////////////////////////////////////////
2768   T.g('Session API')
2769     .t({
2770       name: 'Session API sanity checks',
2771       predicate: ()=>!!capi.sqlite3changegroup_add,
2772       test: function(sqlite3){
2773         warn("The session API tests could use some expansion.");
2774         const db1 = new sqlite3.oo1.DB(), db2 = new sqlite3.oo1.DB();
2775         const sqlInit = [
2776           "create table t(rowid INTEGER PRIMARY KEY,a,b); ",
2777           "insert into t(rowid,a,b) values",
2778           "(1,'a1','b1'),",
2779           "(2,'a2','b2'),",
2780           "(3,'a3','b3');"
2781         ].join('');
2782         db1.exec(sqlInit);
2783         db2.exec(sqlInit);
2784         T.assert(3 === db1.selectValue("select count(*) from t"))
2785           .assert('b3' === db1.selectValue('select b from t where rowid=3'));
2786         const stackPtr = wasm.pstack.pointer;
2787         try{
2788           let ppOut = wasm.pstack.allocPtr();
2789           let rc = capi.sqlite3session_create(db1, "main", ppOut);
2790           T.assert(0===rc);
2791           let pSession = wasm.peekPtr(ppOut);
2792           T.assert(pSession && wasm.isPtr(pSession));
2793           capi.sqlite3session_table_filter(pSession, (pCtx, tbl)=>{
2794             T.assert('t' === tbl).assert( 99 === pCtx );
2795             return 1;
2796           }, 99);
2797           db1.exec([
2798             "update t set b='bTwo' where rowid=2;",
2799             "update t set a='aThree' where rowid=3;",
2800             "delete from t where rowid=1;",
2801             "insert into t(rowid,a,b) values(4,'a4','b4')"
2802           ]);
2803           T.assert('bTwo' === db1.selectValue("select b from t where rowid=2"))
2804             .assert(undefined === db1.selectValue('select a from t where rowid=1'))
2805             .assert('b4' === db1.selectValue('select b from t where rowid=4'))
2806             .assert(3 === db1.selectValue('select count(*) from t'));
2808           const testSessionEnable = false;
2809           if(testSessionEnable){
2810             rc = capi.sqlite3session_enable(pSession, 0);
2811             T.assert( 0 === rc )
2812               .assert( 0 === capi.sqlite3session_enable(pSession, -1) );
2813             db1.exec("delete from t where rowid=2;");
2814             rc = capi.sqlite3session_enable(pSession, 1);
2815             T.assert( rc > 0 )
2816               .assert( capi.sqlite3session_enable(pSession, -1) > 0 )
2817               .assert(undefined === db1.selectValue('select a from t where rowid=2'));
2818           }else{
2819             warn("sqlite3session_enable() tests are currently disabled.");
2820           }
2821           let db1Count = db1.selectValue("select count(*) from t");
2822           T.assert( db1Count === (testSessionEnable ? 2 : 3) );
2824           /* Capture changeset and destroy session. */
2825           let pnChanges = wasm.pstack.alloc('i32'),
2826               ppChanges = wasm.pstack.allocPtr();
2827           rc = capi.sqlite3session_changeset(pSession, pnChanges, ppChanges);
2828           T.assert( 0 === rc );
2829           capi.sqlite3session_delete(pSession);
2830           pSession = 0;
2831           const pChanges = wasm.peekPtr(ppChanges),
2832                 nChanges = wasm.peek32(pnChanges);
2833           T.assert( pChanges && wasm.isPtr( pChanges ) )
2834             .assert( nChanges > 0 );
2836           /* Revert db1 via an inverted changeset, but keep pChanges
2837              and nChanges for application to db2. */
2838           rc = capi.sqlite3changeset_invert( nChanges, pChanges, pnChanges, ppChanges );
2839           T.assert( 0 === rc );
2840           rc = capi.sqlite3changeset_apply(
2841             db1, wasm.peek32(pnChanges), wasm.peekPtr(ppChanges), 0, (pCtx, eConflict, pIter)=>{
2842               return 1;
2843             }, 0
2844           );
2845           T.assert( 0 === rc );
2846           wasm.dealloc( wasm.peekPtr(ppChanges) );
2847           pnChanges = ppChanges = 0;
2848           T.assert('b2' === db1.selectValue("select b from t where rowid=2"))
2849             .assert('a1' === db1.selectValue('select a from t where rowid=1'))
2850             .assert(undefined === db1.selectValue('select b from t where rowid=4'));
2851           db1Count = db1.selectValue("select count(*) from t");
2852           T.assert(3 === db1Count);
2854           /* Apply pre-reverted changeset (pChanges, nChanges) to
2855              db2... */
2856           rc = capi.sqlite3changeset_apply(
2857             db2, nChanges, pChanges, 0, (pCtx, eConflict, pIter)=>{
2858               return pCtx ? 1 : 0
2859             }, 1
2860           );
2861           wasm.dealloc( pChanges );
2862           T.assert( 0 === rc )
2863             .assert( 'b4' === db2.selectValue('select b from t where rowid=4') )
2864             .assert( 'aThree' === db2.selectValue('select a from t where rowid=3') )
2865             .assert( undefined === db2.selectValue('select b from t where rowid=1') );
2866           if(testSessionEnable){
2867             T.assert( (undefined === db2.selectValue('select b from t where rowid=2')),
2868                       "But... the session was disabled when rowid=2 was deleted?" );
2869             log("rowids from db2.t:",db2.selectValues('select rowid from t order by rowid'));
2870             T.assert( 3 === db2.selectValue('select count(*) from t') );
2871           }else{
2872             T.assert( 'bTwo' === db2.selectValue('select b from t where rowid=2') )
2873               .assert( 3 === db2.selectValue('select count(*) from t') );
2874           }
2875         }finally{
2876           wasm.pstack.restore(stackPtr);
2877           db1.close();
2878           db2.close();
2879         }
2880       }
2881     })/*session API sanity tests*/
2882   ;/*end of session API group*/;
2884   ////////////////////////////////////////////////////////////////////////
2885   T.g('OPFS: Origin-Private File System',
2886       (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs")
2887                   || 'requires "opfs" VFS'))
2888     .t({
2889       name: 'OPFS db sanity checks',
2890       test: async function(sqlite3){
2891         const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db';
2892         const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs');
2893         T.assert(pVfs);
2894         const unlink = this.opfsUnlink =
2895               (fn=filename)=>{sqlite3.util.sqlite3__wasm_vfs_unlink(pVfs,fn)};
2896         unlink();
2897         let db = new sqlite3.oo1.OpfsDb(filename);
2898         try {
2899           db.exec([
2900             'create table p(a);',
2901             'insert into p(a) values(1),(2),(3)'
2902           ]);
2903           T.assert(3 === db.selectValue('select count(*) from p'));
2904           db.close();
2905           db = new sqlite3.oo1.OpfsDb(filename);
2906           db.exec('insert into p(a) values(4),(5),(6)');
2907           T.assert(6 === db.selectValue('select count(*) from p'));
2908           this.opfsDbExport = capi.sqlite3_js_db_export(db);
2909           T.assert(this.opfsDbExport instanceof Uint8Array)
2910             .assert(this.opfsDbExport.byteLength>0
2911                     && 0===this.opfsDbExport.byteLength % 512);
2912         }finally{
2913           db.close();
2914           unlink();
2915         }
2916       }
2917     }/*OPFS db sanity checks*/)
2918     .t({
2919       name: 'OPFS import',
2920       test: async function(sqlite3){
2921         let db;
2922         try {
2923           const exp = this.opfsDbExport;
2924           const filename = this.opfsDbFile;
2925           delete this.opfsDbExport;
2926           this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp);
2927           db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
2928           T.assert(6 === db.selectValue('select count(*) from p')).
2929             assert( this.opfsImportSize == exp.byteLength );
2930           db.close();
2931           this.opfsUnlink(filename);
2932           T.assert(!(await sqlite3.opfs.entryExists(filename)));
2933           // Try again with a function as an input source:
2934           let cursor = 0;
2935           const blockSize = 512, end = exp.byteLength;
2936           const reader = async function(){
2937             if(cursor >= exp.byteLength){
2938               return undefined;
2939             }
2940             const rv = exp.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
2941             cursor += blockSize;
2942             return rv;
2943           };
2944           this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, reader);
2945           db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
2946           T.assert(6 === db.selectValue('select count(*) from p')).
2947             assert( this.opfsImportSize == exp.byteLength );
2948         }finally{
2949           if(db) db.close();
2950         }
2951       }
2952     }/*OPFS export/import*/)
2953     .t({
2954       name: '(Internal-use) OPFS utility APIs',
2955       test: async function(sqlite3){
2956         const filename = this.opfsDbFile;
2957         const pVfs = this.opfsVfs;
2958         const unlink = this.opfsUnlink;
2959         T.assert(filename && pVfs && !!unlink);
2960         delete this.opfsDbFile;
2961         delete this.opfsVfs;
2962         delete this.opfsUnlink;
2963         /**************************************************************
2964            ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended
2965            for client-side use. It is only for this project's own
2966            internal use. Its APIs are subject to change or removal at
2967            any time.
2968         ***************************************************************/
2969         const opfs = sqlite3.opfs;
2970         const fSize = this.opfsImportSize;
2971         delete this.opfsImportSize;
2972         let sh;
2973         try{
2974           T.assert(await opfs.entryExists(filename));
2975           const [dirHandle, filenamePart] = await opfs.getDirForFilename(filename, false);
2976           const fh = await dirHandle.getFileHandle(filenamePart);
2977           sh = await fh.createSyncAccessHandle();
2978           T.assert(fSize === await sh.getSize());
2979           await sh.close();
2980           sh = undefined;
2981           unlink();
2982           T.assert(!(await opfs.entryExists(filename)));
2983         }finally{
2984           if(sh) await sh.close();
2985           unlink();
2986         }
2988         // Some sanity checks of the opfs utility functions...
2989         const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
2990         const aDir = testDir+'/test/dir';
2991         T.assert(await opfs.mkdir(aDir), "mkdir failed")
2992           .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
2993           .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
2994           .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
2995           .assert(!(await opfs.unlink(testDir+'/test/dir')),
2996                   "delete 2b should have failed (dir already deleted)")
2997           .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
2998           .assert(!(await opfs.entryExists(testDir)),
2999                   "entryExists(",testDir,") should have failed");
3000       }
3001     }/*OPFS util sanity checks*/)
3002   ;/* end OPFS tests */
3004   ////////////////////////////////////////////////////////////////////////
3005   T.g('OPFS SyncAccessHandle Pool VFS',
3006       (sqlite3)=>(hasOpfs() || "requires OPFS APIs"))
3007     .t({
3008       name: 'SAH sanity checks',
3009       test: async function(sqlite3){
3010         T.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
3011           .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) < 0)
3012         const inst = sqlite3.installOpfsSAHPoolVfs,
3013               catcher = (e)=>{
3014                 error("Cannot load SAH pool VFS.",
3015                       "This might not be a problem,",
3016                       "depending on the environment.");
3017                 return false;
3018               };
3019         let u1, u2;
3020         // Ensure that two immediately-consecutive installations
3021         // resolve to the same Promise instead of triggering
3022         // a locking error.
3023         const P1 = inst(sahPoolConfig).then(u=>u1 = u).catch(catcher),
3024               P2 = inst(sahPoolConfig).then(u=>u2 = u).catch(catcher);
3025         await Promise.all([P1, P2]);
3026         if(!(await P1)) return;
3027         T.assert(u1 === u2)
3028           .assert(sahPoolConfig.name === u1.vfsName)
3029           .assert(sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
3030           .assert(u1.getCapacity() >= sahPoolConfig.initialCapacity
3031                   /* If a test fails before we get to nuke the VFS, we
3032                      can have more than the initial capacity on the next
3033                      run. */)
3034           .assert(u1.getCapacity() + 2 === (await u2.addCapacity(2)))
3035           .assert(2 === (await u2.reduceCapacity(2)))
3036           .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) >= 0);
3038         T.assert(0 === u1.getFileCount());
3039         const dbName = '/foo.db';
3040         let db = new u1.OpfsSAHPoolDb(dbName);
3041         T.assert(db instanceof sqlite3.oo1.DB)
3042           .assert(1 === u1.getFileCount());
3043         db.exec([
3044           'create table t(a);',
3045           'insert into t(a) values(1),(2),(3)'
3046         ]);
3047         T.assert(1 === u1.getFileCount());
3048         T.assert(3 === db.selectValue('select count(*) from t'));
3049         db.close();
3050         T.assert(1 === u1.getFileCount());
3051         db = new u2.OpfsSAHPoolDb(dbName);
3052         T.assert(1 === u1.getFileCount());
3053         db.close();
3054         const fileNames = u1.getFileNames();
3055         T.assert(1 === fileNames.length)
3056           .assert(dbName === fileNames[0])
3057           .assert(1 === u1.getFileCount())
3059         if(1){ // test exportFile() and importDb()
3060           const dbytes = u1.exportFile(dbName);
3061           T.assert(dbytes.length >= 4096);
3062           const dbName2 = '/exported.db';
3063           let nWrote = u1.importDb(dbName2, dbytes);
3064           T.assert( 2 == u1.getFileCount() )
3065             .assert( dbytes.byteLength == nWrote );
3066           let db2 = new u1.OpfsSAHPoolDb(dbName2);
3067           T.assert(db2 instanceof sqlite3.oo1.DB)
3068             .assert(3 === db2.selectValue('select count(*) from t'));
3069           db2.close();
3070           T.assert(true === u1.unlink(dbName2))
3071             .assert(false === u1.unlink(dbName2))
3072             .assert(1 === u1.getFileCount())
3073             .assert(1 === u1.getFileNames().length);
3074           // Try again with a function as an input source:
3075           let cursor = 0;
3076           const blockSize = 1024, end = dbytes.byteLength;
3077           const reader = async function(){
3078             if(cursor >= dbytes.byteLength){
3079               return undefined;
3080             }
3081             const rv = dbytes.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
3082             cursor += blockSize;
3083             return rv;
3084           };
3085           nWrote = await u1.importDb(dbName2, reader);
3086           T.assert( 2 == u1.getFileCount() );
3087           db2 = new u1.OpfsSAHPoolDb(dbName2);
3088           T.assert(db2 instanceof sqlite3.oo1.DB)
3089             .assert(3 === db2.selectValue('select count(*) from t'));
3090           db2.close();
3091           T.assert(true === u1.unlink(dbName2))
3092             .assert(dbytes.byteLength == nWrote);
3093         }
3095         T.assert(true === u1.unlink(dbName))
3096           .assert(false === u1.unlink(dbName))
3097           .assert(0 === u1.getFileCount())
3098           .assert(0 === u1.getFileNames().length);
3100         // Demonstrate that two SAH pools can coexist so long as
3101         // they have different names.
3102         const conf2 = JSON.parse(JSON.stringify(sahPoolConfig));
3103         conf2.name += '-test2';
3104         const POther = await inst(conf2);
3105         //log("Installed second SAH instance as",conf2.name);
3106         T.assert(0 === POther.getFileCount())
3107           .assert(true === await POther.removeVfs());
3109         if(0){
3110            /* Enable this block to inspect vfs's contents via the dev
3111               console or OPFS Explorer browser extension.  The
3112               following bits will remove them. */
3113           return;
3114         }
3115         T.assert(true === await u2.removeVfs())
3116           .assert(false === await u1.removeVfs())
3117           .assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name));
3119         let cErr, u3;
3120         conf2.$testThrowInInit = new Error("Testing throwing during init.");
3121         conf2.name = sahPoolConfig.name+'-err';
3122         const P3 = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e);
3123         T.assert(P3 === conf2.$testThrowInInit)
3124           .assert(cErr === P3)
3125           .assert(undefined === u3)
3126           .assert(!sqlite3.capi.sqlite3_vfs_find(conf2.name));
3127       }
3128     }/*OPFS SAH Pool sanity checks*/)
3130   ////////////////////////////////////////////////////////////////////////
3131   T.g('Bug Reports')
3132     .t({
3133       name: 'Delete via bound parameter in subquery',
3134       test: function(sqlite3){
3135         // Testing https://sqlite.org/forum/forumpost/40ce55bdf5
3136         // with the exception that that post uses "external content"
3137         // for the FTS index.
3138         const db = new sqlite3.oo1.DB();//(':memory:','wt');
3139         db.exec([
3140           "create virtual table f using fts5 (path);",
3141           "insert into f(path) values('abc'),('def'),('ghi');"
3142         ]);
3143         const fetchEm = ()=> db.exec({
3144           sql: "SELECT * FROM f order by path",
3145           rowMode: 'array'
3146         });
3147         const dump = function(lbl){
3148           let rc = fetchEm();
3149           log((lbl ? (lbl+' results') : ''),rc);
3150         };
3151         //dump('Full fts table');
3152         let rc = fetchEm();
3153         T.assert(3===rc.length);
3154         db.exec(`
3155           delete from f where rowid in (
3156           select rowid from f where path = :path
3157            )`,
3158           {bind: {":path": "def"}}
3159         );
3160         //dump('After deleting one entry via subquery');
3161         rc = fetchEm();
3162         T.assert(2===rc.length)
3163           .assert('abcghi'===rc.join(''));
3164         //log('rc =',rc);
3165         db.close();
3166       }
3167     })
3168   ;/*end of Bug Reports group*/;
3170   ////////////////////////////////////////////////////////////////////////
3171   log("Loading and initializing sqlite3 WASM module...");
3172   if(0){
3173     globalThis.sqlite3ApiConfig = {
3174       debug: ()=>{},
3175       log: ()=>{},
3176       warn: ()=>{},
3177       error: ()=>{}
3178     }
3179   }
3180 //#ifnot target=es6-module
3181   if(!globalThis.sqlite3InitModule && !isUIThread()){
3182     /* Vanilla worker, as opposed to an ES6 module worker */
3183     /*
3184       If sqlite3.js is in a directory other than this script, in order
3185       to get sqlite3.js to resolve sqlite3.wasm properly, we have to
3186       explicitly tell it where sqlite3.js is being loaded from. We do
3187       that by passing the `sqlite3.dir=theDirName` URL argument to
3188       _this_ script. That URL argument will be seen by the JS/WASM
3189       loader and it will adjust the sqlite3.wasm path accordingly. If
3190       sqlite3.js/.wasm are in the same directory as this script then
3191       that's not needed.
3193       URL arguments passed as part of the filename via importScripts()
3194       are simply lost, and such scripts see the globalThis.location of
3195       _this_ script.
3196     */
3197     let sqlite3Js = 'sqlite3.js';
3198     const urlParams = new URL(globalThis.location.href).searchParams;
3199     if(urlParams.has('sqlite3.dir')){
3200       sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
3201     }
3202     importScripts(sqlite3Js);
3203   }
3204 //#endif
3205   globalThis.sqlite3InitModule.__isUnderTest =
3206     true /* disables certain API-internal cleanup so that we can
3207             test internal APIs from here */;
3208   globalThis.sqlite3InitModule({
3209     print: log,
3210     printErr: error
3211   }).then(async function(sqlite3){
3212     TestUtil.assert(!!sqlite3.util);
3213     log("Done initializing WASM/JS bits. Running tests...");
3214     sqlite3.config.warn("Installing sqlite3 bits as global S for local dev/test purposes.");
3215     globalThis.S = sqlite3;
3216     /*await sqlite3.installOpfsSAHPoolVfs(sahPoolConfig)
3217       .then((u)=>log("Loaded",u.vfsName,"VFS"))
3218       .catch(e=>{
3219         log("Cannot install OpfsSAHPool.",e);
3220       });*/
3221     capi = sqlite3.capi;
3222     wasm = sqlite3.wasm;
3223     log("sqlite3 version:",capi.sqlite3_libversion(),
3224         capi.sqlite3_sourceid());
3225     if(wasm.bigIntEnabled){
3226       log("BigInt/int64 support is enabled.");
3227     }else{
3228       logClass('warning',"BigInt/int64 support is disabled.");
3229     }
3230     if(haveWasmCTests()){
3231       log("sqlite3__wasm_test_...() APIs are available.");
3232     }else{
3233       logClass('warning',"sqlite3__wasm_test_...() APIs unavailable.");
3234     }
3235     log("registered vfs list =",capi.sqlite3_js_vfs_list().join(', '));
3236     TestUtil.runTests(sqlite3);
3237   });
3238 })(self);