1/* 2 2022-07-22 3 4 The author disclaims copyright to this source code. In place of a 5 legal notice, here is a blessing: 6 7 * May you do good and not evil. 8 * May you find forgiveness for yourself and forgive others. 9 * May you share freely, never taking more than you give. 10 11 *********************************************************************** 12 13 This file glues together disparate pieces of JS which are loaded in 14 previous steps of the sqlite3-api.js bootstrapping process: 15 sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It 16 initializes the main API pieces so that the downstream components 17 (e.g. sqlite3-api-oo1.js) have all that they need. 18*/ 19self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 20 'use strict'; 21 const toss = (...args)=>{throw new Error(args.join(' '))}; 22 const capi = sqlite3.capi, wasm = capi.wasm, util = capi.util; 23 self.WhWasmUtilInstaller(capi.wasm); 24 delete self.WhWasmUtilInstaller; 25 26 if(0){ 27 /* "The problem" is that the following isn't type-safe. 28 OTOH, nothing about WASM pointers is. */ 29 /** 30 Add the `.pointer` xWrap() signature entry to extend the 31 `pointer` arg handler to check for a `pointer` property. This 32 can be used to permit, e.g., passing an sqlite3.oo1.DB instance 33 to a C-style sqlite3_xxx function which takes an `sqlite3*` 34 argument. 35 */ 36 const xPointer = wasm.xWrap.argAdapter('pointer'); 37 const adapter = function(v){ 38 if(v && v.constructor){ 39 const x = v.pointer; 40 if(Number.isInteger(x)) return x; 41 else toss("Invalid (object) type for .pointer-type argument."); 42 } 43 return xPointer(v); 44 }; 45 wasm.xWrap.argAdapter('.pointer', adapter); 46 } /* ".pointer" xWrap() argument adapter */ 47 48 if(1){/* Convert Arrays and certain TypedArrays to strings for 49 'flexible-string'-type arguments */ 50 const xString = wasm.xWrap.argAdapter('string'); 51 wasm.xWrap.argAdapter( 52 'flexible-string', (v)=>xString(util.arrayToString(v)) 53 ); 54 } 55 56 if(1){// WhWasmUtil.xWrap() bindings... 57 /** 58 Add some descriptive xWrap() aliases for '*' intended to (A) 59 initially improve readability/correctness of capi.signatures 60 and (B) eventually perhaps provide automatic conversion from 61 higher-level representations, e.g. capi.sqlite3_vfs to 62 `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. 63 */ 64 const aPtr = wasm.xWrap.argAdapter('*'); 65 wasm.xWrap.argAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr); 66 wasm.xWrap.resultAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr); 67 68 /** 69 Populate api object with sqlite3_...() by binding the "raw" wasm 70 exports into type-converting proxies using wasm.xWrap(). 71 */ 72 for(const e of wasm.bindingSignatures){ 73 capi[e[0]] = wasm.xWrap.apply(null, e); 74 } 75 for(const e of wasm.bindingSignatures.wasm){ 76 capi.wasm[e[0]] = wasm.xWrap.apply(null, e); 77 } 78 79 /* For C API functions which cannot work properly unless 80 wasm.bigIntEnabled is true, install a bogus impl which 81 throws if called when bigIntEnabled is false. */ 82 const fI64Disabled = function(fname){ 83 return ()=>toss(fname+"() disabled due to lack", 84 "of BigInt support in this build."); 85 }; 86 for(const e of wasm.bindingSignatures.int64){ 87 capi[e[0]] = wasm.bigIntEnabled 88 ? wasm.xWrap.apply(null, e) 89 : fI64Disabled(e[0]); 90 } 91 92 if(wasm.exports.sqlite3_wasm_db_error){ 93 util.sqlite3_wasm_db_error = capi.wasm.xWrap( 94 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string' 95 ); 96 }else{ 97 util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ 98 console.warn("sqlite3_wasm_db_error() is not exported.",arguments); 99 return errCode; 100 }; 101 } 102 103 /** 104 When registering a VFS and its related components it may be 105 necessary to ensure that JS keeps a reference to them to keep 106 them from getting garbage collected. Simply pass each such value 107 to this function and a reference will be held to it for the life 108 of the app. 109 */ 110 capi.sqlite3_vfs_register.addReference = function f(...args){ 111 if(!f._) f._ = []; 112 f._.push(...args); 113 }; 114 115 }/*xWrap() bindings*/; 116 117 /** 118 Internal helper to assist in validating call argument counts in 119 the hand-written sqlite3_xyz() wrappers. We do this only for 120 consistency with non-special-case wrappings. 121 */ 122 const __dbArgcMismatch = (pDb,f,n)=>{ 123 return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE, 124 f+"() requires "+n+" argument"+ 125 (1===n?'':'s')+"."); 126 }; 127 128 /** 129 Helper for flexible-string conversions which require a 130 byte-length counterpart argument. Passed a value and its 131 ostensible length, this function returns [V,N], where V 132 is either v or a transformed copy of v and N is either n, 133 -1, or the byte length of v (if it's a byte array). 134 */ 135 const __flexiString = function(v,n){ 136 if('string'===typeof v){ 137 n = -1; 138 }else if(util.isSQLableTypedArray(v)){ 139 n = v.byteLength; 140 v = util.typedArrayToString(v); 141 }else if(Array.isArray(v)){ 142 v = v.join(''); 143 n = -1; 144 } 145 return [v, n]; 146 }; 147 148 if(1){/* Special-case handling of sqlite3_exec() */ 149 const __exec = wasm.xWrap("sqlite3_exec", "int", 150 ["sqlite3*", "flexible-string", "*", "*", "**"]); 151 /* Documented in the api object's initializer. */ 152 capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){ 153 if(f.length!==arguments.length){ 154 return __dbArgcMismatch(pDb,"sqlite3_exec",f.length); 155 }else if('function' !== typeof callback){ 156 return __exec(pDb, sql, callback, pVoid, pErrMsg); 157 } 158 /* Wrap the callback in a WASM-bound function and convert the callback's 159 `(char**)` arguments to arrays of strings... */ 160 const wasm = capi.wasm; 161 const cbwrap = function(pVoid, nCols, pColVals, pColNames){ 162 let rc = capi.SQLITE_ERROR; 163 try { 164 let aVals = [], aNames = [], i = 0, offset = 0; 165 for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){ 166 aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) ); 167 aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) ); 168 } 169 rc = callback(pVoid, nCols, aVals, aNames) | 0; 170 /* The first 2 args of the callback are useless for JS but 171 we want the JS mapping of the C API to be as close to the 172 C API as possible. */ 173 }catch(e){ 174 /* If we set the db error state here, the higher-level exec() call 175 replaces it with its own, so we have no way of reporting the 176 exception message except the console. We must not propagate 177 exceptions through the C API. */ 178 } 179 return rc; 180 }; 181 let pFunc, rc; 182 try{ 183 pFunc = wasm.installFunction("ipipp", cbwrap); 184 rc = __exec(pDb, sql, pFunc, pVoid, pErrMsg); 185 }catch(e){ 186 rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, 187 "Error running exec(): "+e.message); 188 }finally{ 189 if(pFunc) wasm.uninstallFunction(pFunc); 190 } 191 return rc; 192 }; 193 }/*sqlite3_exec() proxy*/; 194 195 if(1){/* Special-case handling of sqlite3_create_function_v2() 196 and sqlite3_create_window_function() */ 197 const sqlite3CreateFunction = wasm.xWrap( 198 "sqlite3_create_function_v2", "int", 199 ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, 200 "int"/*eTextRep*/, "*"/*pApp*/, 201 "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, "*"/*xDestroy*/] 202 ); 203 const sqlite3CreateWindowFunction = wasm.xWrap( 204 "sqlite3_create_window_function", "int", 205 ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, 206 "int"/*eTextRep*/, "*"/*pApp*/, 207 "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, 208 "*"/*xInverse*/, "*"/*xDestroy*/] 209 ); 210 const __setResult = function(pCx, val){ 211 switch(typeof val) { 212 case 'boolean': 213 capi.sqlite3_result_int(pCx, val ? 1 : 0); 214 break; 215 case 'number': { 216 (util.isInt32(val) 217 ? capi.sqlite3_result_int 218 : capi.sqlite3_result_double)(pCx, val); 219 break; 220 } 221 case 'string': 222 capi.sqlite3_result_text(pCx, val, -1, capi.SQLITE_TRANSIENT); 223 break; 224 case 'object': 225 if(null===val/*yes, typeof null === 'object'*/) { 226 capi.sqlite3_result_null(pCx); 227 break; 228 }else if(util.isBindableTypedArray(val)){ 229 const pBlob = wasm.allocFromTypedArray(val); 230 capi.sqlite3_result_blob( 231 pCx, pBlob, val.byteLength, 232 wasm.exports[sqlite3.config.deallocExportName] 233 ); 234 break; 235 } 236 // else fall through 237 default: 238 toss3("Don't not how to handle this UDF result value:",val); 239 }; 240 }/*__setResult()*/; 241 const __extractArgs = function(argc, pArgv){ 242 let i, pVal, valType, arg; 243 const tgt = []; 244 for(i = 0; i < argc; ++i){ 245 pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * i)); 246 /** 247 Curiously: despite ostensibly requiring 8-byte 248 alignment, the pArgv array is parcelled into chunks of 249 4 bytes (1 pointer each). The values those point to 250 have 8-byte alignment but the individual argv entries 251 do not. 252 */ 253 valType = capi.sqlite3_value_type(pVal); 254 switch(valType){ 255 case capi.SQLITE_INTEGER: 256 case capi.SQLITE_FLOAT: 257 arg = capi.sqlite3_value_double(pVal); 258 break; 259 case capi.SQLITE_TEXT: 260 arg = capi.sqlite3_value_text(pVal); 261 break; 262 case capi.SQLITE_BLOB:{ 263 const n = capi.sqlite3_value_bytes(pVal); 264 const pBlob = capi.sqlite3_value_blob(pVal); 265 arg = new Uint8Array(n); 266 let i; 267 const heap = n ? wasm.heap8() : false; 268 for(i = 0; i < n; ++i) arg[i] = heap[pBlob+i]; 269 break; 270 } 271 case capi.SQLITE_NULL: 272 arg = null; break; 273 default: 274 toss3("Unhandled sqlite3_value_type()",valType, 275 "is possibly indicative of incorrect", 276 "pointer size assumption."); 277 } 278 tgt.push(arg); 279 } 280 return tgt; 281 }/*__extractArgs()*/; 282 283 const __setCxErr = (pCx, e)=>{ 284 if(e instanceof capi.WasmAllocError){ 285 capi.sqlite3_result_error_nomem(pCx); 286 }else{ 287 capi.sqlite3_result_error(pCx, e.message, -1); 288 } 289 }; 290 291 /* TODO: pass on the pCx pointer to all callbacks. This requires 292 fixing test code and updating oodles of docs. Once that is in place, 293 export sqlite3_aggregate_context(). 294 */ 295 296 const __xFunc = function(callback){ 297 return function(pCx, argc, pArgv){ 298 try{ __setResult(pCx, callback(...__extractArgs(argc, pArgv))) } 299 catch(e){ __setCxErr(pCx, e) } 300 }; 301 }; 302 303 const __xInverseAndStep = function(callback){ 304 return function(pCx, argc, pArgv){ 305 try{ callback(...__extractArgs(argc, pArgv)) } 306 catch(e){ __setCxErr(pCx, e) } 307 }; 308 }; 309 310 const __xFinalAndValue = function(callback){ 311 return function(pCx){ 312 try{ __setResult(pCx, callback()) } 313 catch(e){ __setCxErr(pCx, e) } 314 }; 315 }; 316 317 const __xDestroy = function(callback){ 318 return function(pVoid){ 319 try{ callback(pVoid) } 320 catch(e){ console.error("UDF xDestroy method threw:",e) } 321 }; 322 }; 323 324 const __xMap = Object.assign(Object.create(null), { 325 xFunc: {sig:'v(pip)', f:__xFunc}, 326 xStep: {sig:'v(pip)', f:__xInverseAndStep}, 327 xInverse: {sig:'v(pip)', f:__xInverseAndStep}, 328 xFinal: {sig:'v(p)', f:__xFinalAndValue}, 329 xValue: {sig:'v(p)', f:__xFinalAndValue}, 330 xDestroy: {sig:'v(p)', f:__xDestroy} 331 }); 332 333 const __xWrapFuncs = function(theFuncs, tgtUninst){ 334 const rc = [] 335 let k; 336 for(k in theFuncs){ 337 let fArg = theFuncs[k]; 338 if('function'===typeof fArg){ 339 const w = __xMap[k]; 340 fArg = wasm.installFunction(w.sig, w.f(fArg)); 341 tgtUninst.push(fArg); 342 } 343 rc.push(fArg); 344 } 345 return rc; 346 }; 347 348 /* Documented in the api object's initializer. */ 349 capi.sqlite3_create_function_v2 = function f( 350 pDb, funcName, nArg, eTextRep, pApp, 351 xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**) 352 xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) 353 xFinal, //void (*xFinal)(sqlite3_context*) 354 xDestroy //void (*xDestroy)(void*) 355 ){ 356 if(f.length!==arguments.length){ 357 return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length); 358 } 359 /* Wrap the callbacks in a WASM-bound functions... */ 360 const wasm = capi.wasm; 361 const uninstall = [/*funcs to uninstall on error*/]; 362 let rc; 363 try{ 364 const funcArgs = __xWrapFuncs({xFunc, xStep, xFinal, xDestroy}, 365 uninstall); 366 rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, 367 pApp, ...funcArgs); 368 }catch(e){ 369 console.error("sqlite3_create_function_v2() setup threw:",e); 370 for(let v of uninstall){ 371 wasm.uninstallFunction(v); 372 } 373 rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, 374 "Creation of UDF threw: "+e.message); 375 } 376 return rc; 377 }; 378 379 capi.sqlite3_create_function = function f( 380 pDb, funcName, nArg, eTextRep, pApp, 381 xFunc, xStep, xFinal 382 ){ 383 return (f.length===arguments.length) 384 ? capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep, 385 pApp, xFunc, xStep, xFinal, 0) 386 : __dbArgcMismatch(pDb,"sqlite3_create_function",f.length); 387 }; 388 389 /* Documented in the api object's initializer. */ 390 capi.sqlite3_create_window_function = function f( 391 pDb, funcName, nArg, eTextRep, pApp, 392 xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) 393 xFinal, //void (*xFinal)(sqlite3_context*) 394 xValue, //void (*xFinal)(sqlite3_context*) 395 xInverse,//void (*xStep)(sqlite3_context*,int,sqlite3_value**) 396 xDestroy //void (*xDestroy)(void*) 397 ){ 398 if(f.length!==arguments.length){ 399 return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length); 400 } 401 /* Wrap the callbacks in a WASM-bound functions... */ 402 const wasm = capi.wasm; 403 const uninstall = [/*funcs to uninstall on error*/]; 404 let rc; 405 try{ 406 const funcArgs = __xWrapFuncs({xStep, xFinal, xValue, xInverse, xDestroy}, 407 uninstall); 408 rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, 409 pApp, ...funcArgs); 410 }catch(e){ 411 console.error("sqlite3_create_function_v2() setup threw:",e); 412 for(let v of uninstall){ 413 wasm.uninstallFunction(v); 414 } 415 rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, 416 "Creation of UDF threw: "+e.message); 417 } 418 return rc; 419 }; 420 421 }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/; 422 423 if(1){/* Special-case handling of sqlite3_prepare_v2() and 424 sqlite3_prepare_v3() */ 425 /** 426 Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). 427 */ 428 const __prepare = Object.create(null); 429 /** 430 This binding expects a JS string as its 2nd argument and 431 null as its final argument. In order to compile multiple 432 statements from a single string, the "full" impl (see 433 below) must be used. 434 */ 435 __prepare.basic = wasm.xWrap('sqlite3_prepare_v3', 436 "int", ["sqlite3*", "string", 437 "int"/*ignored for this impl!*/, 438 "int", "**", 439 "**"/*MUST be 0 or null or undefined!*/]); 440 /** 441 Impl which requires that the 2nd argument be a pointer 442 to the SQL string, instead of being converted to a 443 string. This variant is necessary for cases where we 444 require a non-NULL value for the final argument 445 (exec()'ing multiple statements from one input 446 string). For simpler cases, where only the first 447 statement in the SQL string is required, the wrapper 448 named sqlite3_prepare_v2() is sufficient and easier to 449 use because it doesn't require dealing with pointers. 450 */ 451 __prepare.full = wasm.xWrap('sqlite3_prepare_v3', 452 "int", ["sqlite3*", "*", "int", "int", 453 "**", "**"]); 454 455 /* Documented in the api object's initializer. */ 456 capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ 457 if(f.length!==arguments.length){ 458 return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length); 459 } 460 const [xSql, xSqlLen] = __flexiString(sql, sqlLen); 461 switch(typeof xSql){ 462 case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null); 463 case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail); 464 default: 465 return util.sqlite3_wasm_db_error( 466 pDb, capi.SQLITE_MISUSE, 467 "Invalid SQL argument type for sqlite3_prepare_v2/v3()." 468 ); 469 } 470 }; 471 472 /* Documented in the api object's initializer. */ 473 capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){ 474 return (f.length===arguments.length) 475 ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) 476 : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length); 477 }; 478 }/*sqlite3_prepare_v2/v3()*/; 479 480 if(1){// Extend wasm.pstack, now that the wasm utils are installed 481 /** 482 Allocates n chunks, each sz bytes, as a single memory block and 483 returns the addresses as an array of n element, each holding 484 the address of one chunk. 485 486 Throws a WasmAllocError if allocation fails. 487 488 Example: 489 490 ``` 491 const [p1, p2, p3] = wasm.pstack.allocChunks(3,4); 492 ``` 493 */ 494 wasm.pstack.allocChunks = (n,sz)=>{ 495 const mem = wasm.pstack.alloc(n * sz); 496 const rc = []; 497 let i = 0, offset = 0; 498 for(; i < n; offset = (sz * ++i)){ 499 rc.push(mem + offset); 500 } 501 return rc; 502 }; 503 504 /** 505 A convenience wrapper for allocChunks() which sizes each chunks 506 as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if 507 safePtrSize is falsy). 508 509 How it returns its result differs depending on its first 510 argument: if it's 1, it returns a single pointer value. If it's 511 more than 1, it returns the same as allocChunks(). 512 513 When a returned pointers will refer to a 64-bit value, e.g. a 514 double or int64, and that value must be written or fetched, 515 e.g. using wasm.setMemValue() or wasm.getMemValue(), it is 516 important that the pointer in question be aligned to an 8-byte 517 boundary or else it will not be fetched or written properly and 518 will corrupt or read neighboring memory. 519 520 However, when all pointers involved point to "small" data, it 521 is safe to pass a falsy value to save to memory. 522 */ 523 wasm.pstack.allocPtr = (n=1,safePtrSize=true) =>{ 524 return 1===n 525 ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof) 526 : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof); 527 }; 528 }/*wasm.pstack filler*/ 529 530 /** 531 Install JS<->C struct bindings for the non-opaque struct types we 532 need... */ 533 sqlite3.StructBinder = self.Jaccwabyt({ 534 heap: 0 ? wasm.memory : wasm.heap8u, 535 alloc: wasm.alloc, 536 dealloc: wasm.dealloc, 537 functionTable: wasm.functionTable, 538 bigIntEnabled: wasm.bigIntEnabled, 539 memberPrefix: '$' 540 }); 541 delete self.Jaccwabyt; 542 543 {/* Import C-level constants and structs... */ 544 const cJson = wasm.xCall('sqlite3_wasm_enum_json'); 545 if(!cJson){ 546 toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", 547 "static buffer size!"); 548 } 549 wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); 550 //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); 551 for(const t of ['access', 'blobFinalizers', 'dataTypes', 552 'encodings', 'fcntl', 'flock', 'ioCap', 553 'openFlags', 'prepareFlags', 'resultCodes', 554 'serialize', 'syncFlags', 'udfFlags', 555 'version' 556 ]){ 557 for(const e of Object.entries(wasm.ctype[t])){ 558 // ^^^ [k,v] there triggers a buggy code transormation via one 559 // of the Emscripten-driven optimizers. 560 capi[e[0]] = e[1]; 561 } 562 } 563 const __rcMap = Object.create(null); 564 for(const t of ['resultCodes']){ 565 for(const e of Object.entries(wasm.ctype[t])){ 566 __rcMap[e[1]] = e[0]; 567 } 568 } 569 /** 570 For the given integer, returns the SQLITE_xxx result code as a 571 string, or undefined if no such mapping is found. 572 */ 573 capi.sqlite3_web_rc_str = (rc)=>__rcMap[rc]; 574 /* Bind all registered C-side structs... */ 575 for(const s of wasm.ctype.structs){ 576 capi[s.name] = sqlite3.StructBinder(s); 577 } 578 }/*end C constant imports*/ 579 580 sqlite3.version = Object.assign(Object.create(null),{ 581 library: sqlite3.capi.sqlite3_libversion(), 582 sourceId: sqlite3.capi.sqlite3_sourceid() 583 }); 584}); 585 586