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_prepare_v2() and 196 sqlite3_prepare_v3() */ 197 /** 198 Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). 199 */ 200 const __prepare = Object.create(null); 201 /** 202 This binding expects a JS string as its 2nd argument and 203 null as its final argument. In order to compile multiple 204 statements from a single string, the "full" impl (see 205 below) must be used. 206 */ 207 __prepare.basic = wasm.xWrap('sqlite3_prepare_v3', 208 "int", ["sqlite3*", "string", 209 "int"/*ignored for this impl!*/, 210 "int", "**", 211 "**"/*MUST be 0 or null or undefined!*/]); 212 /** 213 Impl which requires that the 2nd argument be a pointer 214 to the SQL string, instead of being converted to a 215 string. This variant is necessary for cases where we 216 require a non-NULL value for the final argument 217 (exec()'ing multiple statements from one input 218 string). For simpler cases, where only the first 219 statement in the SQL string is required, the wrapper 220 named sqlite3_prepare_v2() is sufficient and easier to 221 use because it doesn't require dealing with pointers. 222 */ 223 __prepare.full = wasm.xWrap('sqlite3_prepare_v3', 224 "int", ["sqlite3*", "*", "int", "int", 225 "**", "**"]); 226 227 /* Documented in the api object's initializer. */ 228 capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ 229 if(6!==arguments.length){ 230 return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",6); 231 } 232 const [xSql, xSqlLen] = __flexiString(sql, sqlLen); 233 switch(typeof xSql){ 234 case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null); 235 case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail); 236 default: 237 return util.sqlite3_wasm_db_error( 238 pDb, capi.SQLITE_MISUSE, 239 "Invalid SQL argument type for sqlite3_prepare_v2/v3()." 240 ); 241 } 242 }; 243 244 /* Documented in the api object's initializer. */ 245 capi.sqlite3_prepare_v2 = function(pDb, sql, sqlLen, ppStmt, pzTail){ 246 return (5==arguments.length) 247 ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) 248 : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",5); 249 }; 250 }/*sqlite3_prepare_v2/v3()*/; 251 252 if(1){// Extend wasm.pstack, now that the wasm utils are installed 253 /** 254 Allocates n chunks, each sz bytes, as a single memory block and 255 returns the addresses as an array of n element, each holding 256 the address of one chunk. 257 258 Throws a WasmAllocError if allocation fails. 259 260 Example: 261 262 ``` 263 const [p1, p2, p3] = wasm.pstack.allocChunks(3,4); 264 ``` 265 */ 266 wasm.pstack.allocChunks = (n,sz)=>{ 267 const mem = wasm.pstack.alloc(n * sz); 268 const rc = []; 269 let i = 0, offset = 0; 270 for(; i < n; offset = (sz * ++i)){ 271 rc.push(mem + offset); 272 } 273 return rc; 274 }; 275 276 /** 277 A convenience wrapper for allocChunks() which sizes each chunks 278 as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if 279 safePtrSize is truthy). 280 281 How it returns its result differs depending on its first 282 argument: if it's 1, it returns a single pointer value. If it's 283 more than 1, it returns the same as allocChunks(). 284 285 When one of the pointers refers to a 64-bit value, e.g. a 286 double or int64, and that value must be written or fetch, 287 e.g. using wasm.setMemValue() or wasm.getMemValue(), it is 288 important that the pointer in question be aligned to an 8-byte 289 boundary or else it will not be fetched or written properly and 290 will corrupt or read neighboring memory. 291 292 However, when all pointers involved are "small", it is safe to 293 pass a falsy value to save to memory. 294 */ 295 wasm.pstack.allocPtr = (n=1,safePtrSize=true) =>{ 296 return 1===n 297 ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof) 298 : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof); 299 }; 300 }/*wasm.pstack filler*/ 301 302 /** 303 Install JS<->C struct bindings for the non-opaque struct types we 304 need... */ 305 sqlite3.StructBinder = self.Jaccwabyt({ 306 heap: 0 ? wasm.memory : wasm.heap8u, 307 alloc: wasm.alloc, 308 dealloc: wasm.dealloc, 309 functionTable: wasm.functionTable, 310 bigIntEnabled: wasm.bigIntEnabled, 311 memberPrefix: '$' 312 }); 313 delete self.Jaccwabyt; 314 315 {/* Import C-level constants and structs... */ 316 const cJson = wasm.xCall('sqlite3_wasm_enum_json'); 317 if(!cJson){ 318 toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", 319 "static buffer size!"); 320 } 321 wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); 322 //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); 323 for(const t of ['access', 'blobFinalizers', 'dataTypes', 324 'encodings', 'fcntl', 'flock', 'ioCap', 325 'openFlags', 'prepareFlags', 'resultCodes', 326 'serialize', 'syncFlags', 'udfFlags', 327 'version' 328 ]){ 329 for(const e of Object.entries(wasm.ctype[t])){ 330 // ^^^ [k,v] there triggers a buggy code transormation via one 331 // of the Emscripten-driven optimizers. 332 capi[e[0]] = e[1]; 333 } 334 } 335 const __rcMap = Object.create(null); 336 for(const t of ['resultCodes']){ 337 for(const e of Object.entries(wasm.ctype[t])){ 338 __rcMap[e[1]] = e[0]; 339 } 340 } 341 /** 342 For the given integer, returns the SQLITE_xxx result code as a 343 string, or undefined if no such mapping is found. 344 */ 345 capi.sqlite3_web_rc_str = (rc)=>__rcMap[rc]; 346 /* Bind all registered C-side structs... */ 347 for(const s of wasm.ctype.structs){ 348 capi[s.name] = sqlite3.StructBinder(s); 349 } 350 }/*end C constant imports*/ 351 352 sqlite3.version = Object.assign(Object.create(null),{ 353 library: sqlite3.capi.sqlite3_libversion(), 354 sourceId: sqlite3.capi.sqlite3_sourceid() 355 }); 356}); 357 358