diff options
Diffstat (limited to 'ext/wasm/api/sqlite3-api-prologue.js')
-rw-r--r-- | ext/wasm/api/sqlite3-api-prologue.js | 551 |
1 files changed, 301 insertions, 250 deletions
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index e28f459c2..faa2dbbbf 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -79,13 +79,9 @@ the `realloc(3)`-compatible routine for the WASM environment. Defaults to `"sqlite3_realloc"`. - - `wasmfsOpfsDir`[^1]: if the environment supports persistent - storage using OPFS-over-WASMFS , this directory names the "mount - point" for that directory. It must be prefixed by `/` and may - contain only a single directory-name part. Using the root - directory name is not supported by any current persistent - backend. This setting is only used in WASMFS-enabled builds. - + - `wasmfsOpfsDir`[^1]: As of 2022-12-17, this feature does not + currently work due to incompatible Emscripten-side changes made + in the WASMFS+OPFS combination. This option is currently ignored. [^1] = This property may optionally be a function, in which case this function re-assigns it to the value returned from that function, @@ -138,6 +134,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( config[k] = config[k](); } }); + config.wasmOpfsDir = + /* 2022-12-17: WASMFS+OPFS can no longer be activated from the + main thread (aborts via a failed assert() if it's attempted), + which eliminates any(?) benefit to supporting it. */ false; /** The main sqlite3 binding API gets installed into this object, @@ -321,14 +321,17 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; /** - Returns true if v appears to be one of our bind()-able TypedArray - types: Uint8Array or Int8Array. Support for TypedArrays with - element sizes >1 is a potential TODO just waiting on a use case - to justify them. + Returns v if v appears to be one of our bind()-able TypedArray + types: Uint8Array or Int8Array or ArrayBuffer. Support for + TypedArrays with element sizes >1 is a potential TODO just + waiting on a use case to justify them. Until then, their `buffer` + property can be used to pass them as an ArrayBuffer. If it's not + a bindable array type, a falsy value is returned. */ const isBindableTypedArray = (v)=>{ - return v && (v instanceof Uint8Array || v instanceof Int8Array); - //v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT); + return v && (v instanceof Uint8Array + || v instanceof Int8Array + || v instanceof ArrayBuffer); }; /** @@ -341,8 +344,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( isSQLableTypedArray() list. */ const isSQLableTypedArray = (v)=>{ - return v && (v instanceof Uint8Array || v instanceof Int8Array); - //v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT); + return v && (v instanceof Uint8Array + || v instanceof Int8Array + || v instanceof ArrayBuffer); }; /** Returns true if isBindableTypedArray(v) does, else throws with a message @@ -418,6 +422,92 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( Object.assign(capi, { /** + sqlite3_bind_blob() works exactly like its C counterpart unless + its 3rd argument is one of: + + - JS string: the 3rd argument is converted to a C string, the + 4th argument is ignored, and the C-string's length is used + in its place. + + - Array: converted to a string as defined for "flexible + strings" and then it's treated as a JS string. + + - Int8Array or Uint8Array: wasm.allocFromTypedArray() is used to + conver the memory to the WASM heap. If the 4th argument is + 0 or greater, it is used as-is, otherwise the array's byteLength + value is used. This is an exception to the C API's undefined + behavior for a negative 4th argument, but results are undefined + if the given 4th argument value is greater than the byteLength + of the input array. + + - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and + treated as that type. + + In all of those cases, the final argument (destructor) is + ignored and capi.SQLITE_WASM_DEALLOC is assumed. + + A 3rd argument of `null` is treated as if it were a WASM pointer + of 0. + + If the 3rd argument is neither a WASM pointer nor one of the + above-described types, capi.SQLITE_MISUSE is returned. + + The first argument may be either an `sqlite3_stmt*` WASM + pointer or an sqlite3.oo1.Stmt instance. + + For consistency with the C API, it requires the same number of + arguments. It returns capi.SQLITE_MISUSE if passed any other + argument count. + */ + sqlite3_bind_blob: undefined/*installed later*/, + + /** + sqlite3_bind_text() works exactly like its C counterpart unless + its 3rd argument is one of: + + - JS string: the 3rd argument is converted to a C string, the + 4th argument is ignored, and the C-string's length is used + in its place. + + - Array: converted to a string as defined for "flexible + strings". The 4th argument is ignored and a value of -1 + is assumed. + + - Int8Array or Uint8Array: is assumed to contain UTF-8 text, is + converted to a string. The 4th argument is ignored, replaced + by the array's byteLength value. + + - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and + treated as that type. + + In each of those cases, the final argument (text destructor) is + ignored and capi.SQLITE_WASM_DEALLOC is assumed. + + A 3rd argument of `null` is treated as if it were a WASM pointer + of 0. + + If the 3rd argument is neither a WASM pointer nor one of the + above-described types, capi.SQLITE_MISUSE is returned. + + The first argument may be either an `sqlite3_stmt*` WASM + pointer or an sqlite3.oo1.Stmt instance. + + For consistency with the C API, it requires the same number of + arguments. It returns capi.SQLITE_MISUSE if passed any other + argument count. + + If client code needs to bind partial strings, it needs to + either parcel the string up before passing it in here or it + must pass in a WASM pointer for the 3rd argument and a valid + 4th-argument value, taking care not to pass a value which + truncates a multi-byte UTF-8 character. When passing + WASM-format strings, it is important that the final argument be + valid or unexpected content can result can result, or even a + crash if the application reads past the WASM heap bounds. + */ + sqlite3_bind_text: undefined/*installed later*/, + + /** sqlite3_create_function_v2() differs from its native counterpart only in the following ways: @@ -425,7 +515,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( any encoding other than sqlite3.SQLITE_UTF8. The JS API does not currently support any other encoding and likely never will. This function does not replace that argument on its own - because it may contain other flags. + because it may contain other flags. As a special case, if + the bottom 4 bits of that argument are 0, SQLITE_UTF8 is + assumed. 2) Any of the four final arguments may be either WASM pointers (assumed to be function pointers) or JS Functions. In the @@ -433,6 +525,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( sqlite3.capi.wasm.installFunction() and that wrapper is passed on to the native implementation. + For consistency with the C API, it requires the same number of + arguments. It returns capi.SQLITE_MISUSE if passed any other + argument count. + The semantics of JS functions are: xFunc: is passed `(pCtx, ...values)`. Its return value becomes @@ -519,18 +615,18 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH` flag. */ - sqlite3_create_function_v2: function( + sqlite3_create_function_v2: ( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal, xDestroy - ){/*installed later*/}, + )=>{/*installed later*/}, /** Equivalent to passing the same arguments to sqlite3_create_function_v2(), with 0 as the final argument. */ - sqlite3_create_function:function( + sqlite3_create_function: ( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal - ){/*installed later*/}, + )=>{/*installed later*/}, /** The sqlite3_create_window_function() JS wrapper differs from its native implementation in the exact same way that @@ -538,10 +634,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( xInverse(), is treated identically to xStep() by the wrapping layer. */ - sqlite3_create_window_function: function( + sqlite3_create_window_function: ( pDb, funcName, nArg, eTextRep, pApp, xStep, xFinal, xValue, xInverse, xDestroy - ){/*installed later*/}, + )=>{/*installed later*/}, /** The sqlite3_prepare_v3() binding handles two different uses with differing JS/WASM semantics: @@ -663,7 +759,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( toss3, typedArrayPart }; - + Object.assign(wasm, { /** Emscripten APIs have a deep-seated assumption that all pointers @@ -879,231 +975,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }/*compileOptionUsed()*/; /** - Signatures for the WASM-exported C-side functions. Each entry - is an array with 2+ elements: - - [ "c-side name", - "result type" (wasm.xWrap() syntax), - [arg types in xWrap() syntax] - // ^^^ this needn't strictly be an array: it can be subsequent - // elements instead: [x,y,z] is equivalent to x,y,z - ] - - Note that support for the API-specific data types in the - result/argument type strings gets plugged in at a later phase in - the API initialization process. - */ - wasm.bindingSignatures = [ - // Please keep these sorted by function name! - ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"], - ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*" - /* TODO: we should arguably write a custom wrapper which knows - how to handle Blob, TypedArrays, and JS strings. */ - ], - ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"], - ["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"], - ["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"], - ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"], - ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"], - ["sqlite3_bind_pointer", "int", - "sqlite3_stmt*", "int", "*", "string:static", "*"], - ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int" - /* We should arguably create a hand-written binding of - bind_text() which does more flexible text conversion, along - the lines of sqlite3_prepare_v3(). The slightly problematic - part is the final argument (text destructor). */ - ], - //["sqlite3_busy_handler","int", "sqlite3*", "*", "*"], - // ^^^^ TODO: custom binding which auto-converts JS function arg - // to a WASM function, noting that calling it multiple times - // would introduce a leak. - ["sqlite3_busy_timeout","int", "sqlite3*", "int"], - ["sqlite3_close_v2", "int", "sqlite3*"], - ["sqlite3_changes", "int", "sqlite3*"], - ["sqlite3_clear_bindings","int", "sqlite3_stmt*"], - ["sqlite3_collation_needed", "int", "sqlite3*", "*", "*"/*=>v(ppis)*/], - ["sqlite3_column_blob","*", "sqlite3_stmt*", "int"], - ["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"], - ["sqlite3_column_count", "int", "sqlite3_stmt*"], - ["sqlite3_column_double","f64", "sqlite3_stmt*", "int"], - ["sqlite3_column_int","int", "sqlite3_stmt*", "int"], - ["sqlite3_column_name","string", "sqlite3_stmt*", "int"], - ["sqlite3_column_text","string", "sqlite3_stmt*", "int"], - ["sqlite3_column_type","int", "sqlite3_stmt*", "int"], - ["sqlite3_compileoption_get", "string", "int"], - ["sqlite3_compileoption_used", "int", "string"], - ["sqlite3_complete", "int", "string:flexible"], - /* sqlite3_create_function(), sqlite3_create_function_v2(), and - sqlite3_create_window_function() use hand-written bindings to - simplify handling of their function-type arguments. */ - /* sqlite3_create_collation() and sqlite3_create_collation_v2() - use hand-written bindings to simplify passing of the callback - function. - ["sqlite3_create_collation", "int", - "sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value - "*", "*"], - ["sqlite3_create_collation_v2", "int", - "sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value - "*", "*", "*"], - */ - ["sqlite3_data_count", "int", "sqlite3_stmt*"], - ["sqlite3_db_filename", "string", "sqlite3*", "string"], - ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"], - ["sqlite3_db_name", "string", "sqlite3*", "int"], - ["sqlite3_db_status", "int", "sqlite3*", "int", "*", "*", "int"], - ["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"] - /* Careful! Short version: de/serialize() are problematic because they - might use a different allocator than the user for managing the - deserialized block. de/serialize() are ONLY safe to use with - sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */, - ["sqlite3_errmsg", "string", "sqlite3*"], - ["sqlite3_error_offset", "int", "sqlite3*"], - ["sqlite3_errstr", "string", "int"], - /*["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**" - Handled seperately to perform translation of the callback - into a WASM-usable one. ],*/ - ["sqlite3_expanded_sql", "string", "sqlite3_stmt*"], - ["sqlite3_extended_errcode", "int", "sqlite3*"], - ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"], - ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], - ["sqlite3_finalize", "int", "sqlite3_stmt*"], - ["sqlite3_free", undefined,"*"], - ["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"], - ["sqlite3_initialize", undefined], - /*["sqlite3_interrupt", undefined, "sqlite3*" - ^^^ we cannot actually currently support this because JS is - single-threaded and we don't have a portable way to access a DB - from 2 SharedWorkers concurrently. ],*/ - ["sqlite3_libversion", "string"], - ["sqlite3_libversion_number", "int"], - ["sqlite3_malloc", "*","int"], - ["sqlite3_open", "int", "string", "*"], - ["sqlite3_open_v2", "int", "string", "*", "int", "string"], - /* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled - separately due to us requiring two different sets of semantics - for those, depending on how their SQL argument is provided. */ - /* sqlite3_randomness() uses a hand-written wrapper to extend - the range of supported argument types. */ - ["sqlite3_realloc", "*","*","int"], - ["sqlite3_reset", "int", "sqlite3_stmt*"], - ["sqlite3_result_blob", undefined, "sqlite3_context*", "*", "int", "*"], - ["sqlite3_result_double", undefined, "sqlite3_context*", "f64"], - ["sqlite3_result_error", undefined, "sqlite3_context*", "string", "int"], - ["sqlite3_result_error_code", undefined, "sqlite3_context*", "int"], - ["sqlite3_result_error_nomem", undefined, "sqlite3_context*"], - ["sqlite3_result_error_toobig", undefined, "sqlite3_context*"], - ["sqlite3_result_int", undefined, "sqlite3_context*", "int"], - ["sqlite3_result_null", undefined, "sqlite3_context*"], - ["sqlite3_result_pointer", undefined, - "sqlite3_context*", "*", "string:static", "*"], - ["sqlite3_result_subtype", undefined, "sqlite3_value*", "int"], - ["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"], - ["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"], - ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"], - ["sqlite3_set_auxdata", undefined, "sqlite3_context*", "int", "*", "*"/* => v(*) */], - ["sqlite3_shutdown", undefined], - ["sqlite3_sourceid", "string"], - ["sqlite3_sql", "string", "sqlite3_stmt*"], - ["sqlite3_status", "int", "int", "*", "*", "int"], - ["sqlite3_step", "int", "sqlite3_stmt*"], - ["sqlite3_stmt_status", "int", "sqlite3_stmt*", "int", "int"], - ["sqlite3_strglob", "int", "string","string"], - ["sqlite3_stricmp", "int", "string", "string"], - ["sqlite3_strlike", "int", "string", "string","int"], - ["sqlite3_strnicmp", "int", "string", "string", "int"], - ["sqlite3_table_column_metadata", "int", - "sqlite3*", "string", "string", "string", - "**", "**", "*", "*", "*"], - ["sqlite3_total_changes", "int", "sqlite3*"], - ["sqlite3_trace_v2", "int", "sqlite3*", "int", "*", "*"], - /* Note that sqlite3_uri_...() have very specific requirements for - their first C-string arguments, so we cannot perform any value - conversion on those. */ - ["sqlite3_uri_boolean", "int", "sqlite3_filename", "string", "int"], - ["sqlite3_uri_key", "string", "sqlite3_filename", "int"], - ["sqlite3_uri_parameter", "string", "sqlite3_filename", "string"], - ["sqlite3_user_data","void*", "sqlite3_context*"], - ["sqlite3_value_blob", "*", "sqlite3_value*"], - ["sqlite3_value_bytes","int", "sqlite3_value*"], - ["sqlite3_value_double","f64", "sqlite3_value*"], - ["sqlite3_value_dup", "sqlite3_value*", "sqlite3_value*"], - ["sqlite3_value_free", undefined, "sqlite3_value*"], - ["sqlite3_value_frombind", "int", "sqlite3_value*"], - ["sqlite3_value_int","int", "sqlite3_value*"], - ["sqlite3_value_nochange", "int", "sqlite3_value*"], - ["sqlite3_value_numeric_type", "int", "sqlite3_value*"], - ["sqlite3_value_pointer", "*", "sqlite3_value*", "string:static"], - ["sqlite3_value_subtype", "int", "sqlite3_value*"], - ["sqlite3_value_text", "string", "sqlite3_value*"], - ["sqlite3_value_type", "int", "sqlite3_value*"], - ["sqlite3_vfs_find", "*", "string"], - ["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"], - ["sqlite3_vfs_unregister", "int", "sqlite3_vfs*"] - ]/*wasm.bindingSignatures*/; - - if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ - /* ^^^ "the problem" is that this is an option feature and the - build-time function-export list does not currently take - optional features into account. */ - wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); - } - - /** - Functions which require BigInt (int64) support are separated from - the others because we need to conditionally bind them or apply - dummy impls, depending on the capabilities of the environment. - - Note that not all of these functions directly require int64 - but are only for use with APIs which require int64. For example, - the vtab-related functions. - */ - wasm.bindingSignatures.int64 = [ - ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]], - ["sqlite3_changes64","i64", ["sqlite3*"]], - ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]], - ["sqlite3_create_module", "int", - ["sqlite3*","string","sqlite3_module*","*"]], - ["sqlite3_create_module_v2", "int", - ["sqlite3*","string","sqlite3_module*","*","*"]], - ["sqlite3_declare_vtab", "int", ["sqlite3*", "string:flexible"]], - ["sqlite3_drop_modules", "int", ["sqlite3*", "**"]], - ["sqlite3_malloc64", "*","i64"], - ["sqlite3_msize", "i64", "*"], - ["sqlite3_overload_function", "int", ["sqlite3*","string","int"]], - ["sqlite3_realloc64", "*","*", "i64"], - ["sqlite3_result_int64", undefined, "*", "i64"], - ["sqlite3_result_zeroblob64", "int", "*", "i64"], - ["sqlite3_status64", "int", "int", "*", "*", "int"], - ["sqlite3_total_changes64", "i64", ["sqlite3*"]], - ["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]], - ["sqlite3_value_int64","i64", "sqlite3_value*"], - ["sqlite3_vtab_collation","string","sqlite3_index_info*","int"], - ["sqlite3_vtab_distinct","int", "sqlite3_index_info*"], - ["sqlite3_vtab_in","int", "sqlite3_index_info*", "int", "int"], - ["sqlite3_vtab_in_first", "int", "sqlite3_value*", "**"], - ["sqlite3_vtab_in_next", "int", "sqlite3_value*", "**"], - /*["sqlite3_vtab_config" is variadic and requires a hand-written - proxy.] */ - ["sqlite3_vtab_nochange","int", "sqlite3_context*"], - ["sqlite3_vtab_on_conflict","int", "sqlite3*"], - ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"] - ]; - - /** - Functions which are intended solely for API-internal use by the - WASM components, not client code. These get installed into - sqlite3.wasm. Some of them get exposed to clients via variants - named sqlite3_js_...(). - */ - wasm.bindingSignatures.wasm = [ - ["sqlite3_wasm_db_reset", "int", "sqlite3*"], - ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], - ["sqlite3_wasm_vfs_create_file", "int", - "sqlite3_vfs*","string","*", "int"], - ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] - ]; - - /** sqlite3.wasm.pstack (pseudo-stack) holds a special-case stack-style allocator intended only for use with _small_ data of not more than (in total) a few kb in size, managed as if it were @@ -1275,6 +1146,13 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** State for sqlite3_wasmfs_opfs_dir(). */ let __wasmfsOpfsDir = undefined; /** + 2022-12-17: incompatible WASMFS changes have made WASMFS+OPFS + unavailable from the main thread, which eliminates the most + significant benefit of supporting WASMFS. This function is now a + no-op which always returns a falsy value. Before that change, + this function behaved as documented below (and how it will again + if we can find a compelling reason to support it). + If the wasm environment has a WASMFS/OPFS-backed persistent storage directory, its path is returned by this function. If it does not then it returns "" (noting that "" is a falsy value). @@ -1291,6 +1169,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir; // If we have no OPFS, there is no persistent dir const pdir = config.wasmfsOpfsDir; + console.error("sqlite3_wasmfs_opfs_dir() can no longer work due "+ + "to incompatible WASMFS changes. It will be removed."); if(!pdir || !self.FileSystemHandle || !self.FileSystemDirectoryHandle @@ -1672,9 +1552,11 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( By default it throws if it cannot determine any sensible conversion. If passed a falsy second argument, it instead returns - `undefined` if no suitable conversion is found. Note that there + `undefined` if no suitable conversion is found. Note that there is no conversion from SQL to JS which results in the `undefined` - value, so `undefined` has an unambiguous meaning here. + value, so `undefined` has an unambiguous meaning here. It will + always throw a WasmAllocError if allocating memory for a + conversion fails. Caveats: @@ -1723,6 +1605,162 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return arg; }; + /** + Requires a C-style array of `sqlite3_value*` objects and the + number of entries in that array. Returns a JS array containing + the results of passing each C array entry to + sqlite3_value_to_js(). The 3rd argument to this function is + passed on as the 2nd argument to that one. + */ + capi.sqlite3_values_to_js = function(argc,pArgv,throwIfCannotConvert=true){ + let i; + const tgt = []; + for(i = 0; i < argc; ++i){ + /** + Curiously: despite ostensibly requiring 8-byte + alignment, the pArgv array is parcelled into chunks of + 4 bytes (1 pointer each). The values those point to + have 8-byte alignment but the individual argv entries + do not. + */ + tgt.push(capi.sqlite3_value_to_js( + wasm.peekPtr(pArgv + (wasm.ptrSizeof * i)) + )); + } + return tgt; + }; + + /** + Calls either sqlite3_result_error_nomem(), if e is-a + WasmAllocError, or sqlite3_result_error(). In the latter case, + the second arugment is coerced to a string to create the error + message. + + The first argument is a (sqlite3_context*). Returns void. + Does not throw. + */ + capi.sqlite3_result_error_js = function(pCtx,e){ + if(e instanceof WasmAllocError){ + capi.sqlite3_result_error_nomem(pCtx); + }else{ + /* Maintenance reminder: ''+e, rather than e.message, + will prefix e.message with e.name, so it includes + the exception's type name in the result. */; + capi.sqlite3_result_error(pCtx, ''+e, -1); + } + }; + + /** + This function passes its 2nd argument to one of the + sqlite3_result_xyz() routines, depending on the type of that + argument: + + - If (val instanceof Error), this function passes it to + sqlite3_result_error_js(). + - `null`: `sqlite3_result_null()` + - `boolean`: `sqlite3_result_int()` with a value of 0 or 1. + - `number`: `sqlite3_result_int()`, `sqlite3_result_int64()`, or + `sqlite3_result_double()`, depending on the range of the number + and whether or not int64 support is enabled. + - `bigint`: similar to `number` but will trigger an error if the + value is too big to store in an int64. + - `string`: `sqlite3_result_text()` + - Uint8Array or Int8Array or ArrayBuffer: `sqlite3_result_blob()` + - `undefined`: is a no-op provided to simplify certain use cases. + + Anything else triggers `sqlite3_result_error()` with a + description of the problem. + + The first argument to this function is a `(sqlite3_context*)`. + Returns void. Does not throw. + */ + capi.sqlite3_result_js = function(pCtx,val){ + if(val instanceof Error){ + capi.sqlite3_result_error_js(pCtx, val); + return; + } + try{ + switch(typeof val) { + case 'undefined': + /* This is a no-op. This routine originated in the create_function() + family of APIs and in that context, passing in undefined indicated + that the caller was responsible for calling sqlite3_result_xxx() + (if needed). */ + break; + case 'boolean': + capi.sqlite3_result_int(pCtx, val ? 1 : 0); + break; + case 'bigint': + if(util.bigIntFits32(val)){ + capi.sqlite3_result_int(pCtx, Number(val)); + }else if(util.bigIntFitsDouble(val)){ + capi.sqlite3_result_double(pCtx, Number(val)); + }else if(wasm.bigIntEnabled){ + if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val); + else toss3("BigInt value",val.toString(),"is too BigInt for int64."); + }else{ + toss3("BigInt value",val.toString(),"is too BigInt."); + } + break; + case 'number': { + let f; + if(util.isInt32(val)){ + f = capi.sqlite3_result_int; + }else if(wasm.bigIntEnabled + && Number.isInteger(val) + && util.bigIntFits64(BigInt(val))){ + f = capi.sqlite3_result_int64; + }else{ + f = capi.sqlite3_result_double; + } + f(pCtx, val); + break; + } + case 'string': { + const [p, n] = wasm.allocCString(val,true); + capi.sqlite3_result_text(pCtx, p, n, capi.SQLITE_WASM_DEALLOC); + break; + } + case 'object': + if(null===val/*yes, typeof null === 'object'*/) { + capi.sqlite3_result_null(pCtx); + break; + }else if(util.isBindableTypedArray(val)){ + const pBlob = wasm.allocFromTypedArray(val); + capi.sqlite3_result_blob( + pCtx, pBlob, val.byteLength, + capi.SQLITE_WASM_DEALLOC + ); + break; + } + // else fall through + default: + toss3("Don't not how to handle this UDF result value:",(typeof val), val); + } + }catch(e){ + capi.sqlite3_result_error_js(pCtx, e); + } + }; + + /** + Returns the result sqlite3_column_value(pStmt,iCol) passed to + sqlite3_value_to_js(). The 3rd argument of this function is + ignored by this function except to pass it on as the second + argument of sqlite3_value_to_js(). If the sqlite3_column_value() + returns NULL (e.g. because the column index is out of range), + this function returns `undefined`, regardless of the 3rd + argument. 3rd argument is falsy and conversion fails, `undefined` + will be returned. + + Note that sqlite3_column_value() returns an "unprotected" value + object, but in a single-threaded environment (like this one) + there is no distinction between protected and unprotected values. + */ + capi.sqlite3_column_js = function(pStmt, iCol, throwIfCannotConvert=true){ + const v = capi.sqlite3_column_value(pStmt, iCol); + return (0===v) ? undefined : capi.sqlite3_value_to_js(v, throwIfCannotConvert); + }; + /* The remainder of the API will be set up in later steps. */ const sqlite3 = { WasmAllocError: WasmAllocError, @@ -1742,6 +1780,15 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( build-generated object. */ version: Object.create(null), + + /** + The library reserves the 'client' property for client-side use + and promises to never define a property with this name nor to + ever rely on specific contents of it. It makes no such guarantees + for other properties. + */ + client: undefined, + /** Performs any optional asynchronous library-level initialization which might be required. This function returns a Promise which @@ -1784,6 +1831,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( some initializers. Retain them when running in test mode so that we can add tests for them. */ delete sqlite3.util; + /* It's conceivable that we might want to expose + StructBinder to client-side code, but it's only useful if + clients build their own sqlite3.wasm which contains their + one C struct types. */ delete sqlite3.StructBinder; } return sqlite3; |