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(pDb, sql, callback, pVoid, pErrMsg){ 153 if(5!==arguments.length){ 154 return __dbArgcMismatch(pDb,"sqlite3_exec",5); 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 const sqlite3CreateFunction = wasm.xWrap( 197 "sqlite3_create_function_v2", "int", 198 ["sqlite3*", "string", "int", "int", "*", 199 "*", "*", "*", "*"] 200 ); 201 const __setResult = function(pCx, val){ 202 switch(typeof val) { 203 case 'boolean': 204 capi.sqlite3_result_int(pCx, val ? 1 : 0); 205 break; 206 case 'number': { 207 (util.isInt32(val) 208 ? capi.sqlite3_result_int 209 : capi.sqlite3_result_double)(pCx, val); 210 break; 211 } 212 case 'string': 213 capi.sqlite3_result_text(pCx, val, -1, capi.SQLITE_TRANSIENT); 214 break; 215 case 'object': 216 if(null===val) { 217 capi.sqlite3_result_null(pCx); 218 break; 219 }else if(util.isBindableTypedArray(val)){ 220 const pBlob = wasm.allocFromTypedArray(val); 221 capi.sqlite3_result_blob(pCx, pBlob, val.byteLength, 222 capi.SQLITE_TRANSIENT); 223 wasm.dealloc(pBlob); 224 break; 225 } 226 // else fall through 227 default: 228 toss3("Don't not how to handle this UDF result value:",val); 229 }; 230 }/*__setResult()*/; 231 const __extractArgs = function(argc, pArgv){ 232 let i, pVal, valType, arg; 233 const tgt = []; 234 for(i = 0; i < argc; ++i){ 235 pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * i)); 236 /** 237 Curiously: despite ostensibly requiring 8-byte 238 alignment, the pArgv array is parcelled into chunks of 239 4 bytes (1 pointer each). The values those point to 240 have 8-byte alignment but the individual argv entries 241 do not. 242 */ 243 valType = capi.sqlite3_value_type(pVal); 244 switch(valType){ 245 case capi.SQLITE_INTEGER: 246 case capi.SQLITE_FLOAT: 247 arg = capi.sqlite3_value_double(pVal); 248 break; 249 case capi.SQLITE_TEXT: 250 arg = capi.sqlite3_value_text(pVal); 251 break; 252 case capi.SQLITE_BLOB:{ 253 const n = capi.sqlite3_value_bytes(pVal); 254 const pBlob = capi.sqlite3_value_blob(pVal); 255 arg = new Uint8Array(n); 256 let i; 257 const heap = n ? wasm.heap8() : false; 258 for(i = 0; i < n; ++i) arg[i] = heap[pBlob+i]; 259 break; 260 } 261 case capi.SQLITE_NULL: 262 arg = null; break; 263 default: 264 toss3("Unhandled sqlite3_value_type()",valType, 265 "is possibly indicative of incorrect", 266 "pointer size assumption."); 267 } 268 tgt.push(arg); 269 } 270 return tgt; 271 }/*__extractArgs()*/; 272 273 const __setCxErr = (pCx, e)=>{ 274 if(e instanceof capi.WasmAllocError){ 275 capi.sqlite3_result_error_nomem(pCx); 276 }else{ 277 capi.sqlite3_result_error(pCx, e.message, -1); 278 } 279 }; 280 281 const __xFunc = function(callback){ 282 return function(pCx, argc, pArgv){ 283 try{__setResult(pCx, callback(...__extractArgs(argc, pArgv)))} 284 catch(e){ __setCxErr(pCx, e) } 285 }; 286 }; 287 288 const __xStep = function(callback){ 289 return function(pCx, argc, pArgv){ 290 try{ callback(...__extractArgs(argc, pArgv)) } 291 catch(e){ __setCxErr(pCx, e) } 292 }; 293 }; 294 295 const __xFinal = function(callback){ 296 return function(pCx){ 297 try{ __setResult(pCx, callback()) } 298 catch(e){ __setCxErr(pCx, e) } 299 }; 300 }; 301 302 const __xDestroy = function(callback){ 303 return function(pVoid){ 304 try{ callback(pVoid) } 305 catch(e){ 306 console.error("UDF xDestroy method threw:",e); 307 } 308 }; 309 }; 310 /* Documented in the api object's initializer. */ 311 capi.sqlite3_create_function_v2 = function f( 312 pDb, funcName, nArg, eTextRep, pApp, 313 xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**) 314 xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) 315 xFinal, //void (*xFinal)(sqlite3_context*) 316 xDestroy //void (*xDestroy)(void*) 317 ){ 318 if(9!==arguments.length){ 319 return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",9); 320 } 321 if(!f._sigs){ 322 f._wrap = Object.assign(Object.create(null), { 323 xFunc: {sig:'v(pip)', f:__xFunc}, 324 xStep: {sig:'v(pip)', f:__xStep}, 325 xFinal: {sig:'v(p)', f:__xFinal}, 326 xDestroy: {sig:'v(p)', f:__xDestroy} 327 }); 328 } 329 const callbacks = []; 330 /* Wrap the callbacks in a WASM-bound functions... */ 331 const wasm = capi.wasm; 332 const funcArgs = [], uninstall = [/*funcs to uninstall on error*/], 333 theFuncs = {xFunc, xStep, xFinal, xDestroy}; 334 let rc; 335 try{ 336 let k; 337 for(k in theFuncs){ 338 let fArg = theFuncs[k]; 339 if('function'===typeof fArg){ 340 const w = f._wrap[k]; 341 fArg = wasm.installFunction(w.sig, w.f(fArg)); 342 uninstall.push(fArg); 343 } 344 funcArgs.push(fArg); 345 } 346 rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, 347 pApp, ...funcArgs); 348 }catch(e){ 349 console.error("sqlite3_create_function_v2() setup threw:",e); 350 for(let v of uninstall){ 351 wasm.uninstallFunction(v); 352 } 353 rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, 354 "Creation of UDF threw: "+e.message); 355 } 356 return rc; 357 }; 358 359 capi.sqlite3_create_function = function( 360 pDb, funcName, nArg, eTextRep, pApp, 361 xFunc, xStep, xFinal 362 ){ 363 if(8!==arguments.length){ 364 return __dbArgcMismatch(pDb,"sqlite3_create_function",8); 365 } 366 return capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep, 367 pApp, xFunc, xStep, xFinal, 0); 368 369 }; 370 }/*sqlite3_create_function_v2() proxy*/; 371 372 if(1){/* Special-case handling of sqlite3_prepare_v2() and 373 sqlite3_prepare_v3() */ 374 /** 375 Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). 376 */ 377 const __prepare = Object.create(null); 378 /** 379 This binding expects a JS string as its 2nd argument and 380 null as its final argument. In order to compile multiple 381 statements from a single string, the "full" impl (see 382 below) must be used. 383 */ 384 __prepare.basic = wasm.xWrap('sqlite3_prepare_v3', 385 "int", ["sqlite3*", "string", 386 "int"/*ignored for this impl!*/, 387 "int", "**", 388 "**"/*MUST be 0 or null or undefined!*/]); 389 /** 390 Impl which requires that the 2nd argument be a pointer 391 to the SQL string, instead of being converted to a 392 string. This variant is necessary for cases where we 393 require a non-NULL value for the final argument 394 (exec()'ing multiple statements from one input 395 string). For simpler cases, where only the first 396 statement in the SQL string is required, the wrapper 397 named sqlite3_prepare_v2() is sufficient and easier to 398 use because it doesn't require dealing with pointers. 399 */ 400 __prepare.full = wasm.xWrap('sqlite3_prepare_v3', 401 "int", ["sqlite3*", "*", "int", "int", 402 "**", "**"]); 403 404 /* Documented in the api object's initializer. */ 405 capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ 406 if(6!==arguments.length){ 407 return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",6); 408 } 409 const [xSql, xSqlLen] = __flexiString(sql, sqlLen); 410 switch(typeof xSql){ 411 case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null); 412 case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail); 413 default: 414 return util.sqlite3_wasm_db_error( 415 pDb, capi.SQLITE_MISUSE, 416 "Invalid SQL argument type for sqlite3_prepare_v2/v3()." 417 ); 418 } 419 }; 420 421 /* Documented in the api object's initializer. */ 422 capi.sqlite3_prepare_v2 = function(pDb, sql, sqlLen, ppStmt, pzTail){ 423 return (5==arguments.length) 424 ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) 425 : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",5); 426 }; 427 }/*sqlite3_prepare_v2/v3()*/; 428 429 if(1){// Extend wasm.pstack, now that the wasm utils are installed 430 /** 431 Allocates n chunks, each sz bytes, as a single memory block and 432 returns the addresses as an array of n element, each holding 433 the address of one chunk. 434 435 Throws a WasmAllocError if allocation fails. 436 437 Example: 438 439 ``` 440 const [p1, p2, p3] = wasm.pstack.allocChunks(3,4); 441 ``` 442 */ 443 wasm.pstack.allocChunks = (n,sz)=>{ 444 const mem = wasm.pstack.alloc(n * sz); 445 const rc = []; 446 let i = 0, offset = 0; 447 for(; i < n; offset = (sz * ++i)){ 448 rc.push(mem + offset); 449 } 450 return rc; 451 }; 452 453 /** 454 A convenience wrapper for allocChunks() which sizes each chunks 455 as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if 456 safePtrSize is falsy). 457 458 How it returns its result differs depending on its first 459 argument: if it's 1, it returns a single pointer value. If it's 460 more than 1, it returns the same as allocChunks(). 461 462 When a returned pointers will refer to a 64-bit value, e.g. a 463 double or int64, and that value must be written or fetched, 464 e.g. using wasm.setMemValue() or wasm.getMemValue(), it is 465 important that the pointer in question be aligned to an 8-byte 466 boundary or else it will not be fetched or written properly and 467 will corrupt or read neighboring memory. 468 469 However, when all pointers involved point to "small" data, it 470 is safe to pass a falsy value to save to memory. 471 */ 472 wasm.pstack.allocPtr = (n=1,safePtrSize=true) =>{ 473 return 1===n 474 ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof) 475 : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof); 476 }; 477 }/*wasm.pstack filler*/ 478 479 /** 480 Install JS<->C struct bindings for the non-opaque struct types we 481 need... */ 482 sqlite3.StructBinder = self.Jaccwabyt({ 483 heap: 0 ? wasm.memory : wasm.heap8u, 484 alloc: wasm.alloc, 485 dealloc: wasm.dealloc, 486 functionTable: wasm.functionTable, 487 bigIntEnabled: wasm.bigIntEnabled, 488 memberPrefix: '$' 489 }); 490 delete self.Jaccwabyt; 491 492 {/* Import C-level constants and structs... */ 493 const cJson = wasm.xCall('sqlite3_wasm_enum_json'); 494 if(!cJson){ 495 toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", 496 "static buffer size!"); 497 } 498 wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); 499 //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); 500 for(const t of ['access', 'blobFinalizers', 'dataTypes', 501 'encodings', 'fcntl', 'flock', 'ioCap', 502 'openFlags', 'prepareFlags', 'resultCodes', 503 'serialize', 'syncFlags', 'udfFlags', 504 'version' 505 ]){ 506 for(const e of Object.entries(wasm.ctype[t])){ 507 // ^^^ [k,v] there triggers a buggy code transormation via one 508 // of the Emscripten-driven optimizers. 509 capi[e[0]] = e[1]; 510 } 511 } 512 const __rcMap = Object.create(null); 513 for(const t of ['resultCodes']){ 514 for(const e of Object.entries(wasm.ctype[t])){ 515 __rcMap[e[1]] = e[0]; 516 } 517 } 518 /** 519 For the given integer, returns the SQLITE_xxx result code as a 520 string, or undefined if no such mapping is found. 521 */ 522 capi.sqlite3_web_rc_str = (rc)=>__rcMap[rc]; 523 /* Bind all registered C-side structs... */ 524 for(const s of wasm.ctype.structs){ 525 capi[s.name] = sqlite3.StructBinder(s); 526 } 527 }/*end C constant imports*/ 528 529 sqlite3.version = Object.assign(Object.create(null),{ 530 library: sqlite3.capi.sqlite3_libversion(), 531 sourceId: sqlite3.capi.sqlite3_sourceid() 532 }); 533}); 534 535