*/
'use strict';
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss;
+ const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
const vh = Object.create(null), vt = Object.create(null);
sqlite3.VfsHelper = vh;
/**
Installs a StructBinder-bound function pointer member of the
given name and function in the given StructType target object.
+
It creates a WASM proxy for the given function and arranges for
- that proxy to be cleaned up when tgt.dispose() is called. Throws
+ that proxy to be cleaned up when tgt.dispose() is called. Throws
on the slightest hint of error, e.g. tgt is-not-a StructType,
name does not map to a struct-bound member, etc.
- Returns a proxy for this function which is bound to tgt and takes
- 2 args (name,func). That function returns the same thing,
- permitting calls to be chained.
+ As a special case, if the given function is a pointer, it is
+ assumed to be an existing WASM-bound function pointer and is used
+ as-is with no extra level of proxying or cleanup. Results are
+ undefined if it's a pointer and it's _not_ a function pointer.
+ It is legal to pass a value of 0, indicating a NULL pointer, with
+ the caveat that 0 _is_ a legal function pointer in WASM but it
+ will not be accepted as such _here_. (Justification: the function
+ at address zero must be one which initially came from the WASM
+ module, not a method we want to bind to a virtual table or VFS.)
+
+ This function returns a proxy for itself which is bound to tgt
+ and takes 2 args (name,func). That function returns the same
+ thing as this one, permitting calls to be chained.
If called with only 1 arg, it has no side effects but returns a
func with the same signature as described above.
- If tgt.ondispose is set before this is called then it _must_
- be an array, to which this function will append entries.
-
ACHTUNG: because we cannot generically know how to transform JS
exceptions into result codes, the installed functions do no
automatic catching of exceptions. It is critical, to avoid
this function do not throw. The exception, as it were, to that
rule is...
- If applyArgcCheck is true then each method gets wrapped in a
- proxy which asserts that it is passed the expected number of
- arguments, throwing if the argument count does not match
- expectations. That is only intended for dev-time usage for sanity
- checking, and will leave the C environment in an undefined
- state. For non-dev-time use, it is a given that the C API will
- never call one of the generated function wrappers with the wrong
- argument count.
+ If applyArgcCheck is true then each JS function (as opposed to
+ function pointers) gets wrapped in a proxy which asserts that it
+ is passed the expected number of arguments, throwing if the
+ argument count does not match expectations. That is only intended
+ for dev-time usage for sanity checking, and will leave the C
+ environment in an undefined state.
*/
vh.installMethod = vt.installMethod = function callee(
tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
){
if(!(tgt instanceof sqlite3.StructBinder.StructType)){
toss("Usage error: target object is-not-a StructType.");
+ }else if(!(func instanceof Function) && !wasm.isPtr(func)){
+ toss("Usage errror: expecting a Function or WASM pointer to one.");
}
if(1===arguments.length){
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}/*static init*/
const sigN = tgt.memberSignature(name);
if(sigN.length<2){
- toss("Member",name," is not a function pointer. Signature =",sigN);
+ toss("Member",name,"does not have a function pointer signature:",sigN);
}
const memKey = tgt.memberKey(name);
- const fProxy = applyArgcCheck
+ const fProxy = (applyArgcCheck && !wasm.isPtr(func))
/** This middle-man proxy is only for use during development, to
confirm that we always pass the proper number of
arguments. We know that the C-level code will always use the
correct argument count. */
? callee.argcProxy(tgt, memKey, func, sigN)
: func;
- const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
- tgt[memKey] = pFunc;
- if(!tgt.ondispose) tgt.ondispose = [];
- else if(!Array.isArray(tgt.ondispose)) tgt.ondispose = [tgt.ondispose];
- if(!tgt.ondispose.__removeFuncList){
- tgt.ondispose.push('ondispose.__removeFuncList handler',
+ if(wasm.isPtr(fProxy)){
+ if(fProxy && !wasm.functionEntry(fProxy)){
+ toss("Pointer",fProxy,"is not a WASM function table entry.");
+ }
+ tgt[memKey] = fProxy;
+ }else{
+ const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
+ tgt[memKey] = pFunc;
+ if(!tgt.ondispose) tgt.ondispose = [];
+ else if(!Array.isArray(tgt.ondispose)) tgt.ondispose = [tgt.ondispose];
+ if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
+ tgt.addOnDispose('ondispose.__removeFuncList handler',
callee.removeFuncList);
- tgt.ondispose.__removeFuncList = [];
+ tgt.ondispose.__removeFuncList = [];
+ }
+ tgt.ondispose.__removeFuncList.push(memKey, pFunc);
}
- tgt.ondispose.__removeFuncList.push(memKey, pFunc);
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}/*installMethod*/;
vh.installMethod.installMethodArgcCheck = false;
this.installMethods(o.struct, o.methods, !!o.applyArgcCheck);
if('vfs'===key){
if(!o.struct.$zName && 'string'===typeof o.name){
- o.struct.$zName = wasm.allocCString(o.name);
- /* Note that we leak that C-string. */
+ o.struct.addOnDispose(
+ o.struct.$zName = wasm.allocCString(o.name)
+ );
}
this.registerVfs(o.struct, !!o.asDefault);
}
vt.sqlite3ValuesToJs = capi.sqlite3_create_function_v2.udfConvertArgs;
/**
- Factory function for wrapXyz() impls.
+ Factory function for xAbc() impls.
*/
- const __xWrapFactory = function(structType){
- return function(ptr,remove=false){
+ const __xWrapFactory = function(methodName,structType){
+ return function(ptr,removeMapping=false){
if(0===arguments.length) ptr = new structType;
if(ptr instanceof structType){
//T.assert(!this.has(ptr.pointer));
this.set(ptr.pointer, ptr);
return ptr;
}else if(!wasm.isPtr(ptr)){
- sqlite3.SQLite3Error.toss("Invalid argument to xWrapFactory");
+ sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()");
}
let rc = this.get(ptr);
- if(remove) this.delete(ptr);
- /*arguable else if(!rc){
- rc = new structType(ptr);
- this.set(ptr, rc);
- }*/
+ if(removeMapping) this.delete(ptr);
return rc;
}.bind(new Map);
};
called from sqlite3_module::xDisconnect() implementations or
in error handling of a failed xCreate() or xConnect().
*/
- vt.xWrapVtab = __xWrapFactory(capi.sqlite3_vtab);
+ vt.xVtab = __xWrapFactory('xVtab',capi.sqlite3_vtab);
/**
EXPERIMENTAL. DO NOT USE IN CLIENT CODE.
intended to be called from xClose() or in error handling of a
failed xOpen().
*/
- vt.xWrapCursor = __xWrapFactory(capi.sqlite3_vtab_cursor);
+ vt.xCursor = __xWrapFactory('xCursor',capi.sqlite3_vtab_cursor);
+
+ /**
+ Convenience form of creating an sqlite3_index_info wrapper,
+ intended for use in xBestIndex implementations. Note that the
+ caller is expected to call dispose() on the returned object
+ before returning. Though not _strictly_ required, as that object
+ does not own the pIdxInfo memory, it is nonetheless good form.
+ */
+ vt.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo);
/**
Given an error object, this function returns
let rc = ...
return rc;
}catch(e){
- return sqlite3.VtabHelper.xMethodError(
+ return sqlite3.VtabHelper.xError(
'xColumn', e, sqlite3.capi.SQLITE_XYZ);
- // where SQLITE_XYZ is some call-appropriate result code
- // defaulting to SQLITE_ERROR.
+ // where SQLITE_XYZ is some call-appropriate result code.
}
```
- If xMethodError.errorReporter is a function, it is called in
+ If no 3rd argument is provided, its default depends on
+ the error type:
+
+ - An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM.
+
+ - If err is an SQLite3Error then its `resultCode` property
+ is used.
+
+ - If all else fails, capi.SQLITE_ERROR is used.
+
+ If xError.errorReporter is a function, it is called in
order to report the error, else the error is not reported.
If that function throws, that exception is ignored.
*/
- vt.xMethodError = function f(methodName, err, defaultRc=capi.SQLITE_ERROR){
+ vt.xError = function f(methodName, err, defaultRc){
if(f.errorReporter instanceof Function){
try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);}
catch(e){/*ignored*/}
}
- return (err instanceof sqlite3.WasmAllocError)
- ? capi.SQLITE_NOMEM
- : defaultRc;
+ let rc;
+ if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM;
+ else if(arguments.length>2) rc = defaultRc;
+ else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode;
+ return rc || capi.SQLITE_ERROR;
};
- vt.xMethodError.errorReporter = 1 ? console.error.bind(console) : false;
+ vt.xError.errorReporter = 1 ? console.error.bind(console) : false;
/**
"The problem" with this is that it introduces an outer function with
/** vt.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
return function(...args){
try { method(...args); }
- }catch(e){ return vt.xMethodError(methodName, e, defaultRc) }
+ }catch(e){ return vt.xError(methodName, e, defaultRc) }
};
*/
/**
- A helper for sqlite3_vtab::xRow() implementations. It must be
+ A helper for sqlite3_vtab::xRowid() implementations. It must be
passed that function's 2nd argument and the value for that
pointer. Returns the same as wasm.setMemValue() and will throw
if the 1st or 2nd arguments are invalid for that function.
*/
- vt.setRowId = (ppRowid64, value)=>wasm.setMemValue(ppRowid64, value, 'i64');
+ vt.xRowid = (ppRowid64, value)=>wasm.setMemValue(ppRowid64, value, 'i64');
+
+ /**
+ Sets up an sqlite3_module() object for later installation into
+ individual databases using sqlite3_create_module(). Requires an
+ object with the following properties:
+
+ - `methods`: an object containing a mapping of properties with
+ the C-side names of the sqlite3_module methods, e.g. xCreate,
+ xBestIndex, etc., to JS implementations for those functions.
+ Certain special-case handling is performed, as described below.
+
+ - `catchExceptions` (default=false): if truthy, the given methods
+ are not mapped as-is, but are instead wrapped inside wrappers
+ which translate exceptions into result codes of SQLITE_ERROR or
+ SQLITE_NOMEM, depending on whether the exception is an
+ sqlite3.WasmAllocError. In the case of the xConnect and xCreate
+ methods, the exception handler also sets the output error
+ string to the exception's error string.
+
+ - OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If
+ not set, one will be created automatically and (on success)
+ added to the object.
+
+ - OPTIONAL `iVersion`: if set, it must be an integer value and it
+ gets assigned to the `$iVersion` member of the struct object.
+ If it's _not_ set, and the passed-in `struct` object's `$iVersion`
+ is 0 (the default) then this function attempts to define a value
+ for that property based on the list of methods it has.
+
+ If `catchExceptions` is false, it is up to the client to ensure
+ that no exceptions escape the methods, as doing so would move
+ them through the C API, leading to undefined
+ behavior. (VtabHelper.xError() is intended to assist in reporting
+ such exceptions.)
+
+ If `methods.xConnect` is `true` then the value of
+ `methods.xCreate` is used in its place, and vice versa. This is
+ to facilitate creation of those methods inline in the passed-in
+ object without requiring the client to explicitly get a reference
+ to one of them in order to assign it to the other one. Note that
+ sqlite treats those two functions specially if they are exactly
+ the same function (same pointer value). The
+ `catchExceptions`-installed handlers will account for identical
+ references to those two functions and will install the same
+ wrapper function for both.
+
+ The given methods are expected to return integer values, as
+ expected by the C API. If `catchExceptions` is truthy, the return
+ value of the wrapped function will be used as-is and will be
+ translated to 0 if the function returns a falsy value (e.g. if it
+ does not have an explicit return). If `catchExceptions` is _not_
+ active, the method implementations must explicitly return integer
+ values.
+
+ Throws on error. Returns the sqlite3_module object on success.
+ */
+ vt.setupModule = function(opt){
+ const mod = opt.struct || new capi.sqlite3_module();
+ try{
+ const methods = opt.methods || toss("Missing 'methods' object.");
+ if(true===methods.xConnect) methods.xConnect = methods.xCreate;
+ else if(true===methods.xCreate) methods.xCreate = methods.xConnect;
+ if(opt.catchExceptions){
+ const fwrap = function(methodName, func){
+ if(['xConnect','xCreate'].indexOf(methodName) >= 0){
+ return function(pDb, pAux, argc, argv, ppVtab, pzErr){
+ try{return func(...arguments) || 0;}
+ catch(e){
+ if(!(e instanceof sqlite3.WasmAllocError)){
+ wasm.setPtrValue(pzErr, wasm.allocCString(e.message));
+ }
+ return vt.xError(methodName, e);
+ }
+ };
+ }else{
+ return function(...args){
+ try{return func(...args) || 0;}
+ catch(e){
+ return vt.xError(methodName, e);
+ }
+ };
+ }
+ };
+ const mnames = [
+ 'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect',
+ 'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext',
+ 'xEof', 'xColumn', 'xRowid', 'xUpdate',
+ 'xBegin', 'xSync', 'xCommit', 'xRollback',
+ 'xFindFunction', 'xRename', 'xSavepoint', 'xRelease',
+ 'xRollbackTo', 'xShadowName'
+ ];
+ const remethods = Object.create(null);
+ for(const k of mnames){
+ const m = methods[k];
+ if(!(m instanceof Function)) continue;
+ else if('xConnect'===k && methods.xCreate===m){
+ remethods[k] = methods.xCreate;
+ }else if('xCreate'===k && methods.xConnect===m){
+ remethods[k] = methods.xConnect;
+ }else{
+ remethods[k] = fwrap(k, m);
+ }
+ }
+ this.installMethods(mod, remethods, false);
+ }else{
+ this.installMethods(
+ mod, methods, !!opt.applyArgcCheck/*undocumented option*/
+ );
+ }
+ if(0===mod.$iVersion){
+ let v;
+ if('number'===typeof opt.iVersion) v = opt.iVersion;
+ else if(mod.$xShadowName) v = 3;
+ else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2;
+ else v = 1;
+ mod.$iVersion = v;
+ }
+ }catch(e){
+ if(!opt.struct) mod.dispose();
+ throw e;
+ }
+ if(!opt.struct) opt.struct = mod;
+ return mod;
+ }/*setupModule()*/;
}/*sqlite3ApiBootstrap.initializers.push()*/);
addTest: function(name, callback){
let predicate;
if(1===arguments.length){
- const opt = arguments[0];
- predicate = opt.predicate;
- name = opt.name;
- callback = opt.test;
+ this.currentTestGroup.addTest(arguments[0]);
+ }else{
+ this.currentTestGroup.addTest({
+ name, predicate, test: callback
+ });
}
- this.currentTestGroup.addTest({
- name, predicate, test: callback
- });
return this;
},
runTests: async function(sqlite3){
catch(e){T.assert("test ing ." === e.message)}
try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) }
- catch(e){ T.assert('SQLITE_SCHEMA' === e.message) }
+ 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)
+ 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){
const dbFile = '/tester1.db';
wasm.sqlite3_wasm_vfs_unlink(0, dbFile);
const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c');
+ db.onclose = {
+ disposeThese: [],
+ after: function(){
+ while(this.disposeThese.length){
+ const v = this.disposeThese.shift();
+ console.debug("db.onclose cleaning up:",v);
+ if(wasm.isPtr(v)) wasm.dealloc(v);
+ else if(v instanceof sqlite3.StructBinder.StructType){
+ v.dispose();
+ }
+ }
+ }
+ };
+
T.assert(Number.isInteger(db.pointer))
.mustThrowMatching(()=>db.pointer=1, /read-only/)
.assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
T.mustThrow(()=>db.exec("select * from foo.bar"));
})
+ ////////////////////////////////////////////////////////////////////
+ .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;
+ const ptrValType = 'i32';
+ try{
+ ptrInt = w.scopedAlloc(4);
+ w.setMemValue(ptrInt,origValue, ptrValType);
+ const cf = w.xGet('sqlite3_wasm_test_intptr');
+ const oldPtrInt = ptrInt;
+ //log('ptrInt',ptrInt);
+ //log('getMemValue(ptrInt)',w.getMemValue(ptrInt));
+ T.assert(origValue === w.getMemValue(ptrInt, ptrValType));
+ const rc = cf(ptrInt);
+ //log('cf(ptrInt)',rc);
+ //log('ptrInt',ptrInt);
+ //log('getMemValue(ptrInt)',w.getMemValue(ptrInt,ptrValType));
+ T.assert(2*origValue === rc).
+ assert(rc === w.getMemValue(ptrInt,ptrValType)).
+ assert(oldPtrInt === ptrInt);
+ const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/;
+ const o64 = 0x010203040506/*>32-bit integer*/;
+ const ptrType64 = 'i64';
+ if(w.bigIntEnabled){
+ w.setMemValue(pi64, o64, ptrType64);
+ //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64);
+ const v64 = ()=>w.getMemValue(pi64,ptrType64)
+ //log("getMemValue(pi64)",v64());
+ T.assert(v64() == o64);
+ //T.assert(o64 === w.getMemValue(pi64, ptrType64));
+ const cf64w = w.xGet('sqlite3_wasm_test_int64ptr');
+ cf64w(pi64);
+ //log("getMemValue(pi64)",v64());
+ 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.getMemValue(p,ptrType64);
+ w.setMemValue(pMin, 0, ptrType64);
+ w.setMemValue(pMax, 0, ptrType64);
+ 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.setMemValue(pMin, minMaxI64[0], ptrType64);
+ 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: 'Custom virtual tables',
- predicate: ()=>wasm.bigIntEnabled,
+ name: 'virtual table #1',
+ predicate: ()=>!!capi.sqlite3_index_info,
test: function(sqlite3){
warn("The vtab/module JS bindings are experimental and subject to change.");
const vth = sqlite3.VtabHelper;
pDb, "CREATE TABLE ignored(a,b)"
);
if(0===rc){
- const t = vth.xWrapVtab();
+ const t = vth.xVtab();
wasm.setPtrValue(ppVtab, t.pointer);
- T.assert(t === vth.xWrapVtab(wasm.getPtrValue(ppVtab)));
+ T.assert(t === vth.xVtab(wasm.getPtrValue(ppVtab)));
}
return rc;
}catch(e){
if(!(e instanceof sqlite3.WasmAllocError)){
wasm.setPtrValue(pzErr, wasm.allocCString(e.message));
}
- return vth.xMethodError('xConnect',e);
+ return vth.xError('xConnect',e);
}
},
xDisconnect: function(pVtab){
try {
- const t = vth.xWrapVtab(pVtab, true);
+ const t = vth.xVtab(pVtab, true);
t.dispose();
return 0;
}catch(e){
- return vth.xMethodError('xDisconnect',e);
+ return vth.xError('xDisconnect',e);
}
},
xOpen: function(pVtab, ppCursor){
try{
- const t = vth.xWrapVtab(pVtab), c = vth.xWrapCursor();
+ const t = vth.xVtab(pVtab), c = vth.xCursor();
T.assert(t instanceof capi.sqlite3_vtab)
.assert(c instanceof capi.sqlite3_vtab_cursor);
wasm.setPtrValue(ppCursor, c.pointer);
c._rowId = 0;
return 0;
}catch(e){
- return vth.xMethodError('xOpen',e);
+ return vth.xError('xOpen',e);
}
},
xClose: function(pCursor){
try{
- const c = vth.xWrapCursor(pCursor,true);
+ const c = vth.xCursor(pCursor,true);
T.assert(c instanceof capi.sqlite3_vtab_cursor)
- .assert(!vth.xWrapCursor(pCursor));
+ .assert(!vth.xCursor(pCursor));
c.dispose();
return 0;
}catch(e){
- return vth.xMethodError('xClose',e);
+ return vth.xError('xClose',e);
}
},
xNext: function(pCursor){
try{
- const c = vth.xWrapCursor(pCursor);
+ const c = vth.xCursor(pCursor);
++c._rowId;
return 0;
}catch(e){
- return vth.xMethodError('xNext',e);
+ return vth.xError('xNext',e);
}
},
xColumn: function(pCursor, pCtx, iCol){
try{
- const c = vth.xWrapCursor(pCursor);
+ const c = vth.xCursor(pCursor);
switch(iCol){
case tmplCols.A:
capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
}
return 0;
}catch(e){
- return vth.xMethodError('xColumn',e);
+ return vth.xError('xColumn',e);
}
},
xRowid: function(pCursor, ppRowid64){
try{
- const c = vth.xWrapCursor(pCursor);
- vth.setRowId(ppRowid64, c._rowId);
+ const c = vth.xCursor(pCursor);
+ vth.xRowid(ppRowid64, c._rowId);
return 0;
}catch(e){
- return vth.xMethodError('xRowid',e);
+ return vth.xError('xRowid',e);
}
},
xEof: function(pCursor){
- const c = vth.xWrapCursor(pCursor);
- return c._rowId>=10;
+ const c = vth.xCursor(pCursor),
+ rc = c._rowId>=10;
+ c.dispose();
+ return rc;
},
xFilter: function(pCursor, idxNum, idxCStr,
argc, argv/* [sqlite3_value* ...] */){
try{
- const c = vth.xWrapCursor(pCursor);
+ const c = vth.xCursor(pCursor);
c._rowId = 0;
const list = vth.sqlite3ValuesToJs(argc, argv);
T.assert(argc === list.length);
//log(argc,"xFilter value(s):",list);
+ c.dispose();
return 0;
}catch(e){
- return vth.xMethodError('xFilter',e);
+ return vth.xError('xFilter',e);
}
},
xBestIndex: function(pVtab, pIdxInfo){
try{
- //const t = vth.xWrapVtab(pVtab);
+ //const t = vth.xVtab(pVtab);
const sii = capi.sqlite3_index_info;
const pii = new sii(pIdxInfo);
pii.$estimatedRows = 10;
pii.dispose();
return 0;
}catch(e){
- return vth.xMethodError('xBestIndex',e);
+ return vth.xError('xBestIndex',e);
}
}
};
}
const tmplMod = new sqlite3.capi.sqlite3_module();
- tmplMod.ondispose = [];
tmplMod.$iVersion = 0;
+ this.db.onclose.disposeThese.push(tmplMod);
vth.installMethods(tmplMod, tmplMethods, true);
if(tmplMethods.xCreate){
T.assert(tmplMod.$xCreate)
.assert(1000===list[0][0])
.assert(2009===list[list.length-1][1])
}
- })/*vtab sanity checks*/
+ })/*custom vtab #1*/
- ////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
.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;
- const ptrValType = 'i32';
- try{
- ptrInt = w.scopedAlloc(4);
- w.setMemValue(ptrInt,origValue, ptrValType);
- const cf = w.xGet('sqlite3_wasm_test_intptr');
- const oldPtrInt = ptrInt;
- //log('ptrInt',ptrInt);
- //log('getMemValue(ptrInt)',w.getMemValue(ptrInt));
- T.assert(origValue === w.getMemValue(ptrInt, ptrValType));
- const rc = cf(ptrInt);
- //log('cf(ptrInt)',rc);
- //log('ptrInt',ptrInt);
- //log('getMemValue(ptrInt)',w.getMemValue(ptrInt,ptrValType));
- T.assert(2*origValue === rc).
- assert(rc === w.getMemValue(ptrInt,ptrValType)).
- assert(oldPtrInt === ptrInt);
- const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/;
- const o64 = 0x010203040506/*>32-bit integer*/;
- const ptrType64 = 'i64';
- if(w.bigIntEnabled){
- w.setMemValue(pi64, o64, ptrType64);
- //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64);
- const v64 = ()=>w.getMemValue(pi64,ptrType64)
- //log("getMemValue(pi64)",v64());
- T.assert(v64() == o64);
- //T.assert(o64 === w.getMemValue(pi64, ptrType64));
- const cf64w = w.xGet('sqlite3_wasm_test_int64ptr');
- cf64w(pi64);
- //log("getMemValue(pi64)",v64());
- 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.getMemValue(p,ptrType64);
- w.setMemValue(pMin, 0, ptrType64);
- w.setMemValue(pMax, 0, ptrType64);
- 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.setMemValue(pMin, minMaxI64[0], ptrType64);
- 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));
+ name: 'virtual table #2 (w/ automated exception wrapping)',
+ predicate: ()=>!!capi.sqlite3_index_info,
+ test: function(sqlite3){
+ warn("The vtab/module JS bindings are experimental and subject to change.");
+ const vth = sqlite3.VtabHelper;
+ 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 throwOnConnect = 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 modConfig = {
+ /* catchExceptions changes how the methods are wrapped */
+ catchExceptions: false,
+ name: "vtab2test",
+ methods:{
+ xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){
+ if(throwOnConnect){
+ sqlite3.SQLite3Error.toss(
+ throwOnConnect,
+ "Throwing a test exception."
+ );
+ }
+ const args = wasm.cArgvToJs(argc, argv);
+ console.debug("xCreate/xConnect args:",args);
+ T.assert(args.length>=3);
+ const rc = capi.sqlite3_declare_vtab(
+ pDb, "CREATE TABLE ignored(a,b)"
+ );
+ if(0===rc){
+ const t = vth.xVtab();
+ wasm.setPtrValue(ppVtab, t.pointer);
+ T.assert(t === vth.xVtab(wasm.getPtrValue(ppVtab)));
+ }
+ return rc;
+ },
+ xDisconnect: function(pVtab){
+ const t = vth.xVtab(pVtab, true);
+ t.dispose();
+ },
+ xOpen: function(pVtab, ppCursor){
+ const t = vth.xVtab(pVtab), c = vth.xCursor();
+ T.assert(t instanceof capi.sqlite3_vtab)
+ .assert(c instanceof capi.sqlite3_vtab_cursor);
+ wasm.setPtrValue(ppCursor, c.pointer);
+ c._rowId = 0;
+ },
+ xClose: function(pCursor){
+ const c = vth.xCursor(pCursor,true);
+ T.assert(c instanceof capi.sqlite3_vtab_cursor)
+ .assert(!vth.xCursor(pCursor));
+ c.dispose();
+ },
+ xNext: function(pCursor){
+ const c = vth.xCursor(pCursor);
+ ++c._rowId;
+ },
+ xColumn: function(pCursor, pCtx, iCol){
+ const c = vth.xCursor(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){
+ const c = vth.xCursor(pCursor);
+ vth.xRowid(ppRowid64, c._rowId);
+ c.dispose();
+ },
+ xEof: function(pCursor){
+ const c = vth.xCursor(pCursor),
+ rc = c._rowId>=10;
+ c.dispose();
+ return rc;
+ },
+ xFilter: function(pCursor, idxNum, idxCStr,
+ argc, argv/* [sqlite3_value* ...] */){
+ const c = vth.xCursor(pCursor);
+ c._rowId = 0;
+ const list = vth.sqlite3ValuesToJs(argc, argv);
+ T.assert(argc === list.length);
+ c.dispose();
+ },
+ xBestIndex: function(pVtab, pIdxInfo){
+ //const t = vth.xVtab(pVtab);
+ const pii = vth.xIndexInfo(pIdxInfo);
+ pii.$estimatedRows = 10;
+ pii.$estimatedCost = 10.0;
+ pii.dispose();
+ }
+ }/*methods*/
+ };
+ const doEponymous =
+ /* Bug (somewhere): non-eponymous is behaving as is
+ the call to sqlite3_create_module() is missing
+ or failed:
+
+ SQL TRACE #63 create virtual table testvtab2 using vtab2test(arg1, arg2)
+
+ => sqlite3 result code 1: no such module: vtab2test
+ */ true;
+ if(doEponymous){
+ warn("Reminder: non-eponymous mode is still not working here.",
+ "Details are in the code comments.");
+ modConfig.methods.xCreate = 0;
+ }else{
+ modConfig.methods.xCreate = (...args)=>0;
+ }
+ const tmplMod = vth.setupModule(modConfig);
+ T.assert(tmplMod instanceof capi.sqlite3_module)
+ .assert(1===tmplMod.$iVersion);
+ if(doEponymous){
+ if(modConfig.methods.xCreate !== 0){
+ T.assert(modConfig.methods.xCreate === modConfig.methods.xConnect)
+ .assert(tmplMod.$xCreate === tmplMod.$xConnect);
}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.");
+ T.assert(0 === tmplMod.$xCreate);
}
- }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);
}
+ this.db.onclose.disposeThese.push(tmplMod);
+ this.db.checkRc(capi.sqlite3_create_module(
+ this.db, modConfig.name, tmplMod, 0
+ ));
+ if(!doEponymous){
+ this.db.exec([
+ "create virtual table testvtab2 using ",
+ modConfig.name,
+ "(arg1, arg2)"
+ ]);
+ }
+ const list = this.db.selectArrays(
+ ["SELECT a,b FROM ",
+ (doEponymous ? modConfig.name : "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])
}
- }/* jaccwabyt-specific tests */)
+ })/*custom vtab #2*/
+ ////////////////////////////////////////////////////////////////////////
.t('Close db', function(){
T.assert(this.db).assert(wasm.isPtr(this.db.pointer));
wasm.sqlite3_wasm_db_reset(this.db);