12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476 |
- /*
- 2022-10-12
- The author disclaims copyright to this source code. In place of a
- legal notice, here is a blessing:
- * May you do good and not evil.
- * May you find forgiveness for yourself and forgive others.
- * May you share freely, never taking more than you give.
- ***********************************************************************
- Main functional and regression tests for the sqlite3 WASM API.
- This mini-framework works like so:
- This script adds a series of test groups, each of which contains an
- arbitrary number of tests, into a queue. After loading of the
- sqlite3 WASM/JS module is complete, that queue is processed. If any
- given test fails, the whole thing fails. This script is built such
- that it can run from the main UI thread or worker thread. Test
- groups and individual tests can be assigned a predicate function
- which determines whether to run them or not, and this is
- specifically intended to be used to toggle certain tests on or off
- for the main/worker threads or the availability (or not) of
- optional features such as int64 support.
- Each test group defines a single state object which gets applied as
- the test functions' `this` for all tests in that group. Test
- functions can use that to, e.g., set up a db in an early test and
- close it in a later test. Each test gets passed the sqlite3
- namespace object as its only argument.
- */
- /*
- This file is intended to be processed by c-pp to inject (or not)
- code specific to ES6 modules which is illegal in non-module code.
- Non-ES6 module build and ES6 module for the main-thread:
- ./c-pp -f tester1.c-pp.js -o tester1.js
- ES6 worker module build:
- ./c-pp -f tester1.c-pp.js -o tester1-esm.js -Dtarget=es6-module
- */
- //#if target=es6-module
- import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs';
- globalThis.sqlite3InitModule = sqlite3InitModule;
- //#else
- 'use strict';
- //#endif
- (function(self){
- /**
- Set up our output channel differently depending
- on whether we are running in a worker thread or
- the main (UI) thread.
- */
- let logClass;
- /* Predicate for tests/groups. */
- const isUIThread = ()=>(globalThis.window===self && globalThis.document);
- /* Predicate for tests/groups. */
- const isWorker = ()=>!isUIThread();
- /* Predicate for tests/groups. */
- const testIsTodo = ()=>false;
- const haveWasmCTests = ()=>{
- return !!wasm.exports.sqlite3__wasm_test_intptr;
- };
- const hasOpfs = ()=>{
- return globalThis.FileSystemHandle
- && globalThis.FileSystemDirectoryHandle
- && globalThis.FileSystemFileHandle
- && globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle
- && navigator?.storage?.getDirectory;
- };
- {
- const mapToString = (v)=>{
- switch(typeof v){
- case 'number': case 'string': case 'boolean':
- case 'undefined': case 'bigint':
- return ''+v;
- default: break;
- }
- if(null===v) return 'null';
- if(v instanceof Error){
- v = {
- message: v.message,
- stack: v.stack,
- errorClass: v.name
- };
- }
- return JSON.stringify(v,undefined,2);
- };
- const normalizeArgs = (args)=>args.map(mapToString);
- if( isUIThread() ){
- console.log("Running in the UI thread.");
- const logTarget = document.querySelector('#test-output');
- logClass = function(cssClass,...args){
- const ln = document.createElement('div');
- if(cssClass){
- for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){
- ln.classList.add(c);
- }
- }
- ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
- logTarget.append(ln);
- };
- const cbReverse = document.querySelector('#cb-log-reverse');
- //cbReverse.setAttribute('checked','checked');
- const cbReverseKey = 'tester1:cb-log-reverse';
- const cbReverseIt = ()=>{
- logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse');
- //localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0);
- };
- cbReverse.addEventListener('change', cbReverseIt, true);
- /*if(localStorage.getItem(cbReverseKey)){
- cbReverse.checked = !!(+localStorage.getItem(cbReverseKey));
- }*/
- cbReverseIt();
- }else{ /* Worker thread */
- console.log("Running in a Worker thread.");
- logClass = function(cssClass,...args){
- postMessage({
- type:'log',
- payload:{cssClass, args: normalizeArgs(args)}
- });
- };
- }
- }
- const reportFinalTestStatus = function(pass){
- if(isUIThread()){
- let e = document.querySelector('#color-target');
- e.classList.add(pass ? 'tests-pass' : 'tests-fail');
- e = document.querySelector('title');
- e.innerText = (pass ? 'PASS' : 'FAIL') + ': ' + e.innerText;
- }else{
- postMessage({type:'test-result', payload:{pass}});
- }
- };
- const log = (...args)=>{
- //console.log(...args);
- logClass('',...args);
- }
- const warn = (...args)=>{
- console.warn(...args);
- logClass('warning',...args);
- }
- const error = (...args)=>{
- console.error(...args);
- logClass('error',...args);
- };
- const toss = (...args)=>{
- error(...args);
- throw new Error(args.join(' '));
- };
- const tossQuietly = (...args)=>{
- throw new Error(args.join(' '));
- };
- const roundMs = (ms)=>Math.round(ms*100)/100;
- /**
- Helpers for writing sqlite3-specific tests.
- */
- const TestUtil = {
- /** Running total of the number of tests run via
- this API. */
- counter: 0,
- /**
- If expr is a function, it is called and its result
- is returned, coerced to a bool, else expr, coerced to
- a bool, is returned.
- */
- toBool: function(expr){
- return (expr instanceof Function) ? !!expr() : !!expr;
- },
- /** Throws if expr is false. If expr is a function, it is called
- and its result is evaluated. If passed multiple arguments,
- those after the first are a message string which get applied
- as an exception message if the assertion fails. The message
- arguments are concatenated together with a space between each.
- */
- assert: function f(expr, ...msg){
- ++this.counter;
- if(!this.toBool(expr)){
- throw new Error(msg.length ? msg.join(' ') : "Assertion failed.");
- }
- return this;
- },
- /** Calls f() and squelches any exception it throws. If it
- does not throw, this function throws. */
- mustThrow: function(f, msg){
- ++this.counter;
- let err;
- try{ f(); } catch(e){err=e;}
- if(!err) throw new Error(msg || "Expected exception.");
- return this;
- },
- /**
- Works like mustThrow() but expects filter to be a regex,
- function, or string to match/filter the resulting exception
- against. If f() does not throw, this test fails and an Error is
- thrown. If filter is a regex, the test passes if
- filter.test(error.message) passes. If it's a function, the test
- passes if filter(error) returns truthy. If it's a string, the
- test passes if the filter matches the exception message
- precisely. In all other cases the test fails, throwing an
- Error.
- If it throws, msg is used as the error report unless it's falsy,
- in which case a default is used.
- */
- mustThrowMatching: function(f, filter, msg){
- ++this.counter;
- let err;
- try{ f(); } catch(e){err=e;}
- if(!err) throw new Error(msg || "Expected exception.");
- let pass = false;
- if(filter instanceof RegExp) pass = filter.test(err.message);
- else if(filter instanceof Function) pass = filter(err);
- else if('string' === typeof filter) pass = (err.message === filter);
- if(!pass){
- throw new Error(msg || ("Filter rejected this exception: "+err.message));
- }
- return this;
- },
- /** Throws if expr is truthy or expr is a function and expr()
- returns truthy. */
- throwIf: function(expr, msg){
- ++this.counter;
- if(this.toBool(expr)) throw new Error(msg || "throwIf() failed");
- return this;
- },
- /** Throws if expr is falsy or expr is a function and expr()
- returns falsy. */
- throwUnless: function(expr, msg){
- ++this.counter;
- if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed");
- return this;
- },
- eqApprox: (v1,v2,factor=0.05)=>(v1>=(v2-factor) && v1<=(v2+factor)),
- TestGroup: (function(){
- let groupCounter = 0;
- const TestGroup = function(name, predicate){
- this.number = ++groupCounter;
- this.name = name;
- this.predicate = predicate;
- this.tests = [];
- };
- TestGroup.prototype = {
- addTest: function(testObj){
- this.tests.push(testObj);
- return this;
- },
- run: async function(sqlite3){
- logClass('group-start',"Group #"+this.number+':',this.name);
- if(this.predicate){
- const p = this.predicate(sqlite3);
- if(!p || 'string'===typeof p){
- logClass(['warning','skipping-group'],
- "SKIPPING group:", p ? p : "predicate says to" );
- return;
- }
- }
- const assertCount = TestUtil.counter;
- const groupState = Object.create(null);
- const skipped = [];
- let runtime = 0, i = 0;
- for(const t of this.tests){
- ++i;
- const n = this.number+"."+i;
- logClass('one-test-line', n+":", t.name);
- if(t.predicate){
- const p = t.predicate(sqlite3);
- if(!p || 'string'===typeof p){
- logClass(['warning','skipping-test'],
- "SKIPPING:", p ? p : "predicate says to" );
- skipped.push( n+': '+t.name );
- continue;
- }
- }
- const tc = TestUtil.counter, now = performance.now();
- let rc = t.test.call(groupState, sqlite3);
- /*if(rc instanceof Promise){
- rc = rc.catch((e)=>{
- error("Test failure:",e);
- throw e;
- });
- }*/
- await rc;
- const then = performance.now();
- runtime += then - now;
- logClass(['faded','one-test-summary'],
- TestUtil.counter - tc, 'assertion(s) in',
- roundMs(then-now),'ms');
- }
- logClass(['green','group-end'],
- "#"+this.number+":",
- (TestUtil.counter - assertCount),
- "assertion(s) in",roundMs(runtime),"ms");
- if(0 && skipped.length){
- logClass('warning',"SKIPPED test(s) in group",this.number+":",skipped);
- }
- }
- };
- return TestGroup;
- })()/*TestGroup*/,
- testGroups: [],
- currentTestGroup: undefined,
- addGroup: function(name, predicate){
- this.testGroups.push( this.currentTestGroup =
- new this.TestGroup(name, predicate) );
- return this;
- },
- addTest: function(name, callback){
- let predicate;
- if(1===arguments.length){
- this.currentTestGroup.addTest(arguments[0]);
- }else{
- this.currentTestGroup.addTest({
- name, predicate, test: callback
- });
- }
- return this;
- },
- runTests: async function(sqlite3){
- return new Promise(async function(pok,pnok){
- try {
- let runtime = 0;
- for(let g of this.testGroups){
- const now = performance.now();
- await g.run(sqlite3);
- runtime += performance.now() - now;
- }
- logClass(['strong','green','full-test-summary'],
- "Done running tests.",TestUtil.counter,"assertions in",
- roundMs(runtime),'ms');
- pok();
- reportFinalTestStatus(true);
- }catch(e){
- error(e);
- pnok(e);
- reportFinalTestStatus(false);
- }
- }.bind(this));
- }
- }/*TestUtil*/;
- const T = TestUtil;
- T.g = T.addGroup;
- T.t = T.addTest;
- let capi, wasm/*assigned after module init*/;
- const sahPoolConfig = {
- name: 'opfs-sahpool-tester1',
- clearOnInit: true,
- initialCapacity: 6
- };
- ////////////////////////////////////////////////////////////////////////
- // End of infrastructure setup. Now define the tests...
- ////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////
- T.g('Basic sanity checks')
- .t({
- name:'sqlite3_config()',
- test:function(sqlite3){
- for(const k of [
- 'SQLITE_CONFIG_GETMALLOC', 'SQLITE_CONFIG_URI'
- ]){
- T.assert(capi[k] > 0);
- }
- T.assert(capi.SQLITE_MISUSE===capi.sqlite3_config(
- capi.SQLITE_CONFIG_URI, 1
- ), "MISUSE because the library has already been initialized.");
- T.assert(capi.SQLITE_MISUSE === capi.sqlite3_config(
- // not enough args
- capi.SQLITE_CONFIG_GETMALLOC
- ));
- T.assert(capi.SQLITE_NOTFOUND === capi.sqlite3_config(
- // unhandled-in-JS config option
- capi.SQLITE_CONFIG_GETMALLOC, 1
- ));
- if(0){
- log("We cannot _fully_ test sqlite3_config() after the library",
- "has been initialized (which it necessarily has been to",
- "set up various bindings) and we cannot shut it down ",
- "without losing the VFS registrations.");
- T.assert(0 === capi.sqlite3_config(
- capi.SQLITE_CONFIG_URI, 1
- ));
- }
- }
- })/*sqlite3_config()*/
- ////////////////////////////////////////////////////////////////////
- .t({
- name: "JS wasm-side allocator",
- test: function(sqlite3){
- if(sqlite3.config.useStdAlloc){
- warn("Using system allocator. This violates the docs and",
- "may cause grief with certain APIs",
- "(e.g. sqlite3_deserialize()).");
- T.assert(wasm.alloc.impl === wasm.exports.malloc)
- .assert(wasm.dealloc === wasm.exports.free)
- .assert(wasm.realloc.impl === wasm.exports.realloc);
- }else{
- T.assert(wasm.alloc.impl === wasm.exports.sqlite3_malloc)
- .assert(wasm.dealloc === wasm.exports.sqlite3_free)
- .assert(wasm.realloc.impl === wasm.exports.sqlite3_realloc);
- }
- }
- })
- .t('Namespace object checks', function(sqlite3){
- const wasmCtypes = wasm.ctype;
- T.assert(wasmCtypes.structs[0].name==='sqlite3_vfs').
- assert(wasmCtypes.structs[0].members.szOsFile.sizeof>=4).
- assert(wasmCtypes.structs[1/*sqlite3_io_methods*/
- ].members.xFileSize.offset>0);
- [ /* Spot-check a handful of constants to make sure they got installed... */
- 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8',
- 'SQLITE_STATIC', 'SQLITE_DIRECTONLY',
- 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE'
- ].forEach((k)=>T.assert('number' === typeof capi[k]));
- [/* Spot-check a few of the WASM API methods. */
- 'alloc', 'dealloc', 'installFunction'
- ].forEach((k)=>T.assert(wasm[k] instanceof Function));
- T.assert(capi.sqlite3_errstr(capi.SQLITE_IOERR_ACCESS).indexOf("I/O")>=0).
- assert(capi.sqlite3_errstr(capi.SQLITE_CORRUPT).indexOf('malformed')>0).
- assert(capi.sqlite3_errstr(capi.SQLITE_OK) === 'not an error');
- try {
- throw new sqlite3.WasmAllocError;
- }catch(e){
- T.assert(e instanceof Error)
- .assert(e instanceof sqlite3.WasmAllocError)
- .assert("Allocation failed." === e.message);
- }
- try {
- throw new sqlite3.WasmAllocError("test",{
- cause: 3
- });
- }catch(e){
- T.assert(3 === e.cause)
- .assert("test" === e.message);
- }
- try {throw new sqlite3.WasmAllocError("test","ing",".")}
- catch(e){T.assert("test ing ." === e.message)}
- try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) }
- catch(e){
- T.assert('SQLITE_SCHEMA' === e.message)
- .assert(capi.SQLITE_SCHEMA === e.resultCode);
- }
- try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) }
- catch(e){
- T.assert('SQLITE_CORRUPT' === e.message)
- .assert(capi.SQLITE_CORRUPT === e.resultCode)
- .assert(true===e.cause);
- }
- try{ sqlite3.SQLite3Error.toss("resultCode check") }
- catch(e){
- T.assert(capi.SQLITE_ERROR === e.resultCode)
- .assert('resultCode check' === e.message);
- }
- })
- ////////////////////////////////////////////////////////////////////
- .t('strglob/strlike', function(sqlite3){
- T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
- assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
- assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
- assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
- })
- ////////////////////////////////////////////////////////////////////
- ;/*end of basic sanity checks*/
- ////////////////////////////////////////////////////////////////////
- T.g('C/WASM Utilities')
- .t('sqlite3.wasm namespace', function(sqlite3){
- // TODO: break this into smaller individual test functions.
- const w = wasm;
- const chr = (x)=>x.charCodeAt(0);
- //log("heap getters...");
- {
- const li = [8, 16, 32];
- if(w.bigIntEnabled) li.push(64);
- for(const n of li){
- const bpe = n/8;
- const s = w.heapForSize(n,false);
- T.assert(bpe===s.BYTES_PER_ELEMENT).
- assert(w.heapForSize(s.constructor) === s);
- const u = w.heapForSize(n,true);
- T.assert(bpe===u.BYTES_PER_ELEMENT).
- assert(s!==u).
- assert(w.heapForSize(u.constructor) === u);
- }
- }
- // alloc(), realloc(), allocFromTypedArray()
- {
- let m = w.alloc(14);
- let m2 = w.realloc(m, 16);
- T.assert(m === m2/* because of alignment */);
- T.assert(0 === w.realloc(m, 0));
- m = m2 = 0;
- // Check allocation limits and allocator's responses...
- T.assert('number' === typeof sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE);
- if(!sqlite3.config.useStdAlloc){
- const tooMuch = sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE + 1,
- isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
- T.mustThrowMatching(()=>w.alloc(tooMuch), isAllocErr)
- .assert(0 === w.alloc.impl(tooMuch))
- .mustThrowMatching(()=>w.realloc(0, tooMuch), isAllocErr)
- .assert(0 === w.realloc.impl(0, tooMuch));
- }
- // Check allocFromTypedArray()...
- const byteList = [11,22,33]
- const u = new Uint8Array(byteList);
- m = w.allocFromTypedArray(u);
- for(let i = 0; i < u.length; ++i){
- T.assert(u[i] === byteList[i])
- .assert(u[i] === w.peek8(m + i));
- }
- w.dealloc(m);
- m = w.allocFromTypedArray(u.buffer);
- for(let i = 0; i < u.length; ++i){
- T.assert(u[i] === byteList[i])
- .assert(u[i] === w.peek8(m + i));
- }
- w.dealloc(m);
- T.mustThrowMatching(
- ()=>w.allocFromTypedArray(1),
- 'Value is not of a supported TypedArray type.'
- );
- }
- { // Test peekXYZ()/pokeXYZ()...
- const m = w.alloc(8);
- T.assert( 17 === w.poke8(m,17).peek8(m) )
- .assert( 31987 === w.poke16(m,31987).peek16(m) )
- .assert( 345678 === w.poke32(m,345678).peek32(m) )
- .assert(
- T.eqApprox( 345678.9, w.poke32f(m,345678.9).peek32f(m) )
- ).assert(
- T.eqApprox( 4567890123.4, w.poke64f(m, 4567890123.4).peek64f(m) )
- );
- if(w.bigIntEnabled){
- T.assert(
- BigInt(Number.MAX_SAFE_INTEGER) ===
- w.poke64(m, Number.MAX_SAFE_INTEGER).peek64(m)
- );
- }
- w.dealloc(m);
- }
- // isPtr32()
- {
- const ip = w.isPtr32;
- T.assert(ip(0))
- .assert(!ip(-1))
- .assert(!ip(1.1))
- .assert(!ip(0xffffffff))
- .assert(ip(0x7fffffff))
- .assert(!ip())
- .assert(!ip(null)/*might change: under consideration*/)
- ;
- }
- //log("jstrlen()...");
- {
- T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc"));
- }
- //log("jstrcpy()...");
- {
- const fillChar = 10;
- let ua = new Uint8Array(8), rc,
- refill = ()=>ua.fill(fillChar);
- refill();
- rc = w.jstrcpy("hello", ua);
- T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]);
- refill();
- ua[5] = chr('!');
- rc = w.jstrcpy("HELLO", ua, 0, -1, false);
- T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]);
- refill();
- rc = w.jstrcpy("the end", ua, 4);
- //log("rc,ua",rc,ua);
- T.assert(4===rc).assert(0===ua[7]).
- assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
- refill();
- rc = w.jstrcpy("the end", ua, 4, -1, false);
- T.assert(4===rc).assert(chr(' ')===ua[7]).
- assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
- refill();
- rc = w.jstrcpy("", ua, 0, 1, true);
- //log("rc,ua",rc,ua);
- T.assert(1===rc).assert(0===ua[0]);
- refill();
- rc = w.jstrcpy("x", ua, 0, 1, true);
- //log("rc,ua",rc,ua);
- T.assert(1===rc).assert(0===ua[0]);
- refill();
- rc = w.jstrcpy('äbä', ua, 0, 1, true);
- T.assert(1===rc, 'Must not write partial multi-byte char.')
- .assert(0===ua[0]);
- refill();
- rc = w.jstrcpy('äbä', ua, 0, 2, true);
- T.assert(1===rc, 'Must not write partial multi-byte char.')
- .assert(0===ua[0]);
- refill();
- rc = w.jstrcpy('äbä', ua, 0, 2, false);
- T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]);
- }/*jstrcpy()*/
- //log("cstrncpy()...");
- {
- const scope = w.scopedAllocPush();
- try {
- let cStr = w.scopedAllocCString("hello");
- const n = w.cstrlen(cStr);
- let cpy = w.scopedAlloc(n+10);
- let rc = w.cstrncpy(cpy, cStr, n+10);
- T.assert(n+1 === rc).
- assert("hello" === w.cstrToJs(cpy)).
- assert(chr('o') === w.peek8(cpy+n-1)).
- assert(0 === w.peek8(cpy+n));
- let cStr2 = w.scopedAllocCString("HI!!!");
- rc = w.cstrncpy(cpy, cStr2, 3);
- T.assert(3===rc).
- assert("HI!lo" === w.cstrToJs(cpy)).
- assert(chr('!') === w.peek8(cpy+2)).
- assert(chr('l') === w.peek8(cpy+3));
- }finally{
- w.scopedAllocPop(scope);
- }
- }
- //log("jstrToUintArray()...");
- {
- let a = w.jstrToUintArray("hello", false);
- T.assert(5===a.byteLength).assert(chr('o')===a[4]);
- a = w.jstrToUintArray("hello", true);
- T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]);
- a = w.jstrToUintArray("äbä", false);
- T.assert(5===a.byteLength).assert(chr('b')===a[2]);
- a = w.jstrToUintArray("äbä", true);
- T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]);
- }
- //log("allocCString()...");
- {
- const jstr = "hällo, world!";
- const [cstr, n] = w.allocCString(jstr, true);
- T.assert(14 === n)
- .assert(0===w.peek8(cstr+n))
- .assert(chr('!')===w.peek8(cstr+n-1));
- w.dealloc(cstr);
- }
- //log("scopedAlloc() and friends...");
- {
- const alloc = w.alloc, dealloc = w.dealloc;
- w.alloc = w.dealloc = null;
- T.assert(!w.scopedAlloc.level)
- .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
- .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
- w.alloc = alloc;
- T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
- w.dealloc = dealloc;
- T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/)
- .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
- .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/);
- const asc = w.scopedAllocPush();
- let asc2;
- try {
- const p1 = w.scopedAlloc(16),
- p2 = w.scopedAlloc(16);
- T.assert(1===w.scopedAlloc.level)
- .assert(Number.isFinite(p1))
- .assert(Number.isFinite(p2))
- .assert(asc[0] === p1)
- .assert(asc[1]===p2);
- asc2 = w.scopedAllocPush();
- const p3 = w.scopedAlloc(16);
- T.assert(2===w.scopedAlloc.level)
- .assert(Number.isFinite(p3))
- .assert(2===asc.length)
- .assert(p3===asc2[0]);
- const [z1, z2, z3] = w.scopedAllocPtr(3);
- T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2)
- .assert(0===w.peek32(z1), 'allocPtr() must zero the targets')
- .assert(0===w.peek32(z3));
- }finally{
- // Pop them in "incorrect" order to make sure they behave:
- w.scopedAllocPop(asc);
- T.assert(0===asc.length);
- T.mustThrowMatching(()=>w.scopedAllocPop(asc),
- /^Invalid state object/);
- if(asc2){
- T.assert(2===asc2.length,'Should be p3 and z1');
- w.scopedAllocPop(asc2);
- T.assert(0===asc2.length);
- T.mustThrowMatching(()=>w.scopedAllocPop(asc2),
- /^Invalid state object/);
- }
- }
- T.assert(0===w.scopedAlloc.level);
- w.scopedAllocCall(function(){
- T.assert(1===w.scopedAlloc.level);
- const [cstr, n] = w.scopedAllocCString("hello, world", true);
- T.assert(12 === n)
- .assert(0===w.peek8(cstr+n))
- .assert(chr('d')===w.peek8(cstr+n-1));
- });
- }/*scopedAlloc()*/
- //log("xCall()...");
- {
- const pJson = w.xCall('sqlite3__wasm_enum_json');
- T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
- }
- //log("xWrap()...");
- {
- T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'),
- /requires 0 arg/).
- assert(w.xWrap.resultAdapter('i32') instanceof Function).
- assert(w.xWrap.argAdapter('i32') instanceof Function);
- let fw = w.xWrap('sqlite3_libversion','utf8');
- T.mustThrowMatching(()=>fw(1), /requires 0 arg/);
- let rc = fw();
- T.assert('string'===typeof rc).assert(rc.length>5);
- rc = w.xCallWrapped('sqlite3__wasm_enum_json','*');
- T.assert(rc>0 && Number.isFinite(rc));
- rc = w.xCallWrapped('sqlite3__wasm_enum_json','utf8');
- T.assert('string'===typeof rc).assert(rc.length>300);
- { // 'string:static' argAdapter() sanity checks...
- let argAd = w.xWrap.argAdapter('string:static');
- let p0 = argAd('foo'), p1 = argAd('bar');
- T.assert(w.isPtr(p0) && w.isPtr(p1))
- .assert(p0 !== p1)
- .assert(p0 === argAd('foo'))
- .assert(p1 === argAd('bar'));
- }
- // 'string:flexible' argAdapter() sanity checks...
- w.scopedAllocCall(()=>{
- const argAd = w.xWrap.argAdapter('string:flexible');
- const cj = (v)=>w.cstrToJs(argAd(v));
- T.assert('Hi' === cj('Hi'))
- .assert('hi' === cj(['h','i']))
- .assert('HI' === cj(new Uint8Array([72, 73])));
- });
- // jsFuncToWasm()
- {
- const fsum3 = (x,y,z)=>x+y+z;
- fw = w.jsFuncToWasm('i(iii)', fsum3);
- T.assert(fw instanceof Function)
- .assert( fsum3 !== fw )
- .assert( 3 === fw.length )
- .assert( 6 === fw(1,2,3) );
- T.mustThrowMatching( ()=>w.jsFuncToWasm('x()', function(){}),
- 'Invalid signature letter: x');
- }
- // xWrap(Function,...)
- {
- let fp;
- try {
- const fmy = function fmy(i,s,d){
- if(fmy.debug) log("fmy(",...arguments,")");
- T.assert( 3 === i )
- .assert( w.isPtr(s) )
- .assert( w.cstrToJs(s) === 'a string' )
- .assert( T.eqApprox(1.2, d) );
- return w.allocCString("hi");
- };
- fmy.debug = false;
- const xwArgs = ['string:dealloc', ['i32', 'string', 'f64']];
- fw = w.xWrap(fmy, ...xwArgs);
- const fmyArgs = [3, 'a string', 1.2];
- let rc = fw(...fmyArgs);
- T.assert( 'hi' === rc );
- if(0){
- /* Retain this as a "reminder to self"...
- This extra level of indirection does not work: the
- string argument is ending up as a null in fmy() but
- the numeric arguments are making their ways through
- What's happening is: installFunction() is creating a
- WASM-compatible function instance. When we pass a JS string
- into there it's getting coerced into `null` before being passed
- on to the lower-level wrapper.
- */
- fmy.debug = true;
- fp = wasm.installFunction('i(isd)', fw);
- fw = w.functionEntry(fp);
- rc = fw(...fmyArgs);
- log("rc =",rc);
- T.assert( 'hi' === rc );
- // Similarly, this does not work:
- //let fpw = w.xWrap(fp, null, [null,null,null]);
- //rc = fpw(...fmyArgs);
- //log("rc =",rc);
- //T.assert( 'hi' === rc );
- }
- }finally{
- wasm.uninstallFunction(fp);
- }
- }
- if(haveWasmCTests()){
- if(!sqlite3.config.useStdAlloc){
- fw = w.xWrap('sqlite3__wasm_test_str_hello', 'utf8:dealloc',['i32']);
- rc = fw(0);
- T.assert('hello'===rc);
- rc = fw(1);
- T.assert(null===rc);
- }
- if(w.bigIntEnabled){
- w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v));
- w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v));
- fw = w.xWrap('sqlite3__wasm_test_int64_times2','thrice','twice');
- rc = fw(1);
- T.assert(12n===rc);
- w.scopedAllocCall(function(){
- const pI1 = w.scopedAlloc(8), pI2 = pI1+4;
- w.pokePtr([pI1, pI2], 0);
- const f = w.xWrap('sqlite3__wasm_test_int64_minmax',undefined,['i64*','i64*']);
- const [r1, r2] = w.peek64([pI1, pI2]);
- T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
- });
- }
- }
- }/*xWrap()*/
- }/*WhWasmUtil*/)
- ////////////////////////////////////////////////////////////////////
- .t({
- name: 'sqlite3.StructBinder (jaccwabyt🐇)',
- predicate: (sqlite3)=>!!sqlite3.wasm.exports.sqlite3__wasm_test_struct
- || "Built without SQLITE_WASM_ENABLE_C_TESTS",
- test: function(sqlite3){
- const S = sqlite3, W = S.wasm;
- const MyStructDef = {
- sizeof: 16,
- members: {
- p4: {offset: 0, sizeof: 4, signature: "i"},
- pP: {offset: 4, sizeof: 4, signature: "P"},
- ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true},
- cstr: {offset: 12, sizeof: 4, signature: "s"}
- }
- };
- if(W.bigIntEnabled){
- const m = MyStructDef;
- m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"};
- m.sizeof += m.members.p8.sizeof;
- }
- const StructType = S.StructBinder.StructType;
- const K = S.StructBinder('my_struct',MyStructDef);
- T.mustThrowMatching(()=>K(), /via 'new'/).
- mustThrowMatching(()=>new K('hi'), /^Invalid pointer/);
- const k1 = new K(), k2 = new K();
- try {
- T.assert(k1.constructor === K).
- assert(K.isA(k1)).
- assert(k1 instanceof K).
- assert(K.prototype.lookupMember('p4').key === '$p4').
- assert(K.prototype.lookupMember('$p4').name === 'p4').
- mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/).
- assert(undefined === K.prototype.lookupMember('nope',false)).
- assert(k1 instanceof StructType).
- assert(StructType.isA(k1)).
- mustThrowMatching(()=>k1.$ro = 1, /read-only/);
- Object.keys(MyStructDef.members).forEach(function(key){
- key = K.memberKey(key);
- T.assert(0 == k1[key],
- "Expecting allocation to zero the memory "+
- "for "+key+" but got: "+k1[key]+
- " from "+k1.memoryDump());
- });
- T.assert('number' === typeof k1.pointer).
- mustThrowMatching(()=>k1.pointer = 1, /pointer/);
- k1.$p4 = 1; k1.$pP = 2;
- T.assert(1 === k1.$p4).assert(2 === k1.$pP);
- if(MyStructDef.members.$p8){
- k1.$p8 = 1/*must not throw despite not being a BigInt*/;
- k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2);
- T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8);
- }
- T.assert(!k1.ondispose);
- k1.setMemberCString('cstr', "A C-string.");
- T.assert(Array.isArray(k1.ondispose)).
- assert(k1.ondispose[0] === k1.$cstr).
- assert('number' === typeof k1.$cstr).
- assert('A C-string.' === k1.memberToJsString('cstr'));
- k1.$pP = k2;
- T.assert(k1.$pP === k2.pointer);
- k1.$pP = null/*null is special-cased to 0.*/;
- T.assert(0===k1.$pP);
- let ptr = k1.pointer;
- k1.dispose();
- T.assert(undefined === k1.pointer).
- mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
- }finally{
- k1.dispose();
- k2.dispose();
- }
- if(!W.bigIntEnabled){
- log("Skipping WasmTestStruct tests: BigInt not enabled.");
- return;
- }
- const WTStructDesc =
- W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0];
- const autoResolvePtr = true /* EXPERIMENTAL */;
- if(autoResolvePtr){
- WTStructDesc.members.ppV.signature = 'P';
- }
- const WTStruct = S.StructBinder(WTStructDesc);
- //log(WTStruct.structName, WTStruct.structInfo);
- const wts = new WTStruct();
- //log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype));
- try{
- T.assert(wts.constructor === WTStruct).
- assert(WTStruct.memberKeys().indexOf('$ppV')>=0).
- assert(wts.memberKeys().indexOf('$v8')>=0).
- assert(!K.isA(wts)).
- assert(WTStruct.isA(wts)).
- assert(wts instanceof WTStruct).
- assert(wts instanceof StructType).
- assert(StructType.isA(wts)).
- assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
- assert(0===wts.$ppV).assert(0===wts.$xFunc);
- const testFunc =
- W.xGet('sqlite3__wasm_test_struct'/*name gets mangled in -O3 builds!*/);
- let counter = 0;
- //log("wts.pointer =",wts.pointer);
- const wtsFunc = function(arg){
- /*log("This from a JS function called from C, "+
- "which itself was called from JS. arg =",arg);*/
- ++counter;
- if(3===counter){
- tossQuietly("Testing exception propagation.");
- }
- }
- wts.$v4 = 10; wts.$v8 = 20;
- wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc'))
- T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8)
- .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc)
- .assert(0 === wts.$cstr)
- .assert(wts.memberIsString('$cstr'))
- .assert(!wts.memberIsString('$v4'))
- .assert(null === wts.memberToJsString('$cstr'))
- .assert(W.functionEntry(wts.$xFunc) instanceof Function);
- /* It might seem silly to assert that the values match
- what we just set, but recall that all of those property
- reads and writes are, via property interceptors,
- actually marshaling their data to/from a raw memory
- buffer, so merely reading them back is actually part of
- testing the struct-wrapping API. */
- testFunc(wts.pointer);
- //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV);
- T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8)
- .assert(wts.$ppV === wts.pointer)
- .assert('string' === typeof wts.memberToJsString('cstr'))
- .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr'))
- .mustThrowMatching(()=>wts.memberToJsString('xFunc'),
- /Invalid member type signature for C-string/)
- ;
- testFunc(wts.pointer);
- T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8)
- .assert(wts.$ppV === wts.pointer);
- /** The 3rd call to wtsFunc throw from JS, which is called
- from C, which is called from JS. Let's ensure that
- that exception propagates back here... */
- T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/);
- W.uninstallFunction(wts.$xFunc);
- wts.$xFunc = 0;
- wts.$ppV = 0;
- T.assert(!wts.$ppV);
- //WTStruct.debugFlags(0x03);
- wts.$ppV = wts;
- T.assert(wts.pointer === wts.$ppV)
- wts.setMemberCString('cstr', "A C-string.");
- T.assert(Array.isArray(wts.ondispose)).
- assert(wts.ondispose[0] === wts.$cstr).
- assert('A C-string.' === wts.memberToJsString('cstr'));
- const ptr = wts.pointer;
- wts.dispose();
- T.assert(ptr).assert(undefined === wts.pointer);
- }finally{
- wts.dispose();
- }
- if(1){ // ondispose of other struct instances
- const s1 = new WTStruct, s2 = new WTStruct, s3 = new WTStruct;
- T.assert(s1.lookupMember instanceof Function)
- .assert(s1.addOnDispose instanceof Function);
- s1.addOnDispose(s2,"testing variadic args");
- T.assert(2===s1.ondispose.length);
- s2.addOnDispose(s3);
- s1.dispose();
- T.assert(!s2.pointer,"Expecting s2 to be ondispose'd by s1.");
- T.assert(!s3.pointer,"Expecting s3 to be ondispose'd by s2.");
- }
- }
- }/*StructBinder*/)
- ////////////////////////////////////////////////////////////////////
- .t('sqlite3.wasm.pstack', function(sqlite3){
- const P = wasm.pstack;
- const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
- const stack = P.pointer;
- T.assert(0===stack % 8 /* must be 8-byte aligned */);
- try{
- const remaining = P.remaining;
- T.assert(P.quota >= 4096)
- .assert(remaining === P.quota)
- .mustThrowMatching(()=>P.alloc(0), isAllocErr)
- .mustThrowMatching(()=>P.alloc(-1), isAllocErr)
- .mustThrowMatching(
- ()=>P.alloc('i33'),
- (e)=>e instanceof sqlite3.WasmAllocError
- );
- ;
- let p1 = P.alloc(12);
- T.assert(p1 === stack - 16/*8-byte aligned*/)
- .assert(P.pointer === p1);
- let p2 = P.alloc(7);
- T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/)
- .mustThrowMatching(()=>P.alloc(remaining), isAllocErr)
- .assert(24 === stack - p2)
- .assert(P.pointer === p2);
- let n = remaining - (stack - p2);
- let p3 = P.alloc(n);
- T.assert(p3 === stack-remaining)
- .mustThrowMatching(()=>P.alloc(1), isAllocErr);
- }finally{
- P.restore(stack);
- }
- T.assert(P.pointer === stack);
- try {
- const [p1, p2, p3] = P.allocChunks(3,'i32');
- T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/)
- .assert(p2 === p1 + 4)
- .assert(p3 === p2 + 4);
- T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16),
- (e)=>e instanceof sqlite3.WasmAllocError)
- }finally{
- P.restore(stack);
- }
- T.assert(P.pointer === stack);
- try {
- let [p1, p2, p3] = P.allocPtr(3,false);
- let sPos = stack-16/*always rounded to multiple of 8*/;
- T.assert(P.pointer === sPos)
- .assert(p2 === p1 + 4)
- .assert(p3 === p2 + 4);
- [p1, p2, p3] = P.allocPtr(3);
- T.assert(P.pointer === sPos-24/*3 x 8 bytes*/)
- .assert(p2 === p1 + 8)
- .assert(p3 === p2 + 8);
- p1 = P.allocPtr();
- T.assert('number'===typeof p1);
- }finally{
- P.restore(stack);
- }
- }/*pstack tests*/)
- ////////////////////////////////////////////////////////////////////
- ;/*end of C/WASM utils checks*/
- T.g('sqlite3_randomness()')
- .t('To memory buffer', function(sqlite3){
- const stack = wasm.pstack.pointer;
- try{
- const n = 520;
- const p = wasm.pstack.alloc(n);
- T.assert(0===wasm.peek8(p))
- .assert(0===wasm.peek8(p+n-1));
- T.assert(undefined === capi.sqlite3_randomness(n - 10, p));
- let j, check = 0;
- const heap = wasm.heap8u();
- for(j = 0; j < 10 && 0===check; ++j){
- check += heap[p + j];
- }
- T.assert(check > 0);
- check = 0;
- // Ensure that the trailing bytes were not modified...
- for(j = n - 10; j < n && 0===check; ++j){
- check += heap[p + j];
- }
- T.assert(0===check);
- }finally{
- wasm.pstack.restore(stack);
- }
- })
- .t('To byte array', function(sqlite3){
- const ta = new Uint8Array(117);
- let i, n = 0;
- for(i=0; i<ta.byteLength && 0===n; ++i){
- n += ta[i];
- }
- T.assert(0===n)
- .assert(ta === capi.sqlite3_randomness(ta));
- for(i=ta.byteLength-10; i<ta.byteLength && 0===n; ++i){
- n += ta[i];
- }
- T.assert(n>0);
- const t0 = new Uint8Array(0);
- T.assert(t0 === capi.sqlite3_randomness(t0),
- "0-length array is a special case");
- })
- ;/*end sqlite3_randomness() checks*/
- ////////////////////////////////////////////////////////////////////////
- T.g('sqlite3.oo1')
- .t({
- name:'Create db',
- //predicate: (sqlite3)=>
- test: function(sqlite3){
- const dbFile = '/tester1.db';
- sqlite3.util.sqlite3__wasm_vfs_unlink(0, dbFile);
- const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c');
- db.onclose = {
- disposeAfter: [],
- disposeBefore: [
- (db)=>{
- //console.debug("db.onclose.before dropping modules");
- //sqlite3.capi.sqlite3_drop_modules(db.pointer, 0);
- }
- ],
- before: function(db){
- while(this.disposeBefore.length){
- const v = this.disposeBefore.shift();
- console.debug("db.onclose.before cleaning up:",v);
- if(wasm.isPtr(v)) wasm.dealloc(v);
- else if(v instanceof sqlite3.StructBinder.StructType){
- v.dispose();
- }else if(v instanceof Function){
- try{ v(db) } catch(e){
- console.warn("beforeDispose() callback threw:",e);
- }
- }
- }
- },
- after: function(){
- while(this.disposeAfter.length){
- const v = this.disposeAfter.shift();
- console.debug("db.onclose.after cleaning up:",v);
- if(wasm.isPtr(v)) wasm.dealloc(v);
- else if(v instanceof sqlite3.StructBinder.StructType){
- v.dispose();
- }else if(v instanceof Function){
- try{v()} catch(e){/*ignored*/}
- }
- }
- }
- };
- T.assert(wasm.isPtr(db.pointer))
- .mustThrowMatching(()=>db.pointer=1, /read-only/)
- .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
- .assert('main'===db.dbName(0))
- .assert('string' === typeof db.dbVfsName())
- .assert(db.pointer === wasm.xWrap.testConvertArg('sqlite3*',db));
- // Custom db error message handling via sqlite3_prepare_v2/v3()
- let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null);
- T.assert(capi.SQLITE_MISUSE === rc)
- .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL"))
- .assert(dbFile === db.dbFilename())
- .assert(!db.dbFilename('nope'));
- //Sanity check DB.checkRc()...
- let ex;
- try{db.checkRc(rc)}
- catch(e){ex = e}
- T.assert(ex instanceof sqlite3.SQLite3Error)
- .assert(capi.SQLITE_MISUSE===ex.resultCode)
- .assert(0===ex.message.indexOf("SQLITE_MISUSE: sqlite3 result code"))
- .assert(ex.message.indexOf("Invalid SQL")>0);
- T.assert(db === db.checkRc(0))
- .assert(db === sqlite3.oo1.DB.checkRc(db,0))
- .assert(null === sqlite3.oo1.DB.checkRc(null,0));
- this.progressHandlerCount = 0;
- if( wasm.compileOptionUsed('OMIT_PROGRESS_CALLBACK') ){
- T.assert( !capi.sqlite3_progress_handler );
- }else{
- T.assert( !!capi.sqlite3_progress_handler );
- capi.sqlite3_progress_handler(db, 5, (p)=>{
- ++this.progressHandlerCount;
- return 0;
- }, 0);
- }
- }
- })
- ////////////////////////////////////////////////////////////////////
- .t('sqlite3_db_config() and sqlite3_db_status()', function(sqlite3){
- let rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, 0, 0);
- T.assert(0===rc);
- rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAX+1, 0);
- T.assert(capi.SQLITE_MISUSE === rc);
- rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAINDBNAME, "main");
- T.assert(0 === rc);
- const stack = wasm.pstack.pointer;
- try {
- const [pCur, pHi] = wasm.pstack.allocChunks(2,'i64');
- wasm.poke32([pCur, pHi], 0);
- let [vCur, vHi] = wasm.peek32(pCur, pHi);
- T.assert(0===vCur).assert(0===vHi);
- rc = capi.sqlite3_status(capi.SQLITE_STATUS_MEMORY_USED,
- pCur, pHi, 0);
- [vCur, vHi] = wasm.peek32(pCur, pHi);
- //console.warn("i32 vCur,vHi",vCur,vHi);
- T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur);
- if(wasm.bigIntEnabled){
- // Again in 64-bit. Recall that pCur and pHi are allocated
- // large enough to account for this re-use.
- wasm.poke64([pCur, pHi], 0);
- rc = capi.sqlite3_status64(capi.SQLITE_STATUS_MEMORY_USED,
- pCur, pHi, 0);
- [vCur, vHi] = wasm.peek64([pCur, pHi]);
- //console.warn("i64 vCur,vHi",vCur,vHi);
- T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur);
- }
- }finally{
- wasm.pstack.restore(stack);
- }
- })
- ////////////////////////////////////////////////////////////////////
- .t('DB.Stmt', function(sqlite3){
- let st = this.db.prepare(
- new TextEncoder('utf-8').encode("select 3 as a")
- );
- let rc;
- try {
- T.assert(wasm.isPtr(st.pointer))
- .mustThrowMatching(()=>st.pointer=1, /read-only/)
- .assert(1===this.db.openStatementCount())
- .assert(
- capi.sqlite3_stmt_status(
- st, capi.SQLITE_STMTSTATUS_RUN, 0
- ) === 0)
- .assert(!st._mayGet)
- .assert('a' === st.getColumnName(0))
- .mustThrowMatching(()=>st.columnCount=2,
- /columnCount property is read-only/)
- .assert(1===st.columnCount)
- .assert(0===st.parameterCount)
- .mustThrow(()=>st.bind(1,null))
- .assert(true===st.step())
- .assert(3 === st.get(0))
- .mustThrow(()=>st.get(1))
- .mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER))
- .assert(3 === st.get(0,capi.SQLITE_INTEGER))
- .assert(3 === st.getInt(0))
- .assert('3' === st.get(0,capi.SQLITE_TEXT))
- .assert('3' === st.getString(0))
- .assert(3.0 === st.get(0,capi.SQLITE_FLOAT))
- .assert(3.0 === st.getFloat(0))
- .assert(3 === st.get({}).a)
- .assert(3 === st.get([])[0])
- .assert(3 === st.getJSON(0))
- .assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array)
- .assert(1===st.get(0,capi.SQLITE_BLOB).length)
- .assert(st.getBlob(0) instanceof Uint8Array)
- .assert('3'.charCodeAt(0) === st.getBlob(0)[0])
- .assert(st._mayGet)
- .assert(false===st.step())
- .assert(!st._mayGet)
- .assert(
- capi.sqlite3_stmt_status(
- st, capi.SQLITE_STMTSTATUS_RUN, 0
- ) > 0);
- T.assert(this.progressHandlerCount>0
- || wasm.compileOptionUsed('OMIT_PROGRESS_CALLBACK'),
- "Expecting progress callback.").
- assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
- assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
- assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
- assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
- }finally{
- rc = st.finalize();
- }
- T.assert(!st.pointer)
- .assert(0===this.db.openStatementCount())
- .assert(0===rc);
- T.mustThrowMatching(()=>new sqlite3.oo1.Stmt("hi"), function(err){
- return (err instanceof sqlite3.SQLite3Error)
- && capi.SQLITE_MISUSE === err.resultCode
- && 0 < err.message.indexOf("Do not call the Stmt constructor directly.")
- });
- })
- ////////////////////////////////////////////////////////////////////////
- .t('sqlite3_js_...()', function(){
- const db = this.db;
- if(1){
- const vfsList = capi.sqlite3_js_vfs_list();
- T.assert(vfsList.length>1);
- wasm.scopedAllocCall(()=>{
- const vfsArg = (v)=>wasm.xWrap.testConvertArg('sqlite3_vfs*',v);
- for(const v of vfsList){
- T.assert('string' === typeof v);
- const pVfs = capi.sqlite3_vfs_find(v);
- T.assert(wasm.isPtr(pVfs))
- .assert(pVfs===vfsArg(v));
- const vfs = new capi.sqlite3_vfs(pVfs);
- try { T.assert(vfsArg(vfs)===pVfs) }
- finally{ vfs.dispose() }
- }
- });
- }
- /**
- Trivia: the magic db name ":memory:" does not actually use the
- "memdb" VFS unless "memdb" is _explicitly_ provided as the VFS
- name. Instead, it uses the default VFS with an in-memory btree.
- Thus this.db's VFS may not be memdb even though it's an in-memory
- db.
- */
- const pVfsMem = capi.sqlite3_vfs_find('memdb'),
- pVfsDflt = capi.sqlite3_vfs_find(0),
- pVfsDb = capi.sqlite3_js_db_vfs(db.pointer);
- T.assert(pVfsMem > 0)
- .assert(pVfsDflt > 0)
- .assert(pVfsDb > 0)
- .assert(pVfsMem !== pVfsDflt
- /* memdb lives on top of the default vfs */)
- .assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem)
- ;
- /*const vMem = new capi.sqlite3_vfs(pVfsMem),
- vDflt = new capi.sqlite3_vfs(pVfsDflt),
- vDb = new capi.sqlite3_vfs(pVfsDb);*/
- const duv = capi.sqlite3_js_db_uses_vfs;
- T.assert(pVfsDflt === duv(db.pointer, 0)
- || pVfsMem === duv(db.pointer,0))
- .assert(!duv(db.pointer, "foo"))
- ;
- }/*sqlite3_js_...()*/)
- ////////////////////////////////////////////////////////////////////
- .t('Table t', function(sqlite3){
- const db = this.db;
- let list = [];
- this.progressHandlerCount = 0;
- let rc = db.exec({
- sql:['CREATE TABLE t(a,b);',
- // ^^^ using TEMP TABLE breaks the db export test
- "INSERT INTO t(a,b) VALUES(1,2),(3,4),",
- "(?,?)"/*intentionally missing semicolon to test for
- off-by-one bug in string-to-WASM conversion*/],
- saveSql: list,
- bind: [5,6]
- });
- //debug("Exec'd SQL:", list);
- T.assert(rc === db)
- .assert(2 === list.length)
- .assert('string'===typeof list[1])
- .assert(3===db.changes())
- .assert(this.progressHandlerCount > 0
- || wasm.compileOptionUsed('OMIT_PROGRESS_CALLBACK'),
- "Expecting progress callback.")
- if(wasm.bigIntEnabled){
- T.assert(3n===db.changes(false,true));
- }
- rc = db.exec({
- sql: "INSERT INTO t(a,b) values('blob',X'6869') RETURNING 13",
- rowMode: 0
- });
- T.assert(Array.isArray(rc))
- .assert(1===rc.length)
- .assert(13 === rc[0])
- .assert(1===db.changes());
- let vals = db.selectValues('select a from t order by a limit 2');
- T.assert( 2 === vals.length )
- .assert( 1===vals[0] && 3===vals[1] );
- vals = db.selectValues('select a from t order by a limit $L',
- {$L:2}, capi.SQLITE_TEXT);
- T.assert( 2 === vals.length )
- .assert( '1'===vals[0] && '3'===vals[1] );
- vals = undefined;
- let blob = db.selectValue("select b from t where a='blob'");
- T.assert(blob instanceof Uint8Array).
- assert(0x68===blob[0] && 0x69===blob[1]);
- blob = null;
- let counter = 0, colNames = [];
- list.length = 0;
- db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{
- rowMode: 'object',
- resultRows: list,
- columnNames: colNames,
- _myState: 3 /* Accessible from the callback */,
- callback: function(row,stmt){
- ++counter;
- T.assert(
- 3 === this._myState
- /* Recall that "this" is the options object. */
- ).assert(
- this.columnNames===colNames
- ).assert(
- this.columnNames[0]==='a' && this.columnNames[1]==='b'
- ).assert(
- (row.a%2 && row.a<6) || 'blob'===row.a
- );
- }
- });
- T.assert(2 === colNames.length)
- .assert('a' === colNames[0])
- .assert(4 === counter)
- .assert(4 === list.length);
- colNames = [];
- db.exec({
- /* Ensure that columnNames is populated for empty result sets. */
- sql: "SELECT a a, b B FROM t WHERE 0",
- columnNames: colNames
- });
- T.assert(2===colNames.length)
- .assert('a'===colNames[0] && 'B'===colNames[1]);
- list.length = 0;
- db.exec("SELECT a a, b b FROM t",{
- rowMode: 'array',
- callback: function(row,stmt){
- ++counter;
- T.assert(Array.isArray(row))
- .assert((0===row[1]%2 && row[1]<7)
- || (row[1] instanceof Uint8Array));
- }
- });
- T.assert(8 === counter);
- T.assert(Number.MIN_SAFE_INTEGER ===
- db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)).
- assert(Number.MAX_SAFE_INTEGER ===
- db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER));
- counter = 0;
- let rv = db.exec({
- sql: "SELECT a FROM t",
- callback: ()=>(1===++counter),
- });
- T.assert(db === rv)
- .assert(2===counter,
- "Expecting exec step() loop to stop if callback returns false.");
- /** If exec() is passed neither callback nor returnValue but
- is passed an explicit rowMode then the default returnValue
- is the whole result set, as if an empty resultRows option
- had been passed. */
- rv = db.exec({
- sql: "SELECT -1 UNION ALL SELECT -2 UNION ALL SELECT -3 ORDER BY 1 DESC",
- rowMode: 0
- });
- T.assert(Array.isArray(rv)).assert(3===rv.length)
- .assert(-1===rv[0]).assert(-3===rv[2]);
- rv = db.exec("SELECT 1 WHERE 0",{rowMode: 0});
- T.assert(Array.isArray(rv)).assert(0===rv.length);
- if(wasm.bigIntEnabled && haveWasmCTests()){
- const mI = wasm.xCall('sqlite3__wasm_test_int64_max');
- const b = BigInt(Number.MAX_SAFE_INTEGER * 2);
- T.assert(b === db.selectValue("SELECT "+b)).
- assert(b === db.selectValue("SELECT ?", b)).
- assert(mI == db.selectValue("SELECT $x", {$x:mI}));
- }else{
- /* Curiously, the JS spec seems to be off by one with the definitions
- of MIN/MAX_SAFE_INTEGER:
- https://github.com/emscripten-core/emscripten/issues/17391 */
- T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))).
- mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1)));
- }
- let st = db.prepare("update t set b=:b where a='blob'");
- try {
- T.assert(0===st.columnCount)
- .assert( false===st.isReadOnly() );
- const ndx = st.getParamIndex(':b');
- T.assert(1===ndx);
- st.bindAsBlob(ndx, "ima blob")
- /*step() skipped intentionally*/.reset(true);
- } finally {
- T.assert(0===st.finalize())
- .assert(undefined===st.finalize());
- }
- try {
- db.prepare("/*empty SQL*/");
- toss("Must not be reached.");
- }catch(e){
- T.assert(e instanceof sqlite3.SQLite3Error)
- .assert(0==e.message.indexOf('Cannot prepare empty'));
- }
- counter = 0;
- db.exec({
- // Check for https://sqlite.org/forum/forumpost/895425b49a
- sql: "pragma table_info('t')",
- rowMode: 'object',
- callback: function(row){
- ++counter;
- T.assert(row.name==='a' || row.name==='b');
- }
- });
- T.assert(2===counter);
- })/*setup table T*/
- ////////////////////////////////////////////////////////////////////
- .t({
- name: "sqlite3_set_authorizer()",
- predicate: ()=>!!wasm.exports.sqlite3_set_authorizer || "Missing sqlite3_set_authorizer()",
- test:function(sqlite3){
- T.assert(capi.SQLITE_IGNORE>0)
- .assert(capi.SQLITE_DENY>0);
- const db = this.db;
- const ssa = capi.sqlite3_set_authorizer;
- const n = db.selectValue('select count(*) from t');
- T.assert(n>0);
- let authCount = 0;
- let rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
- ++authCount;
- return capi.SQLITE_IGNORE;
- }, 0);
- T.assert(0===rc)
- .assert(
- undefined === db.selectValue('select count(*) from t')
- /* Note that the count() never runs, so we get undefined
- instead of 0. */
- )
- .assert(authCount>0);
- authCount = 0;
- db.exec("update t set a=-9999");
- T.assert(authCount>0);
- /* Reminder: we don't use DELETE because, from the C API docs:
- "If the action code is [SQLITE_DELETE] and the callback
- returns [SQLITE_IGNORE] then the [DELETE] operation proceeds
- but the [truncate optimization] is disabled and all rows are
- deleted individually."
- */
- rc = ssa(db, null, 0);
- authCount = 0;
- T.assert(-9999 != db.selectValue('select a from t'))
- .assert(0===authCount);
- rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
- ++authCount;
- return capi.SQLITE_DENY;
- }, 0);
- T.assert(0===rc);
- let err;
- try{ db.exec("select 1 from t") }
- catch(e){ err = e }
- T.assert(err instanceof sqlite3.SQLite3Error)
- .assert(err.message.indexOf('not authorized'>0))
- .assert(1===authCount);
- authCount = 0;
- rc = ssa(db, function(...args){
- ++authCount;
- return capi.SQLITE_OK;
- }, 0);
- T.assert(0===rc);
- T.assert(n === db.selectValue('select count(*) from t'))
- .assert(authCount>0);
- authCount = 0;
- rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
- ++authCount;
- throw new Error("Testing catching of authorizer.");
- }, 0);
- T.assert(0===rc);
- authCount = 0;
- err = undefined;
- try{ db.exec("select 1 from t") }
- catch(e){err = e}
- T.assert(err instanceof Error)
- .assert(err.message.indexOf('not authorized')>0)
- /* Note that the thrown message is trumped/overwritten
- by the authorizer process. */
- .assert(1===authCount);
- rc = ssa(db, 0, 0);
- authCount = 0;
- T.assert(0===rc);
- T.assert(n === db.selectValue('select count(*) from t'))
- .assert(0===authCount);
- }
- })/*sqlite3_set_authorizer()*/
- ////////////////////////////////////////////////////////////////////////
- .t("sqlite3_table_column_metadata()", function(sqlite3){
- const stack = wasm.pstack.pointer;
- try{
- const [pzDT, pzColl, pNotNull, pPK, pAuto] =
- wasm.pstack.allocPtr(5);
- const rc = capi.sqlite3_table_column_metadata(
- this.db, "main", "t", "rowid",
- pzDT, pzColl, pNotNull, pPK, pAuto
- );
- T.assert(0===rc)
- .assert("INTEGER"===wasm.cstrToJs(wasm.peekPtr(pzDT)))
- .assert("BINARY"===wasm.cstrToJs(wasm.peekPtr(pzColl)))
- .assert(0===wasm.peek32(pNotNull))
- .assert(1===wasm.peek32(pPK))
- .assert(0===wasm.peek32(pAuto))
- }finally{
- wasm.pstack.restore(stack);
- }
- })
- ////////////////////////////////////////////////////////////////////////
- .t('selectArray/Object()', function(sqlite3){
- const db = this.db;
- let rc = db.selectArray('select a, b from t where a=?', 5);
- T.assert(Array.isArray(rc))
- .assert(2===rc.length)
- .assert(5===rc[0] && 6===rc[1]);
- rc = db.selectArray('select a, b from t where b=-1');
- T.assert(undefined === rc);
- rc = db.selectObject('select a A, b b from t where b=?', 6);
- T.assert(rc && 'object'===typeof rc)
- .assert(5===rc.A)
- .assert(6===rc.b);
- rc = db.selectArray('select a, b from t where b=-1');
- T.assert(undefined === rc);
- })
- ////////////////////////////////////////////////////////////////////////
- .t('selectArrays/Objects()', function(sqlite3){
- const db = this.db;
- const sql = 'select a, b from t where a=? or b=? order by a';
- let rc = db.selectArrays(sql, [1, 4]);
- T.assert(Array.isArray(rc))
- .assert(2===rc.length)
- .assert(2===rc[0].length)
- .assert(1===rc[0][0])
- .assert(2===rc[0][1])
- .assert(3===rc[1][0])
- .assert(4===rc[1][1])
- rc = db.selectArrays(sql, [99,99]);
- T.assert(Array.isArray(rc)).assert(0===rc.length);
- rc = db.selectObjects(sql, [1,4]);
- T.assert(Array.isArray(rc))
- .assert(2===rc.length)
- .assert('object' === typeof rc[1])
- .assert(1===rc[0].a)
- .assert(2===rc[0].b)
- .assert(3===rc[1].a)
- .assert(4===rc[1].b);
- })
- ////////////////////////////////////////////////////////////////////////
- .t('selectArray/Object/Values() via INSERT/UPDATE...RETURNING', function(sqlite3){
- let rc = this.db.selectObject("INSERT INTO t(a,b) VALUES(83,84) RETURNING a as AA");
- T.assert(83===rc.AA);
- rc = this.db.selectArray("UPDATE T set a=85 WHERE a=83 RETURNING b as BB");
- T.assert(Array.isArray(rc)).assert(84===rc[0]);
- //log("select * from t:",this.db.selectObjects("select * from t order by a"));
- rc = this.db.selectValues("UPDATE T set a=a*1 RETURNING a");
- T.assert(Array.isArray(rc))
- .assert(5 === rc.length)
- .assert('number'===typeof rc[0])
- .assert(rc[0]|0 === rc[0] /* is small integer */);
- })
- ////////////////////////////////////////////////////////////////////////
- .t({
- name: 'sqlite3_js_db_export()',
- predicate: ()=>true,
- test: function(sqlite3){
- const db = this.db;
- const xp = capi.sqlite3_js_db_export(db.pointer);
- T.assert(xp instanceof Uint8Array)
- .assert(xp.byteLength>0)
- .assert(0 === xp.byteLength % 512);
- this.dbExport = xp;
- }
- }/*sqlite3_js_db_export()*/)
- .t({
- name: 'sqlite3_js_posix_create_file()',
- predicate: ()=>true,
- test: function(sqlite3){
- const db = this.db;
- const filename = "sqlite3_js_posix_create_file.db";
- capi.sqlite3_js_posix_create_file(filename, this.dbExport);
- delete this.dbExport;
- const db2 = new sqlite3.oo1.DB(filename,'r');
- try {
- const sql = "select count(*) from t";
- const n = db.selectValue(sql);
- T.assert(n>0 && db2.selectValue(sql) === n);
- }finally{
- db2.close();
- sqlite3.util.sqlite3__wasm_vfs_unlink(0, filename);
- }
- }
- }/*sqlite3_js_posix_create_file()*/)
- ////////////////////////////////////////////////////////////////////
- .t({
- name:'Scalar UDFs',
- test: function(sqlite3){
- const db = this.db;
- db.createFunction("foo",(pCx,a,b)=>a+b);
- T.assert(7===db.selectValue("select foo(3,4)")).
- assert(5===db.selectValue("select foo(3,?)",2)).
- assert(5===db.selectValue("select foo(?,?2)",[1,4])).
- assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
- db.createFunction("bar", {
- arity: -1,
- xFunc: (pCx,...args)=>{
- T.assert(db.pointer === capi.sqlite3_context_db_handle(pCx));
- let rc = 0;
- for(const v of args) rc += v;
- return rc;
- }
- }).createFunction({
- name: "asis",
- xFunc: (pCx,arg)=>arg
- });
- T.assert(0===db.selectValue("select bar()")).
- assert(1===db.selectValue("select bar(1)")).
- assert(3===db.selectValue("select bar(1,2)")).
- assert(-1===db.selectValue("select bar(1,2,-4)")).
- assert('hi' === db.selectValue("select asis('hi')")).
- assert('hi' === db.selectValue("select ?",'hi')).
- assert(null === db.selectValue("select null")).
- assert(null === db.selectValue("select asis(null)")).
- assert(1 === db.selectValue("select ?",1)).
- assert(2 === db.selectValue("select ?",[2])).
- assert(3 === db.selectValue("select $a",{$a:3})).
- assert(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
- assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")));
- let blobArg = new Uint8Array([0x68, 0x69]);
- let blobRc = db.selectValue(
- "select asis(?1)",
- blobArg.buffer/*confirm that ArrayBuffer is handled as a Uint8Array*/
- );
- T.assert(blobRc instanceof Uint8Array).
- assert(2 === blobRc.length).
- assert(0x68==blobRc[0] && 0x69==blobRc[1]);
- blobRc = db.selectValue("select asis(X'6869')");
- T.assert(blobRc instanceof Uint8Array).
- assert(2 === blobRc.length).
- assert(0x68==blobRc[0] && 0x69==blobRc[1]);
- blobArg = new Int8Array([0x68, 0x69]);
- //debug("blobArg=",blobArg);
- blobRc = db.selectValue("select asis(?1)", blobArg);
- T.assert(blobRc instanceof Uint8Array).
- assert(2 === blobRc.length);
- //debug("blobRc=",blobRc);
- T.assert(0x68==blobRc[0] && 0x69==blobRc[1]);
- let rc = sqlite3.capi.sqlite3_create_function_v2(
- this.db, "foo", 0, -1, 0, 0, 0, 0, 0
- );
- T.assert(
- sqlite3.capi.SQLITE_FORMAT === rc,
- "For invalid eTextRep argument."
- );
- rc = sqlite3.capi.sqlite3_create_function_v2(this.db, "foo", 0);
- T.assert(
- sqlite3.capi.SQLITE_MISUSE === rc,
- "For invalid arg count."
- );
- /* Confirm that we can map and unmap the same function with
- multiple arities... */
- const fCounts = [0,0];
- const fArityCheck = function(pCx){
- return ++fCounts[arguments.length-1];
- };
- //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
- rc = capi.sqlite3_create_function_v2(
- db, "nary", 0, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0
- );
- T.assert( 0===rc );
- rc = capi.sqlite3_create_function_v2(
- db, "nary", 1, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0
- );
- T.assert( 0===rc );
- const sqlFArity0 = "select nary()";
- const sqlFArity1 = "select nary(1)";
- T.assert( 1 === db.selectValue(sqlFArity0) )
- .assert( 1 === fCounts[0] ).assert( 0 === fCounts[1] );
- T.assert( 1 === db.selectValue(sqlFArity1) )
- .assert( 1 === fCounts[0] ).assert( 1 === fCounts[1] );
- capi.sqlite3_create_function_v2(
- db, "nary", 0, capi.SQLITE_UTF8, 0, 0, 0, 0, 0
- );
- T.mustThrowMatching((()=>db.selectValue(sqlFArity0)),
- (e)=>((e instanceof sqlite3.SQLite3Error)
- && e.message.indexOf("wrong number of arguments")>0),
- "0-arity variant was uninstalled.");
- T.assert( 2 === db.selectValue(sqlFArity1) )
- .assert( 1 === fCounts[0] ).assert( 2 === fCounts[1] );
- capi.sqlite3_create_function_v2(
- db, "nary", 1, capi.SQLITE_UTF8, 0, 0, 0, 0, 0
- );
- T.mustThrowMatching((()=>db.selectValue(sqlFArity1)),
- (e)=>((e instanceof sqlite3.SQLite3Error)
- && e.message.indexOf("no such function")>0),
- "1-arity variant was uninstalled.");
- //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
- }
- })
- ////////////////////////////////////////////////////////////////////
- .t({
- name: 'Aggregate UDFs',
- //predicate: ()=>false,
- test: function(sqlite3){
- const db = this.db;
- const sjac = capi.sqlite3_js_aggregate_context;
- db.createFunction({
- name: 'summer',
- xStep: (pCtx, n)=>{
- const ac = sjac(pCtx, 4);
- wasm.poke32(ac, wasm.peek32(ac) + Number(n));
- },
- xFinal: (pCtx)=>{
- const ac = sjac(pCtx, 0);
- return ac ? wasm.peek32(ac) : 0;
- }
- });
- let v = db.selectValue([
- "with cte(v) as (",
- "select 3 union all select 5 union all select 7",
- ") select summer(v), summer(v+1) from cte"
- /* ------------------^^^^^^^^^^^ ensures that we're handling
- sqlite3_aggregate_context() properly. */
- ]);
- T.assert(15===v);
- T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
- /wrong number of arguments/);
- db.createFunction({
- name: 'summerN',
- arity: -1,
- xStep: (pCtx, ...args)=>{
- const ac = sjac(pCtx, 4);
- let sum = wasm.peek32(ac);
- for(const v of args) sum += Number(v);
- wasm.poke32(ac, sum);
- },
- xFinal: (pCtx)=>{
- const ac = sjac(pCtx, 0);
- capi.sqlite3_result_int( pCtx, ac ? wasm.peek32(ac) : 0 );
- // xFinal() may either return its value directly or call
- // sqlite3_result_xyz() and return undefined. Both are
- // functionally equivalent.
- }
- });
- T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)'));
- T.mustThrowMatching(()=>{
- db.createFunction('nope',{
- xFunc: ()=>{}, xStep: ()=>{}
- });
- }, /scalar or aggregate\?/);
- T.mustThrowMatching(()=>{
- db.createFunction('nope',{xStep: ()=>{}});
- }, /Missing xFinal/);
- T.mustThrowMatching(()=>{
- db.createFunction('nope',{xFinal: ()=>{}});
- }, /Missing xStep/);
- T.mustThrowMatching(()=>{
- db.createFunction('nope',{});
- }, /Missing function-type properties/);
- T.mustThrowMatching(()=>{
- db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'});
- }, /xDestroy property must be a function/);
- T.mustThrowMatching(()=>{
- db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'});
- }, /Invalid value for pApp/);
- }
- }/*aggregate UDFs*/)
- ////////////////////////////////////////////////////////////////////////
- .t({
- name: 'Aggregate UDFs (64-bit)',
- predicate: ()=>wasm.bigIntEnabled,
- //predicate: ()=>false,
- test: function(sqlite3){
- const db = this.db;
- const sjac = capi.sqlite3_js_aggregate_context;
- db.createFunction({
- name: 'summer64',
- xStep: (pCtx, n)=>{
- const ac = sjac(pCtx, 8);
- wasm.poke64(ac, wasm.peek64(ac) + BigInt(n));
- },
- xFinal: (pCtx)=>{
- const ac = sjac(pCtx, 0);
- return ac ? wasm.peek64(ac) : 0n;
- }
- });
- let v = db.selectValue([
- "with cte(v) as (",
- "select 9007199254740991 union all select 1 union all select 2",
- ") select summer64(v), summer64(v+1) from cte"
- ]);
- T.assert(9007199254740994n===v);
- }
- }/*aggregate UDFs*/)
- ////////////////////////////////////////////////////////////////////
- .t({
- name: 'Window UDFs',
- predicate: (sqlite3)=>!!sqlite3.wasm.exports.sqlite3_create_window_function
- /*!sqlite3.wasm.compileOptionUsed('OMIT_WINDOWFUNC')*/
- || "Missing window functions",
- test: function(){
- /* Example window function, table, and results taken from:
- https://sqlite.org/windowfunctions.html#udfwinfunc */
- const db = this.db;
- const sjac = (cx,n=4)=>capi.sqlite3_js_aggregate_context(cx,n);
- const xValueFinal = (pCtx)=>{
- const ac = sjac(pCtx, 0);
- return ac ? wasm.peek32(ac) : 0;
- };
- const xStepInverse = (pCtx, n)=>{
- const ac = sjac(pCtx);
- wasm.poke32(ac, wasm.peek32(ac) + Number(n));
- };
- db.createFunction({
- name: 'winsumint',
- xStep: (pCtx, n)=>xStepInverse(pCtx, n),
- xInverse: (pCtx, n)=>xStepInverse(pCtx, -n),
- xFinal: xValueFinal,
- xValue: xValueFinal
- });
- db.exec([
- "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
- "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
- ]);
- let rc = db.exec({
- returnValue: 'resultRows',
- sql:[
- "SELECT x, winsumint(y) OVER (",
- "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
- ") AS sum_y ",
- "FROM twin ORDER BY x;"
- ]
- });
- T.assert(Array.isArray(rc))
- .assert(5 === rc.length);
- let count = 0;
- for(const row of rc){
- switch(++count){
- case 1: T.assert('a'===row[0] && 9===row[1]); break;
- case 2: T.assert('b'===row[0] && 12===row[1]); break;
- case 3: T.assert('c'===row[0] && 16===row[1]); break;
- case 4: T.assert('d'===row[0] && 12===row[1]); break;
- case 5: T.assert('e'===row[0] && 9===row[1]); break;
- default: toss("Too many rows to window function.");
- }
- }
- const resultRows = [];
- rc = db.exec({
- resultRows,
- returnValue: 'resultRows',
- sql:[
- "SELECT x, winsumint(y) OVER (",
- "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
- ") AS sum_y ",
- "FROM twin ORDER BY x;"
- ]
- });
- T.assert(rc === resultRows)
- .assert(5 === rc.length);
- rc = db.exec({
- returnValue: 'saveSql',
- sql: "select 1; select 2; -- empty\n; select 3"
- });
- T.assert(Array.isArray(rc))
- .assert(3===rc.length)
- .assert('select 1;' === rc[0])
- .assert('select 2;' === rc[1])
- .assert('-- empty\n; select 3' === rc[2]
- /* Strange but true. */);
- T.mustThrowMatching(()=>{
- db.exec({sql:'', returnValue: 'nope'});
- }, /^Invalid returnValue/);
- db.exec("DROP TABLE twin");
- }
- }/*window UDFs*/)
- ////////////////////////////////////////////////////////////////////
- .t("ATTACH", function(){
- const db = this.db;
- const resultRows = [];
- db.exec({
- sql:new TextEncoder('utf-8').encode([
- // ^^^ testing string-vs-typedarray handling in exec()
- "attach 'session' as foo;",
- "create table foo.bar(a);",
- "insert into foo.bar(a) values(1),(2),(3);",
- "select a from foo.bar order by a;"
- ].join('')),
- rowMode: 0,
- resultRows
- });
- T.assert(3===resultRows.length)
- .assert(2===resultRows[1]);
- T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a'));
- /** Demonstrate the JS-simplified form of the sqlite3_exec() callback... */
- let colCount = 0, rowCount = 0;
- let rc = capi.sqlite3_exec(
- db, "select a, a*2 from foo.bar", function(aVals, aNames){
- //console.warn("execCallback(",arguments,")");
- colCount = aVals.length;
- ++rowCount;
- T.assert(2===aVals.length)
- .assert(2===aNames.length)
- .assert(+(aVals[1]) === 2 * +(aVals[0]));
- }, 0, 0
- );
- T.assert(0===rc).assert(3===rowCount).assert(2===colCount);
- rc = capi.sqlite3_exec(
- db.pointer, "select a from foo.bar", ()=>{
- tossQuietly("Testing throwing from exec() callback.");
- }, 0, 0
- );
- T.assert(capi.SQLITE_ABORT === rc);
- /* Demonstrate how to get access to the "full" callback
- signature, as opposed to the simplified JS-specific one... */
- rowCount = colCount = 0;
- const pCb = wasm.installFunction('i(pipp)', function(pVoid,nCols,aVals,aCols){
- /* Tip: wasm.cArgvToJs() can be used to convert aVals and
- aCols to arrays: const vals = wasm.cArgvToJs(nCols,
- aVals); */
- ++rowCount;
- colCount = nCols;
- T.assert(2 === nCols)
- .assert(wasm.isPtr(pVoid))
- .assert(wasm.isPtr(aVals))
- .assert(wasm.isPtr(aCols))
- .assert(+wasm.cstrToJs(wasm.peekPtr(aVals + wasm.ptrSizeof))
- === 2 * +wasm.cstrToJs(wasm.peekPtr(aVals)));
- return 0;
- });
- try {
- T.assert(wasm.isPtr(pCb));
- rc = capi.sqlite3_exec(
- db, new TextEncoder('utf-8').encode("select a, a*2 from foo.bar"),
- pCb, 0, 0
- );
- T.assert(0===rc)
- .assert(3===rowCount)
- .assert(2===colCount);
- }finally{
- wasm.uninstallFunction(pCb);
- }
- // Demonstrate that an OOM result does not propagate through sqlite3_exec()...
- rc = capi.sqlite3_exec(
- db, ["select a,"," a*2 from foo.bar"], (aVals, aNames)=>{
- sqlite3.WasmAllocError.toss("just testing");
- }, 0, 0
- );
- T.assert(capi.SQLITE_ABORT === rc);
- db.exec("detach foo");
- T.mustThrow(()=>db.exec("select * from foo.bar"),
- "Because foo is no longer attached.");
- })
- ////////////////////////////////////////////////////////////////////
- .t("Read-only", function(sqlite3){
- T.assert( 0===capi.sqlite3_db_readonly(this.db, "main") );
- const db = new sqlite3.oo1.DB('file://'+this.db.filename+'?mode=ro');
- T.assert( 1===capi.sqlite3_db_readonly(db, "main") );
- T.assert( -1===capi.sqlite3_db_readonly(db, "nope") );
- db.close();
- })
- ////////////////////////////////////////////////////////////////////
- .t({
- name: 'C-side WASM tests',
- predicate: ()=>(haveWasmCTests() || "Not compiled in."),
- test: function(){
- const w = wasm, db = this.db;
- const stack = w.scopedAllocPush();
- let ptrInt;
- const origValue = 512;
- try{
- ptrInt = w.scopedAlloc(4);
- w.poke32(ptrInt,origValue);
- const cf = w.xGet('sqlite3__wasm_test_intptr');
- const oldPtrInt = ptrInt;
- T.assert(origValue === w.peek32(ptrInt));
- const rc = cf(ptrInt);
- T.assert(2*origValue === rc).
- assert(rc === w.peek32(ptrInt)).
- assert(oldPtrInt === ptrInt);
- const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/;
- const o64 = 0x010203040506/*>32-bit integer*/;
- if(w.bigIntEnabled){
- w.poke64(pi64, o64);
- //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64);
- const v64 = ()=>w.peek64(pi64)
- T.assert(v64() == o64);
- //T.assert(o64 === w.peek64(pi64));
- const cf64w = w.xGet('sqlite3__wasm_test_int64ptr');
- cf64w(pi64);
- T.assert(v64() == BigInt(2 * o64));
- cf64w(pi64);
- T.assert(v64() == BigInt(4 * o64));
- const biTimes2 = w.xGet('sqlite3__wasm_test_int64_times2');
- T.assert(BigInt(2 * o64) ===
- biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError
- in the call :/ */));
- const pMin = w.scopedAlloc(16);
- const pMax = pMin + 8;
- const g64 = (p)=>w.peek64(p);
- w.poke64([pMin, pMax], 0);
- const minMaxI64 = [
- w.xCall('sqlite3__wasm_test_int64_min'),
- w.xCall('sqlite3__wasm_test_int64_max')
- ];
- T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)).
- assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER));
- //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]);
- w.xCall('sqlite3__wasm_test_int64_minmax', pMin, pMax);
- T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch").
- assert(g64(pMax) === minMaxI64[1], "int64 mismatch");
- //log("pMin",g64(pMin), "pMax",g64(pMax));
- w.poke64(pMin, minMaxI64[0]);
- T.assert(g64(pMin) === minMaxI64[0]).
- assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))).
- assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax)));
- const rxRange = /too big/;
- T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))},
- rxRange).
- mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))},
- (e)=>rxRange.test(e.message));
- }else{
- log("No BigInt support. Skipping related tests.");
- log("\"The problem\" here is that we can manipulate, at the byte level,",
- "heap memory to set 64-bit values, but we can't get those values",
- "back into JS because of the lack of 64-bit integer support.");
- }
- }finally{
- const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1);
- //log("x=",x,"y=",y,"z=",z); // just looking at the alignment
- w.scopedAllocPop(stack);
- }
- }
- }/* jaccwabyt-specific tests */)
- ////////////////////////////////////////////////////////////////////////
- .t({
- name: 'virtual table #1: eponymous w/ manual exception handling',
- predicate: (sqlite3)=>!!sqlite3.capi.sqlite3_vtab || "Missing vtab support",
- test: function(sqlite3){
- const VT = sqlite3.vtab;
- const tmplCols = Object.assign(Object.create(null),{
- A: 0, B: 1
- });
- /**
- The vtab demonstrated here is a JS-ification of
- ext/misc/templatevtab.c.
- */
- const tmplMod = new sqlite3.capi.sqlite3_module();
- T.assert(0===tmplMod.$xUpdate);
- tmplMod.setupModule({
- catchExceptions: false,
- methods: {
- xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){
- try{
- const args = wasm.cArgvToJs(argc, argv);
- T.assert(args.length>=3)
- .assert(args[0] === 'testvtab')
- .assert(args[1] === 'main')
- .assert(args[2] === 'testvtab');
- //console.debug("xConnect() args =",args);
- const rc = capi.sqlite3_declare_vtab(
- pDb, "CREATE TABLE ignored(a,b)"
- );
- if(0===rc){
- const t = VT.xVtab.create(ppVtab);
- T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab)));
- }
- return rc;
- }catch(e){
- if(!(e instanceof sqlite3.WasmAllocError)){
- wasm.dealloc(wasm.peekPtr, pzErr);
- wasm.pokePtr(pzErr, wasm.allocCString(e.message));
- }
- return VT.xError('xConnect',e);
- }
- },
- xCreate: true /* just for testing. Will be removed afterwards. */,
- xDisconnect: function(pVtab){
- try {
- VT.xVtab.unget(pVtab).dispose();
- return 0;
- }catch(e){
- return VT.xError('xDisconnect',e);
- }
- },
- xOpen: function(pVtab, ppCursor){
- try{
- const t = VT.xVtab.get(pVtab),
- c = VT.xCursor.create(ppCursor);
- T.assert(t instanceof capi.sqlite3_vtab)
- .assert(c instanceof capi.sqlite3_vtab_cursor);
- c._rowId = 0;
- return 0;
- }catch(e){
- return VT.xError('xOpen',e);
- }
- },
- xClose: function(pCursor){
- try{
- const c = VT.xCursor.unget(pCursor);
- T.assert(c instanceof capi.sqlite3_vtab_cursor)
- .assert(!VT.xCursor.get(pCursor));
- c.dispose();
- return 0;
- }catch(e){
- return VT.xError('xClose',e);
- }
- },
- xNext: function(pCursor){
- try{
- const c = VT.xCursor.get(pCursor);
- ++c._rowId;
- return 0;
- }catch(e){
- return VT.xError('xNext',e);
- }
- },
- xColumn: function(pCursor, pCtx, iCol){
- try{
- const c = VT.xCursor.get(pCursor);
- switch(iCol){
- case tmplCols.A:
- capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
- break;
- case tmplCols.B:
- capi.sqlite3_result_int(pCtx, 2000 + c._rowId);
- break;
- default: sqlite3.SQLite3Error.toss("Invalid column id",iCol);
- }
- return 0;
- }catch(e){
- return VT.xError('xColumn',e);
- }
- },
- xRowid: function(pCursor, ppRowid64){
- try{
- const c = VT.xCursor.get(pCursor);
- VT.xRowid(ppRowid64, c._rowId);
- return 0;
- }catch(e){
- return VT.xError('xRowid',e);
- }
- },
- xEof: function(pCursor){
- const c = VT.xCursor.get(pCursor),
- rc = c._rowId>=10;
- return rc;
- },
- xFilter: function(pCursor, idxNum, idxCStr,
- argc, argv/* [sqlite3_value* ...] */){
- try{
- const c = VT.xCursor.get(pCursor);
- c._rowId = 0;
- const list = capi.sqlite3_values_to_js(argc, argv);
- T.assert(argc === list.length);
- //log(argc,"xFilter value(s):",list);
- return 0;
- }catch(e){
- return VT.xError('xFilter',e);
- }
- },
- xBestIndex: function(pVtab, pIdxInfo){
- try{
- //const t = VT.xVtab.get(pVtab);
- const sii = capi.sqlite3_index_info;
- const pii = new sii(pIdxInfo);
- pii.$estimatedRows = 10;
- pii.$estimatedCost = 10.0;
- //log("xBestIndex $nConstraint =",pii.$nConstraint);
- if(pii.$nConstraint>0){
- // Validate nthConstraint() and nthConstraintUsage()
- const max = pii.$nConstraint;
- for(let i=0; i < max; ++i ){
- let v = pii.nthConstraint(i,true);
- T.assert(wasm.isPtr(v));
- v = pii.nthConstraint(i);
- T.assert(v instanceof sii.sqlite3_index_constraint)
- .assert(v.pointer >= pii.$aConstraint);
- v.dispose();
- v = pii.nthConstraintUsage(i,true);
- T.assert(wasm.isPtr(v));
- v = pii.nthConstraintUsage(i);
- T.assert(v instanceof sii.sqlite3_index_constraint_usage)
- .assert(v.pointer >= pii.$aConstraintUsage);
- v.$argvIndex = i;//just to get some values into xFilter
- v.dispose();
- }
- }
- //log("xBestIndex $nOrderBy =",pii.$nOrderBy);
- if(pii.$nOrderBy>0){
- // Validate nthOrderBy()
- const max = pii.$nOrderBy;
- for(let i=0; i < max; ++i ){
- let v = pii.nthOrderBy(i,true);
- T.assert(wasm.isPtr(v));
- v = pii.nthOrderBy(i);
- T.assert(v instanceof sii.sqlite3_index_orderby)
- .assert(v.pointer >= pii.$aOrderBy);
- v.dispose();
- }
- }
- pii.dispose();
- return 0;
- }catch(e){
- return VT.xError('xBestIndex',e);
- }
- }
- }
- });
- this.db.onclose.disposeAfter.push(tmplMod);
- T.assert(0===tmplMod.$xUpdate)
- .assert(tmplMod.$xCreate)
- .assert(tmplMod.$xCreate === tmplMod.$xConnect,
- "setup() must make these equivalent and "+
- "installMethods() must avoid re-compiling identical functions");
- tmplMod.$xCreate = 0 /* make tmplMod eponymous-only */;
- let rc = capi.sqlite3_create_module(
- this.db, "testvtab", tmplMod, 0
- );
- this.db.checkRc(rc);
- const list = this.db.selectArrays(
- "SELECT a,b FROM testvtab where a<9999 and b>1 order by a, b"
- /* Query is shaped so that it will ensure that some constraints
- end up in xBestIndex(). */
- );
- T.assert(10===list.length)
- .assert(1000===list[0][0])
- .assert(2009===list[list.length-1][1]);
- }
- })/*custom vtab #1*/
- ////////////////////////////////////////////////////////////////////////
- .t({
- name: 'virtual table #2: non-eponymous w/ automated exception wrapping',
- predicate: (sqlite3)=>!!sqlite3.capi.sqlite3_vtab || "Missing vtab support",
- test: function(sqlite3){
- const VT = sqlite3.vtab;
- const tmplCols = Object.assign(Object.create(null),{
- A: 0, B: 1
- });
- /**
- The vtab demonstrated here is a JS-ification of
- ext/misc/templatevtab.c.
- */
- let throwOnCreate = 1 ? 0 : capi.SQLITE_CANTOPEN
- /* ^^^ just for testing exception wrapping. Note that sqlite
- always translates errors from a vtable to a generic
- SQLITE_ERROR unless it's from xConnect()/xCreate() and that
- callback sets an error string. */;
- const vtabTrace = 1
- ? ()=>{}
- : (methodName,...args)=>console.debug('sqlite3_module::'+methodName+'():',...args);
- const modConfig = {
- /* catchExceptions changes how the methods are wrapped */
- catchExceptions: true,
- name: "vtab2test",
- methods:{
- xCreate: function(pDb, pAux, argc, argv, ppVtab, pzErr){
- vtabTrace("xCreate",...arguments);
- if(throwOnCreate){
- sqlite3.SQLite3Error.toss(
- throwOnCreate,
- "Throwing a test exception."
- );
- }
- const args = wasm.cArgvToJs(argc, argv);
- vtabTrace("xCreate","argv:",args);
- T.assert(args.length>=3);
- const rc = capi.sqlite3_declare_vtab(
- pDb, "CREATE TABLE ignored(a,b)"
- );
- if(0===rc){
- const t = VT.xVtab.create(ppVtab);
- T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab)));
- vtabTrace("xCreate",...arguments," ppVtab =",t.pointer);
- }
- return rc;
- },
- xConnect: true,
- xDestroy: function(pVtab){
- vtabTrace("xDestroy/xDisconnect",pVtab);
- VT.xVtab.dispose(pVtab);
- },
- xDisconnect: true,
- xOpen: function(pVtab, ppCursor){
- const t = VT.xVtab.get(pVtab),
- c = VT.xCursor.create(ppCursor);
- T.assert(t instanceof capi.sqlite3_vtab)
- .assert(c instanceof capi.sqlite3_vtab_cursor);
- vtabTrace("xOpen",...arguments," cursor =",c.pointer);
- c._rowId = 0;
- },
- xClose: function(pCursor){
- vtabTrace("xClose",...arguments);
- const c = VT.xCursor.unget(pCursor);
- T.assert(c instanceof capi.sqlite3_vtab_cursor)
- .assert(!VT.xCursor.get(pCursor));
- c.dispose();
- },
- xNext: function(pCursor){
- vtabTrace("xNext",...arguments);
- const c = VT.xCursor.get(pCursor);
- ++c._rowId;
- },
- xColumn: function(pCursor, pCtx, iCol){
- vtabTrace("xColumn",...arguments);
- const c = VT.xCursor.get(pCursor);
- switch(iCol){
- case tmplCols.A:
- capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
- break;
- case tmplCols.B:
- capi.sqlite3_result_int(pCtx, 2000 + c._rowId);
- break;
- default: sqlite3.SQLite3Error.toss("Invalid column id",iCol);
- }
- },
- xRowid: function(pCursor, ppRowid64){
- vtabTrace("xRowid",...arguments);
- const c = VT.xCursor.get(pCursor);
- VT.xRowid(ppRowid64, c._rowId);
- },
- xEof: function(pCursor){
- vtabTrace("xEof",...arguments);
- return VT.xCursor.get(pCursor)._rowId>=10;
- },
- xFilter: function(pCursor, idxNum, idxCStr,
- argc, argv/* [sqlite3_value* ...] */){
- vtabTrace("xFilter",...arguments);
- const c = VT.xCursor.get(pCursor);
- c._rowId = 0;
- const list = capi.sqlite3_values_to_js(argc, argv);
- T.assert(argc === list.length);
- },
- xBestIndex: function(pVtab, pIdxInfo){
- vtabTrace("xBestIndex",...arguments);
- //const t = VT.xVtab.get(pVtab);
- const pii = VT.xIndexInfo(pIdxInfo);
- pii.$estimatedRows = 10;
- pii.$estimatedCost = 10.0;
- pii.dispose();
- }
- }/*methods*/
- };
- const tmplMod = VT.setupModule(modConfig);
- T.assert(1===tmplMod.$iVersion);
- this.db.onclose.disposeAfter.push(tmplMod);
- this.db.checkRc(capi.sqlite3_create_module(
- this.db.pointer, modConfig.name, tmplMod.pointer, 0
- ));
- this.db.exec([
- "create virtual table testvtab2 using ",
- modConfig.name,
- "(arg1 blah, arg2 bloop)"
- ]);
- if(0){
- /* If we DROP TABLE then xDestroy() is called. If the
- vtab is instead destroyed when the db is closed,
- xDisconnect() is called. */
- this.db.onclose.disposeBefore.push(function(db){
- console.debug("Explicitly dropping testvtab2 via disposeBefore handler...");
- db.exec(
- /** DROP TABLE is the only way to get xDestroy() to be called. */
- "DROP TABLE testvtab2"
- );
- });
- }
- let list = this.db.selectArrays(
- "SELECT a,b FROM testvtab2 where a<9999 and b>1 order by a, b"
- /* Query is shaped so that it will ensure that some
- constraints end up in xBestIndex(). */
- );
- T.assert(10===list.length)
- .assert(1000===list[0][0])
- .assert(2009===list[list.length-1][1]);
- list = this.db.selectArrays(
- "SELECT a,b FROM testvtab2 where a<9999 and b>1 order by b, a limit 5"
- );
- T.assert(5===list.length)
- .assert(1000===list[0][0])
- .assert(2004===list[list.length-1][1]);
- // Call it as a table-valued function...
- list = this.db.selectArrays([
- "SELECT a,b FROM ", modConfig.name,
- " where a<9999 and b>1 order by b, a limit 1"
- ]);
- T.assert(1===list.length)
- .assert(1000===list[0][0])
- .assert(2000===list[0][1]);
- }
- })/*custom vtab #2*/
- ////////////////////////////////////////////////////////////////////////
- .t('Custom collation', function(sqlite3){
- let collationCounter = 0;
- let myCmp = function(pArg,n1,p1,n2,p2){
- //int (*)(void*,int,const void*,int,const void*)
- ++collationCounter;
- const rc = wasm.exports.sqlite3_strnicmp(p1,p2,(n1<n2?n1:n2));
- return rc ? rc : (n1 - n2);
- };
- let rc = capi.sqlite3_create_collation_v2(this.db, "mycollation", capi.SQLITE_UTF8,
- 0, myCmp, 0);
- this.db.checkRc(rc);
- rc = this.db.selectValue("select 'hi' = 'HI' collate mycollation");
- T.assert(1===rc).assert(1===collationCounter);
- rc = this.db.selectValue("select 'hii' = 'HI' collate mycollation");
- T.assert(0===rc).assert(2===collationCounter);
- rc = this.db.selectValue("select 'hi' = 'HIi' collate mycollation");
- T.assert(0===rc).assert(3===collationCounter);
- rc = capi.sqlite3_create_collation(this.db,"hi",capi.SQLITE_UTF8/*not enough args*/);
- T.assert(capi.SQLITE_MISUSE === rc);
- rc = capi.sqlite3_create_collation_v2(this.db,"hi",capi.SQLITE_UTF8+1/*invalid encoding*/,0,0,0);
- T.assert(capi.SQLITE_FORMAT === rc)
- .mustThrowMatching(()=>this.db.checkRc(rc),
- /SQLITE_UTF8 is the only supported encoding./);
- /*
- We need to ensure that replacing that collation function does
- the right thing. We don't have a handle to the underlying WASM
- pointer from here, so cannot verify (without digging through
- internal state) that the old one gets uninstalled, but we can
- verify that a new one properly replaces it. (That said,
- console.warn() output has shown that the uninstallation does
- happen.)
- */
- collationCounter = 0;
- myCmp = function(pArg,n1,p1,n2,p2){
- --collationCounter;
- return 0;
- };
- rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8,
- 0, myCmp, 0);
- this.db.checkRc(rc);
- rc = this.db.selectValue("select 'hi' = 'HI' collate mycollation");
- T.assert(rc>0).assert(-1===collationCounter);
- rc = this.db.selectValue("select 'a' = 'b' collate mycollation");
- T.assert(rc>0).assert(-2===collationCounter);
- rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8,
- 0, null, 0);
- this.db.checkRc(rc);
- rc = 0;
- try {
- this.db.selectValue("select 'a' = 'b' collate mycollation");
- }catch(e){
- /* Why is e.resultCode not automatically an extended result
- code? The DB() class enables those automatically. */
- rc = sqlite3.capi.sqlite3_extended_errcode(this.db);
- }
- T.assert(capi.SQLITE_ERROR_MISSING_COLLSEQ === rc);
- })/*custom collation*/
- ////////////////////////////////////////////////////////////////////////
- .t('Close db', function(){
- T.assert(this.db).assert(wasm.isPtr(this.db.pointer));
- //wasm.sqlite3__wasm_db_reset(this.db); // will leak virtual tables!
- this.db.close();
- T.assert(!this.db.pointer);
- })
- ;/* end of oo1 checks */
- ////////////////////////////////////////////////////////////////////////
- T.g('kvvfs')
- .t({
- name: 'kvvfs is disabled in worker',
- predicate: ()=>(isWorker() || "test is only valid in a Worker"),
- test: function(sqlite3){
- T.assert(
- !capi.sqlite3_vfs_find('kvvfs'),
- "Expecting kvvfs to be unregistered."
- );
- }
- })
- .t({
- name: 'kvvfs in main thread',
- predicate: ()=>(isUIThread()
- || "local/sessionStorage are unavailable in a Worker"),
- test: function(sqlite3){
- const filename = this.kvvfsDbFile = 'session';
- const pVfs = capi.sqlite3_vfs_find('kvvfs');
- T.assert(pVfs);
- const JDb = this.JDb = sqlite3.oo1.JsStorageDb;
- const unlink = this.kvvfsUnlink = ()=>JDb.clearStorage(this.kvvfsDbFile);
- unlink();
- let db = new JDb(filename);
- try {
- db.exec([
- 'create table kvvfs(a);',
- 'insert into kvvfs(a) values(1),(2),(3)'
- ]);
- T.assert(3 === db.selectValue('select count(*) from kvvfs'));
- db.close();
- db = new JDb(filename);
- db.exec('insert into kvvfs(a) values(4),(5),(6)');
- T.assert(6 === db.selectValue('select count(*) from kvvfs'));
- }finally{
- db.close();
- }
- }
- }/*kvvfs sanity checks*/)
- //#if enable-see
- .t({
- name: 'kvvfs with SEE encryption',
- predicate: ()=>(isUIThread()
- || "Only available in main thread."),
- test: function(sqlite3){
- this.kvvfsUnlink();
- let db;
- const encOpt1 = 1
- ? {textkey: 'foo'}
- : {key: 'foo'};
- const encOpt2 = encOpt1.textkey
- ? encOpt1
- : {hexkey: new Uint8Array([0x66,0x6f,0x6f]/*==>"foo"*/)}
- try{
- db = new this.JDb({
- filename: this.kvvfsDbFile,
- ...encOpt1
- });
- db.exec([
- "create table t(a,b);",
- "insert into t(a,b) values(1,2),(3,4)"
- ]);
- db.close();
- let err;
- try{
- db = new this.JDb({
- filename: this.kvvfsDbFile,
- flags: 'ct'
- });
- T.assert(db) /* opening is fine, but... */;
- db.exec("select 1 from sqlite_schema");
- console.warn("sessionStorage =",sessionStorage);
- }catch(e){
- err = e;
- }finally{
- db.close();
- }
- T.assert(err,"Expecting an exception")
- .assert(sqlite3.capi.SQLITE_NOTADB==err.resultCode,
- "Expecting NOTADB");
- db = new sqlite3.oo1.DB({
- filename: this.kvvfsDbFile,
- vfs: 'kvvfs',
- ...encOpt2
- });
- T.assert( 4===db.selectValue('select sum(a) from t') );
- }finally{
- if( db ) db.close();
- this.kvvfsUnlink();
- }
- }
- })/*kvvfs with SEE*/
- //#endif enable-see
- ;/* end kvvfs tests */
- ////////////////////////////////////////////////////////////////////////
- T.g('Hook APIs')
- .t({
- name: "sqlite3_commit/rollback/update_hook()",
- predicate: ()=>wasm.bigIntEnabled || "Update hook requires int64",
- test: function(sqlite3){
- let countCommit = 0, countRollback = 0;;
- const db = new sqlite3.oo1.DB(':memory:',1 ? 'c' : 'ct');
- let rc = capi.sqlite3_commit_hook(db, (p)=>{
- ++countCommit;
- return (1 === p) ? 0 : capi.SQLITE_ERROR;
- }, 1);
- T.assert( 0 === rc /*void pointer*/ );
- // Commit hook...
- T.assert( 0!=capi.sqlite3_get_autocommit(db) );
- db.exec("BEGIN; SELECT 1; COMMIT");
- T.assert(0 === countCommit,
- "No-op transactions (mostly) do not trigger commit hook.");
- db.exec("BEGIN EXCLUSIVE; SELECT 1; COMMIT");
- T.assert(1 === countCommit,
- "But EXCLUSIVE transactions do.");
- db.transaction((d)=>{
- T.assert( 0==capi.sqlite3_get_autocommit(db) );
- d.exec("create table t(a)");
- });
- T.assert(2 === countCommit);
- // Rollback hook:
- rc = capi.sqlite3_rollback_hook(db, (p)=>{
- ++countRollback;
- T.assert( 2 === p );
- }, 2);
- T.assert( 0 === rc /*void pointer*/ );
- T.mustThrowMatching(()=>{
- db.transaction('drop table t',()=>{})
- }, (e)=>{
- return (capi.SQLITE_MISUSE === e.resultCode)
- && ( e.message.indexOf('Invalid argument') > 0 );
- });
- T.assert(0 === countRollback, "Transaction was not started.");
- T.mustThrowMatching(()=>{
- db.transaction('immediate', ()=>{
- sqlite3.SQLite3Error.toss(capi.SQLITE_FULL,'testing rollback hook');
- });
- }, (e)=>{
- return capi.SQLITE_FULL === e.resultCode
- });
- T.assert(1 === countRollback);
- // Update hook...
- const countUpdate = Object.create(null);
- capi.sqlite3_update_hook(db, (p,op,dbName,tbl,rowid)=>{
- T.assert('main' === dbName.toLowerCase())
- .assert('t' === tbl.toLowerCase())
- .assert(3===p)
- .assert('bigint' === typeof rowid);
- switch(op){
- case capi.SQLITE_INSERT:
- case capi.SQLITE_UPDATE:
- case capi.SQLITE_DELETE:
- countUpdate[op] = (countUpdate[op]||0) + 1;
- break;
- default: toss("Unexpected hook operator:",op);
- }
- }, 3);
- db.transaction((d)=>{
- d.exec([
- "insert into t(a) values(1);",
- "update t set a=2;",
- "update t set a=3;",
- "delete from t where a=3"
- // update hook is not called for an unqualified DELETE
- ]);
- });
- T.assert(1 === countRollback)
- .assert(3 === countCommit)
- .assert(1 === countUpdate[capi.SQLITE_INSERT])
- .assert(2 === countUpdate[capi.SQLITE_UPDATE])
- .assert(1 === countUpdate[capi.SQLITE_DELETE]);
- //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
- T.assert(1 === capi.sqlite3_commit_hook(db, 0, 0));
- T.assert(2 === capi.sqlite3_rollback_hook(db, 0, 0));
- T.assert(3 === capi.sqlite3_update_hook(db, 0, 0));
- //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
- db.close();
- }
- })/* commit/rollback/update hooks */
- .t({
- name: "sqlite3_preupdate_hook()",
- predicate: ()=>capi.sqlite3_preupdate_hook || "Missing pre-update hook API",
- test: function(sqlite3){
- const db = new sqlite3.oo1.DB(':memory:', 1 ? 'c' : 'ct');
- const countHook = Object.create(null);
- let rc = capi.sqlite3_preupdate_hook(
- db, function(p, pDb, op, zDb, zTbl, iKey1, iKey2){
- T.assert(9 === p)
- .assert(db.pointer === pDb)
- .assert(1 === capi.sqlite3_preupdate_count(pDb))
- .assert( 0 > capi.sqlite3_preupdate_blobwrite(pDb) );
- countHook[op] = (countHook[op]||0) + 1;
- switch(op){
- case capi.SQLITE_INSERT:
- case capi.SQLITE_UPDATE:
- T.assert('number' === typeof capi.sqlite3_preupdate_new_js(pDb, 0));
- break;
- case capi.SQLITE_DELETE:
- T.assert('number' === typeof capi.sqlite3_preupdate_old_js(pDb, 0));
- break;
- default: toss("Unexpected hook operator:",op);
- }
- },
- 9
- );
- db.transaction((d)=>{
- d.exec([
- "create table t(a);",
- "insert into t(a) values(1);",
- "update t set a=2;",
- "update t set a=3;",
- "delete from t where a=3"
- ]);
- });
- T.assert(1 === countHook[capi.SQLITE_INSERT])
- .assert(2 === countHook[capi.SQLITE_UPDATE])
- .assert(1 === countHook[capi.SQLITE_DELETE]);
- //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
- db.close();
- //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
- }
- })/*pre-update hooks*/
- ;/*end hook API tests*/
- ////////////////////////////////////////////////////////////////////////
- T.g('Auto-extension API')
- .t({
- name: "Auto-extension sanity checks.",
- test: function(sqlite3){
- let counter = 0;
- const fp = wasm.installFunction('i(ppp)', function(pDb,pzErr,pApi){
- ++counter;
- return 0;
- });
- (new sqlite3.oo1.DB()).close();
- T.assert( 0===counter );
- capi.sqlite3_auto_extension(fp);
- (new sqlite3.oo1.DB()).close();
- T.assert( 1===counter );
- (new sqlite3.oo1.DB()).close();
- T.assert( 2===counter );
- capi.sqlite3_cancel_auto_extension(fp);
- wasm.uninstallFunction(fp);
- (new sqlite3.oo1.DB()).close();
- T.assert( 2===counter );
- }
- });
- ////////////////////////////////////////////////////////////////////////
- T.g('Session API')
- .t({
- name: 'Session API sanity checks',
- predicate: ()=>!!capi.sqlite3changegroup_add || "Missing session API",
- test: function(sqlite3){
- //warn("The session API tests could use some expansion.");
- const db1 = new sqlite3.oo1.DB(), db2 = new sqlite3.oo1.DB();
- const sqlInit = [
- "create table t(rowid INTEGER PRIMARY KEY,a,b); ",
- "insert into t(rowid,a,b) values",
- "(1,'a1','b1'),",
- "(2,'a2','b2'),",
- "(3,'a3','b3');"
- ].join('');
- db1.exec(sqlInit);
- db2.exec(sqlInit);
- T.assert(3 === db1.selectValue("select count(*) from t"))
- .assert('b3' === db1.selectValue('select b from t where rowid=3'));
- const stackPtr = wasm.pstack.pointer;
- try{
- let ppOut = wasm.pstack.allocPtr();
- let rc = capi.sqlite3session_create(db1, "main", ppOut);
- T.assert(0===rc);
- let pSession = wasm.peekPtr(ppOut);
- T.assert(pSession && wasm.isPtr(pSession));
- capi.sqlite3session_table_filter(pSession, (pCtx, tbl)=>{
- T.assert('t' === tbl).assert( 99 === pCtx );
- return 1;
- }, 99);
- db1.exec([
- "update t set b='bTwo' where rowid=2;",
- "update t set a='aThree' where rowid=3;",
- "delete from t where rowid=1;",
- "insert into t(rowid,a,b) values(4,'a4','b4')"
- ]);
- T.assert('bTwo' === db1.selectValue("select b from t where rowid=2"))
- .assert(undefined === db1.selectValue('select a from t where rowid=1'))
- .assert('b4' === db1.selectValue('select b from t where rowid=4'))
- .assert(3 === db1.selectValue('select count(*) from t'));
- const testSessionEnable =
- false /* it's not yet clear whether these test failures are
- broken tests or broken bindings. */;
- if(testSessionEnable){
- rc = capi.sqlite3session_enable(pSession, 0);
- T.assert( 0 === rc )
- .assert( 0 === capi.sqlite3session_enable(pSession, -1) );
- db1.exec("delete from t where rowid=2;");
- rc = capi.sqlite3session_enable(pSession, 1);
- T.assert( rc > 0 )
- .assert( capi.sqlite3session_enable(pSession, -1) > 0 )
- .assert(undefined === db1.selectValue('select a from t where rowid=2'));
- }else{
- //warn("sqlite3session_enable() tests are currently disabled.");
- }
- let db1Count = db1.selectValue("select count(*) from t");
- T.assert( db1Count === (testSessionEnable ? 2 : 3) );
- /* Capture changeset and destroy session. */
- let pnChanges = wasm.pstack.alloc('i32'),
- ppChanges = wasm.pstack.allocPtr();
- rc = capi.sqlite3session_changeset(pSession, pnChanges, ppChanges);
- T.assert( 0 === rc );
- capi.sqlite3session_delete(pSession);
- pSession = 0;
- const pChanges = wasm.peekPtr(ppChanges),
- nChanges = wasm.peek32(pnChanges);
- T.assert( pChanges && wasm.isPtr( pChanges ) )
- .assert( nChanges > 0 );
- /* Revert db1 via an inverted changeset, but keep pChanges
- and nChanges for application to db2. */
- rc = capi.sqlite3changeset_invert( nChanges, pChanges, pnChanges, ppChanges );
- T.assert( 0 === rc );
- rc = capi.sqlite3changeset_apply(
- db1, wasm.peek32(pnChanges), wasm.peekPtr(ppChanges), 0, (pCtx, eConflict, pIter)=>{
- return 1;
- }, 0
- );
- T.assert( 0 === rc );
- wasm.dealloc( wasm.peekPtr(ppChanges) );
- pnChanges = ppChanges = 0;
- T.assert('b2' === db1.selectValue("select b from t where rowid=2"))
- .assert('a1' === db1.selectValue('select a from t where rowid=1'))
- .assert(undefined === db1.selectValue('select b from t where rowid=4'));
- db1Count = db1.selectValue("select count(*) from t");
- T.assert(3 === db1Count);
- /* Apply pre-reverted changeset (pChanges, nChanges) to
- db2... */
- rc = capi.sqlite3changeset_apply(
- db2, nChanges, pChanges, 0, (pCtx, eConflict, pIter)=>{
- return pCtx ? 1 : 0
- }, 1
- );
- wasm.dealloc( pChanges );
- T.assert( 0 === rc )
- .assert( 'b4' === db2.selectValue('select b from t where rowid=4') )
- .assert( 'aThree' === db2.selectValue('select a from t where rowid=3') )
- .assert( undefined === db2.selectValue('select b from t where rowid=1') );
- if(testSessionEnable){
- T.assert( (undefined === db2.selectValue('select b from t where rowid=2')),
- "But... the session was disabled when rowid=2 was deleted?" );
- log("rowids from db2.t:",db2.selectValues('select rowid from t order by rowid'));
- T.assert( 3 === db2.selectValue('select count(*) from t') );
- }else{
- T.assert( 'bTwo' === db2.selectValue('select b from t where rowid=2') )
- .assert( 3 === db2.selectValue('select count(*) from t') );
- }
- }finally{
- wasm.pstack.restore(stackPtr);
- db1.close();
- db2.close();
- }
- }
- })/*session API sanity tests*/
- ;/*end of session API group*/;
- ////////////////////////////////////////////////////////////////////////
- T.g('OPFS: Origin-Private File System',
- (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs")
- || 'requires "opfs" VFS'))
- .t({
- name: 'OPFS db sanity checks',
- test: async function(sqlite3){
- T.assert(capi.sqlite3_vfs_find('opfs'));
- const opfs = sqlite3.opfs;
- const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db';
- const fileUri = 'file://'+filename+'?delete-before-open=1';
- const initSql = [
- 'create table p(a);',
- 'insert into p(a) values(1),(2),(3)'
- ];
- let db = new sqlite3.oo1.OpfsDb(fileUri);
- try {
- db.exec(initSql);
- T.assert(3 === db.selectValue('select count(*) from p'));
- db.close();
- db = new sqlite3.oo1.OpfsDb(filename);
- db.exec('insert into p(a) values(4),(5),(6)');
- T.assert(6 === db.selectValue('select count(*) from p'));
- this.opfsDbExport = capi.sqlite3_js_db_export(db);
- T.assert(this.opfsDbExport instanceof Uint8Array)
- .assert(this.opfsDbExport.byteLength>0
- && 0===this.opfsDbExport.byteLength % 512);
- }finally{
- db.close();
- }
- T.assert(await opfs.entryExists(filename));
- try {
- db = new sqlite3.oo1.OpfsDb(fileUri);
- db.exec(initSql) /* will throw if delete-before-open did not work */;
- T.assert(3 === db.selectValue('select count(*) from p'));
- }finally{
- if(db) db.close();
- }
- }
- }/*OPFS db sanity checks*/)
- .t({
- name: 'OPFS import',
- test: async function(sqlite3){
- let db;
- const filename = this.opfsDbFile;
- try {
- const exp = this.opfsDbExport;
- delete this.opfsDbExport;
- this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp);
- db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
- T.assert(6 === db.selectValue('select count(*) from p')).
- assert( this.opfsImportSize == exp.byteLength );
- db.close();
- const unlink = this.opfsUnlink =
- (fn=filename)=>sqlite3.util.sqlite3__wasm_vfs_unlink("opfs",fn);
- this.opfsUnlink(filename);
- T.assert(!(await sqlite3.opfs.entryExists(filename)));
- // Try again with a function as an input source:
- let cursor = 0;
- const blockSize = 512, end = exp.byteLength;
- const reader = async function(){
- if(cursor >= exp.byteLength){
- return undefined;
- }
- const rv = exp.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
- cursor += blockSize;
- return rv;
- };
- this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, reader);
- db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
- T.assert(6 === db.selectValue('select count(*) from p')).
- assert( this.opfsImportSize == exp.byteLength );
- }finally{
- if(db) db.close();
- }
- }
- }/*OPFS export/import*/)
- .t({
- name: '(Internal-use) OPFS utility APIs',
- test: async function(sqlite3){
- const filename = this.opfsDbFile;
- const unlink = this.opfsUnlink;
- T.assert(filename && !!unlink);
- delete this.opfsDbFile;
- delete this.opfsUnlink;
- /**************************************************************
- ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended
- for client-side use. It is only for this project's own
- internal use. Its APIs are subject to change or removal at
- any time.
- ***************************************************************/
- const opfs = sqlite3.opfs;
- const fSize = this.opfsImportSize;
- delete this.opfsImportSize;
- let sh;
- try{
- T.assert(await opfs.entryExists(filename));
- const [dirHandle, filenamePart] = await opfs.getDirForFilename(filename, false);
- const fh = await dirHandle.getFileHandle(filenamePart);
- sh = await fh.createSyncAccessHandle();
- T.assert(fSize === await sh.getSize());
- await sh.close();
- sh = undefined;
- unlink();
- T.assert(!(await opfs.entryExists(filename)));
- }finally{
- if(sh) await sh.close();
- unlink();
- }
- // Some sanity checks of the opfs utility functions...
- const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
- const aDir = testDir+'/test/dir';
- T.assert(await opfs.mkdir(aDir), "mkdir failed")
- .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
- .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
- .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
- .assert(!(await opfs.unlink(testDir+'/test/dir')),
- "delete 2b should have failed (dir already deleted)")
- .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
- .assert(!(await opfs.entryExists(testDir)),
- "entryExists(",testDir,") should have failed");
- }
- }/*OPFS util sanity checks*/)
- ;/* end OPFS tests */
- ////////////////////////////////////////////////////////////////////////
- T.g('OPFS SyncAccessHandle Pool VFS',
- (sqlite3)=>(hasOpfs() || "requires OPFS APIs"))
- .t({
- name: 'SAH sanity checks',
- test: async function(sqlite3){
- T.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
- .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) < 0)
- const inst = sqlite3.installOpfsSAHPoolVfs,
- catcher = (e)=>{
- error("Cannot load SAH pool VFS.",
- "This might not be a problem,",
- "depending on the environment.");
- return false;
- };
- let u1, u2;
- // Ensure that two immediately-consecutive installations
- // resolve to the same Promise instead of triggering
- // a locking error.
- const P1 = inst(sahPoolConfig).then(u=>u1 = u).catch(catcher),
- P2 = inst(sahPoolConfig).then(u=>u2 = u).catch(catcher);
- await Promise.all([P1, P2]);
- if(!(await P1)) return;
- T.assert(u1 === u2)
- .assert(sahPoolConfig.name === u1.vfsName)
- .assert(sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
- .assert(u1.getCapacity() >= sahPoolConfig.initialCapacity
- /* If a test fails before we get to nuke the VFS, we
- can have more than the initial capacity on the next
- run. */)
- .assert(u1.getCapacity() + 2 === (await u2.addCapacity(2)))
- .assert(2 === (await u2.reduceCapacity(2)))
- .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) >= 0);
- T.assert(0 === u1.getFileCount());
- const dbName = '/foo.db';
- let db = new u1.OpfsSAHPoolDb(dbName);
- T.assert(db instanceof sqlite3.oo1.DB)
- .assert(1 === u1.getFileCount());
- db.exec([
- 'pragma locking_mode=exclusive;',
- 'pragma journal_mode=wal;'
- /* WAL mode only works in this VFS if locking_mode=exclusive
- is invoked prior to the first db access, as this build
- does not have the shared-memory APIs needed for WAL without
- exclusive-mode locking. See:
- https://sqlite.org/wal.html#use_of_wal_without_shared_memory
- Note that WAL mode here DOES NOT add any concurrency capabilities
- to this VFS, but it MAY provide slightly improved performance
- over the other journaling modes.
- */,
- 'create table t(a);',
- 'insert into t(a) values(1),(2),(3)'
- ]);
- T.assert(2 === u1.getFileCount() /* one is the journal file */)
- .assert(3 === db.selectValue('select count(*) from t'))
- .assert(
- 'wal'===db.selectValue('pragma journal_mode')
- || wasm.compileOptionUsed('OMIT_WAL')
- );
- db.close();
- T.assert(1 === u1.getFileCount());
- db = new u2.OpfsSAHPoolDb(dbName);
- T.assert(1 === u1.getFileCount());
- db.close();
- const fileNames = u1.getFileNames();
- T.assert(1 === fileNames.length)
- .assert(dbName === fileNames[0])
- .assert(1 === u1.getFileCount())
- if(1){ // test exportFile() and importDb()
- const dbytes = u1.exportFile(dbName);
- T.assert(dbytes.length >= 4096);
- const dbName2 = '/exported.db';
- let nWrote = u1.importDb(dbName2, dbytes);
- T.assert( 2 == u1.getFileCount() )
- .assert( dbytes.byteLength == nWrote );
- let db2 = new u1.OpfsSAHPoolDb(dbName2);
- T.assert(db2 instanceof sqlite3.oo1.DB)
- .assert('wal' !== db2.selectValue("pragma journal_mode")
- /* importDb() unsets the WAL-mode header for
- historical reasons. Because clients must
- explicitly enable pragma locking_mode=exclusive
- before using WAL, that behavior is retained. */)
- .assert(3 === db2.selectValue('select count(*) from t'));
- db2.close();
- T.assert(true === u1.unlink(dbName2))
- .assert(false === u1.unlink(dbName2))
- .assert(1 === u1.getFileCount())
- .assert(1 === u1.getFileNames().length);
- // Try again with a function as an input source:
- let cursor = 0;
- const blockSize = 1024, end = dbytes.byteLength;
- const reader = async function(){
- if(cursor >= dbytes.byteLength){
- return undefined;
- }
- const rv = dbytes.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
- cursor += blockSize;
- return rv;
- };
- nWrote = await u1.importDb(dbName2, reader);
- T.assert( 2 == u1.getFileCount() );
- db2 = new u1.OpfsSAHPoolDb(dbName2);
- T.assert(db2 instanceof sqlite3.oo1.DB)
- .assert(3 === db2.selectValue('select count(*) from t'));
- db2.close();
- T.assert(true === u1.unlink(dbName2))
- .assert(dbytes.byteLength == nWrote);
- }
- T.assert(true === u1.unlink(dbName))
- .assert(false === u1.unlink(dbName))
- .assert(0 === u1.getFileCount())
- .assert(0 === u1.getFileNames().length);
- // Demonstrate that two SAH pools can coexist so long as
- // they have different names.
- const conf2 = JSON.parse(JSON.stringify(sahPoolConfig));
- conf2.name += '-test2';
- const POther = await inst(conf2);
- //log("Installed second SAH instance as",conf2.name);
- T.assert(0 === POther.getFileCount())
- .assert(true === await POther.removeVfs());
- if(0){
- /* Enable this block to inspect vfs's contents via the dev
- console or OPFS Explorer browser extension. The
- following bits will remove them. */
- return;
- }
- T.assert(true === await u2.removeVfs())
- .assert(false === await u1.removeVfs())
- .assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name));
- let cErr, u3;
- conf2.$testThrowPhase2 = new Error("Testing throwing during init.");
- conf2.name = sahPoolConfig.name+'-err';
- const P3 = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e);
- T.assert(P3 === conf2.$testThrowPhase2)
- .assert(cErr === P3)
- .assert(undefined === u3)
- .assert(!sqlite3.capi.sqlite3_vfs_find(conf2.name));
- delete conf2.$testThrowPhase2;
- T.assert(cErr === await inst(conf2).catch(e=>e),
- "Init result is cached even if it failed");
- /* Ensure that the forceReinitIfPreviouslyFailed fallback bypasses
- the VFS init cache... */
- cErr = u3 = undefined;
- conf2.forceReinitIfPreviouslyFailed = true;
- conf2.verbosity = 3;
- const P3b = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e);
- T.assert(undefined === cErr)
- .assert(P3b === u3)
- .assert(P3b === await inst(conf2))
- .assert(true === await u3.removeVfs())
- .assert(false === await P3b.removeVfs());
- }
- }/*OPFS SAH Pool sanity checks*/)
- ////////////////////////////////////////////////////////////////////////
- T.g('Misc. APIs')
- .t('bind_parameter_...', function(sqlite3){
- const db = new sqlite3.oo1.DB();
- db.exec("create table t(a)");
- const stmt = db.prepare("insert into t(a) values($a)");
- T.assert( 1===capi.sqlite3_bind_parameter_count(stmt) )
- .assert( 1===capi.sqlite3_bind_parameter_index(stmt, "$a") )
- .assert( 0===capi.sqlite3_bind_parameter_index(stmt, ":a") )
- .assert( 1===stmt.getParamIndex("$a") )
- .assert( 0===stmt.getParamIndex(":a") )
- .assert( "$a"===capi.sqlite3_bind_parameter_name(stmt, 1) )
- .assert( null===capi.sqlite3_bind_parameter_name(stmt, 0) )
- .assert( "$a"===stmt.getParamName(1) )
- .assert( null===stmt.getParamName(0) );
- stmt.finalize();
- db.close();
- })
- ////////////////////////////////////////////////////////////////////
- .t("Misc. stmt_...", function(sqlite3){
- const db = new sqlite3.oo1.DB();
- db.exec("create table t(a doggiebiscuits); insert into t(a) values(123)");
- const stmt = db.prepare("select a, a+1 from t");
- T.assert( stmt.isReadOnly() )
- .assert( 0===capi.sqlite3_stmt_isexplain(stmt) )
- .assert( 0===capi.sqlite3_stmt_explain(stmt, 1) )
- .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) )
- .assert( 0===capi.sqlite3_stmt_explain(stmt, 2) )
- .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) )
- .assert( 0===capi.sqlite3_stmt_explain(stmt, 0) )
- .assert( 0===capi.sqlite3_stmt_isexplain(stmt) );
- let n = 0;
- while( capi.SQLITE_ROW === capi.sqlite3_step(stmt) ){
- ++n;
- T.assert( 0!==capi.sqlite3_stmt_explain(stmt, 1),
- "Because stmt is busy" )
- .assert( capi.sqlite3_stmt_busy(stmt) )
- .assert( stmt.isBusy() )
- .assert( 0!==capi.sqlite3_stmt_readonly(stmt) )
- .assert( true===stmt.isReadOnly() );
- const sv = capi.sqlite3_column_value(stmt, 0);
- T.assert( 123===capi.sqlite3_value_int(sv) )
- .assert( "doggiebiscuits"===capi.sqlite3_column_decltype(stmt,0) )
- .assert( null===capi.sqlite3_column_decltype(stmt,1) );
- }
- T.assert( 1===n )
- .assert( 0===capi.sqlite3_stmt_busy(stmt) )
- .assert( !stmt.isBusy() );
- stmt.finalize();
- db.close();
- })
- ////////////////////////////////////////////////////////////////////
- .t("interrupt", function(sqlite3){
- const db = new sqlite3.oo1.DB();
- T.assert( 0===capi.sqlite3_is_interrupted(db) );
- capi.sqlite3_interrupt(db);
- T.assert( 0!==capi.sqlite3_is_interrupted(db) );
- db.close();
- })
- ////////////////////////////////////////////////////////////////////////
- T.g('Bug Reports')
- .t({
- name: 'Delete via bound parameter in subquery',
- predicate: ()=>wasm.compileOptionUsed('ENABLE_FTS5') || "Missing FTS5",
- test: function(sqlite3){
- // Testing https://sqlite.org/forum/forumpost/40ce55bdf5
- // with the exception that that post uses "external content"
- // for the FTS index.
- const db = new sqlite3.oo1.DB();//(':memory:','wt');
- db.exec([
- "create virtual table f using fts5 (path);",
- "insert into f(path) values('abc'),('def'),('ghi');"
- ]);
- const fetchEm = ()=> db.exec({
- sql: "SELECT * FROM f order by path",
- rowMode: 'array'
- });
- const dump = function(lbl){
- let rc = fetchEm();
- log((lbl ? (lbl+' results') : ''),rc);
- };
- //dump('Full fts table');
- let rc = fetchEm();
- T.assert(3===rc.length);
- db.exec(`
- delete from f where rowid in (
- select rowid from f where path = :path
- )`,
- {bind: {":path": "def"}}
- );
- //dump('After deleting one entry via subquery');
- rc = fetchEm();
- T.assert(2===rc.length)
- .assert('abcghi'===rc.join(''));
- //log('rc =',rc);
- db.close();
- }
- })
- .t({
- name: 'r/o connection recovery from write op error',
- predicate: ()=>hasOpfs() || "Requires OPFS to reproduce",
- //predicate: ()=>false,
- test: async function(sqlite3){
- /* https://sqlite.org/forum/forumpost/cf37d5ff1182c31081
- The "opfs" VFS (but not SAHPool) was formerly misbehaving
- after a write attempt was made on a db opened with
- mode=ro. This test ensures that that behavior is fixed and
- compares that behavior with other VFSes. */
- const tryOne = function(vfsName,descr){
- const uri = 'file:///foo.db';
- let db = new sqlite3.oo1.DB(uri + (vfsName ? '?vfs='+vfsName : ''));
- db.exec([
- "drop table if exists t;",
- "create table t(a);",
- "insert into t(a) values('abc'),('def'),('ghi');"
- ]);
- db.close();
- db = new sqlite3.oo1.DB(uri+'?mode=ro'+
- (vfsName ? '&vfs='+vfsName : ''));
- let err;
- try {
- db.exec('insert into t(a) values(1)');
- }catch(e){
- err = e;
- }
- T.assert(err && (err.message.indexOf('SQLITE_READONLY')===0));
- try{
- db.exec('select a from t');
- }finally{
- db.close();
- }
- };
- const poolConfig = JSON.parse(JSON.stringify(sahPoolConfig));
- poolConfig.name = 'opfs-sahpool-cf37d5ff11';
- let poolUtil;
- await sqlite3.installOpfsSAHPoolVfs(poolConfig).then(p=>poolUtil=p);
- T.assert(!!sqlite3.capi.sqlite3_vfs_find(poolConfig.name), "Expecting to find just-registered VFS");
- try{
- tryOne(false, "Emscripten filesystem");
- tryOne(poolConfig.name);
- tryOne('opfs');
- }finally{
- await poolUtil.removeVfs();
- }
- }
- })
- ;/*end of Bug Reports group*/;
- ////////////////////////////////////////////////////////////////////////
- log("Loading and initializing sqlite3 WASM module...");
- if(0){
- globalThis.sqlite3ApiConfig = {
- debug: ()=>{},
- log: ()=>{},
- warn: ()=>{},
- error: ()=>{}
- }
- }
- //#ifnot target=es6-module
- if(!globalThis.sqlite3InitModule && !isUIThread()){
- /* Vanilla worker, as opposed to an ES6 module worker */
- /*
- If sqlite3.js is in a directory other than this script, in order
- to get sqlite3.js to resolve sqlite3.wasm properly, we have to
- explicitly tell it where sqlite3.js is being loaded from. We do
- that by passing the `sqlite3.dir=theDirName` URL argument to
- _this_ script. That URL argument will be seen by the JS/WASM
- loader and it will adjust the sqlite3.wasm path accordingly. If
- sqlite3.js/.wasm are in the same directory as this script then
- that's not needed.
- URL arguments passed as part of the filename via importScripts()
- are simply lost, and such scripts see the globalThis.location of
- _this_ script.
- */
- let sqlite3Js = 'sqlite3.js';
- const urlParams = new URL(globalThis.location.href).searchParams;
- if(urlParams.has('sqlite3.dir')){
- sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
- }
- importScripts(sqlite3Js);
- }
- //#endif
- globalThis.sqlite3InitModule.__isUnderTest =
- true /* disables certain API-internal cleanup so that we can
- test internal APIs from here */;
- globalThis.sqlite3InitModule({
- print: log,
- printErr: error
- }).then(async function(sqlite3){
- TestUtil.assert(!!sqlite3.util);
- log("Done initializing WASM/JS bits. Running tests...");
- sqlite3.config.warn("Installing sqlite3 bits as global S for local dev/test purposes.");
- globalThis.S = sqlite3;
- /*await sqlite3.installOpfsSAHPoolVfs(sahPoolConfig)
- .then((u)=>log("Loaded",u.vfsName,"VFS"))
- .catch(e=>{
- log("Cannot install OpfsSAHPool.",e);
- });*/
- capi = sqlite3.capi;
- wasm = sqlite3.wasm;
- log("sqlite3 version:",capi.sqlite3_libversion(),
- capi.sqlite3_sourceid());
- if(wasm.bigIntEnabled){
- log("BigInt/int64 support is enabled.");
- }else{
- logClass('warning',"BigInt/int64 support is disabled.");
- }
- if(haveWasmCTests()){
- log("sqlite3__wasm_test_...() APIs are available.");
- }else{
- logClass('warning',"sqlite3__wasm_test_...() APIs unavailable.");
- }
- log("registered vfs list =",capi.sqlite3_js_vfs_list().join(', '));
- TestUtil.runTests(sqlite3);
- });
- })(self);
|