13961b263Sstephan/* 23961b263Sstephan 2022-07-22 33961b263Sstephan 43961b263Sstephan The author disclaims copyright to this source code. In place of a 53961b263Sstephan legal notice, here is a blessing: 63961b263Sstephan 73961b263Sstephan * May you do good and not evil. 83961b263Sstephan * May you find forgiveness for yourself and forgive others. 93961b263Sstephan * May you share freely, never taking more than you give. 103961b263Sstephan 113961b263Sstephan *********************************************************************** 123961b263Sstephan 133961b263Sstephan This file glues together disparate pieces of JS which are loaded in 143961b263Sstephan previous steps of the sqlite3-api.js bootstrapping process: 153961b263Sstephan sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It 163961b263Sstephan initializes the main API pieces so that the downstream components 173961b263Sstephan (e.g. sqlite3-api-oo1.js) have all that they need. 183961b263Sstephan*/ 19e3cd6760Sstephanself.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 203961b263Sstephan 'use strict'; 213961b263Sstephan const toss = (...args)=>{throw new Error(args.join(' '))}; 22510a9d1cSstephan const toss3 = sqlite3.SQLite3Error.toss; 238948fbeeSstephan const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util; 248948fbeeSstephan self.WhWasmUtilInstaller(wasm); 253961b263Sstephan delete self.WhWasmUtilInstaller; 263961b263Sstephan 273961b263Sstephan /** 287ff8da87Sstephan Install JS<->C struct bindings for the non-opaque struct types we 297ff8da87Sstephan need... */ 307ff8da87Sstephan sqlite3.StructBinder = self.Jaccwabyt({ 317ff8da87Sstephan heap: 0 ? wasm.memory : wasm.heap8u, 327ff8da87Sstephan alloc: wasm.alloc, 337ff8da87Sstephan dealloc: wasm.dealloc, 347ff8da87Sstephan functionTable: wasm.functionTable, 357ff8da87Sstephan bigIntEnabled: wasm.bigIntEnabled, 367ff8da87Sstephan memberPrefix: '$' 377ff8da87Sstephan }); 387ff8da87Sstephan delete self.Jaccwabyt; 397ff8da87Sstephan 407ff8da87Sstephan if(0){ 417ff8da87Sstephan /* "The problem" is that the following isn't even remotely 427ff8da87Sstephan type-safe. OTOH, nothing about WASM pointers is. */ 437ff8da87Sstephan const argPointer = wasm.xWrap.argAdapter('*'); 447ff8da87Sstephan wasm.xWrap.argAdapter('StructType', (v)=>{ 457ff8da87Sstephan if(v && v.constructor && v instanceof StructBinder.StructType){ 467ff8da87Sstephan v = v.pointer; 473961b263Sstephan } 48de868175Sstephan return wasm.isPtr(v) 497ff8da87Sstephan ? argPointer(v) 507ff8da87Sstephan : toss("Invalid (object) type for StructType-type argument."); 517ff8da87Sstephan }); 527ff8da87Sstephan } 533961b263Sstephan 540f32760eSstephan {/* Convert Arrays and certain TypedArrays to strings for 55e67a0f40Sstephan 'flexible-string'-type arguments */ 56e67a0f40Sstephan const xString = wasm.xWrap.argAdapter('string'); 57e67a0f40Sstephan wasm.xWrap.argAdapter( 58ef9cd12eSstephan 'flexible-string', (v)=>xString(util.flexibleString(v)) 59e67a0f40Sstephan ); 60e67a0f40Sstephan } 61e67a0f40Sstephan 62e67a0f40Sstephan if(1){// WhWasmUtil.xWrap() bindings... 633961b263Sstephan /** 6463e9ec2fSstephan Add some descriptive xWrap() aliases for '*' intended to (A) 6563e9ec2fSstephan initially improve readability/correctness of capi.signatures 6663e9ec2fSstephan and (B) eventually perhaps provide automatic conversion from 6763e9ec2fSstephan higher-level representations, e.g. capi.sqlite3_vfs to 6863e9ec2fSstephan `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. 693961b263Sstephan */ 703961b263Sstephan const aPtr = wasm.xWrap.argAdapter('*'); 71510a9d1cSstephan wasm.xWrap.argAdapter('sqlite3*', aPtr) 72510a9d1cSstephan ('sqlite3_stmt*', aPtr) 73510a9d1cSstephan ('sqlite3_context*', aPtr) 74510a9d1cSstephan ('sqlite3_value*', aPtr) 75842c5ee8Sstephan ('sqlite3_vfs*', aPtr) 76510a9d1cSstephan ('void*', aPtr); 77510a9d1cSstephan wasm.xWrap.resultAdapter('sqlite3*', aPtr) 78510a9d1cSstephan ('sqlite3_context*', aPtr) 79842c5ee8Sstephan ('sqlite3_stmt*', aPtr) 80842c5ee8Sstephan ('sqlite3_vfs*', aPtr) 81510a9d1cSstephan ('void*', aPtr); 823961b263Sstephan 833961b263Sstephan /** 843961b263Sstephan Populate api object with sqlite3_...() by binding the "raw" wasm 853961b263Sstephan exports into type-converting proxies using wasm.xWrap(). 863961b263Sstephan */ 873961b263Sstephan for(const e of wasm.bindingSignatures){ 883961b263Sstephan capi[e[0]] = wasm.xWrap.apply(null, e); 893961b263Sstephan } 9073079dbaSstephan for(const e of wasm.bindingSignatures.wasm){ 918948fbeeSstephan wasm[e[0]] = wasm.xWrap.apply(null, e); 9273079dbaSstephan } 933961b263Sstephan 94e3cd6760Sstephan /* For C API functions which cannot work properly unless 953961b263Sstephan wasm.bigIntEnabled is true, install a bogus impl which 963961b263Sstephan throws if called when bigIntEnabled is false. */ 973961b263Sstephan const fI64Disabled = function(fname){ 983961b263Sstephan return ()=>toss(fname+"() disabled due to lack", 993961b263Sstephan "of BigInt support in this build."); 1003961b263Sstephan }; 1013961b263Sstephan for(const e of wasm.bindingSignatures.int64){ 1023961b263Sstephan capi[e[0]] = wasm.bigIntEnabled 1033961b263Sstephan ? wasm.xWrap.apply(null, e) 1043961b263Sstephan : fI64Disabled(e[0]); 1053961b263Sstephan } 1063961b263Sstephan 107f03ddccaSstephan /* There's no(?) need to expose bindingSignatures to clients, 108f03ddccaSstephan implicitly making it part of the public interface. */ 109f03ddccaSstephan delete wasm.bindingSignatures; 110f03ddccaSstephan 1113961b263Sstephan if(wasm.exports.sqlite3_wasm_db_error){ 1128948fbeeSstephan util.sqlite3_wasm_db_error = wasm.xWrap( 1133961b263Sstephan 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string' 1143961b263Sstephan ); 1153961b263Sstephan }else{ 1163961b263Sstephan util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ 1173961b263Sstephan console.warn("sqlite3_wasm_db_error() is not exported.",arguments); 1183961b263Sstephan return errCode; 1193961b263Sstephan }; 1203961b263Sstephan } 1213961b263Sstephan 122f03ddccaSstephan }/*xWrap() bindings*/; 123f03ddccaSstephan 1243961b263Sstephan /** 1253961b263Sstephan When registering a VFS and its related components it may be 1263961b263Sstephan necessary to ensure that JS keeps a reference to them to keep 1273961b263Sstephan them from getting garbage collected. Simply pass each such value 1283961b263Sstephan to this function and a reference will be held to it for the life 1293961b263Sstephan of the app. 1303961b263Sstephan */ 1313961b263Sstephan capi.sqlite3_vfs_register.addReference = function f(...args){ 1323961b263Sstephan if(!f._) f._ = []; 1333961b263Sstephan f._.push(...args); 1343961b263Sstephan }; 1353961b263Sstephan 1363961b263Sstephan /** 137e67a0f40Sstephan Internal helper to assist in validating call argument counts in 138e67a0f40Sstephan the hand-written sqlite3_xyz() wrappers. We do this only for 139e67a0f40Sstephan consistency with non-special-case wrappings. 140e67a0f40Sstephan */ 141e67a0f40Sstephan const __dbArgcMismatch = (pDb,f,n)=>{ 142e67a0f40Sstephan return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE, 143e67a0f40Sstephan f+"() requires "+n+" argument"+ 144*4df2ab57Sstephan (1===n?"":'s')+"."); 145e67a0f40Sstephan }; 146e67a0f40Sstephan 147e67a0f40Sstephan /** 148e67a0f40Sstephan Helper for flexible-string conversions which require a 149e67a0f40Sstephan byte-length counterpart argument. Passed a value and its 150e67a0f40Sstephan ostensible length, this function returns [V,N], where V 151e67a0f40Sstephan is either v or a transformed copy of v and N is either n, 152e67a0f40Sstephan -1, or the byte length of v (if it's a byte array). 153e67a0f40Sstephan */ 154e67a0f40Sstephan const __flexiString = function(v,n){ 155e67a0f40Sstephan if('string'===typeof v){ 156e67a0f40Sstephan n = -1; 157e67a0f40Sstephan }else if(util.isSQLableTypedArray(v)){ 158e67a0f40Sstephan n = v.byteLength; 159e67a0f40Sstephan v = util.typedArrayToString(v); 160e67a0f40Sstephan }else if(Array.isArray(v)){ 161*4df2ab57Sstephan v = v.join(""); 162e67a0f40Sstephan n = -1; 163e67a0f40Sstephan } 164e67a0f40Sstephan return [v, n]; 165e67a0f40Sstephan }; 166e67a0f40Sstephan 167e67a0f40Sstephan if(1){/* Special-case handling of sqlite3_exec() */ 168e67a0f40Sstephan const __exec = wasm.xWrap("sqlite3_exec", "int", 169e67a0f40Sstephan ["sqlite3*", "flexible-string", "*", "*", "**"]); 170e67a0f40Sstephan /* Documented in the api object's initializer. */ 17192ede964Sstephan capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){ 17292ede964Sstephan if(f.length!==arguments.length){ 17392ede964Sstephan return __dbArgcMismatch(pDb,"sqlite3_exec",f.length); 174e67a0f40Sstephan }else if('function' !== typeof callback){ 175e67a0f40Sstephan return __exec(pDb, sql, callback, pVoid, pErrMsg); 176e67a0f40Sstephan } 177e67a0f40Sstephan /* Wrap the callback in a WASM-bound function and convert the callback's 178e67a0f40Sstephan `(char**)` arguments to arrays of strings... */ 179e67a0f40Sstephan const cbwrap = function(pVoid, nCols, pColVals, pColNames){ 180e67a0f40Sstephan let rc = capi.SQLITE_ERROR; 181e67a0f40Sstephan try { 182e67a0f40Sstephan let aVals = [], aNames = [], i = 0, offset = 0; 183e67a0f40Sstephan for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){ 184e67a0f40Sstephan aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) ); 185e67a0f40Sstephan aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) ); 186e67a0f40Sstephan } 187e67a0f40Sstephan rc = callback(pVoid, nCols, aVals, aNames) | 0; 188e67a0f40Sstephan /* The first 2 args of the callback are useless for JS but 189e67a0f40Sstephan we want the JS mapping of the C API to be as close to the 190e67a0f40Sstephan C API as possible. */ 191e67a0f40Sstephan }catch(e){ 192e67a0f40Sstephan /* If we set the db error state here, the higher-level exec() call 193e67a0f40Sstephan replaces it with its own, so we have no way of reporting the 194e67a0f40Sstephan exception message except the console. We must not propagate 195e67a0f40Sstephan exceptions through the C API. */ 196e67a0f40Sstephan } 197e67a0f40Sstephan return rc; 198e67a0f40Sstephan }; 199e67a0f40Sstephan let pFunc, rc; 200e67a0f40Sstephan try{ 201e67a0f40Sstephan pFunc = wasm.installFunction("ipipp", cbwrap); 202e67a0f40Sstephan rc = __exec(pDb, sql, pFunc, pVoid, pErrMsg); 203e67a0f40Sstephan }catch(e){ 204e67a0f40Sstephan rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, 205e67a0f40Sstephan "Error running exec(): "+e.message); 206e67a0f40Sstephan }finally{ 207e67a0f40Sstephan if(pFunc) wasm.uninstallFunction(pFunc); 208e67a0f40Sstephan } 209e67a0f40Sstephan return rc; 210e67a0f40Sstephan }; 211e67a0f40Sstephan }/*sqlite3_exec() proxy*/; 212e67a0f40Sstephan 21392ede964Sstephan if(1){/* Special-case handling of sqlite3_create_function_v2() 21492ede964Sstephan and sqlite3_create_window_function() */ 2159892883eSstephan const sqlite3CreateFunction = wasm.xWrap( 2169892883eSstephan "sqlite3_create_function_v2", "int", 21792ede964Sstephan ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, 21892ede964Sstephan "int"/*eTextRep*/, "*"/*pApp*/, 21992ede964Sstephan "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, "*"/*xDestroy*/] 22092ede964Sstephan ); 22192ede964Sstephan const sqlite3CreateWindowFunction = wasm.xWrap( 22292ede964Sstephan "sqlite3_create_window_function", "int", 22392ede964Sstephan ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, 22492ede964Sstephan "int"/*eTextRep*/, "*"/*pApp*/, 22592ede964Sstephan "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, 22692ede964Sstephan "*"/*xInverse*/, "*"/*xDestroy*/] 2279892883eSstephan ); 228510a9d1cSstephan 229ef9cd12eSstephan const __udfSetResult = function(pCtx, val){ 230ef9cd12eSstephan //console.warn("udfSetResult",typeof val, val); 2319892883eSstephan switch(typeof val) { 2326f3286caSstephan case 'undefined': 2336f3286caSstephan /* Assume that the client already called sqlite3_result_xxx(). */ 2346f3286caSstephan break; 2359892883eSstephan case 'boolean': 236510a9d1cSstephan capi.sqlite3_result_int(pCtx, val ? 1 : 0); 237510a9d1cSstephan break; 238510a9d1cSstephan case 'bigint': 239510a9d1cSstephan if(wasm.bigIntEnabled){ 240510a9d1cSstephan if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val); 241510a9d1cSstephan else toss3("BigInt value",val.toString(),"is too BigInt for int64."); 242510a9d1cSstephan }else if(util.bigIntFits32(val)){ 243510a9d1cSstephan capi.sqlite3_result_int(pCtx, Number(val)); 244510a9d1cSstephan }else if(util.bigIntFitsDouble(val)){ 245510a9d1cSstephan capi.sqlite3_result_double(pCtx, Number(val)); 246510a9d1cSstephan }else{ 247510a9d1cSstephan toss3("BigInt value",val.toString(),"is too BigInt."); 248510a9d1cSstephan } 2499892883eSstephan break; 2509892883eSstephan case 'number': { 2519892883eSstephan (util.isInt32(val) 2529892883eSstephan ? capi.sqlite3_result_int 253510a9d1cSstephan : capi.sqlite3_result_double)(pCtx, val); 2549892883eSstephan break; 2559892883eSstephan } 2569892883eSstephan case 'string': 257510a9d1cSstephan capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT); 2589892883eSstephan break; 2599892883eSstephan case 'object': 26092ede964Sstephan if(null===val/*yes, typeof null === 'object'*/) { 261510a9d1cSstephan capi.sqlite3_result_null(pCtx); 2629892883eSstephan break; 2639892883eSstephan }else if(util.isBindableTypedArray(val)){ 2649892883eSstephan const pBlob = wasm.allocFromTypedArray(val); 26592ede964Sstephan capi.sqlite3_result_blob( 266510a9d1cSstephan pCtx, pBlob, val.byteLength, 26792ede964Sstephan wasm.exports[sqlite3.config.deallocExportName] 26892ede964Sstephan ); 2699892883eSstephan break; 2709892883eSstephan } 2719892883eSstephan // else fall through 2729892883eSstephan default: 273510a9d1cSstephan toss3("Don't not how to handle this UDF result value:",(typeof val), val); 2749892883eSstephan }; 275ef9cd12eSstephan }/*__udfSetResult()*/; 276510a9d1cSstephan 277ef9cd12eSstephan const __udfConvertArgs = function(argc, pArgv){ 2789892883eSstephan let i, pVal, valType, arg; 2799892883eSstephan const tgt = []; 2809892883eSstephan for(i = 0; i < argc; ++i){ 2819892883eSstephan pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * i)); 2829892883eSstephan /** 2839892883eSstephan Curiously: despite ostensibly requiring 8-byte 2849892883eSstephan alignment, the pArgv array is parcelled into chunks of 2859892883eSstephan 4 bytes (1 pointer each). The values those point to 2869892883eSstephan have 8-byte alignment but the individual argv entries 2879892883eSstephan do not. 2889892883eSstephan */ 2899892883eSstephan valType = capi.sqlite3_value_type(pVal); 2909892883eSstephan switch(valType){ 2919892883eSstephan case capi.SQLITE_INTEGER: 292510a9d1cSstephan if(wasm.bigIntEnabled){ 293510a9d1cSstephan arg = capi.sqlite3_value_int64(pVal); 294510a9d1cSstephan if(util.bigIntFitsDouble(arg)) arg = Number(arg); 295510a9d1cSstephan } 296510a9d1cSstephan else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/; 297510a9d1cSstephan break; 2989892883eSstephan case capi.SQLITE_FLOAT: 2999892883eSstephan arg = capi.sqlite3_value_double(pVal); 3009892883eSstephan break; 3019892883eSstephan case capi.SQLITE_TEXT: 3029892883eSstephan arg = capi.sqlite3_value_text(pVal); 3039892883eSstephan break; 3049892883eSstephan case capi.SQLITE_BLOB:{ 3059892883eSstephan const n = capi.sqlite3_value_bytes(pVal); 3069892883eSstephan const pBlob = capi.sqlite3_value_blob(pVal); 307510a9d1cSstephan if(n && !pBlob) sqlite3.WasmAllocError.toss( 308510a9d1cSstephan "Cannot allocate memory for blob argument of",n,"byte(s)" 309510a9d1cSstephan ); 310510a9d1cSstephan arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null; 3119892883eSstephan break; 3129892883eSstephan } 3139892883eSstephan case capi.SQLITE_NULL: 3149892883eSstephan arg = null; break; 3159892883eSstephan default: 3169892883eSstephan toss3("Unhandled sqlite3_value_type()",valType, 3179892883eSstephan "is possibly indicative of incorrect", 3189892883eSstephan "pointer size assumption."); 3199892883eSstephan } 3209892883eSstephan tgt.push(arg); 3219892883eSstephan } 3229892883eSstephan return tgt; 323ef9cd12eSstephan }/*__udfConvertArgs()*/; 3249892883eSstephan 325ef9cd12eSstephan const __udfSetError = (pCtx, e)=>{ 326510a9d1cSstephan if(e instanceof sqlite3.WasmAllocError){ 327510a9d1cSstephan capi.sqlite3_result_error_nomem(pCtx); 3289892883eSstephan }else{ 3296f3286caSstephan const msg = ('string'===typeof e) ? e : e.message; 3306f3286caSstephan capi.sqlite3_result_error(pCtx, msg, -1); 3319892883eSstephan } 3329892883eSstephan }; 3339892883eSstephan 3349892883eSstephan const __xFunc = function(callback){ 335510a9d1cSstephan return function(pCtx, argc, pArgv){ 336ef9cd12eSstephan try{ __udfSetResult(pCtx, callback(pCtx, ...__udfConvertArgs(argc, pArgv))) } 337510a9d1cSstephan catch(e){ 338510a9d1cSstephan //console.error('xFunc() caught:',e); 339ef9cd12eSstephan __udfSetError(pCtx, e); 340510a9d1cSstephan } 3419892883eSstephan }; 3429892883eSstephan }; 3439892883eSstephan 34492ede964Sstephan const __xInverseAndStep = function(callback){ 345510a9d1cSstephan return function(pCtx, argc, pArgv){ 346ef9cd12eSstephan try{ callback(pCtx, ...__udfConvertArgs(argc, pArgv)) } 347ef9cd12eSstephan catch(e){ __udfSetError(pCtx, e) } 3489892883eSstephan }; 3499892883eSstephan }; 3509892883eSstephan 35192ede964Sstephan const __xFinalAndValue = function(callback){ 352510a9d1cSstephan return function(pCtx){ 353ef9cd12eSstephan try{ __udfSetResult(pCtx, callback(pCtx)) } 354ef9cd12eSstephan catch(e){ __udfSetError(pCtx, e) } 3559892883eSstephan }; 3569892883eSstephan }; 3579892883eSstephan 3589892883eSstephan const __xDestroy = function(callback){ 3599892883eSstephan return function(pVoid){ 3609892883eSstephan try{ callback(pVoid) } 36192ede964Sstephan catch(e){ console.error("UDF xDestroy method threw:",e) } 36292ede964Sstephan }; 36392ede964Sstephan }; 36492ede964Sstephan 36592ede964Sstephan const __xMap = Object.assign(Object.create(null), { 36692ede964Sstephan xFunc: {sig:'v(pip)', f:__xFunc}, 36792ede964Sstephan xStep: {sig:'v(pip)', f:__xInverseAndStep}, 36892ede964Sstephan xInverse: {sig:'v(pip)', f:__xInverseAndStep}, 36992ede964Sstephan xFinal: {sig:'v(p)', f:__xFinalAndValue}, 37092ede964Sstephan xValue: {sig:'v(p)', f:__xFinalAndValue}, 37192ede964Sstephan xDestroy: {sig:'v(p)', f:__xDestroy} 37292ede964Sstephan }); 37392ede964Sstephan 37492ede964Sstephan const __xWrapFuncs = function(theFuncs, tgtUninst){ 37592ede964Sstephan const rc = [] 37692ede964Sstephan let k; 37792ede964Sstephan for(k in theFuncs){ 37892ede964Sstephan let fArg = theFuncs[k]; 37992ede964Sstephan if('function'===typeof fArg){ 38092ede964Sstephan const w = __xMap[k]; 38192ede964Sstephan fArg = wasm.installFunction(w.sig, w.f(fArg)); 38292ede964Sstephan tgtUninst.push(fArg); 3839892883eSstephan } 38492ede964Sstephan rc.push(fArg); 38592ede964Sstephan } 38692ede964Sstephan return rc; 3879892883eSstephan }; 38892ede964Sstephan 3899892883eSstephan /* Documented in the api object's initializer. */ 3909892883eSstephan capi.sqlite3_create_function_v2 = function f( 3919892883eSstephan pDb, funcName, nArg, eTextRep, pApp, 3929892883eSstephan xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**) 3939892883eSstephan xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) 3949892883eSstephan xFinal, //void (*xFinal)(sqlite3_context*) 3959892883eSstephan xDestroy //void (*xDestroy)(void*) 3969892883eSstephan ){ 39792ede964Sstephan if(f.length!==arguments.length){ 39892ede964Sstephan return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length); 3999892883eSstephan } 4009892883eSstephan /* Wrap the callbacks in a WASM-bound functions... */ 40192ede964Sstephan const uninstall = [/*funcs to uninstall on error*/]; 4029892883eSstephan let rc; 4039892883eSstephan try{ 40492ede964Sstephan const funcArgs = __xWrapFuncs({xFunc, xStep, xFinal, xDestroy}, 40592ede964Sstephan uninstall); 4069892883eSstephan rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, 4079892883eSstephan pApp, ...funcArgs); 4089892883eSstephan }catch(e){ 4099892883eSstephan console.error("sqlite3_create_function_v2() setup threw:",e); 4109892883eSstephan for(let v of uninstall){ 4119892883eSstephan wasm.uninstallFunction(v); 4129892883eSstephan } 4139892883eSstephan rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, 4149892883eSstephan "Creation of UDF threw: "+e.message); 4159892883eSstephan } 4169892883eSstephan return rc; 4179892883eSstephan }; 4189892883eSstephan 41992ede964Sstephan capi.sqlite3_create_function = function f( 4209892883eSstephan pDb, funcName, nArg, eTextRep, pApp, 4219892883eSstephan xFunc, xStep, xFinal 4229892883eSstephan ){ 42392ede964Sstephan return (f.length===arguments.length) 42492ede964Sstephan ? capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep, 42592ede964Sstephan pApp, xFunc, xStep, xFinal, 0) 42692ede964Sstephan : __dbArgcMismatch(pDb,"sqlite3_create_function",f.length); 4279892883eSstephan }; 42892ede964Sstephan 42992ede964Sstephan /* Documented in the api object's initializer. */ 43092ede964Sstephan capi.sqlite3_create_window_function = function f( 43192ede964Sstephan pDb, funcName, nArg, eTextRep, pApp, 43292ede964Sstephan xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) 43392ede964Sstephan xFinal, //void (*xFinal)(sqlite3_context*) 43492ede964Sstephan xValue, //void (*xFinal)(sqlite3_context*) 43592ede964Sstephan xInverse,//void (*xStep)(sqlite3_context*,int,sqlite3_value**) 43692ede964Sstephan xDestroy //void (*xDestroy)(void*) 43792ede964Sstephan ){ 43892ede964Sstephan if(f.length!==arguments.length){ 43992ede964Sstephan return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length); 44092ede964Sstephan } 44192ede964Sstephan /* Wrap the callbacks in a WASM-bound functions... */ 44292ede964Sstephan const uninstall = [/*funcs to uninstall on error*/]; 44392ede964Sstephan let rc; 44492ede964Sstephan try{ 44592ede964Sstephan const funcArgs = __xWrapFuncs({xStep, xFinal, xValue, xInverse, xDestroy}, 44692ede964Sstephan uninstall); 447a6ca996eSstephan rc = sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, 44892ede964Sstephan pApp, ...funcArgs); 44992ede964Sstephan }catch(e){ 450a6ca996eSstephan console.error("sqlite3_create_window_function() setup threw:",e); 45192ede964Sstephan for(let v of uninstall){ 45292ede964Sstephan wasm.uninstallFunction(v); 45392ede964Sstephan } 45492ede964Sstephan rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, 45592ede964Sstephan "Creation of UDF threw: "+e.message); 45692ede964Sstephan } 45792ede964Sstephan return rc; 45892ede964Sstephan }; 459510a9d1cSstephan /** 460510a9d1cSstephan A helper for UDFs implemented in JS and bound to WASM by the 461ef9cd12eSstephan client. Given a JS value, udfSetResult(pCtx,X) calls one of the 462510a9d1cSstephan sqlite3_result_xyz(pCtx,...) routines, depending on X's data 463510a9d1cSstephan type: 464510a9d1cSstephan 465510a9d1cSstephan - `null`: sqlite3_result_null() 466510a9d1cSstephan - `boolean`: sqlite3_result_int() 4678d9e5955Sstephan - `number`: sqlite3_result_int() or sqlite3_result_double() 468510a9d1cSstephan - `string`: sqlite3_result_text() 469510a9d1cSstephan - Uint8Array or Int8Array: sqlite3_result_blob() 4706f3286caSstephan - `undefined`: indicates that the UDF called one of the 4716f3286caSstephan `sqlite3_result_xyz()` routines on its own, making this 4726f3286caSstephan function a no-op. Results are _undefined_ if this function is 4736f3286caSstephan passed the `undefined` value but did _not_ call one of the 4746f3286caSstephan `sqlite3_result_xyz()` routines. 475510a9d1cSstephan 476510a9d1cSstephan Anything else triggers sqlite3_result_error(). 477510a9d1cSstephan */ 478ef9cd12eSstephan capi.sqlite3_create_function_v2.udfSetResult = 479ef9cd12eSstephan capi.sqlite3_create_function.udfSetResult = 480ef9cd12eSstephan capi.sqlite3_create_window_function.udfSetResult = __udfSetResult; 481510a9d1cSstephan 482510a9d1cSstephan /** 483510a9d1cSstephan A helper for UDFs implemented in JS and bound to WASM by the 484510a9d1cSstephan client. When passed the 485510a9d1cSstephan (argc,argv) values from the UDF-related functions which receive 486510a9d1cSstephan them (xFunc, xStep, xInverse), it creates a JS array 487510a9d1cSstephan representing those arguments, converting each to JS in a manner 488510a9d1cSstephan appropriate to its data type: numeric, text, blob 4898d9e5955Sstephan (Uint8Array), or null. 490510a9d1cSstephan 491510a9d1cSstephan Results are undefined if it's passed anything other than those 492510a9d1cSstephan two arguments from those specific contexts. 493510a9d1cSstephan 494510a9d1cSstephan Thus an argc of 4 will result in a length-4 array containing 495510a9d1cSstephan the converted values from the corresponding argv. 496510a9d1cSstephan 497510a9d1cSstephan The conversion will throw only on allocation error or an internal 498510a9d1cSstephan error. 499510a9d1cSstephan */ 500ef9cd12eSstephan capi.sqlite3_create_function_v2.udfConvertArgs = 501ef9cd12eSstephan capi.sqlite3_create_function.udfConvertArgs = 502ef9cd12eSstephan capi.sqlite3_create_window_function.udfConvertArgs = __udfConvertArgs; 503510a9d1cSstephan 504510a9d1cSstephan /** 505510a9d1cSstephan A helper for UDFs implemented in JS and bound to WASM by the 506510a9d1cSstephan client. It expects to be a passed `(sqlite3_context*, Error)` 5076f3286caSstephan (an exception object or message string). And it sets the 5086f3286caSstephan current UDF's result to sqlite3_result_error_nomem() or 5096f3286caSstephan sqlite3_result_error(), depending on whether the 2nd argument 5106f3286caSstephan is a sqlite3.WasmAllocError object or not. 511510a9d1cSstephan */ 512ef9cd12eSstephan capi.sqlite3_create_function_v2.udfSetError = 513ef9cd12eSstephan capi.sqlite3_create_function.udfSetError = 514ef9cd12eSstephan capi.sqlite3_create_window_function.udfSetError = __udfSetError; 51592ede964Sstephan 51692ede964Sstephan }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/; 5179892883eSstephan 518e67a0f40Sstephan if(1){/* Special-case handling of sqlite3_prepare_v2() and 519e67a0f40Sstephan sqlite3_prepare_v3() */ 520e67a0f40Sstephan /** 5213961b263Sstephan Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). 5223961b263Sstephan */ 5233961b263Sstephan const __prepare = Object.create(null); 5243961b263Sstephan /** 5253961b263Sstephan This binding expects a JS string as its 2nd argument and 5263961b263Sstephan null as its final argument. In order to compile multiple 5273961b263Sstephan statements from a single string, the "full" impl (see 5283961b263Sstephan below) must be used. 5293961b263Sstephan */ 5303961b263Sstephan __prepare.basic = wasm.xWrap('sqlite3_prepare_v3', 5313961b263Sstephan "int", ["sqlite3*", "string", 532453af2f6Sstephan "int"/*ignored for this impl!*/, 5333961b263Sstephan "int", "**", 5343961b263Sstephan "**"/*MUST be 0 or null or undefined!*/]); 5353961b263Sstephan /** 5363961b263Sstephan Impl which requires that the 2nd argument be a pointer 5373961b263Sstephan to the SQL string, instead of being converted to a 5383961b263Sstephan string. This variant is necessary for cases where we 5393961b263Sstephan require a non-NULL value for the final argument 5403961b263Sstephan (exec()'ing multiple statements from one input 5413961b263Sstephan string). For simpler cases, where only the first 5423961b263Sstephan statement in the SQL string is required, the wrapper 5433961b263Sstephan named sqlite3_prepare_v2() is sufficient and easier to 5443961b263Sstephan use because it doesn't require dealing with pointers. 5453961b263Sstephan */ 5463961b263Sstephan __prepare.full = wasm.xWrap('sqlite3_prepare_v3', 5473961b263Sstephan "int", ["sqlite3*", "*", "int", "int", 5483961b263Sstephan "**", "**"]); 5493961b263Sstephan 5503961b263Sstephan /* Documented in the api object's initializer. */ 5513961b263Sstephan capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ 55292ede964Sstephan if(f.length!==arguments.length){ 55392ede964Sstephan return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length); 554e67a0f40Sstephan } 555e67a0f40Sstephan const [xSql, xSqlLen] = __flexiString(sql, sqlLen); 556e67a0f40Sstephan switch(typeof xSql){ 557e67a0f40Sstephan case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null); 558e67a0f40Sstephan case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail); 5593961b263Sstephan default: 5603961b263Sstephan return util.sqlite3_wasm_db_error( 5613961b263Sstephan pDb, capi.SQLITE_MISUSE, 5623961b263Sstephan "Invalid SQL argument type for sqlite3_prepare_v2/v3()." 5633961b263Sstephan ); 5643961b263Sstephan } 5653961b263Sstephan }; 5663961b263Sstephan 567e67a0f40Sstephan /* Documented in the api object's initializer. */ 56892ede964Sstephan capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){ 56992ede964Sstephan return (f.length===arguments.length) 570e67a0f40Sstephan ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) 57192ede964Sstephan : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length); 572e67a0f40Sstephan }; 573e67a0f40Sstephan }/*sqlite3_prepare_v2/v3()*/; 5743961b263Sstephan 5753961b263Sstephan {/* Import C-level constants and structs... */ 5763961b263Sstephan const cJson = wasm.xCall('sqlite3_wasm_enum_json'); 5773961b263Sstephan if(!cJson){ 5783961b263Sstephan toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", 5793961b263Sstephan "static buffer size!"); 5803961b263Sstephan } 5813961b263Sstephan wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); 5823961b263Sstephan //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); 5833961b263Sstephan for(const t of ['access', 'blobFinalizers', 'dataTypes', 5845b915007Sstephan 'encodings', 'fcntl', 'flock', 'ioCap', 5853961b263Sstephan 'openFlags', 'prepareFlags', 'resultCodes', 5864f5bbedbSstephan 'serialize', 'syncFlags', 'trace', 'udfFlags', 58753d4e01dSstephan 'version' 5883961b263Sstephan ]){ 589f75c0c70Sstephan for(const e of Object.entries(wasm.ctype[t])){ 590f75c0c70Sstephan // ^^^ [k,v] there triggers a buggy code transormation via one 591f75c0c70Sstephan // of the Emscripten-driven optimizers. 592f75c0c70Sstephan capi[e[0]] = e[1]; 5933961b263Sstephan } 5943961b263Sstephan } 5951f095d48Sstephan const __rcMap = Object.create(null); 5961f095d48Sstephan for(const t of ['resultCodes']){ 5971f095d48Sstephan for(const e of Object.entries(wasm.ctype[t])){ 5981f095d48Sstephan __rcMap[e[1]] = e[0]; 5991f095d48Sstephan } 6001f095d48Sstephan } 6011f095d48Sstephan /** 6021f095d48Sstephan For the given integer, returns the SQLITE_xxx result code as a 6031f095d48Sstephan string, or undefined if no such mapping is found. 6041f095d48Sstephan */ 6058a8244b5Sstephan capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; 6063961b263Sstephan /* Bind all registered C-side structs... */ 607d92c652aSstephan const notThese = Object.assign(Object.create(null),{ 608d92c652aSstephan // Structs NOT to register 609d92c652aSstephan WasmTestStruct: true 610d92c652aSstephan }); 611a6ca996eSstephan if(!util.isUIThread()){ 612a6ca996eSstephan /* We remove the kvvfs VFS from Worker threads below. */ 613a6ca996eSstephan notThese.sqlite3_kvvfs_methods = true; 614a6ca996eSstephan } 6153961b263Sstephan for(const s of wasm.ctype.structs){ 616d92c652aSstephan if(!notThese[s.name]){ 6173961b263Sstephan capi[s.name] = sqlite3.StructBinder(s); 6183961b263Sstephan } 619d92c652aSstephan } 6201f095d48Sstephan }/*end C constant imports*/ 621d18f1bbfSstephan 622a6ca996eSstephan const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); 623a6ca996eSstephan if( pKvvfs ){/* kvvfs-specific glue */ 624a6ca996eSstephan if(util.isUIThread()){ 6254fbf90eeSstephan const kvvfsMethods = new capi.sqlite3_kvvfs_methods( 6264fbf90eeSstephan wasm.exports.sqlite3_wasm_kvvfs_methods() 6274fbf90eeSstephan ); 6284fbf90eeSstephan delete capi.sqlite3_kvvfs_methods; 6294fbf90eeSstephan 6304fbf90eeSstephan const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, 6314fbf90eeSstephan pstack = wasm.pstack, 6324fbf90eeSstephan pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc; 6334fbf90eeSstephan 6344fbf90eeSstephan const kvvfsStorage = (zClass)=> 6354fbf90eeSstephan ((115/*=='s'*/===wasm.getMemValue(zClass)) 6364fbf90eeSstephan ? sessionStorage : localStorage); 6374fbf90eeSstephan 63802d15a7eSstephan /** 63902d15a7eSstephan Implementations for members of the object referred to by 64002d15a7eSstephan sqlite3_wasm_kvvfs_methods(). We swap out the native 64102d15a7eSstephan implementations with these, which use localStorage or 64202d15a7eSstephan sessionStorage for their backing store. 64302d15a7eSstephan */ 6444fbf90eeSstephan const kvvfsImpls = { 6454fbf90eeSstephan xRead: (zClass, zKey, zBuf, nBuf)=>{ 6464fbf90eeSstephan const stack = pstack.pointer, 6474fbf90eeSstephan astack = wasm.scopedAllocPush(); 6484fbf90eeSstephan try { 6494fbf90eeSstephan const zXKey = kvvfsMakeKey(zClass,zKey); 6504fbf90eeSstephan if(!zXKey) return -3/*OOM*/; 6514fbf90eeSstephan const jKey = wasm.cstringToJs(zXKey); 6524fbf90eeSstephan const jV = kvvfsStorage(zClass).getItem(jKey); 6534fbf90eeSstephan if(!jV) return -1; 6544fbf90eeSstephan const nV = jV.length /* Note that we are relying 100% on v being 6554fbf90eeSstephan ASCII so that jV.length is equal to the 6564fbf90eeSstephan C-string's byte length. */; 6574fbf90eeSstephan if(nBuf<=0) return nV; 6584fbf90eeSstephan else if(1===nBuf){ 6594fbf90eeSstephan wasm.setMemValue(zBuf, 0); 6604fbf90eeSstephan return nV; 6614fbf90eeSstephan } 6624fbf90eeSstephan const zV = wasm.scopedAllocCString(jV); 6634fbf90eeSstephan if(nBuf > nV + 1) nBuf = nV + 1; 6644fbf90eeSstephan wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1); 6654fbf90eeSstephan wasm.setMemValue(zBuf + nBuf - 1, 0); 6664fbf90eeSstephan return nBuf - 1; 6674fbf90eeSstephan }catch(e){ 6684fbf90eeSstephan console.error("kvstorageRead()",e); 6694fbf90eeSstephan return -2; 6704fbf90eeSstephan }finally{ 6714fbf90eeSstephan pstack.restore(stack); 6724fbf90eeSstephan wasm.scopedAllocPop(astack); 6734fbf90eeSstephan } 6744fbf90eeSstephan }, 6754fbf90eeSstephan xWrite: (zClass, zKey, zData)=>{ 6764fbf90eeSstephan const stack = pstack.pointer; 6774fbf90eeSstephan try { 6784fbf90eeSstephan const zXKey = kvvfsMakeKey(zClass,zKey); 6794fbf90eeSstephan if(!zXKey) return 1/*OOM*/; 6804fbf90eeSstephan const jKey = wasm.cstringToJs(zXKey); 6814fbf90eeSstephan kvvfsStorage(zClass).setItem(jKey, wasm.cstringToJs(zData)); 6824fbf90eeSstephan return 0; 6834fbf90eeSstephan }catch(e){ 6844fbf90eeSstephan console.error("kvstorageWrite()",e); 6854fbf90eeSstephan return capi.SQLITE_IOERR; 6864fbf90eeSstephan }finally{ 6874fbf90eeSstephan pstack.restore(stack); 6884fbf90eeSstephan } 6894fbf90eeSstephan }, 6904fbf90eeSstephan xDelete: (zClass, zKey)=>{ 6914fbf90eeSstephan const stack = pstack.pointer; 6924fbf90eeSstephan try { 6934fbf90eeSstephan const zXKey = kvvfsMakeKey(zClass,zKey); 6944fbf90eeSstephan if(!zXKey) return 1/*OOM*/; 6954fbf90eeSstephan kvvfsStorage(zClass).removeItem(wasm.cstringToJs(zXKey)); 6964fbf90eeSstephan return 0; 6974fbf90eeSstephan }catch(e){ 6984fbf90eeSstephan console.error("kvstorageDelete()",e); 6994fbf90eeSstephan return capi.SQLITE_IOERR; 7004fbf90eeSstephan }finally{ 7014fbf90eeSstephan pstack.restore(stack); 7024fbf90eeSstephan } 7034fbf90eeSstephan } 7044fbf90eeSstephan }/*kvvfsImpls*/; 70502d15a7eSstephan for(const k of Object.keys(kvvfsImpls)){ 7064fbf90eeSstephan kvvfsMethods[kvvfsMethods.memberKey(k)] = 7074fbf90eeSstephan wasm.installFunction( 7084fbf90eeSstephan kvvfsMethods.memberSignature(k), 7094fbf90eeSstephan kvvfsImpls[k] 7104fbf90eeSstephan ); 7114fbf90eeSstephan } 712a6ca996eSstephan }else{ 713a6ca996eSstephan /* Worker thread: unregister kvvfs to avoid it being used 714a6ca996eSstephan for anything other than local/sessionStorage. It "can" 715a6ca996eSstephan be used that way but it's not really intended to be. */ 716a6ca996eSstephan capi.sqlite3_vfs_unregister(pKvvfs); 717a6ca996eSstephan } 718a6ca996eSstephan }/*pKvvfs*/ 7194fbf90eeSstephan 720d18f1bbfSstephan}); 721