1/* 2 2022-05-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 is intended to be combined at build-time with other 14 related code, most notably a header and footer which wraps this whole 15 file into an Emscripten Module.postRun() handler which has a parameter 16 named "Module" (the Emscripten Module object). The exact requirements, 17 conventions, and build process are very much under construction and 18 will be (re)documented once they've stopped fluctuating so much. 19 20 Specific goals of this project: 21 22 - Except where noted in the non-goals, provide a more-or-less 23 feature-complete wrapper to the sqlite3 C API, insofar as WASM 24 feature parity with C allows for. In fact, provide at least 3 25 APIs... 26 27 1) Bind a low-level sqlite3 API which is as close to the native 28 one as feasible in terms of usage. 29 30 2) A higher-level API, more akin to sql.js and node.js-style 31 implementations. This one speaks directly to the low-level 32 API. This API must be used from the same thread as the 33 low-level API. 34 35 3) A second higher-level API which speaks to the previous APIs via 36 worker messages. This one is intended for use in the main 37 thread, with the lower-level APIs installed in a Worker thread, 38 and talking to them via Worker messages. Because Workers are 39 asynchronouns and have only a single message channel, some 40 acrobatics are needed here to feed async work results back to 41 the client (as we cannot simply pass around callbacks between 42 the main and Worker threads). 43 44 - Insofar as possible, support client-side storage using JS 45 filesystem APIs. As of this writing, such things are still very 46 much TODO. Initial testing with using IndexedDB as backing storage 47 showed it to work reasonably well, but it's also too easy to 48 corrupt by using a web page in two browser tabs because IndexedDB 49 lacks the locking features needed to support that. 50 51 Specific non-goals of this project: 52 53 - As WASM is a web-centric technology and UTF-8 is the King of 54 Encodings in that realm, there are no currently plans to support 55 the UTF16-related sqlite3 APIs. They would add a complication to 56 the bindings for no appreciable benefit. Though web-related 57 implementation details take priority, the lower-level WASM module 58 "should" work in non-web WASM environments. 59 60 - Supporting old or niche-market platforms. WASM is built for a 61 modern web and requires modern platforms. 62 63 - Though scalar User-Defined Functions (UDFs) may be created in 64 JavaScript, there are currently no plans to add support for 65 aggregate and window functions. 66 67 Attribution: 68 69 This project is endebted to the work of sql.js: 70 71 https://github.com/sql-js/sql.js 72 73 sql.js was an essential stepping stone in this code's development as 74 it demonstrated how to handle some of the WASM-related voodoo (like 75 handling pointers-to-pointers and adding JS implementations of 76 C-bound callback functions). These APIs have a considerably 77 different shape than sql.js's, however. 78*/ 79 80/** 81 This global symbol is is only a temporary measure: the JS-side 82 post-processing will remove that object from the global scope when 83 setup is complete. We require it there temporarily in order to glue 84 disparate parts together during the loading of the API (which spans 85 several components). 86 87 This function requires a configuration object intended to abstract 88 away details specific to any given WASM environment, primarily so 89 that it can be used without any _direct_ dependency on Emscripten. 90 (That said, OO API #1 requires, as of this writing, Emscripten's 91 virtual filesystem API. Baby steps.) 92*/ 93self.sqlite3ApiBootstrap = function(config){ 94 'use strict'; 95 96 /** Throws a new Error, the message of which is the concatenation 97 all args with a space between each. */ 98 const toss = (...args)=>{throw new Error(args.join(' '))}; 99 100 /** 101 Returns true if n is a 32-bit (signed) integer, else 102 false. This is used for determining when we need to switch to 103 double-type DB operations for integer values in order to keep 104 more precision. 105 */ 106 const isInt32 = function(n){ 107 return ('bigint'!==typeof n /*TypeError: can't convert BigInt to number*/) 108 && !!(n===(n|0) && n<=2147483647 && n>=-2147483648); 109 }; 110 111 /** Returns v if v appears to be a TypedArray, else false. */ 112 const isTypedArray = (v)=>{ 113 return (v && v.constructor && isInt32(v.constructor.BYTES_PER_ELEMENT)) ? v : false; 114 }; 115 116 /** 117 Returns true if v appears to be one of our bind()-able 118 TypedArray types: Uint8Array or Int8Array. Support for 119 TypedArrays with element sizes >1 is TODO. 120 */ 121 const isBindableTypedArray = (v)=>{ 122 return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT); 123 }; 124 125 /** 126 Returns true if v appears to be one of the TypedArray types 127 which is legal for holding SQL code (as opposed to binary blobs). 128 129 Currently this is the same as isBindableTypedArray() but it 130 seems likely that we'll eventually want to add Uint32Array 131 and friends to the isBindableTypedArray() list but not to the 132 isSQLableTypedArray() list. 133 */ 134 const isSQLableTypedArray = (v)=>{ 135 return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT); 136 }; 137 138 /** Returns true if isBindableTypedArray(v) does, else throws with a message 139 that v is not a supported TypedArray value. */ 140 const affirmBindableTypedArray = (v)=>{ 141 return isBindableTypedArray(v) 142 || toss("Value is not of a supported TypedArray type."); 143 }; 144 145 const utf8Decoder = new TextDecoder('utf-8'); 146 const typedArrayToString = (str)=>utf8Decoder.decode(str); 147 148 /** 149 An Error subclass specifically for reporting Wasm-level malloc() 150 failure and enabling clients to unambiguously identify such 151 exceptions. 152 */ 153 class WasmAllocError extends Error { 154 constructor(...args){ 155 super(...args); 156 this.name = 'WasmAllocError'; 157 } 158 }; 159 160 /** 161 The main sqlite3 binding API gets installed into this object, 162 mimicking the C API as closely as we can. The numerous members 163 names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as 164 possible, identically to the C-native counterparts, as documented at: 165 166 https://www.sqlite.org/c3ref/intro.html 167 168 A very few exceptions require an additional level of proxy 169 function or may otherwise require special attention in the WASM 170 environment, and all such cases are document here. Those not 171 documented here are installed as 1-to-1 proxies for their C-side 172 counterparts. 173 */ 174 const capi = { 175 /** 176 An Error subclass which is thrown by this object's alloc() method 177 on OOM. 178 */ 179 WasmAllocError: WasmAllocError, 180 /** 181 The API's one single point of access to the WASM-side memory 182 allocator. Works like malloc(3) (and is likely bound to 183 malloc()) but throws an WasmAllocError if allocation fails. It is 184 important that any code which might pass through the sqlite3 C 185 API NOT throw and must instead return SQLITE_NOMEM (or 186 equivalent, depending on the context). 187 188 That said, very few cases in the API can result in 189 client-defined functions propagating exceptions via the C-style 190 API. Most notably, this applies ot User-defined SQL Functions 191 (UDFs) registered via sqlite3_create_function_v2(). For that 192 specific case it is recommended that all UDF creation be 193 funneled through a utility function and that a wrapper function 194 be added around the UDF which catches any exception and sets 195 the error state to OOM. (The overall complexity of registering 196 UDFs essentially requires a helper for doing so!) 197 */ 198 alloc: undefined/*installed later*/, 199 /** 200 The API's one single point of access to the WASM-side memory 201 deallocator. Works like free(3) (and is likely bound to 202 free()). 203 */ 204 dealloc: undefined/*installed later*/, 205 /** 206 When using sqlite3_open_v2() it is important to keep the following 207 in mind: 208 209 https://www.sqlite.org/c3ref/open.html 210 211 - The flags for use with its 3rd argument are installed in this 212 object using the C-cide names, e.g. SQLITE_OPEN_CREATE. 213 214 - If the combination of flags passed to it are invalid, 215 behavior is undefined. Thus is is never okay to call this 216 with fewer than 3 arguments, as JS will default the 217 missing arguments to `undefined`, which will result in a 218 flag value of 0. Most of the available SQLITE_OPEN_xxx 219 flags are meaningless in the WASM build, e.g. the mutext- 220 and cache-related flags, but they are retained in this 221 API for consistency's sake. 222 223 - The final argument to this function specifies the VFS to 224 use, which is largely (but not entirely!) meaningless in 225 the WASM environment. It should always be null or 226 undefined, and it is safe to elide that argument when 227 calling this function. 228 */ 229 sqlite3_open_v2: function(filename,dbPtrPtr,flags,vfsStr){}/*installed later*/, 230 /** 231 The sqlite3_prepare_v3() binding handles two different uses 232 with differing JS/WASM semantics: 233 234 1) sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt [, null]) 235 236 2) sqlite3_prepare_v3(pDb, sqlPointer, sqlByteLen, prepFlags, ppStmt, sqlPointerToPointer) 237 238 Note that the SQL length argument (the 3rd argument) must, for 239 usage (1), always be negative because it must be a byte length 240 and that value is expensive to calculate from JS (where only 241 the character length of strings is readily available). It is 242 retained in this API's interface for code/documentation 243 compatibility reasons but is currently _always_ ignored. With 244 usage (2), the 3rd argument is used as-is but is is still 245 critical that the C-style input string (2nd argument) be 246 terminated with a 0 byte. 247 248 In usage (1), the 2nd argument must be of type string, 249 Uint8Array, or Int8Array (either of which is assumed to 250 hold SQL). If it is, this function assumes case (1) and 251 calls the underyling C function with the equivalent of: 252 253 (pDb, sqlAsString, -1, prepFlags, ppStmt, null) 254 255 The pzTail argument is ignored in this case because its result 256 is meaningless when a string-type value is passed through 257 (because the string goes through another level of internal 258 conversion for WASM's sake and the result pointer would refer 259 to that transient conversion's memory, not the passed-in 260 string). 261 262 If the sql argument is not a string, it must be a _pointer_ to 263 a NUL-terminated string which was allocated in the WASM memory 264 (e.g. using cwapi.wasm.alloc() or equivalent). In that case, 265 the final argument may be 0/null/undefined or must be a pointer 266 to which the "tail" of the compiled SQL is written, as 267 documented for the C-side sqlite3_prepare_v3(). In case (2), 268 the underlying C function is called with the equivalent of: 269 270 (pDb, sqlAsPointer, (sqlByteLen||-1), prepFlags, ppStmt, pzTail) 271 272 It returns its result and compiled statement as documented in 273 the C API. Fetching the output pointers (5th and 6th 274 parameters) requires using capi.wasm.getMemValue() (or 275 equivalent) and the pzTail will point to an address relative to 276 the sqlAsPointer value. 277 278 If passed an invalid 2nd argument type, this function will 279 return SQLITE_MISUSE but will unfortunately be able to return 280 any additional error information because we have no way to set 281 the db's error state such that this function could return a 282 non-0 integer and the client could call sqlite3_errcode() or 283 sqlite3_errmsg() to fetch it. See the RFE at: 284 285 https://sqlite.org/forum/forumpost/f9eb79b11aefd4fc81d 286 287 The alternative would be to throw an exception for that case, 288 but that would be in strong constrast to the rest of the 289 C-level API and seems likely to cause more confusion. 290 291 Side-note: in the C API the function does not fail if provided 292 an empty string but its result output pointer will be NULL. 293 */ 294 sqlite3_prepare_v3: function(dbPtr, sql, sqlByteLen, prepFlags, 295 stmtPtrPtr, strPtrPtr){}/*installed later*/, 296 297 /** 298 Equivalent to calling sqlite3_prapare_v3() with 0 as its 4th argument. 299 */ 300 sqlite3_prepare_v2: function(dbPtr, sql, sqlByteLen, stmtPtrPtr, 301 strPtrPtr){}/*installed later*/, 302 303 /** 304 Various internal-use utilities are added here as needed. They 305 are bound to an object only so that we have access to them in 306 the differently-scoped steps of the API bootstrapping 307 process. At the end of the API setup process, this object gets 308 removed. 309 */ 310 util:{ 311 isInt32, isTypedArray, isBindableTypedArray, isSQLableTypedArray, 312 affirmBindableTypedArray, typedArrayToString 313 }, 314 315 /** 316 Holds state which are specific to the WASM-related 317 infrastructure and glue code. It is not expected that client 318 code will normally need these, but they're exposed here in case 319 it does. These APIs are _not_ to be considered an 320 official/stable part of the sqlite3 WASM API. They may change 321 as the developers' experience suggests appropriate changes. 322 323 Note that a number of members of this object are injected 324 dynamically after the api object is fully constructed, so 325 not all are documented inline here. 326 */ 327 wasm: { 328 //^^^ TODO?: move wasm from sqlite3.capi.wasm to sqlite3.wasm 329 /** 330 Emscripten APIs have a deep-seated assumption that all pointers 331 are 32 bits. We'll remain optimistic that that won't always be 332 the case and will use this constant in places where we might 333 otherwise use a hard-coded 4. 334 */ 335 ptrSizeof: config.wasmPtrSizeof || 4, 336 /** 337 The WASM IR (Intermediate Representation) value for 338 pointer-type values. It MUST refer to a value type of the 339 size described by this.ptrSizeof _or_ it may be any value 340 which ends in '*', which Emscripten's glue code internally 341 translates to i32. 342 */ 343 ptrIR: config.wasmPtrIR || "i32", 344 /** 345 True if BigInt support was enabled via (e.g.) the 346 Emscripten -sWASM_BIGINT flag, else false. When 347 enabled, certain 64-bit sqlite3 APIs are enabled which 348 are not otherwise enabled due to JS/WASM int64 349 impedence mismatches. 350 */ 351 bigIntEnabled: !!config.bigIntEnabled, 352 /** 353 The symbols exported by the WASM environment. 354 */ 355 exports: config.exports 356 || toss("Missing API config.exports (WASM module exports)."), 357 358 /** 359 When Emscripten compiles with `-sIMPORT_MEMORY`, it 360 initalizes the heap and imports it into wasm, as opposed to 361 the other way around. In this case, the memory is not 362 available via this.exports.memory. 363 */ 364 memory: config.memory || config.exports['memory'] 365 || toss("API config object requires a WebAssembly.Memory object", 366 "in either config.exports.memory (exported)", 367 "or config.memory (imported)."), 368 /* Many more wasm-related APIs get installed later on. */ 369 }/*wasm*/ 370 }/*capi*/; 371 372 /** 373 capi.wasm.alloc()'s srcTypedArray.byteLength bytes, 374 populates them with the values from the source 375 TypedArray, and returns the pointer to that memory. The 376 returned pointer must eventually be passed to 377 capi.wasm.dealloc() to clean it up. 378 379 As a special case, to avoid further special cases where 380 this is used, if srcTypedArray.byteLength is 0, it 381 allocates a single byte and sets it to the value 382 0. Even in such cases, calls must behave as if the 383 allocated memory has exactly srcTypedArray.byteLength 384 bytes. 385 386 ACHTUNG: this currently only works for Uint8Array and 387 Int8Array types and will throw if srcTypedArray is of 388 any other type. 389 */ 390 capi.wasm.mallocFromTypedArray = function(srcTypedArray){ 391 affirmBindableTypedArray(srcTypedArray); 392 const pRet = this.alloc(srcTypedArray.byteLength || 1); 393 this.heapForSize(srcTypedArray.constructor).set(srcTypedArray.byteLength ? srcTypedArray : [0], pRet); 394 return pRet; 395 }.bind(capi.wasm); 396 397 const keyAlloc = config.allocExportName || 'malloc', 398 keyDealloc = config.deallocExportName || 'free'; 399 for(const key of [keyAlloc, keyDealloc]){ 400 const f = capi.wasm.exports[key]; 401 if(!(f instanceof Function)) toss("Missing required exports[",key,"] function."); 402 } 403 capi.wasm.alloc = function(n){ 404 const m = this.exports[keyAlloc](n); 405 if(!m) throw new WasmAllocError("Failed to allocate "+n+" bytes."); 406 return m; 407 }.bind(capi.wasm) 408 capi.wasm.dealloc = (m)=>capi.wasm.exports[keyDealloc](m); 409 410 /** 411 Reports info about compile-time options using 412 sqlite_compileoption_get() and sqlite3_compileoption_used(). It 413 has several distinct uses: 414 415 If optName is an array then it is expected to be a list of 416 compilation options and this function returns an object 417 which maps each such option to true or false, indicating 418 whether or not the given option was included in this 419 build. That object is returned. 420 421 If optName is an object, its keys are expected to be compilation 422 options and this function sets each entry to true or false, 423 indicating whether the compilation option was used or not. That 424 object is returned. 425 426 If passed no arguments then it returns an object mapping 427 all known compilation options to their compile-time values, 428 or boolean true if they are defined with no value. This 429 result, which is relatively expensive to compute, is cached 430 and returned for future no-argument calls. 431 432 In all other cases it returns true if the given option was 433 active when when compiling the sqlite3 module, else false. 434 435 Compile-time option names may optionally include their 436 "SQLITE_" prefix. When it returns an object of all options, 437 the prefix is elided. 438 */ 439 capi.wasm.compileOptionUsed = function f(optName){ 440 if(!arguments.length){ 441 if(f._result) return f._result; 442 else if(!f._opt){ 443 f._rx = /^([^=]+)=(.+)/; 444 f._rxInt = /^-?\d+$/; 445 f._opt = function(opt, rv){ 446 const m = f._rx.exec(opt); 447 rv[0] = (m ? m[1] : opt); 448 rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true; 449 }; 450 } 451 const rc = {}, ov = [0,0]; 452 let i = 0, k; 453 while((k = capi.sqlite3_compileoption_get(i++))){ 454 f._opt(k,ov); 455 rc[ov[0]] = ov[1]; 456 } 457 return f._result = rc; 458 }else if(Array.isArray(optName)){ 459 const rc = {}; 460 optName.forEach((v)=>{ 461 rc[v] = capi.sqlite3_compileoption_used(v); 462 }); 463 return rc; 464 }else if('object' === typeof optName){ 465 Object.keys(optName).forEach((k)=> { 466 optName[k] = capi.sqlite3_compileoption_used(k); 467 }); 468 return optName; 469 } 470 return ( 471 'string'===typeof optName 472 ) ? !!capi.sqlite3_compileoption_used(optName) : false; 473 }/*compileOptionUsed()*/; 474 475 capi.wasm.bindingSignatures = [ 476 /** 477 Signatures for the WASM-exported C-side functions. Each entry 478 is an array with 2+ elements: 479 480 ["c-side name", 481 "result type" (capi.wasm.xWrap() syntax), 482 [arg types in xWrap() syntax] 483 // ^^^ this needn't strictly be an array: it can be subsequent 484 // elements instead: [x,y,z] is equivalent to x,y,z 485 ] 486 */ 487 // Please keep these sorted by function name! 488 ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"], 489 ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"], 490 ["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"], 491 ["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"], 492 ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"], 493 ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"], 494 ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int"], 495 ["sqlite3_close_v2", "int", "sqlite3*"], 496 ["sqlite3_changes", "int", "sqlite3*"], 497 ["sqlite3_clear_bindings","int", "sqlite3_stmt*"], 498 ["sqlite3_column_blob","*", "sqlite3_stmt*", "int"], 499 ["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"], 500 ["sqlite3_column_count", "int", "sqlite3_stmt*"], 501 ["sqlite3_column_double","f64", "sqlite3_stmt*", "int"], 502 ["sqlite3_column_int","int", "sqlite3_stmt*", "int"], 503 ["sqlite3_column_name","string", "sqlite3_stmt*", "int"], 504 ["sqlite3_column_text","string", "sqlite3_stmt*", "int"], 505 ["sqlite3_column_type","int", "sqlite3_stmt*", "int"], 506 ["sqlite3_compileoption_get", "string", "int"], 507 ["sqlite3_compileoption_used", "int", "string"], 508 ["sqlite3_create_function_v2", "int", 509 "sqlite3*", "string", "int", "int", "*", "*", "*", "*", "*"], 510 ["sqlite3_data_count", "int", "sqlite3_stmt*"], 511 ["sqlite3_db_filename", "string", "sqlite3*", "string"], 512 ["sqlite3_db_name", "string", "sqlite3*", "int"], 513 ["sqlite3_errmsg", "string", "sqlite3*"], 514 ["sqlite3_error_offset", "int", "sqlite3*"], 515 ["sqlite3_errstr", "string", "int"], 516 //["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**"], 517 // ^^^ TODO: we need a wrapper to support passing a function pointer or a function 518 // for the callback. 519 ["sqlite3_expanded_sql", "string", "sqlite3_stmt*"], 520 ["sqlite3_extended_errcode", "int", "sqlite3*"], 521 ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"], 522 ["sqlite3_finalize", "int", "sqlite3_stmt*"], 523 ["sqlite3_initialize", undefined], 524 ["sqlite3_interrupt", undefined, "sqlite3*" 525 /* ^^^ we cannot actually currently support this because JS is 526 single-threaded and we don't have a portable way to access a DB 527 from 2 SharedWorkers concurrently. */], 528 ["sqlite3_libversion", "string"], 529 ["sqlite3_libversion_number", "int"], 530 ["sqlite3_open", "int", "string", "*"], 531 ["sqlite3_open_v2", "int", "string", "*", "int", "string"], 532 /* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled 533 separately due to us requiring two different sets of semantics 534 for those, depending on how their SQL argument is provided. */ 535 ["sqlite3_reset", "int", "sqlite3_stmt*"], 536 ["sqlite3_result_blob",undefined, "*", "*", "int", "*"], 537 ["sqlite3_result_double",undefined, "*", "f64"], 538 ["sqlite3_result_error",undefined, "*", "string", "int"], 539 ["sqlite3_result_error_code", undefined, "*", "int"], 540 ["sqlite3_result_error_nomem", undefined, "*"], 541 ["sqlite3_result_error_toobig", undefined, "*"], 542 ["sqlite3_result_int",undefined, "*", "int"], 543 ["sqlite3_result_null",undefined, "*"], 544 ["sqlite3_result_text",undefined, "*", "string", "int", "*"], 545 ["sqlite3_sourceid", "string"], 546 ["sqlite3_sql", "string", "sqlite3_stmt*"], 547 ["sqlite3_step", "int", "sqlite3_stmt*"], 548 ["sqlite3_strglob", "int", "string","string"], 549 ["sqlite3_strlike", "int", "string","string","int"], 550 ["sqlite3_total_changes", "int", "sqlite3*"], 551 ["sqlite3_value_blob", "*", "*"], 552 ["sqlite3_value_bytes","int", "*"], 553 ["sqlite3_value_double","f64", "*"], 554 ["sqlite3_value_text", "string", "*"], 555 ["sqlite3_value_type", "int", "*"], 556 ["sqlite3_vfs_find", "*", "string"], 557 ["sqlite3_vfs_register", "int", "*", "int"] 558 ]/*capi.wasm.bindingSignatures*/; 559 560 if(false && capi.wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ 561 /* ^^^ "the problem" is that this is an option feature and the 562 build-time function-export list does not currently take 563 optional features into account. */ 564 capi.wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); 565 } 566 567 /** 568 Functions which require BigInt (int64) support are separated from 569 the others because we need to conditionally bind them or apply 570 dummy impls, depending on the capabilities of the environment. 571 */ 572 capi.wasm.bindingSignatures.int64 = [ 573 ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]], 574 ["sqlite3_changes64","i64", ["sqlite3*"]], 575 ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]], 576 ["sqlite3_total_changes64", "i64", ["sqlite3*"]] 577 ]; 578 579 /* The remainder of the API will be set up in later steps. */ 580 return { 581 capi, 582 postInit: [ 583 /* some pieces of the API may install functions into this array, 584 and each such function will be called, passed (self,sqlite3), 585 at the very end of the API load/init process, where self is 586 the current global object and sqlite3 is the object returned 587 from sqlite3ApiBootstrap(). This array will be removed at the 588 end of the API setup process. */], 589 /** Config is needed downstream for gluing pieces together. It 590 will be removed at the end of the API setup process. */ 591 config 592 }; 593}/*sqlite3ApiBootstrap()*/; 594