aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api/sqlite3-api-glue.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/api/sqlite3-api-glue.js')
-rw-r--r--ext/wasm/api/sqlite3-api-glue.js693
1 files changed, 586 insertions, 107 deletions
diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js
index e962c93b6..6f7301ece 100644
--- a/ext/wasm/api/sqlite3-api-glue.js
+++ b/ext/wasm/api/sqlite3-api-glue.js
@@ -16,59 +16,67 @@
initializes the main API pieces so that the downstream components
(e.g. sqlite3-api-oo1.js) have all that they need.
*/
-(function(self){
+self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
'use strict';
const toss = (...args)=>{throw new Error(args.join(' '))};
-
- self.sqlite3 = self.sqlite3ApiBootstrap({
- Module: Module /* ==> Emscripten-style Module object. Currently
- needs to be exposed here for test code. NOT part
- of the public API. */,
- exports: Module['asm'],
- memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */,
- bigIntEnabled: !!self.BigInt64Array,
- allocExportName: 'malloc',
- deallocExportName: 'free'
- });
- delete self.sqlite3ApiBootstrap;
-
- const sqlite3 = self.sqlite3;
+ const toss3 = sqlite3.SQLite3Error.toss;
const capi = sqlite3.capi, wasm = capi.wasm, util = capi.util;
self.WhWasmUtilInstaller(capi.wasm);
delete self.WhWasmUtilInstaller;
+ /**
+ Install JS<->C struct bindings for the non-opaque struct types we
+ need... */
+ sqlite3.StructBinder = self.Jaccwabyt({
+ heap: 0 ? wasm.memory : wasm.heap8u,
+ alloc: wasm.alloc,
+ dealloc: wasm.dealloc,
+ functionTable: wasm.functionTable,
+ bigIntEnabled: wasm.bigIntEnabled,
+ memberPrefix: '$'
+ });
+ delete self.Jaccwabyt;
+
if(0){
- /* "The problem" is that the following isn't type-safe.
- OTOH, nothing about WASM pointers is. */
- /**
- Add the `.pointer` xWrap() signature entry to extend
- the `pointer` arg handler to check for a `pointer`
- property. This can be used to permit, e.g., passing
- an SQLite3.DB instance to a C-style sqlite3_xxx function
- which takes an `sqlite3*` argument.
- */
- const oldP = wasm.xWrap.argAdapter('pointer');
- const adapter = function(v){
- if(v && 'object'===typeof v && v.constructor){
- const x = v.pointer;
- if(Number.isInteger(x)) return x;
- else toss("Invalid (object) type for pointer-type argument.");
+ /* "The problem" is that the following isn't even remotely
+ type-safe. OTOH, nothing about WASM pointers is. */
+ const argPointer = wasm.xWrap.argAdapter('*');
+ wasm.xWrap.argAdapter('StructType', (v)=>{
+ if(v && v.constructor && v instanceof StructBinder.StructType){
+ v = v.pointer;
}
- return oldP(v);
- };
- wasm.xWrap.argAdapter('.pointer', adapter);
+ return (v === (v | 0) /* v is a 32-bit integer */)
+ ? argPointer(v)
+ : toss("Invalid (object) type for StructType-type argument.");
+ });
}
- // WhWasmUtil.xWrap() bindings...
- {
+ if(1){/* Convert Arrays and certain TypedArrays to strings for
+ 'flexible-string'-type arguments */
+ const xString = wasm.xWrap.argAdapter('string');
+ wasm.xWrap.argAdapter(
+ 'flexible-string', (v)=>xString(util.flexibleString(v))
+ );
+ }
+
+ if(1){// WhWasmUtil.xWrap() bindings...
/**
- Add some descriptive xWrap() aliases for '*' intended to
- (A) initially improve readability/correctness of capi.signatures
- and (B) eventually perhaps provide some sort of type-safety
- in their conversions.
+ Add some descriptive xWrap() aliases for '*' intended to (A)
+ initially improve readability/correctness of capi.signatures
+ and (B) eventually perhaps provide automatic conversion from
+ higher-level representations, e.g. capi.sqlite3_vfs to
+ `sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
*/
const aPtr = wasm.xWrap.argAdapter('*');
- wasm.xWrap.argAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr);
+ wasm.xWrap.argAdapter('sqlite3*', aPtr)
+ ('sqlite3_stmt*', aPtr)
+ ('sqlite3_context*', aPtr)
+ ('sqlite3_value*', aPtr)
+ ('void*', aPtr);
+ wasm.xWrap.resultAdapter('sqlite3*', aPtr)
+ ('sqlite3_stmt*', aPtr)
+ ('sqlite3_context*', aPtr)
+ ('void*', aPtr);
/**
Populate api object with sqlite3_...() by binding the "raw" wasm
@@ -77,8 +85,11 @@
for(const e of wasm.bindingSignatures){
capi[e[0]] = wasm.xWrap.apply(null, e);
}
+ for(const e of wasm.bindingSignatures.wasm){
+ capi.wasm[e[0]] = wasm.xWrap.apply(null, e);
+ }
- /* For functions which cannot work properly unless
+ /* For C API functions which cannot work properly unless
wasm.bigIntEnabled is true, install a bogus impl which
throws if called when bigIntEnabled is false. */
const fI64Disabled = function(fname){
@@ -117,73 +128,437 @@
}/*xWrap() bindings*/;
/**
- Scope-local holder of the two impls of sqlite3_prepare_v2/v3().
- */
- const __prepare = Object.create(null);
- /**
- This binding expects a JS string as its 2nd argument and
- null as its final argument. In order to compile multiple
- statements from a single string, the "full" impl (see
- below) must be used.
+ Internal helper to assist in validating call argument counts in
+ the hand-written sqlite3_xyz() wrappers. We do this only for
+ consistency with non-special-case wrappings.
*/
- __prepare.basic = wasm.xWrap('sqlite3_prepare_v3',
- "int", ["sqlite3*", "string",
- "int"/*MUST always be negative*/,
- "int", "**",
- "**"/*MUST be 0 or null or undefined!*/]);
+ const __dbArgcMismatch = (pDb,f,n)=>{
+ return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE,
+ f+"() requires "+n+" argument"+
+ (1===n?'':'s')+".");
+ };
+
/**
- Impl which requires that the 2nd argument be a pointer
- to the SQL string, instead of being converted to a
- string. This variant is necessary for cases where we
- require a non-NULL value for the final argument
- (exec()'ing multiple statements from one input
- string). For simpler cases, where only the first
- statement in the SQL string is required, the wrapper
- named sqlite3_prepare_v2() is sufficient and easier to
- use because it doesn't require dealing with pointers.
+ Helper for flexible-string conversions which require a
+ byte-length counterpart argument. Passed a value and its
+ ostensible length, this function returns [V,N], where V
+ is either v or a transformed copy of v and N is either n,
+ -1, or the byte length of v (if it's a byte array).
*/
- __prepare.full = wasm.xWrap('sqlite3_prepare_v3',
- "int", ["sqlite3*", "*", "int", "int",
- "**", "**"]);
-
- /* Documented in the api object's initializer. */
- capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
- /* 2022-07-08: xWrap() 'string' arg handling may be able do this
- special-case handling for us. It needs to be tested. Or maybe
- not: we always want to treat pzTail as null when passed a
- non-pointer SQL string and the argument adapters don't have
- enough state to know that. Maybe they could/should, by passing
- the currently-collected args as an array as the 2nd arg to the
- argument adapters? Or maybe we collect all args in an array,
- pass that to an optional post-args-collected callback, and give
- it a chance to manipulate the args before we pass them on? */
- if(util.isSQLableTypedArray(sql)) sql = util.typedArrayToString(sql);
- switch(typeof sql){
- case 'string': return __prepare.basic(pDb, sql, -1, prepFlags, ppStmt, null);
- case 'number': return __prepare.full(pDb, sql, sqlLen||-1, prepFlags, ppStmt, pzTail);
- default:
- return util.sqlite3_wasm_db_error(
- pDb, capi.SQLITE_MISUSE,
- "Invalid SQL argument type for sqlite3_prepare_v2/v3()."
- );
+ const __flexiString = function(v,n){
+ if('string'===typeof v){
+ n = -1;
+ }else if(util.isSQLableTypedArray(v)){
+ n = v.byteLength;
+ v = util.typedArrayToString(v);
+ }else if(Array.isArray(v)){
+ v = v.join('');
+ n = -1;
}
+ return [v, n];
};
- capi.sqlite3_prepare_v2 =
- (pDb, sql, sqlLen, ppStmt, pzTail)=>capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail);
+ if(1){/* Special-case handling of sqlite3_exec() */
+ const __exec = wasm.xWrap("sqlite3_exec", "int",
+ ["sqlite3*", "flexible-string", "*", "*", "**"]);
+ /* Documented in the api object's initializer. */
+ capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){
+ if(f.length!==arguments.length){
+ return __dbArgcMismatch(pDb,"sqlite3_exec",f.length);
+ }else if('function' !== typeof callback){
+ return __exec(pDb, sql, callback, pVoid, pErrMsg);
+ }
+ /* Wrap the callback in a WASM-bound function and convert the callback's
+ `(char**)` arguments to arrays of strings... */
+ const wasm = capi.wasm;
+ const cbwrap = function(pVoid, nCols, pColVals, pColNames){
+ let rc = capi.SQLITE_ERROR;
+ try {
+ let aVals = [], aNames = [], i = 0, offset = 0;
+ for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){
+ aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) );
+ aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) );
+ }
+ rc = callback(pVoid, nCols, aVals, aNames) | 0;
+ /* The first 2 args of the callback are useless for JS but
+ we want the JS mapping of the C API to be as close to the
+ C API as possible. */
+ }catch(e){
+ /* If we set the db error state here, the higher-level exec() call
+ replaces it with its own, so we have no way of reporting the
+ exception message except the console. We must not propagate
+ exceptions through the C API. */
+ }
+ return rc;
+ };
+ let pFunc, rc;
+ try{
+ pFunc = wasm.installFunction("ipipp", cbwrap);
+ rc = __exec(pDb, sql, pFunc, pVoid, pErrMsg);
+ }catch(e){
+ rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR,
+ "Error running exec(): "+e.message);
+ }finally{
+ if(pFunc) wasm.uninstallFunction(pFunc);
+ }
+ return rc;
+ };
+ }/*sqlite3_exec() proxy*/;
- /**
- Install JS<->C struct bindings for the non-opaque struct types we
- need... */
- sqlite3.StructBinder = self.Jaccwabyt({
- heap: 0 ? wasm.memory : wasm.heap8u,
- alloc: wasm.alloc,
- dealloc: wasm.dealloc,
- functionTable: wasm.functionTable,
- bigIntEnabled: wasm.bigIntEnabled,
- memberPrefix: '$'
- });
- delete self.Jaccwabyt;
+ if(1){/* Special-case handling of sqlite3_create_function_v2()
+ and sqlite3_create_window_function() */
+ const sqlite3CreateFunction = wasm.xWrap(
+ "sqlite3_create_function_v2", "int",
+ ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
+ "int"/*eTextRep*/, "*"/*pApp*/,
+ "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, "*"/*xDestroy*/]
+ );
+ const sqlite3CreateWindowFunction = wasm.xWrap(
+ "sqlite3_create_window_function", "int",
+ ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
+ "int"/*eTextRep*/, "*"/*pApp*/,
+ "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/,
+ "*"/*xInverse*/, "*"/*xDestroy*/]
+ );
+
+ const __udfSetResult = function(pCtx, val){
+ //console.warn("udfSetResult",typeof val, val);
+ switch(typeof val) {
+ case 'boolean':
+ capi.sqlite3_result_int(pCtx, val ? 1 : 0);
+ break;
+ case 'bigint':
+ if(wasm.bigIntEnabled){
+ if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val);
+ else toss3("BigInt value",val.toString(),"is too BigInt for int64.");
+ }else if(util.bigIntFits32(val)){
+ capi.sqlite3_result_int(pCtx, Number(val));
+ }else if(util.bigIntFitsDouble(val)){
+ capi.sqlite3_result_double(pCtx, Number(val));
+ }else{
+ toss3("BigInt value",val.toString(),"is too BigInt.");
+ }
+ break;
+ case 'number': {
+ (util.isInt32(val)
+ ? capi.sqlite3_result_int
+ : capi.sqlite3_result_double)(pCtx, val);
+ break;
+ }
+ case 'string':
+ capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT);
+ break;
+ case 'object':
+ if(null===val/*yes, typeof null === 'object'*/) {
+ capi.sqlite3_result_null(pCtx);
+ break;
+ }else if(util.isBindableTypedArray(val)){
+ const pBlob = wasm.allocFromTypedArray(val);
+ capi.sqlite3_result_blob(
+ pCtx, pBlob, val.byteLength,
+ wasm.exports[sqlite3.config.deallocExportName]
+ );
+ break;
+ }
+ // else fall through
+ default:
+ toss3("Don't not how to handle this UDF result value:",(typeof val), val);
+ };
+ }/*__udfSetResult()*/;
+
+ const __udfConvertArgs = function(argc, pArgv){
+ let i, pVal, valType, arg;
+ const tgt = [];
+ for(i = 0; i < argc; ++i){
+ pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * i));
+ /**
+ Curiously: despite ostensibly requiring 8-byte
+ alignment, the pArgv array is parcelled into chunks of
+ 4 bytes (1 pointer each). The values those point to
+ have 8-byte alignment but the individual argv entries
+ do not.
+ */
+ valType = capi.sqlite3_value_type(pVal);
+ switch(valType){
+ case capi.SQLITE_INTEGER:
+ if(wasm.bigIntEnabled){
+ arg = capi.sqlite3_value_int64(pVal);
+ if(util.bigIntFitsDouble(arg)) arg = Number(arg);
+ }
+ else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/;
+ break;
+ case capi.SQLITE_FLOAT:
+ arg = capi.sqlite3_value_double(pVal);
+ break;
+ case capi.SQLITE_TEXT:
+ arg = capi.sqlite3_value_text(pVal);
+ break;
+ case capi.SQLITE_BLOB:{
+ const n = capi.sqlite3_value_bytes(pVal);
+ const pBlob = capi.sqlite3_value_blob(pVal);
+ if(n && !pBlob) sqlite3.WasmAllocError.toss(
+ "Cannot allocate memory for blob argument of",n,"byte(s)"
+ );
+ arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null;
+ break;
+ }
+ case capi.SQLITE_NULL:
+ arg = null; break;
+ default:
+ toss3("Unhandled sqlite3_value_type()",valType,
+ "is possibly indicative of incorrect",
+ "pointer size assumption.");
+ }
+ tgt.push(arg);
+ }
+ return tgt;
+ }/*__udfConvertArgs()*/;
+
+ const __udfSetError = (pCtx, e)=>{
+ if(e instanceof sqlite3.WasmAllocError){
+ capi.sqlite3_result_error_nomem(pCtx);
+ }else{
+ capi.sqlite3_result_error(pCtx, e.message, -1);
+ }
+ };
+
+ const __xFunc = function(callback){
+ return function(pCtx, argc, pArgv){
+ try{ __udfSetResult(pCtx, callback(pCtx, ...__udfConvertArgs(argc, pArgv))) }
+ catch(e){
+ //console.error('xFunc() caught:',e);
+ __udfSetError(pCtx, e);
+ }
+ };
+ };
+
+ const __xInverseAndStep = function(callback){
+ return function(pCtx, argc, pArgv){
+ try{ callback(pCtx, ...__udfConvertArgs(argc, pArgv)) }
+ catch(e){ __udfSetError(pCtx, e) }
+ };
+ };
+
+ const __xFinalAndValue = function(callback){
+ return function(pCtx){
+ try{ __udfSetResult(pCtx, callback(pCtx)) }
+ catch(e){ __udfSetError(pCtx, e) }
+ };
+ };
+
+ const __xDestroy = function(callback){
+ return function(pVoid){
+ try{ callback(pVoid) }
+ catch(e){ console.error("UDF xDestroy method threw:",e) }
+ };
+ };
+
+ const __xMap = Object.assign(Object.create(null), {
+ xFunc: {sig:'v(pip)', f:__xFunc},
+ xStep: {sig:'v(pip)', f:__xInverseAndStep},
+ xInverse: {sig:'v(pip)', f:__xInverseAndStep},
+ xFinal: {sig:'v(p)', f:__xFinalAndValue},
+ xValue: {sig:'v(p)', f:__xFinalAndValue},
+ xDestroy: {sig:'v(p)', f:__xDestroy}
+ });
+
+ const __xWrapFuncs = function(theFuncs, tgtUninst){
+ const rc = []
+ let k;
+ for(k in theFuncs){
+ let fArg = theFuncs[k];
+ if('function'===typeof fArg){
+ const w = __xMap[k];
+ fArg = wasm.installFunction(w.sig, w.f(fArg));
+ tgtUninst.push(fArg);
+ }
+ rc.push(fArg);
+ }
+ return rc;
+ };
+
+ /* Documented in the api object's initializer. */
+ capi.sqlite3_create_function_v2 = function f(
+ pDb, funcName, nArg, eTextRep, pApp,
+ xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**)
+ xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**)
+ xFinal, //void (*xFinal)(sqlite3_context*)
+ xDestroy //void (*xDestroy)(void*)
+ ){
+ if(f.length!==arguments.length){
+ return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length);
+ }
+ /* Wrap the callbacks in a WASM-bound functions... */
+ const wasm = capi.wasm;
+ const uninstall = [/*funcs to uninstall on error*/];
+ let rc;
+ try{
+ const funcArgs = __xWrapFuncs({xFunc, xStep, xFinal, xDestroy},
+ uninstall);
+ rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep,
+ pApp, ...funcArgs);
+ }catch(e){
+ console.error("sqlite3_create_function_v2() setup threw:",e);
+ for(let v of uninstall){
+ wasm.uninstallFunction(v);
+ }
+ rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR,
+ "Creation of UDF threw: "+e.message);
+ }
+ return rc;
+ };
+
+ capi.sqlite3_create_function = function f(
+ pDb, funcName, nArg, eTextRep, pApp,
+ xFunc, xStep, xFinal
+ ){
+ return (f.length===arguments.length)
+ ? capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep,
+ pApp, xFunc, xStep, xFinal, 0)
+ : __dbArgcMismatch(pDb,"sqlite3_create_function",f.length);
+ };
+
+ /* Documented in the api object's initializer. */
+ capi.sqlite3_create_window_function = function f(
+ pDb, funcName, nArg, eTextRep, pApp,
+ xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**)
+ xFinal, //void (*xFinal)(sqlite3_context*)
+ xValue, //void (*xFinal)(sqlite3_context*)
+ xInverse,//void (*xStep)(sqlite3_context*,int,sqlite3_value**)
+ xDestroy //void (*xDestroy)(void*)
+ ){
+ if(f.length!==arguments.length){
+ return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length);
+ }
+ /* Wrap the callbacks in a WASM-bound functions... */
+ const wasm = capi.wasm;
+ const uninstall = [/*funcs to uninstall on error*/];
+ let rc;
+ try{
+ const funcArgs = __xWrapFuncs({xStep, xFinal, xValue, xInverse, xDestroy},
+ uninstall);
+ rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep,
+ pApp, ...funcArgs);
+ }catch(e){
+ console.error("sqlite3_create_function_v2() setup threw:",e);
+ for(let v of uninstall){
+ wasm.uninstallFunction(v);
+ }
+ rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR,
+ "Creation of UDF threw: "+e.message);
+ }
+ return rc;
+ };
+ /**
+ A helper for UDFs implemented in JS and bound to WASM by the
+ client. Given a JS value, udfSetResult(pCtx,X) calls one of the
+ sqlite3_result_xyz(pCtx,...) routines, depending on X's data
+ type:
+
+ - `null`: sqlite3_result_null()
+ - `boolean`: sqlite3_result_int()
+ - `number`: sqlite3_result_int() or sqlite3_result_double()
+ - `string`: sqlite3_result_text()
+ - Uint8Array or Int8Array: sqlite3_result_blob()
+
+ Anything else triggers sqlite3_result_error().
+ */
+ capi.sqlite3_create_function_v2.udfSetResult =
+ capi.sqlite3_create_function.udfSetResult =
+ capi.sqlite3_create_window_function.udfSetResult = __udfSetResult;
+
+ /**
+ A helper for UDFs implemented in JS and bound to WASM by the
+ client. When passed the
+ (argc,argv) values from the UDF-related functions which receive
+ them (xFunc, xStep, xInverse), it creates a JS array
+ representing those arguments, converting each to JS in a manner
+ appropriate to its data type: numeric, text, blob
+ (Uint8Array), or null.
+
+ Results are undefined if it's passed anything other than those
+ two arguments from those specific contexts.
+
+ Thus an argc of 4 will result in a length-4 array containing
+ the converted values from the corresponding argv.
+
+ The conversion will throw only on allocation error or an internal
+ error.
+ */
+ capi.sqlite3_create_function_v2.udfConvertArgs =
+ capi.sqlite3_create_function.udfConvertArgs =
+ capi.sqlite3_create_window_function.udfConvertArgs = __udfConvertArgs;
+
+ /**
+ A helper for UDFs implemented in JS and bound to WASM by the
+ client. It expects to be a passed `(sqlite3_context*, Error)`
+ (i.e. an exception object). And it sets the current UDF's
+ result to sqlite3_result_error_nomem() or sqlite3_result_error(),
+ depending on whether the 2nd argument is a
+ sqlite3.WasmAllocError object or not.
+ */
+ capi.sqlite3_create_function_v2.udfSetError =
+ capi.sqlite3_create_function.udfSetError =
+ capi.sqlite3_create_window_function.udfSetError = __udfSetError;
+
+ }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/;
+
+ if(1){/* Special-case handling of sqlite3_prepare_v2() and
+ sqlite3_prepare_v3() */
+ /**
+ Scope-local holder of the two impls of sqlite3_prepare_v2/v3().
+ */
+ const __prepare = Object.create(null);
+ /**
+ This binding expects a JS string as its 2nd argument and
+ null as its final argument. In order to compile multiple
+ statements from a single string, the "full" impl (see
+ below) must be used.
+ */
+ __prepare.basic = wasm.xWrap('sqlite3_prepare_v3',
+ "int", ["sqlite3*", "string",
+ "int"/*ignored for this impl!*/,
+ "int", "**",
+ "**"/*MUST be 0 or null or undefined!*/]);
+ /**
+ Impl which requires that the 2nd argument be a pointer
+ to the SQL string, instead of being converted to a
+ string. This variant is necessary for cases where we
+ require a non-NULL value for the final argument
+ (exec()'ing multiple statements from one input
+ string). For simpler cases, where only the first
+ statement in the SQL string is required, the wrapper
+ named sqlite3_prepare_v2() is sufficient and easier to
+ use because it doesn't require dealing with pointers.
+ */
+ __prepare.full = wasm.xWrap('sqlite3_prepare_v3',
+ "int", ["sqlite3*", "*", "int", "int",
+ "**", "**"]);
+
+ /* Documented in the api object's initializer. */
+ capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
+ if(f.length!==arguments.length){
+ return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length);
+ }
+ const [xSql, xSqlLen] = __flexiString(sql, sqlLen);
+ switch(typeof xSql){
+ case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null);
+ case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail);
+ default:
+ return util.sqlite3_wasm_db_error(
+ pDb, capi.SQLITE_MISUSE,
+ "Invalid SQL argument type for sqlite3_prepare_v2/v3()."
+ );
+ }
+ };
+
+ /* Documented in the api object's initializer. */
+ capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){
+ return (f.length===arguments.length)
+ ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail)
+ : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length);
+ };
+ }/*sqlite3_prepare_v2/v3()*/;
{/* Import C-level constants and structs... */
const cJson = wasm.xCall('sqlite3_wasm_enum_json');
@@ -194,18 +569,122 @@
wasm.ctype = JSON.parse(wasm.cstringToJs(cJson));
//console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
for(const t of ['access', 'blobFinalizers', 'dataTypes',
- 'encodings', 'flock', 'ioCap',
+ 'encodings', 'fcntl', 'flock', 'ioCap',
'openFlags', 'prepareFlags', 'resultCodes',
- 'syncFlags', 'udfFlags', 'version'
+ 'serialize', 'syncFlags', 'trace', 'udfFlags',
+ 'version'
]){
- for(const [k,v] of Object.entries(wasm.ctype[t])){
- capi[k] = v;
+ for(const e of Object.entries(wasm.ctype[t])){
+ // ^^^ [k,v] there triggers a buggy code transormation via one
+ // of the Emscripten-driven optimizers.
+ capi[e[0]] = e[1];
+ }
+ }
+ const __rcMap = Object.create(null);
+ for(const t of ['resultCodes']){
+ for(const e of Object.entries(wasm.ctype[t])){
+ __rcMap[e[1]] = e[0];
}
}
+ /**
+ For the given integer, returns the SQLITE_xxx result code as a
+ string, or undefined if no such mapping is found.
+ */
+ capi.sqlite3_web_rc_str = (rc)=>__rcMap[rc];
/* Bind all registered C-side structs... */
for(const s of wasm.ctype.structs){
capi[s.name] = sqlite3.StructBinder(s);
}
- }
+ }/*end C constant imports*/
+
+ if( util.isMainWindow()
+ && 0!==capi.sqlite3_vfs_find("kvvfs") ){/* kvvfs-specific glue */
+ const kvvfsMethods = new capi.sqlite3_kvvfs_methods(
+ wasm.exports.sqlite3_wasm_kvvfs_methods()
+ );
+ delete capi.sqlite3_kvvfs_methods;
+
+ const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack,
+ pstack = wasm.pstack,
+ pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc;
+
+ const kvvfsStorage = (zClass)=>
+ ((115/*=='s'*/===wasm.getMemValue(zClass))
+ ? sessionStorage : localStorage);
+
+ const kvvfsImpls = {
+ xRead: (zClass, zKey, zBuf, nBuf)=>{
+ const stack = pstack.pointer,
+ astack = wasm.scopedAllocPush();
+ try {
+ const zXKey = kvvfsMakeKey(zClass,zKey);
+ if(!zXKey) return -3/*OOM*/;
+ const jKey = wasm.cstringToJs(zXKey);
+ const jV = kvvfsStorage(zClass).getItem(jKey);
+ if(!jV) return -1;
+ const nV = jV.length /* Note that we are relying 100% on v being
+ ASCII so that jV.length is equal to the
+ C-string's byte length. */;
+ if(nBuf<=0) return nV;
+ else if(1===nBuf){
+ wasm.setMemValue(zBuf, 0);
+ return nV;
+ }
+ const zV = wasm.scopedAllocCString(jV);
+ if(nBuf > nV + 1) nBuf = nV + 1;
+ wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1);
+ wasm.setMemValue(zBuf + nBuf - 1, 0);
+ return nBuf - 1;
+ }catch(e){
+ console.error("kvstorageRead()",e);
+ return -2;
+ }finally{
+ pstack.restore(stack);
+ wasm.scopedAllocPop(astack);
+ }
+ },
+ xWrite: (zClass, zKey, zData)=>{
+ const stack = pstack.pointer;
+ try {
+ const zXKey = kvvfsMakeKey(zClass,zKey);
+ if(!zXKey) return 1/*OOM*/;
+ const jKey = wasm.cstringToJs(zXKey);
+ kvvfsStorage(zClass).setItem(jKey, wasm.cstringToJs(zData));
+ return 0;
+ }catch(e){
+ console.error("kvstorageWrite()",e);
+ return capi.SQLITE_IOERR;
+ }finally{
+ pstack.restore(stack);
+ }
+ },
+ xDelete: (zClass, zKey)=>{
+ const stack = pstack.pointer;
+ try {
+ const zXKey = kvvfsMakeKey(zClass,zKey);
+ if(!zXKey) return 1/*OOM*/;
+ kvvfsStorage(zClass).removeItem(wasm.cstringToJs(zXKey));
+ return 0;
+ }catch(e){
+ console.error("kvstorageDelete()",e);
+ return capi.SQLITE_IOERR;
+ }finally{
+ pstack.restore(stack);
+ }
+ }
+ }/*kvvfsImpls*/;
+ for(let k of Object.keys(kvvfsImpls)){
+ kvvfsMethods[kvvfsMethods.memberKey(k)] =
+ wasm.installFunction(
+ kvvfsMethods.memberSignature(k),
+ kvvfsImpls[k]
+ );
+ }
+ }/*kvvfs*/
+
+ sqlite3.version = Object.assign(Object.create(null),{
+ library: sqlite3.capi.sqlite3_libversion(),
+ sourceId: sqlite3.capi.sqlite3_sourceid()
+ });
+});
-})(self);