diff options
author | drh <> | 2022-12-13 00:51:34 +0000 |
---|---|---|
committer | drh <> | 2022-12-13 00:51:34 +0000 |
commit | 393021938db7325e9c32428c196716f2a63c8d78 (patch) | |
tree | 602751ed5ed805adb48113557fccdb274b9776fa /ext/wasm/api/sqlite3-api-glue.js | |
parent | 93f41e22d4863404adc2b8f141b0dde9da9627e6 (diff) | |
parent | 7ca4312ff2dc94bed3897275eeb822aa286f96bb (diff) | |
download | sqlite-393021938db7325e9c32428c196716f2a63c8d78.tar.gz sqlite-393021938db7325e9c32428c196716f2a63c8d78.zip |
Merge recent trunk changes into the coroutine-exp2 branch.
FossilOrigin-Name: c43f433bcab29db0f1f8afd3948f5a4149e1f277c853c66f99c31f226d82bc0f
Diffstat (limited to 'ext/wasm/api/sqlite3-api-glue.js')
-rw-r--r-- | ext/wasm/api/sqlite3-api-glue.js | 787 |
1 files changed, 527 insertions, 260 deletions
diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index ac1e9fd6d..91b42cd93 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -25,58 +25,334 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ delete self.WhWasmUtilInstaller; /** + 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_column_value","sqlite3_value*", "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_errcode", "int", "sqlite3*"], + ["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_keyword_count", "int"], + ["sqlite3_keyword_name", "int", ["int", "**", "*"]], + ["sqlite3_keyword_check", "int", ["string", "int"]], + ["sqlite3_libversion", "string"], + ["sqlite3_libversion_number", "int"], + ["sqlite3_limit", "int", ["sqlite3*", "int", "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_progress_handler", undefined, [ + "sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({ + name: 'xProgressHandler', + signature: 'i(p)', + bindScope: 'context', + contextKey: (argIndex,argv)=>'sqlite3@'+argv[0] + }), "*" + ] + ], + ["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_isexplain", "int", ["sqlite3_stmt*"]], + ["sqlite3_stmt_readonly", "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", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_trace_v2::callback', + signature: 'i(ippp)', + contextKey: (argIndex, argv)=>'sqlite3@'+argv[0] + }), "*"], + ["sqlite3_txn_state", "int", ["sqlite3*","string"]], + /* 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_last_insert_rowid", "i64", ["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_set_last_insert_rowid", undefined, ["sqlite3*", "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"] + ]; + + /** Install JS<->C struct bindings for the non-opaque struct types we need... */ sqlite3.StructBinder = self.Jaccwabyt({ heap: 0 ? wasm.memory : wasm.heap8u, alloc: wasm.alloc, dealloc: wasm.dealloc, - functionTable: wasm.functionTable, bigIntEnabled: wasm.bigIntEnabled, - memberPrefix: '$' + memberPrefix: /* Never change this: this prefix is baked into any + amount of code and client-facing docs. */ '$' }); delete self.Jaccwabyt; - if(0){ - /* "The problem" is that the following isn't even remotely - type-safe. OTOH, nothing about WASM pointers is. */ - const argPointer = wasm.xWrap.argAdapter('*'); - wasm.xWrap.argAdapter('StructType', (v)=>{ - if(v && v.constructor && v instanceof StructBinder.StructType){ - v = v.pointer; - } - return wasm.isPtr(v) - ? argPointer(v) - : toss("Invalid (object) type for StructType-type argument."); - }); - } - {/* Convert Arrays and certain TypedArrays to strings for - 'flexible-string'-type arguments */ + 'string:flexible'-type arguments */ const xString = wasm.xWrap.argAdapter('string'); wasm.xWrap.argAdapter( - 'flexible-string', (v)=>xString(util.flexibleString(v)) + 'string:flexible', (v)=>xString(util.flexibleString(v)) ); - } + + /** + The 'string:static' argument adapter treats its argument as + either... + + - WASM pointer: assumed to be a long-lived C-string which gets + returned as-is. + + - Anything else: gets coerced to a JS string for use as a map + key. If a matching entry is found (as described next), it is + returned, else wasm.allocCString() is used to create a a new + string, map its pointer to (''+v) for the remainder of the + application's life, and returns that pointer value for this + call and all future calls which are passed a + string-equivalent argument. + + Use case: sqlite3_bind_pointer() and sqlite3_result_pointer() + call for "a static string and preferably a string + literal". This converter is used to ensure that the string + value seen by those functions is long-lived and behaves as they + need it to. + */ + wasm.xWrap.argAdapter( + 'string:static', + function(v){ + if(wasm.isPtr(v)) return v; + v = ''+v; + let rc = this[v]; + return rc || (rc = this[v] = wasm.allocCString(v)); + }.bind(Object.create(null)) + ); + }/* special-case string-type argument conversions */ if(1){// WhWasmUtil.xWrap() bindings... /** Add some descriptive xWrap() aliases for '*' intended to (A) initially improve readability/correctness of capi.signatures - and (B) eventually perhaps provide automatic conversion from - higher-level representations, e.g. capi.sqlite3_vfs to - `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. + and (B) provide automatic conversion from higher-level + representations, e.g. capi.sqlite3_vfs to `sqlite3_vfs*` via + capi.sqlite3_vfs.pointer. */ const aPtr = wasm.xWrap.argAdapter('*'); + const nilType = function(){}; wasm.xWrap.argAdapter('sqlite3_filename', aPtr) - ('sqlite3_stmt*', aPtr) ('sqlite3_context*', aPtr) ('sqlite3_value*', aPtr) ('void*', aPtr) - ('sqlite3*', (v)=>{ - if(sqlite3.oo1 && v instanceof sqlite3.oo1.DB) v = v.pointer; - return aPtr(v); - }) + ('sqlite3_stmt*', (v)=> + aPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType)) + ? v.pointer : v)) + ('sqlite3*', (v)=> + aPtr((v instanceof (sqlite3?.oo1?.DB || nilType)) + ? v.pointer : v)) + ('sqlite3_index_info*', (v)=> + aPtr((v instanceof (capi.sqlite3_index_info || nilType)) + ? v.pointer : v)) + ('sqlite3_module*', (v)=> + aPtr((v instanceof (capi.sqlite3_module || nilType)) + ? v.pointer : v)) /** `sqlite3_vfs*`: @@ -87,21 +363,23 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ ('sqlite3_vfs*', (v)=>{ if('string'===typeof v){ - const x = capi.sqlite3_vfs_find(v); /* A NULL sqlite3_vfs pointer will be treated as the default VFS in many contexts. We specifically do not want that behavior here. */ - if(!x) sqlite3.SQLite3Error.toss("Unknown sqlite3_vfs name:",v); - return x; - }else if(v instanceof sqlite3.capi.sqlite3_vfs) v = v.pointer; - return aPtr(v); + return capi.sqlite3_vfs_find(v) + || sqlite3.SQLite3Error.toss("Unknown sqlite3_vfs name:",v); + } + return aPtr((v instanceof (capi.sqlite3_vfs || nilType)) + ? v.pointer : v); }); - wasm.xWrap.resultAdapter('sqlite3*', aPtr) - ('sqlite3_context*', aPtr) - ('sqlite3_stmt*', aPtr) - ('sqlite3_vfs*', aPtr) - ('void*', aPtr); + const rPtr = wasm.xWrap.resultAdapter('*'); + wasm.xWrap.resultAdapter('sqlite3*', rPtr) + ('sqlite3_context*', rPtr) + ('sqlite3_stmt*', rPtr) + ('sqlite3_value*', rPtr) + ('sqlite3_vfs*', rPtr) + ('void*', rPtr); /** Populate api object with sqlite3_...() by binding the "raw" wasm @@ -127,36 +405,49 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ : fI64Disabled(e[0]); } - /* There's no(?) need to expose bindingSignatures to clients, + /* There's no need to expose bindingSignatures to clients, implicitly making it part of the public interface. */ delete wasm.bindingSignatures; if(wasm.exports.sqlite3_wasm_db_error){ - util.sqlite3_wasm_db_error = wasm.xWrap( + const __db_err = wasm.xWrap( 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string' ); + /** + Sets the given db's error state. Accepts: + + - (sqlite3*, int code, string msg) + - (sqlite3*, Error e [,string msg = ''+e]) + + If passed a WasmAllocError, the message is ingored and the + result code is SQLITE_NOMEM. If passed any other Error type, + the result code defaults to SQLITE_ERROR unless the Error + object has a resultCode property, in which case that is used + (e.g. SQLite3Error has that). If passed a non-WasmAllocError + exception, the message string defaults to theError.message. + + Returns the resulting code. Pass (pDb,0,0) to clear the error + state. + */ + util.sqlite3_wasm_db_error = function(pDb, resultCode, message){ + if(resultCode instanceof sqlite3.WasmAllocError){ + resultCode = capi.SQLITE_NOMEM; + message = 0 /*avoid allocating message string*/; + }else if(resultCode instanceof Error){ + message = message || ''+resultCode; + resultCode = (resultCode.resultCode || capi.SQLITE_ERROR); + } + return __db_err(pDb, resultCode, message); + }; }else{ util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ console.warn("sqlite3_wasm_db_error() is not exported.",arguments); return errCode; }; } - }/*xWrap() bindings*/; /** - When registering a VFS and its related components it may be - necessary to ensure that JS keeps a reference to them to keep - them from getting garbage collected. Simply pass each such value - to this function and a reference will be held to it for the life - of the app. - */ - capi.sqlite3_vfs_register.addReference = function f(...args){ - if(!f._) f._ = []; - f._.push(...args); - }; - - /** Internal helper to assist in validating call argument counts in the hand-written sqlite3_xyz() wrappers. We do this only for consistency with non-special-case wrappings. @@ -167,34 +458,103 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ (1===n?"":'s')+"."); }; - /** - Helper for flexible-string conversions which require a - byte-length counterpart argument. Passed a value and its - ostensible length, this function returns [V,N], where V - is either v or a transformed copy of v and N is either n, - -1, or the byte length of v (if it's a byte array). - */ - const __flexiString = function(v,n){ - if('string'===typeof v){ - n = -1; - }else if(util.isSQLableTypedArray(v)){ - n = v.byteLength; - v = util.typedArrayToString(v); - }else if(Array.isArray(v)){ - v = v.join(""); - n = -1; + if(1){/* Bindings for sqlite3_create_collation() */ + + const __collationContextKey = (argIndex,argv)=>{ + return 'argv['+argIndex+']:sqlite3@'+argv[0]+ + ':'+((/*THIS IS WRONG. We can't sensibly use a converted-to-C-string + address here and don't have access to the JS string (IF ANY) + which the user passed in.*/ + ''+argv[1] + ).toLowerCase()); + }; + const __ccv2 = wasm.xWrap( + 'sqlite3_create_collation_v2', 'int', + 'sqlite3*','string','int','*','*','*' + /* int(*xCompare)(void*,int,const void*,int,const void*) */ + /* void(*xDestroy(void*) */ + ); + if(0){ + // Problem: we cannot, due to xWrap() arg-passing limitations, + // currently easily/efficiently get a per-collation distinct + // key for purposes of creating distinct FuncPtrAdapter contexts. + new wasm.xWrap.FuncPtrAdapter({ + /* int(*xCompare)(void*,int,const void*,int,const void*) */ + name: 'xCompare', + signature: 'i(pipip)', + bindScope: 'context', + contextKey: __collationContextKey + }), + new wasm.xWrap.FuncPtrAdapter({ + /* void(*xDestroy(void*) */ + name: 'xDestroy', + signature: 'v(p)', + bindScope: 'context', + contextKey: __collationContextKey + }) } - return [v, n]; - }; + + /** + Works exactly like C's sqlite3_create_collation_v2() except that: + + 1) It accepts JS functions for its function-pointer arguments, + for which it will install WASM-bound proxies. The bindings + are "permanent," in that they will stay in the WASM environment + until it shuts down unless the client somehow finds and removes + them. + + 2) It returns capi.SQLITE_FORMAT if the 3rd argument is not + capi.SQLITE_UTF8. No other encodings are supported. + + Returns 0 on success, non-0 on error, in which case the error + state of pDb (of type `sqlite3*` or argument-convertible to it) + may contain more information. + */ + capi.sqlite3_create_collation_v2 = function(pDb,zName,eTextRep,pArg,xCompare,xDestroy){ + if(6!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_create_collation_v2', 6); + else if(capi.SQLITE_UTF8!==eTextRep){ + return util.sqlite3_wasm_db_error( + pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding." + ); + } + let rc, pfCompare, pfDestroy; + try{ + if(xCompare instanceof Function){ + pfCompare = wasm.installFunction(xCompare, 'i(pipip)'); + } + if(xDestroy instanceof Function){ + pfDestroy = wasm.installFunction(xDestroy, 'v(p)'); + } + rc = __ccv2(pDb, zName, eTextRep, pArg, + pfCompare || xCompare, pfDestroy || xDestroy); + }catch(e){ + if(pfCompare) wasm.uninstallFunction(pfCompare); + if(pfDestroy) wasm.uninstallFunction(pfDestroy); + rc = util.sqlite3_wasm_db_error(pDb, e); + } + return rc; + }; + + capi.sqlite3_create_collation = (pDb,zName,eTextRep,pArg,xCompare)=>{ + return (5===arguments.length) + ? capi.sqlite3_create_collation_v2(pDb,zName,eTextRep,pArg,xCompare,0) + : __dbArgcMismatch(pDb, 'sqlite3_create_collation', 5); + } + + }/*sqlite3_create_collation() and friends*/ if(1){/* Special-case handling of sqlite3_exec() */ const __exec = wasm.xWrap("sqlite3_exec", "int", - ["sqlite3*", "flexible-string", "*", "*", "**"]); + ["sqlite3*", "string:flexible", + new wasm.xWrap.FuncPtrAdapter({ + signature: 'i(pipp)', + bindScope: 'transient' + }), "*", "**"]); /* Documented in the api object's initializer. */ capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){ if(f.length!==arguments.length){ return __dbArgcMismatch(pDb,"sqlite3_exec",f.length); - }else if('function' !== typeof callback){ + }else if(!(callback instanceof Function)){ return __exec(pDb, sql, callback, pVoid, pErrMsg); } /* Wrap the callback in a WASM-bound function and convert the callback's @@ -204,8 +564,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try { let aVals = [], aNames = [], i = 0, offset = 0; for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){ - aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) ); - aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) ); + aVals.push( wasm.cstrToJs(wasm.peekPtr(pColVals + offset)) ); + aNames.push( wasm.cstrToJs(wasm.peekPtr(pColNames + offset)) ); } rc = callback(pVoid, nCols, aVals, aNames) | 0; /* The first 2 args of the callback are useless for JS but @@ -219,15 +579,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } return rc; }; - let pFunc, rc; + let rc; try{ - pFunc = wasm.installFunction("ipipp", cbwrap); - rc = __exec(pDb, sql, pFunc, pVoid, pErrMsg); + rc = __exec(pDb, sql, cbwrap, pVoid, pErrMsg); }catch(e){ rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, - "Error running exec(): "+e.message); - }finally{ - if(pFunc) wasm.uninstallFunction(pFunc); + "Error running exec(): "+e); } return rc; }; @@ -249,132 +606,31 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "*"/*xInverse*/, "*"/*xDestroy*/] ); - const __udfSetResult = function(pCtx, val){ - //console.warn("udfSetResult",typeof val, val); - switch(typeof val) { - case 'undefined': - /* Assume that the client already called sqlite3_result_xxx(). */ - break; - case 'boolean': - capi.sqlite3_result_int(pCtx, val ? 1 : 0); - break; - case 'bigint': - 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 if(util.bigIntFits32(val)){ - capi.sqlite3_result_int(pCtx, Number(val)); - }else if(util.bigIntFitsDouble(val)){ - capi.sqlite3_result_double(pCtx, Number(val)); - }else{ - toss3("BigInt value",val.toString(),"is too BigInt."); - } - break; - case 'number': { - (util.isInt32(val) - ? capi.sqlite3_result_int - : capi.sqlite3_result_double)(pCtx, val); - break; - } - case 'string': - capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT); - 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, - wasm.exports[sqlite3.config.deallocExportName] - ); - break; - } - // else fall through - default: - toss3("Don't not how to handle this UDF result value:",(typeof val), val); - }; - }/*__udfSetResult()*/; - - const __udfConvertArgs = function(argc, pArgv){ - let i, pVal, valType, arg; - const tgt = []; - for(i = 0; i < argc; ++i){ - pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * 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. - */ - valType = capi.sqlite3_value_type(pVal); - switch(valType){ - case capi.SQLITE_INTEGER: - if(wasm.bigIntEnabled){ - arg = capi.sqlite3_value_int64(pVal); - if(util.bigIntFitsDouble(arg)) arg = Number(arg); - } - else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/; - break; - case capi.SQLITE_FLOAT: - arg = capi.sqlite3_value_double(pVal); - break; - case capi.SQLITE_TEXT: - arg = capi.sqlite3_value_text(pVal); - break; - case capi.SQLITE_BLOB:{ - const n = capi.sqlite3_value_bytes(pVal); - const pBlob = capi.sqlite3_value_blob(pVal); - if(n && !pBlob) sqlite3.WasmAllocError.toss( - "Cannot allocate memory for blob argument of",n,"byte(s)" - ); - arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null; - break; - } - case capi.SQLITE_NULL: - arg = null; break; - default: - toss3("Unhandled sqlite3_value_type()",valType, - "is possibly indicative of incorrect", - "pointer size assumption."); - } - tgt.push(arg); - } - return tgt; - }/*__udfConvertArgs()*/; - - const __udfSetError = (pCtx, e)=>{ - if(e instanceof sqlite3.WasmAllocError){ - capi.sqlite3_result_error_nomem(pCtx); - }else{ - const msg = ('string'===typeof e) ? e : e.message; - capi.sqlite3_result_error(pCtx, msg, -1); - } - }; - const __xFunc = function(callback){ return function(pCtx, argc, pArgv){ - try{ __udfSetResult(pCtx, callback(pCtx, ...__udfConvertArgs(argc, pArgv))) } - catch(e){ + try{ + capi.sqlite3_result_js( + pCtx, + callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) + ); + }catch(e){ //console.error('xFunc() caught:',e); - __udfSetError(pCtx, e); + capi.sqlite3_result_error_js(pCtx, e); } }; }; const __xInverseAndStep = function(callback){ return function(pCtx, argc, pArgv){ - try{ callback(pCtx, ...__udfConvertArgs(argc, pArgv)) } - catch(e){ __udfSetError(pCtx, e) } + try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) } + catch(e){ capi.sqlite3_result_error_js(pCtx, e) } }; }; const __xFinalAndValue = function(callback){ return function(pCtx){ - try{ __udfSetResult(pCtx, callback(pCtx)) } - catch(e){ __udfSetError(pCtx, e) } + try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) } + catch(e){ capi.sqlite3_result_error_js(pCtx, e) } }; }; @@ -480,67 +736,54 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return rc; }; /** - A helper for UDFs implemented in JS and bound to WASM by the - client. Given a JS value, udfSetResult(pCtx,X) calls one of the - sqlite3_result_xyz(pCtx,...) routines, depending on X's data - type: - - - `null`: sqlite3_result_null() - - `boolean`: sqlite3_result_int() - - `number`: sqlite3_result_int() or sqlite3_result_double() - - `string`: sqlite3_result_text() - - Uint8Array or Int8Array: sqlite3_result_blob() - - `undefined`: indicates that the UDF called one of the - `sqlite3_result_xyz()` routines on its own, making this - function a no-op. Results are _undefined_ if this function is - passed the `undefined` value but did _not_ call one of the - `sqlite3_result_xyz()` routines. - - Anything else triggers sqlite3_result_error(). + A _deprecated_ alias for capi.sqlite3_result_js() which + predates the addition of that function in the public API. */ capi.sqlite3_create_function_v2.udfSetResult = capi.sqlite3_create_function.udfSetResult = - capi.sqlite3_create_window_function.udfSetResult = __udfSetResult; + capi.sqlite3_create_window_function.udfSetResult = capi.sqlite3_result_js; /** - A helper for UDFs implemented in JS and bound to WASM by the - client. When passed the - (argc,argv) values from the UDF-related functions which receive - them (xFunc, xStep, xInverse), it creates a JS array - representing those arguments, converting each to JS in a manner - appropriate to its data type: numeric, text, blob - (Uint8Array), or null. - - Results are undefined if it's passed anything other than those - two arguments from those specific contexts. - - Thus an argc of 4 will result in a length-4 array containing - the converted values from the corresponding argv. - - The conversion will throw only on allocation error or an internal - error. + A _deprecated_ alias for capi.sqlite3_values_to_js() which + predates the addition of that function in the public API. */ capi.sqlite3_create_function_v2.udfConvertArgs = capi.sqlite3_create_function.udfConvertArgs = - capi.sqlite3_create_window_function.udfConvertArgs = __udfConvertArgs; + capi.sqlite3_create_window_function.udfConvertArgs = capi.sqlite3_values_to_js; /** - A helper for UDFs implemented in JS and bound to WASM by the - client. It expects to be a passed `(sqlite3_context*, Error)` - (an exception object or message string). And it sets the - current UDF's result to sqlite3_result_error_nomem() or - sqlite3_result_error(), depending on whether the 2nd argument - is a sqlite3.WasmAllocError object or not. + A _deprecated_ alias for capi.sqlite3_result_error_js() which + predates the addition of that function in the public API. */ capi.sqlite3_create_function_v2.udfSetError = capi.sqlite3_create_function.udfSetError = - capi.sqlite3_create_window_function.udfSetError = __udfSetError; + capi.sqlite3_create_window_function.udfSetError = capi.sqlite3_result_error_js; }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/; if(1){/* Special-case handling of sqlite3_prepare_v2() and sqlite3_prepare_v3() */ /** + Helper for string:flexible conversions which require a + byte-length counterpart argument. Passed a value and its + ostensible length, this function returns [V,N], where V + is either v or a transformed copy of v and N is either n, + -1, or the byte length of v (if it's a byte array). + */ + const __flexiString = (v,n)=>{ + if('string'===typeof v){ + n = -1; + }else if(util.isSQLableTypedArray(v)){ + n = v.byteLength; + v = util.typedArrayToString(v); + }else if(Array.isArray(v)){ + v = v.join(""); + n = -1; + } + return [v, n]; + }; + + /** Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). */ const __prepare = Object.create(null); @@ -570,7 +813,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "int", ["sqlite3*", "*", "int", "int", "**", "**"]); - /* Documented in the api object's initializer. */ + /* Documented in the capi object's initializer. */ capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ if(f.length!==arguments.length){ return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length); @@ -587,7 +830,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } }; - /* Documented in the api object's initializer. */ + /* Documented in the capi object's initializer. */ capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){ return (f.length===arguments.length) ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) @@ -601,15 +844,22 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", "static buffer size!"); } - wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); + wasm.ctype = JSON.parse(wasm.cstrToJs(cJson)); //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); - for(const t of ['access', 'blobFinalizers', 'dataTypes', - 'encodings', 'fcntl', 'flock', 'ioCap', - 'limits', - 'openFlags', 'prepareFlags', 'resultCodes', - 'serialize', 'syncFlags', 'trace', 'udfFlags', - 'version' - ]){ + const defineGroups = ['access', 'authorizer', + 'blobFinalizers', 'dataTypes', + 'dbConfig', 'dbStatus', + 'encodings', 'fcntl', 'flock', 'ioCap', + 'limits', 'openFlags', + 'prepareFlags', 'resultCodes', + 'serialize', 'sqlite3Status', + 'stmtStatus', 'syncFlags', + 'trace', 'txnState', 'udfFlags', + 'version' ]; + if(wasm.bigIntEnabled){ + defineGroups.push('vtab'); + } + for(const t of defineGroups){ for(const e of Object.entries(wasm.ctype[t])){ // ^^^ [k,v] there triggers a buggy code transformation via // one of the Emscripten-driven optimizers. @@ -629,19 +879,37 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; /* Bind all registered C-side structs... */ const notThese = Object.assign(Object.create(null),{ - // Structs NOT to register - WasmTestStruct: true + // For each struct to NOT register, map its name to true: + WasmTestStruct: true, + /* We unregister the kvvfs VFS from Worker threads below. */ + sqlite3_kvvfs_methods: !util.isUIThread(), + /* sqlite3_index_info and friends require int64: */ + sqlite3_index_info: !wasm.bigIntEnabled, + sqlite3_index_constraint: !wasm.bigIntEnabled, + sqlite3_index_orderby: !wasm.bigIntEnabled, + sqlite3_index_constraint_usage: !wasm.bigIntEnabled }); - if(!util.isUIThread()){ - /* We remove the kvvfs VFS from Worker threads below. */ - notThese.sqlite3_kvvfs_methods = true; - } for(const s of wasm.ctype.structs){ if(!notThese[s.name]){ capi[s.name] = sqlite3.StructBinder(s); } } - }/*end C constant imports*/ + if(capi.sqlite3_index_info){ + /* Move these inner structs into sqlite3_index_info. Binding + ** them to WASM requires that we create global-scope structs to + ** model them with, but those are no longer needed after we've + ** passed them to StructBinder. */ + for(const k of ['sqlite3_index_constraint', + 'sqlite3_index_orderby', + 'sqlite3_index_constraint_usage']){ + capi.sqlite3_index_info[k] = capi[k]; + delete capi[k]; + } + capi.sqlite3_vtab_config = + (pDb, op, arg=0)=>wasm.exports.sqlite3_wasm_vtab_config( + wasm.xWrap.argAdapter('sqlite3*')(pDb), op, arg); + }/* end vtab-related setup */ + }/*end C constant and struct imports*/ const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); if( pKvvfs ){/* kvvfs-specific glue */ @@ -652,11 +920,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ delete capi.sqlite3_kvvfs_methods; const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, - pstack = wasm.pstack, - pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc; + pstack = wasm.pstack; const kvvfsStorage = (zClass)=> - ((115/*=='s'*/===wasm.getMemValue(zClass)) + ((115/*=='s'*/===wasm.peek(zClass)) ? sessionStorage : localStorage); /** @@ -672,7 +939,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try { const zXKey = kvvfsMakeKey(zClass,zKey); if(!zXKey) return -3/*OOM*/; - const jKey = wasm.cstringToJs(zXKey); + const jKey = wasm.cstrToJs(zXKey); const jV = kvvfsStorage(zClass).getItem(jKey); if(!jV) return -1; const nV = jV.length /* Note that we are relying 100% on v being @@ -680,13 +947,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ C-string's byte length. */; if(nBuf<=0) return nV; else if(1===nBuf){ - wasm.setMemValue(zBuf, 0); + wasm.poke(zBuf, 0); return nV; } const zV = wasm.scopedAllocCString(jV); if(nBuf > nV + 1) nBuf = nV + 1; wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1); - wasm.setMemValue(zBuf + nBuf - 1, 0); + wasm.poke(zBuf + nBuf - 1, 0); return nBuf - 1; }catch(e){ console.error("kvstorageRead()",e); @@ -701,8 +968,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try { const zXKey = kvvfsMakeKey(zClass,zKey); if(!zXKey) return 1/*OOM*/; - const jKey = wasm.cstringToJs(zXKey); - kvvfsStorage(zClass).setItem(jKey, wasm.cstringToJs(zData)); + const jKey = wasm.cstrToJs(zXKey); + kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData)); return 0; }catch(e){ console.error("kvstorageWrite()",e); @@ -716,7 +983,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try { const zXKey = kvvfsMakeKey(zClass,zKey); if(!zXKey) return 1/*OOM*/; - kvvfsStorage(zClass).removeItem(wasm.cstringToJs(zXKey)); + kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey)); return 0; }catch(e){ console.error("kvstorageDelete()",e); |