| /* |
| 2022-07-22 |
| |
| The author disclaims copyright to this source code. In place of a |
| legal notice, here is a blessing: |
| |
| * May you do good and not evil. |
| * May you find forgiveness for yourself and forgive others. |
| * May you share freely, never taking more than you give. |
| |
| *********************************************************************** |
| |
| This file glues together disparate pieces of JS which are loaded in |
| previous steps of the sqlite3-api.js bootstrapping process: |
| sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It |
| initializes the main API pieces so that the downstream components |
| (e.g. sqlite3-api-oo1.js) have all of the infrastructure that they |
| need. |
| */ |
| globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ |
| 'use strict'; |
| const toss = (...args)=>{throw new Error(args.join(' '))}; |
| const toss3 = sqlite3.SQLite3Error.toss; |
| const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util; |
| globalThis.WhWasmUtilInstaller(wasm); |
| delete globalThis.WhWasmUtilInstaller; |
| |
| if(0){ |
| /** |
| Please keep this block around as a maintenance reminder |
| that we cannot rely on this type of check. |
| |
| This block fails on Safari, per a report at |
| https://sqlite.org/forum/forumpost/e5b20e1feb. |
| |
| It turns out that what Safari serves from the indirect function |
| table (e.g. wasm.functionEntry(X)) is anonymous functions which |
| wrap the WASM functions, rather than returning the WASM |
| functions themselves. That means comparison of such functions |
| is useless for determining whether or not we have a specific |
| function from wasm.exports. i.e. if function X is indirection |
| function table entry N then wasm.exports.X is not equal to |
| wasm.functionEntry(N) in Safari, despite being so in the other |
| browsers. |
| */ |
| /** |
| Find a mapping for SQLITE_WASM_DEALLOC, which the API |
| guarantees is a WASM pointer to the same underlying function as |
| wasm.dealloc() (noting that wasm.dealloc() is permitted to be a |
| JS wrapper around the WASM function). There is unfortunately no |
| O(1) algorithm for finding this pointer: we have to walk the |
| WASM indirect function table to find it. However, experience |
| indicates that that particular function is always very close to |
| the front of the table (it's been entry #3 in all relevant |
| tests). |
| */ |
| const dealloc = wasm.exports[sqlite3.config.deallocExportName]; |
| const nFunc = wasm.functionTable().length; |
| let i; |
| for(i = 0; i < nFunc; ++i){ |
| const e = wasm.functionEntry(i); |
| if(dealloc === e){ |
| capi.SQLITE_WASM_DEALLOC = i; |
| break; |
| } |
| } |
| if(dealloc !== wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){ |
| toss("Internal error: cannot find function pointer for SQLITE_WASM_DEALLOC."); |
| } |
| } |
| |
| /** |
| 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_auto_extension() has a hand-written binding. */ |
| /* sqlite3_bind_blob() and sqlite3_bind_text() have hand-written |
| bindings to permit more flexible inputs. */ |
| ["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_parameter_name", "string", "sqlite3_stmt*", "int"], |
| ["sqlite3_bind_pointer", "int", |
| "sqlite3_stmt*", "int", "*", "string:static", "*"], |
| ["sqlite3_busy_handler","int", [ |
| "sqlite3*", |
| new wasm.xWrap.FuncPtrAdapter({ |
| signature: 'i(pi)', |
| contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] |
| }), |
| "*" |
| ]], |
| ["sqlite3_busy_timeout","int", "sqlite3*", "int"], |
| /* sqlite3_cancel_auto_extension() has a hand-written binding. */ |
| /* sqlite3_close_v2() is implemented by hand to perform some |
| extra work. */ |
| ["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_decltype", "string", "sqlite3_stmt*", "int"], |
| ["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_commit_hook", "void*", [ |
| "sqlite3*", |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'sqlite3_commit_hook', |
| signature: 'i(p)', |
| contextKey: (argv)=>argv[0/* sqlite3* */] |
| }), |
| '*' |
| ]], |
| ["sqlite3_compileoption_get", "string", "int"], |
| ["sqlite3_compileoption_used", "int", "string"], |
| ["sqlite3_complete", "int", "string:flexible"], |
| ["sqlite3_context_db_handle", "sqlite3*", "sqlite3_context*"], |
| /* sqlite3_create_collation() and sqlite3_create_collation_v2() |
| use hand-written bindings to simplify passing of the callback |
| function. */ |
| /* 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_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_readonly", "int", "sqlite3*", "string"], |
| ["sqlite3_db_status", "int", "sqlite3*", "int", "*", "*", "int"], |
| ["sqlite3_errcode", "int", "sqlite3*"], |
| ["sqlite3_errmsg", "string", "sqlite3*"], |
| ["sqlite3_error_offset", "int", "sqlite3*"], |
| ["sqlite3_errstr", "string", "int"], |
| ["sqlite3_exec", "int", [ |
| "sqlite3*", "string:flexible", |
| new wasm.xWrap.FuncPtrAdapter({ |
| signature: 'i(pipp)', |
| bindScope: 'transient', |
| callProxy: (callback)=>{ |
| let aNames; |
| return (pVoid, nCols, pColVals, pColNames)=>{ |
| try { |
| const aVals = wasm.cArgvToJs(nCols, pColVals); |
| if(!aNames) aNames = wasm.cArgvToJs(nCols, pColNames); |
| return callback(aVals, aNames) | 0; |
| }catch(e){ |
| /* If we set the db error state here, the higher-level |
| exec() call replaces it with its own, so we have no way |
| of reporting the exception message except the console. We |
| must not propagate exceptions through the C API. Though |
| we make an effort to report OOM here, sqlite3_exec() |
| translates that into SQLITE_ABORT as well. */ |
| return e.resultCode || capi.SQLITE_ERROR; |
| } |
| } |
| } |
| }), |
| "*", "**" |
| ]], |
| ["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_autocommit", "int", "sqlite3*"], |
| ["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"], |
| ["sqlite3_initialize", undefined], |
| ["sqlite3_interrupt", undefined, "sqlite3*"], |
| ["sqlite3_is_interrupted", "int", "sqlite3*"], |
| ["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_realloc", "*","*","int"], |
| ["sqlite3_reset", "int", "sqlite3_stmt*"], |
| /* sqlite3_reset_auto_extension() has a hand-written binding. */ |
| ["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_rollback_hook", "void*", [ |
| "sqlite3*", |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'sqlite3_rollback_hook', |
| signature: 'v(p)', |
| contextKey: (argv)=>argv[0/* sqlite3* */] |
| }), |
| '*' |
| ]], |
| ["sqlite3_set_auxdata", undefined, [ |
| "sqlite3_context*", "int", "*", |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xDestroyAuxData', |
| signature: 'v(*)', |
| contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */] |
| }) |
| ]], |
| ["sqlite3_shutdown", undefined], |
| ["sqlite3_sourceid", "string"], |
| ["sqlite3_sql", "string", "sqlite3_stmt*"], |
| ["sqlite3_status", "int", "int", "*", "*", "int"], |
| ["sqlite3_step", "int", "sqlite3_stmt*"], |
| ["sqlite3_stmt_busy", "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: (argv,argIndex)=>argv[0/* sqlite3* */] |
| }), |
| "*" |
| ]], |
| ["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( !!wasm.exports.sqlite3_progress_handler ){ |
| wasm.bindingSignatures.push( |
| ["sqlite3_progress_handler", undefined, [ |
| "sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xProgressHandler', |
| signature: 'i(p)', |
| bindScope: 'context', |
| contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] |
| }), "*" |
| ]] |
| ); |
| } |
| |
| if( !!wasm.exports.sqlite3_stmt_explain ){ |
| wasm.bindingSignatures.push( |
| ["sqlite3_stmt_explain", "int", "sqlite3_stmt*", "int"], |
| ["sqlite3_stmt_isexplain", "int", "sqlite3_stmt*"] |
| ); |
| } |
| |
| if( !!wasm.exports.sqlite3_set_authorizer ){ |
| wasm.bindingSignatures.push( |
| ["sqlite3_set_authorizer", "int", [ |
| "sqlite3*", |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: "sqlite3_set_authorizer::xAuth", |
| signature: "i(pi"+"ssss)", |
| contextKey: (argv, argIndex)=>argv[0/*(sqlite3*)*/], |
| callProxy: (callback)=>{ |
| return (pV, iCode, s0, s1, s2, s3)=>{ |
| try{ |
| s0 = s0 && wasm.cstrToJs(s0); s1 = s1 && wasm.cstrToJs(s1); |
| s2 = s2 && wasm.cstrToJs(s2); s3 = s3 && wasm.cstrToJs(s3); |
| return callback(pV, iCode, s0, s1, s2, s3) || 0; |
| }catch(e){ |
| return e.resultCode || capi.SQLITE_ERROR; |
| } |
| } |
| } |
| }), |
| "*"/*pUserData*/ |
| ]] |
| ); |
| }/* sqlite3_set_authorizer() */ |
| |
| if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ |
| /* ^^^ "the problem" is that this is an optional 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*"]); |
| } |
| |
| //#if enable-see |
| if(wasm.exports.sqlite3_key_v2 instanceof Function){ |
| /** |
| This code is capable of using an SEE build but note that an SEE |
| WASM build is generally incompatible with SEE's license |
| conditions. It is permitted for use internally in organizations |
| which have licensed SEE, but not for public sites because |
| exposing an SEE build of sqlite3.wasm effectively provides all |
| clients with a working copy of the commercial SEE code. |
| */ |
| wasm.bindingSignatures.push( |
| ["sqlite3_key", "int", "sqlite3*", "string", "int"], |
| ["sqlite3_key_v2","int","sqlite3*","string","*","int"], |
| ["sqlite3_rekey", "int", "sqlite3*", "string", "int"], |
| ["sqlite3_rekey_v2", "int", "sqlite3*", "string", "*", "int"], |
| ["sqlite3_activate_see", undefined, "string"] |
| ); |
| } |
| //#endif enable-see |
| |
| /** |
| 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. |
| (That said: we never actually build without BigInt support, |
| and such builds are untested.) |
| |
| 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_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. Because |
| of this, the canonical builds of sqlite3.wasm/js guarantee that |
| sqlite3.wasm.alloc() and friends use those allocators. Custom builds |
| may not guarantee that, however. */, |
| ["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_serialize","*", "sqlite3*", "string", "*", "int"], |
| ["sqlite3_set_last_insert_rowid", undefined, ["sqlite3*", "i64"]], |
| ["sqlite3_status64", "int", "int", "*", "*", "int"], |
| ["sqlite3_total_changes64", "i64", ["sqlite3*"]], |
| ["sqlite3_update_hook", "*", [ |
| "sqlite3*", |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'sqlite3_update_hook', |
| signature: "v(iippj)", |
| contextKey: (argv)=>argv[0/* sqlite3* */], |
| callProxy: (callback)=>{ |
| return (p,op,z0,z1,rowid)=>{ |
| callback(p, op, wasm.cstrToJs(z0), wasm.cstrToJs(z1), rowid); |
| }; |
| } |
| }), |
| "*" |
| ]], |
| ["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]], |
| ["sqlite3_value_int64","i64", "sqlite3_value*"] |
| ]; |
| |
| if( wasm.bigIntEnabled && !!wasm.exports.sqlite3_declare_vtab ){ |
| wasm.bindingSignatures.int64.push( |
| ["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_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", "**"] |
| ); |
| }/* virtual table APIs */ |
| |
| if(wasm.bigIntEnabled && !!wasm.exports.sqlite3_preupdate_hook){ |
| wasm.bindingSignatures.int64.push( |
| ["sqlite3_preupdate_blobwrite", "int", "sqlite3*"], |
| ["sqlite3_preupdate_count", "int", "sqlite3*"], |
| ["sqlite3_preupdate_depth", "int", "sqlite3*"], |
| ["sqlite3_preupdate_hook", "*", [ |
| "sqlite3*", |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'sqlite3_preupdate_hook', |
| signature: "v(ppippjj)", |
| contextKey: (argv)=>argv[0/* sqlite3* */], |
| callProxy: (callback)=>{ |
| return (p,db,op,zDb,zTbl,iKey1,iKey2)=>{ |
| callback(p, db, op, wasm.cstrToJs(zDb), wasm.cstrToJs(zTbl), |
| iKey1, iKey2); |
| }; |
| } |
| }), |
| "*" |
| ]], |
| ["sqlite3_preupdate_new", "int", ["sqlite3*", "int", "**"]], |
| ["sqlite3_preupdate_old", "int", ["sqlite3*", "int", "**"]] |
| ); |
| } /* preupdate API */ |
| |
| // Add session/changeset APIs... |
| if(wasm.bigIntEnabled |
| && !!wasm.exports.sqlite3changegroup_add |
| && !!wasm.exports.sqlite3session_create |
| && !!wasm.exports.sqlite3_preupdate_hook /* required by the session API */){ |
| /** |
| FuncPtrAdapter options for session-related callbacks with the |
| native signature "i(ps)". This proxy converts the 2nd argument |
| from a C string to a JS string before passing the arguments on |
| to the client-provided JS callback. |
| */ |
| const __ipsProxy = { |
| signature: 'i(ps)', |
| callProxy:(callback)=>{ |
| return (p,s)=>{ |
| try{return callback(p, wasm.cstrToJs(s)) | 0} |
| catch(e){return e.resultCode || capi.SQLITE_ERROR} |
| } |
| } |
| }; |
| |
| wasm.bindingSignatures.int64.push(...[ |
| ['sqlite3changegroup_add', 'int', ['sqlite3_changegroup*', 'int', 'void*']], |
| ['sqlite3changegroup_add_strm', 'int', [ |
| 'sqlite3_changegroup*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*' |
| ]], |
| ['sqlite3changegroup_delete', undefined, ['sqlite3_changegroup*']], |
| ['sqlite3changegroup_new', 'int', ['**']], |
| ['sqlite3changegroup_output', 'int', ['sqlite3_changegroup*', 'int*', '**']], |
| ['sqlite3changegroup_output_strm', 'int', [ |
| 'sqlite3_changegroup*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' |
| }), |
| 'void*' |
| ]], |
| ['sqlite3changeset_apply', 'int', [ |
| 'sqlite3*', 'int', 'void*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xFilter', bindScope: 'transient', ...__ipsProxy |
| }), |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' |
| }), |
| 'void*' |
| ]], |
| ['sqlite3changeset_apply_strm', 'int', [ |
| 'sqlite3*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xFilter', bindScope: 'transient', ...__ipsProxy |
| }), |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' |
| }), |
| 'void*' |
| ]], |
| ['sqlite3changeset_apply_v2', 'int', [ |
| 'sqlite3*', 'int', 'void*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xFilter', bindScope: 'transient', ...__ipsProxy |
| }), |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' |
| }), |
| 'void*', '**', 'int*', 'int' |
| |
| ]], |
| ['sqlite3changeset_apply_v2_strm', 'int', [ |
| 'sqlite3*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xFilter', bindScope: 'transient', ...__ipsProxy |
| }), |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' |
| }), |
| 'void*', '**', 'int*', 'int' |
| ]], |
| ['sqlite3changeset_concat', 'int', ['int','void*', 'int', 'void*', 'int*', '**']], |
| ['sqlite3changeset_concat_strm', 'int', [ |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xInputA', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xInputB', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' |
| }), |
| 'void*' |
| ]], |
| ['sqlite3changeset_conflict', 'int', ['sqlite3_changeset_iter*', 'int', '**']], |
| ['sqlite3changeset_finalize', 'int', ['sqlite3_changeset_iter*']], |
| ['sqlite3changeset_fk_conflicts', 'int', ['sqlite3_changeset_iter*', 'int*']], |
| ['sqlite3changeset_invert', 'int', ['int', 'void*', 'int*', '**']], |
| ['sqlite3changeset_invert_strm', 'int', [ |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' |
| }), |
| 'void*' |
| ]], |
| ['sqlite3changeset_new', 'int', ['sqlite3_changeset_iter*', 'int', '**']], |
| ['sqlite3changeset_next', 'int', ['sqlite3_changeset_iter*']], |
| ['sqlite3changeset_old', 'int', ['sqlite3_changeset_iter*', 'int', '**']], |
| ['sqlite3changeset_op', 'int', [ |
| 'sqlite3_changeset_iter*', '**', 'int*', 'int*','int*' |
| ]], |
| ['sqlite3changeset_pk', 'int', ['sqlite3_changeset_iter*', '**', 'int*']], |
| ['sqlite3changeset_start', 'int', ['**', 'int', '*']], |
| ['sqlite3changeset_start_strm', 'int', [ |
| '**', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*' |
| ]], |
| ['sqlite3changeset_start_v2', 'int', ['**', 'int', '*', 'int']], |
| ['sqlite3changeset_start_v2_strm', 'int', [ |
| '**', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*', 'int' |
| ]], |
| ['sqlite3session_attach', 'int', ['sqlite3_session*', 'string']], |
| ['sqlite3session_changeset', 'int', ['sqlite3_session*', 'int*', '**']], |
| ['sqlite3session_changeset_size', 'i64', ['sqlite3_session*']], |
| ['sqlite3session_changeset_strm', 'int', [ |
| 'sqlite3_session*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*' |
| ]], |
| ['sqlite3session_config', 'int', ['int', 'void*']], |
| ['sqlite3session_create', 'int', ['sqlite3*', 'string', '**']], |
| //sqlite3session_delete() is bound manually |
| ['sqlite3session_diff', 'int', ['sqlite3_session*', 'string', 'string', '**']], |
| ['sqlite3session_enable', 'int', ['sqlite3_session*', 'int']], |
| ['sqlite3session_indirect', 'int', ['sqlite3_session*', 'int']], |
| ['sqlite3session_isempty', 'int', ['sqlite3_session*']], |
| ['sqlite3session_memory_used', 'i64', ['sqlite3_session*']], |
| ['sqlite3session_object_config', 'int', ['sqlite3_session*', 'int', 'void*']], |
| ['sqlite3session_patchset', 'int', ['sqlite3_session*', '*', '**']], |
| ['sqlite3session_patchset_strm', 'int', [ |
| 'sqlite3_session*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient' |
| }), |
| 'void*' |
| ]], |
| ['sqlite3session_table_filter', undefined, [ |
| 'sqlite3_session*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| name: 'xFilter', ...__ipsProxy, |
| contextKey: (argv,argIndex)=>argv[0/* (sqlite3_session*) */] |
| }), |
| '*' |
| ]] |
| ]); |
| }/*session/changeset APIs*/ |
| |
| /** |
| Functions which are intended solely for API-internal use by the |
| WASM components, not client code. These get installed into |
| sqlite3.util. Some of them get exposed to clients via variants |
| in sqlite3_js_...(). |
| |
| 2024-01-11: these were renamed, with two underscores in the |
| prefix, to ensure that clients do not accidentally depend on |
| them. They have always been documented as internal-use-only, so |
| no clients "should" be depending on the old names. |
| */ |
| wasm.bindingSignatures.wasmInternal = [ |
| ["sqlite3__wasm_db_reset", "int", "sqlite3*"], |
| ["sqlite3__wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], |
| [/* DO NOT USE. This is deprecated since 2023-08-11 because it can |
| trigger assert() in debug builds when used with file sizes |
| which are not sizes to a multiple of a valid db page size. */ |
| "sqlite3__wasm_vfs_create_file", "int", "sqlite3_vfs*","string","*", "int" |
| ], |
| ["sqlite3__wasm_posix_create_file", "int", "string","*", "int"], |
| ["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"], |
| ["sqlite3__wasm_qfmt_token","string:dealloc", "string","int"] |
| ]; |
| |
| /** |
| Install JS<->C struct bindings for the non-opaque struct types we |
| need... */ |
| sqlite3.StructBinder = globalThis.Jaccwabyt({ |
| heap: 0 ? wasm.memory : wasm.heap8u, |
| alloc: wasm.alloc, |
| dealloc: wasm.dealloc, |
| bigIntEnabled: wasm.bigIntEnabled, |
| memberPrefix: /* Never change this: this prefix is baked into any |
| amount of code and client-facing docs. */ '$' |
| }); |
| delete globalThis.Jaccwabyt; |
| |
| {// wasm.xWrap() bindings... |
| |
| /* Convert Arrays and certain TypedArrays to strings for |
| 'string:flexible'-type arguments */ |
| const __xString = wasm.xWrap.argAdapter('string'); |
| wasm.xWrap.argAdapter( |
| '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 || (this[v] = wasm.allocCString(v)); |
| }.bind(Object.create(null)) |
| ); |
| |
| /** |
| Add some descriptive xWrap() aliases for '*' intended to (A) |
| initially improve readability/correctness of |
| wasm.bindingSignatures and (B) provide automatic conversion |
| from higher-level representations, e.g. capi.sqlite3_vfs to |
| `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. |
| */ |
| const __xArgPtr = wasm.xWrap.argAdapter('*'); |
| const nilType = function(){ |
| /*a class which no value can ever be an instance of*/ |
| }; |
| wasm.xWrap.argAdapter('sqlite3_filename', __xArgPtr) |
| ('sqlite3_context*', __xArgPtr) |
| ('sqlite3_value*', __xArgPtr) |
| ('void*', __xArgPtr) |
| ('sqlite3_changegroup*', __xArgPtr) |
| ('sqlite3_changeset_iter*', __xArgPtr) |
| ('sqlite3_session*', __xArgPtr) |
| ('sqlite3_stmt*', (v)=> |
| __xArgPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType)) |
| ? v.pointer : v)) |
| ('sqlite3*', (v)=> |
| __xArgPtr((v instanceof (sqlite3?.oo1?.DB || nilType)) |
| ? v.pointer : v)) |
| /** |
| `sqlite3_vfs*`: |
| |
| - v is-a string: use the result of sqlite3_vfs_find(v) but |
| throw if it returns 0. |
| - v is-a capi.sqlite3_vfs: use v.pointer. |
| - Else return the same as the `'*'` argument conversion. |
| */ |
| ('sqlite3_vfs*', (v)=>{ |
| if('string'===typeof v){ |
| /* A NULL sqlite3_vfs pointer will be treated as the default |
| VFS in many contexts. We specifically do not want that |
| behavior here. */ |
| return capi.sqlite3_vfs_find(v) |
| || sqlite3.SQLite3Error.toss( |
| capi.SQLITE_NOTFOUND, |
| "Unknown sqlite3_vfs name:", v |
| ); |
| } |
| return __xArgPtr((v instanceof (capi.sqlite3_vfs || nilType)) |
| ? v.pointer : v); |
| }); |
| if( wasm.exports.sqlite3_declare_vtab ){ |
| wasm.xWrap.argAdapter('sqlite3_index_info*', (v)=> |
| __xArgPtr((v instanceof (capi.sqlite3_index_info || nilType)) |
| ? v.pointer : v)) |
| ('sqlite3_module*', (v)=> |
| __xArgPtr((v instanceof (capi.sqlite3_module || nilType)) |
| ? v.pointer : v) |
| ); |
| } |
| |
| const __xRcPtr = wasm.xWrap.resultAdapter('*'); |
| wasm.xWrap.resultAdapter('sqlite3*', __xRcPtr) |
| ('sqlite3_context*', __xRcPtr) |
| ('sqlite3_stmt*', __xRcPtr) |
| ('sqlite3_value*', __xRcPtr) |
| ('sqlite3_vfs*', __xRcPtr) |
| ('void*', __xRcPtr); |
| |
| /** |
| Populate api object with sqlite3_...() by binding the "raw" wasm |
| exports into type-converting proxies using wasm.xWrap(). |
| */ |
| if(0 === wasm.exports.sqlite3_step.length){ |
| /* This environment wraps exports in nullary functions, which means |
| we must disable the arg-count validation we otherwise perform |
| on the wrappers. */ |
| wasm.xWrap.doArgcCheck = false; |
| sqlite3.config.warn( |
| "Disabling sqlite3.wasm.xWrap.doArgcCheck due to environmental quirks." |
| ); |
| } |
| for(const e of wasm.bindingSignatures){ |
| capi[e[0]] = wasm.xWrap.apply(null, e); |
| } |
| for(const e of wasm.bindingSignatures.wasmInternal){ |
| util[e[0]] = wasm.xWrap.apply(null, e); |
| } |
| |
| /* For C API functions which cannot work properly unless |
| wasm.bigIntEnabled is true, install a bogus impl which throws |
| if called when bigIntEnabled is false. The alternative would be |
| to elide these functions altogether, which seems likely to |
| cause more confusion. */ |
| const fI64Disabled = function(fname){ |
| return ()=>toss(fname+"() is unavailable due to lack", |
| "of BigInt support in this build."); |
| }; |
| for(const e of wasm.bindingSignatures.int64){ |
| capi[e[0]] = wasm.bigIntEnabled |
| ? wasm.xWrap.apply(null, e) |
| : fI64Disabled(e[0]); |
| } |
| |
| /* 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){ |
| 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 ignored 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 pDb ? __db_err(pDb, resultCode, message) : resultCode; |
| }; |
| }else{ |
| util.sqlite3__wasm_db_error = function(pDb,errCode,msg){ |
| console.warn("sqlite3__wasm_db_error() is not exported.",arguments); |
| return errCode; |
| }; |
| } |
| }/*xWrap() bindings*/ |
| |
| {/* Import C-level constants and structs... */ |
| const cJson = wasm.xCall('sqlite3__wasm_enum_json'); |
| if(!cJson){ |
| toss("Maintenance required: increase sqlite3__wasm_enum_json()'s", |
| "static buffer size!"); |
| } |
| //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); |
| wasm.ctype = JSON.parse(wasm.cstrToJs(cJson)); |
| // Groups of SQLITE_xyz macros... |
| const defineGroups = ['access', 'authorizer', |
| 'blobFinalizers', 'changeset', |
| 'config', 'dataTypes', |
| 'dbConfig', 'dbStatus', |
| 'encodings', 'fcntl', 'flock', 'ioCap', |
| 'limits', 'openFlags', |
| 'prepareFlags', 'resultCodes', |
| 'sqlite3Status', |
| 'stmtStatus', 'syncFlags', |
| 'trace', 'txnState', 'udfFlags', |
| 'version' ]; |
| if(wasm.bigIntEnabled){ |
| defineGroups.push('serialize', 'session', '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. |
| capi[e[0]] = e[1]; |
| } |
| } |
| if(!wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){ |
| toss("Internal error: cannot resolve exported function", |
| "entry SQLITE_WASM_DEALLOC (=="+capi.SQLITE_WASM_DEALLOC+")."); |
| } |
| const __rcMap = Object.create(null); |
| for(const t of ['resultCodes']){ |
| for(const e of Object.entries(wasm.ctype[t])){ |
| __rcMap[e[1]] = e[0]; |
| } |
| } |
| /** |
| For the given integer, returns the SQLITE_xxx result code as a |
| string, or undefined if no such mapping is found. |
| */ |
| capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; |
| /* Bind all registered C-side structs... */ |
| const notThese = Object.assign(Object.create(null),{ |
| // 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 |
| }); |
| for(const s of wasm.ctype.structs){ |
| if(!notThese[s.name]){ |
| capi[s.name] = sqlite3.StructBinder(s); |
| } |
| } |
| 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 = wasm.xWrap( |
| 'sqlite3__wasm_vtab_config','int',[ |
| 'sqlite3*', 'int', 'int'] |
| ); |
| }/* end vtab-related setup */ |
| }/*end C constant and struct imports*/ |
| |
| /** |
| 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. |
| */ |
| const __dbArgcMismatch = (pDb,f,n)=>{ |
| return util.sqlite3__wasm_db_error(pDb, capi.SQLITE_MISUSE, |
| f+"() requires "+n+" argument"+ |
| (1===n?"":'s')+"."); |
| }; |
| |
| /** Code duplication reducer for functions which take an encoding |
| argument and require SQLITE_UTF8. Sets the db error code to |
| SQLITE_FORMAT, installs a descriptive error message, |
| and returns SQLITE_FORMAT. */ |
| const __errEncoding = (pDb)=>{ |
| return util.sqlite3__wasm_db_error( |
| pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding." |
| ); |
| }; |
| |
| /** |
| __dbCleanupMap is infrastructure for recording registration of |
| UDFs and collations so that sqlite3_close_v2() can clean up any |
| automated JS-to-WASM function conversions installed by those. |
| */ |
| const __argPDb = (pDb)=>wasm.xWrap.argAdapter('sqlite3*')(pDb); |
| const __argStr = (str)=>wasm.isPtr(str) ? wasm.cstrToJs(str) : str; |
| const __dbCleanupMap = function( |
| pDb, mode/*0=remove, >0=create if needed, <0=do not create if missing*/ |
| ){ |
| pDb = __argPDb(pDb); |
| let m = this.dbMap.get(pDb); |
| if(!mode){ |
| this.dbMap.delete(pDb); |
| return m; |
| }else if(!m && mode>0){ |
| this.dbMap.set(pDb, (m = Object.create(null))); |
| } |
| return m; |
| }.bind(Object.assign(Object.create(null),{ |
| dbMap: new Map |
| })); |
| |
| __dbCleanupMap.addCollation = function(pDb, name){ |
| const m = __dbCleanupMap(pDb, 1); |
| if(!m.collation) m.collation = new Set; |
| m.collation.add(__argStr(name).toLowerCase()); |
| }; |
| |
| __dbCleanupMap._addUDF = function(pDb, name, arity, map){ |
| /* Map UDF name to a Set of arity values */ |
| name = __argStr(name).toLowerCase(); |
| let u = map.get(name); |
| if(!u) map.set(name, (u = new Set)); |
| u.add((arity<0) ? -1 : arity); |
| }; |
| |
| __dbCleanupMap.addFunction = function(pDb, name, arity){ |
| const m = __dbCleanupMap(pDb, 1); |
| if(!m.udf) m.udf = new Map; |
| this._addUDF(pDb, name, arity, m.udf); |
| }; |
| |
| if( wasm.exports.sqlite3_create_window_function ){ |
| __dbCleanupMap.addWindowFunc = function(pDb, name, arity){ |
| const m = __dbCleanupMap(pDb, 1); |
| if(!m.wudf) m.wudf = new Map; |
| this._addUDF(pDb, name, arity, m.wudf); |
| }; |
| } |
| |
| /** |
| Intended to be called _only_ from sqlite3_close_v2(), |
| passed its non-0 db argument. |
| |
| This function frees up certain automatically-installed WASM |
| function bindings which were installed on behalf of the given db, |
| as those may otherwise leak. |
| |
| Notable caveat: this is only ever run via |
| sqlite3.capi.sqlite3_close_v2(). If a client, for whatever |
| reason, uses sqlite3.wasm.exports.sqlite3_close_v2() (the |
| function directly exported from WASM), this cleanup will not |
| happen. |
| |
| This is not a silver bullet for avoiding automation-related |
| leaks but represents "an honest effort." |
| |
| The issue being addressed here is covered at: |
| |
| https://sqlite.org/wasm/doc/trunk/api-c-style.md#convert-func-ptr |
| */ |
| __dbCleanupMap.cleanup = function(pDb){ |
| pDb = __argPDb(pDb); |
| //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; |
| /** |
| Installing NULL functions in the C API will remove those |
| bindings. The FuncPtrAdapter which sits between us and the C |
| API will also treat that as an opportunity to |
| wasm.uninstallFunction() any WASM function bindings it has |
| installed for pDb. |
| */ |
| const closeArgs = [pDb]; |
| for(const name of [ |
| 'sqlite3_busy_handler', |
| 'sqlite3_commit_hook', |
| 'sqlite3_preupdate_hook', |
| 'sqlite3_progress_handler', |
| 'sqlite3_rollback_hook', |
| 'sqlite3_set_authorizer', |
| 'sqlite3_trace_v2', |
| 'sqlite3_update_hook' |
| ]) { |
| const x = wasm.exports[name]; |
| if( !x ){ |
| /* assume it was built without this API */ |
| continue; |
| } |
| closeArgs.length = x.length/*==argument count*/ |
| /* recall that undefined entries translate to 0 when passed to |
| WASM. */; |
| try{ capi[name](...closeArgs) } |
| catch(e){ |
| sqlite3.config.warn("close-time call of",name+"(",closeArgs,") threw:",e); |
| } |
| } |
| const m = __dbCleanupMap(pDb, 0); |
| if(!m) return; |
| if(m.collation){ |
| for(const name of m.collation){ |
| try{ |
| capi.sqlite3_create_collation_v2( |
| pDb, name, capi.SQLITE_UTF8, 0, 0, 0 |
| ); |
| }catch(e){ |
| /*ignored*/ |
| } |
| } |
| delete m.collation; |
| } |
| let i; |
| for(i = 0; i < 2; ++i){ /* Clean up UDFs... */ |
| const fmap = i ? m.wudf : m.udf; |
| if(!fmap) continue; |
| const func = i |
| ? capi.sqlite3_create_window_function |
| : capi.sqlite3_create_function_v2; |
| for(const e of fmap){ |
| const name = e[0], arities = e[1]; |
| const fargs = [pDb, name, 0/*arity*/, capi.SQLITE_UTF8, 0, 0, 0, 0, 0]; |
| if(i) fargs.push(0); |
| for(const arity of arities){ |
| try{ fargs[2] = arity; func.apply(null, fargs); } |
| catch(e){/*ignored*/} |
| } |
| arities.clear(); |
| } |
| fmap.clear(); |
| } |
| delete m.udf; |
| delete m.wudf; |
| }/*__dbCleanupMap.cleanup()*/; |
| |
| {/* Binding of sqlite3_close_v2() */ |
| const __sqlite3CloseV2 = wasm.xWrap("sqlite3_close_v2", "int", "sqlite3*"); |
| capi.sqlite3_close_v2 = function(pDb){ |
| if(1!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_close_v2', 1); |
| if(pDb){ |
| try{__dbCleanupMap.cleanup(pDb)} catch(e){/*ignored*/} |
| } |
| return __sqlite3CloseV2(pDb); |
| }; |
| }/*sqlite3_close_v2()*/ |
| |
| if(capi.sqlite3session_create){ |
| const __sqlite3SessionDelete = wasm.xWrap( |
| 'sqlite3session_delete', undefined, ['sqlite3_session*'] |
| ); |
| capi.sqlite3session_delete = function(pSession){ |
| if(1!==arguments.length){ |
| return __dbArgcMismatch(pDb, 'sqlite3session_delete', 1); |
| /* Yes, we're returning a value from a void function. That seems |
| like the lesser evil compared to not maintaining arg-count |
| consistency as we do with other similar bindings. */ |
| } |
| else if(pSession){ |
| //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; |
| capi.sqlite3session_table_filter(pSession, 0, 0); |
| } |
| __sqlite3SessionDelete(pSession); |
| }; |
| } |
| |
| {/* Bindings for sqlite3_create_collation[_v2]() */ |
| // contextKey() impl for wasm.xWrap.FuncPtrAdapter |
| const contextKey = (argv,argIndex)=>{ |
| return 'argv['+argIndex+']:'+argv[0/* sqlite3* */]+ |
| ':'+wasm.cstrToJs(argv[1/* collation name */]).toLowerCase() |
| }; |
| const __sqlite3CreateCollationV2 = wasm.xWrap( |
| 'sqlite3_create_collation_v2', 'int', [ |
| 'sqlite3*', 'string', 'int', '*', |
| new wasm.xWrap.FuncPtrAdapter({ |
| /* int(*xCompare)(void*,int,const void*,int,const void*) */ |
| name: 'xCompare', signature: 'i(pipip)', contextKey |
| }), |
| new wasm.xWrap.FuncPtrAdapter({ |
| /* void(*xDestroy(void*) */ |
| name: 'xDestroy', signature: 'v(p)', contextKey |
| }) |
| ] |
| ); |
| |
| /** |
| Works exactly like C's sqlite3_create_collation_v2() except that: |
| |
| 1) It returns capi.SQLITE_FORMAT if the 3rd argument contains |
| any encoding-related value other than capi.SQLITE_UTF8. No |
| other encodings are supported. As a special case, if the |
| bottom 4 bits of that argument are 0, SQLITE_UTF8 is |
| assumed. |
| |
| 2) 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 calls this again with the |
| same collation name and a value of 0 or null for the |
| the function pointer(s). |
| |
| For consistency with the C API, it requires the same number of |
| arguments. It returns capi.SQLITE_MISUSE if passed any other |
| argument count. |
| |
| 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( 0 === (eTextRep & 0xf) ){ |
| eTextRep |= capi.SQLITE_UTF8; |
| }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ |
| return __errEncoding(pDb); |
| } |
| try{ |
| const rc = __sqlite3CreateCollationV2(pDb, zName, eTextRep, pArg, xCompare, xDestroy); |
| if(0===rc && xCompare instanceof Function){ |
| __dbCleanupMap.addCollation(pDb, zName); |
| } |
| return rc; |
| }catch(e){ |
| return util.sqlite3__wasm_db_error(pDb, e); |
| } |
| }; |
| |
| 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*/ |
| |
| {/* Special-case handling of sqlite3_create_function_v2() |
| and sqlite3_create_window_function(). */ |
| /** FuncPtrAdapter for contextKey() for sqlite3_create_function() |
| and friends. */ |
| const contextKey = function(argv,argIndex){ |
| return ( |
| argv[0/* sqlite3* */] |
| +':'+(argv[2/*number of UDF args*/] < 0 ? -1 : argv[2]) |
| +':'+argIndex/*distinct for each xAbc callback type*/ |
| +':'+wasm.cstrToJs(argv[1]).toLowerCase() |
| ) |
| }; |
| |
| /** |
| JS proxies for the various sqlite3_create[_window]_function() |
| callbacks, structured in a form usable by wasm.xWrap.FuncPtrAdapter. |
| */ |
| const __cfProxy = Object.assign(Object.create(null), { |
| xInverseAndStep: { |
| signature:'v(pip)', contextKey, |
| callProxy: (callback)=>{ |
| return (pCtx, argc, pArgv)=>{ |
| try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) } |
| catch(e){ capi.sqlite3_result_error_js(pCtx, e) } |
| }; |
| } |
| }, |
| xFinalAndValue: { |
| signature:'v(p)', contextKey, |
| callProxy: (callback)=>{ |
| return (pCtx)=>{ |
| try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) } |
| catch(e){ capi.sqlite3_result_error_js(pCtx, e) } |
| }; |
| } |
| }, |
| xFunc: { |
| signature:'v(pip)', contextKey, |
| callProxy: (callback)=>{ |
| return (pCtx, argc, pArgv)=>{ |
| try{ |
| capi.sqlite3_result_js( |
| pCtx, |
| callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) |
| ); |
| }catch(e){ |
| //console.error('xFunc() caught:',e); |
| capi.sqlite3_result_error_js(pCtx, e); |
| } |
| }; |
| } |
| }, |
| xDestroy: { |
| signature:'v(p)', contextKey, |
| //Arguable: a well-behaved destructor doesn't require a proxy. |
| callProxy: (callback)=>{ |
| return (pVoid)=>{ |
| try{ callback(pVoid) } |
| catch(e){ console.error("UDF xDestroy method threw:",e) } |
| }; |
| } |
| } |
| })/*__cfProxy*/; |
| |
| const __sqlite3CreateFunction = wasm.xWrap( |
| "sqlite3_create_function_v2", "int", [ |
| "sqlite3*", "string"/*funcName*/, "int"/*nArg*/, |
| "int"/*eTextRep*/, "*"/*pApp*/, |
| new wasm.xWrap.FuncPtrAdapter({name: 'xFunc', ...__cfProxy.xFunc}), |
| new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}), |
| new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}), |
| new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy}) |
| ] |
| ); |
| |
| const __sqlite3CreateWindowFunction = |
| wasm.exports.sqlite3_create_window_function |
| ? wasm.xWrap( |
| "sqlite3_create_window_function", "int", [ |
| "sqlite3*", "string"/*funcName*/, "int"/*nArg*/, |
| "int"/*eTextRep*/, "*"/*pApp*/, |
| new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}), |
| new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}), |
| new wasm.xWrap.FuncPtrAdapter({name: 'xValue', ...__cfProxy.xFinalAndValue}), |
| new wasm.xWrap.FuncPtrAdapter({name: 'xInverse', ...__cfProxy.xInverseAndStep}), |
| new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy}) |
| ] |
| ) |
| : undefined; |
| |
| /* Documented in the api object's initializer. */ |
| capi.sqlite3_create_function_v2 = function f( |
| pDb, funcName, nArg, eTextRep, pApp, |
| xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**) |
| xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) |
| xFinal, //void (*xFinal)(sqlite3_context*) |
| xDestroy //void (*xDestroy)(void*) |
| ){ |
| if( f.length!==arguments.length ){ |
| return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length); |
| }else if( 0 === (eTextRep & 0xf) ){ |
| eTextRep |= capi.SQLITE_UTF8; |
| }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ |
| return __errEncoding(pDb); |
| } |
| try{ |
| const rc = __sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, |
| pApp, xFunc, xStep, xFinal, xDestroy); |
| if(0===rc && (xFunc instanceof Function |
| || xStep instanceof Function |
| || xFinal instanceof Function |
| || xDestroy instanceof Function)){ |
| __dbCleanupMap.addFunction(pDb, funcName, nArg); |
| } |
| return rc; |
| }catch(e){ |
| console.error("sqlite3_create_function_v2() setup threw:",e); |
| return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e); |
| } |
| }; |
| |
| /* Documented in the api object's initializer. */ |
| capi.sqlite3_create_function = function f( |
| pDb, funcName, nArg, eTextRep, pApp, |
| xFunc, xStep, xFinal |
| ){ |
| return (f.length===arguments.length) |
| ? capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep, |
| pApp, xFunc, xStep, xFinal, 0) |
| : __dbArgcMismatch(pDb,"sqlite3_create_function",f.length); |
| }; |
| |
| /* Documented in the api object's initializer. */ |
| if( __sqlite3CreateWindowFunction ){ |
| capi.sqlite3_create_window_function = function f( |
| pDb, funcName, nArg, eTextRep, pApp, |
| xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) |
| xFinal, //void (*xFinal)(sqlite3_context*) |
| xValue, //void (*xValue)(sqlite3_context*) |
| xInverse,//void (*xInverse)(sqlite3_context*,int,sqlite3_value**) |
| xDestroy //void (*xDestroy)(void*) |
| ){ |
| if( f.length!==arguments.length ){ |
| return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length); |
| }else if( 0 === (eTextRep & 0xf) ){ |
| eTextRep |= capi.SQLITE_UTF8; |
| }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ |
| return __errEncoding(pDb); |
| } |
| try{ |
| const rc = __sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, |
| pApp, xStep, xFinal, xValue, |
| xInverse, xDestroy); |
| if(0===rc && (xStep instanceof Function |
| || xFinal instanceof Function |
| || xValue instanceof Function |
| || xInverse instanceof Function |
| || xDestroy instanceof Function)){ |
| __dbCleanupMap.addWindowFunc(pDb, funcName, nArg); |
| } |
| return rc; |
| }catch(e){ |
| console.error("sqlite3_create_window_function() setup threw:",e); |
| return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e); |
| } |
| }; |
| }else{ |
| delete capi.sqlite3_create_window_function; |
| } |
| /** |
| 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_result_js; |
| if(capi.sqlite3_create_window_function){ |
| capi.sqlite3_create_window_function.udfSetResult = capi.sqlite3_result_js; |
| } |
| |
| /** |
| 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_values_to_js; |
| if(capi.sqlite3_create_window_function){ |
| capi.sqlite3_create_window_function.udfConvertArgs = capi.sqlite3_values_to_js; |
| } |
| |
| /** |
| 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_result_error_js; |
| if(capi.sqlite3_create_window_function){ |
| capi.sqlite3_create_window_function.udfSetError = capi.sqlite3_result_error_js; |
| } |
| |
| }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/; |
| |
| {/* 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 or ArrayBuffer). |
| */ |
| const __flexiString = (v,n)=>{ |
| if('string'===typeof v){ |
| n = -1; |
| }else if(util.isSQLableTypedArray(v)){ |
| n = v.byteLength; |
| v = util.typedArrayToString( |
| (v instanceof ArrayBuffer) ? new Uint8Array(v) : 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 = { |
| /** |
| This binding expects a JS string as its 2nd argument and |
| null as its final argument. In order to compile multiple |
| statements from a single string, the "full" impl (see |
| below) must be used. |
| */ |
| basic: wasm.xWrap('sqlite3_prepare_v3', |
| "int", ["sqlite3*", "string", |
| "int"/*ignored for this impl!*/, |
| "int", "**", |
| "**"/*MUST be 0 or null or undefined!*/]), |
| /** |
| Impl which requires that the 2nd argument be a pointer |
| to the SQL string, instead of being converted to a |
| string. This variant is necessary for cases where we |
| require a non-NULL value for the final argument |
| (exec()'ing multiple statements from one input |
| string). For simpler cases, where only the first |
| statement in the SQL string is required, the wrapper |
| named sqlite3_prepare_v2() is sufficient and easier to |
| use because it doesn't require dealing with pointers. |
| */ |
| full: wasm.xWrap('sqlite3_prepare_v3', |
| "int", ["sqlite3*", "*", "int", "int", |
| "**", "**"]) |
| }; |
| |
| /* 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); |
| } |
| const [xSql, xSqlLen] = __flexiString(sql, sqlLen); |
| switch(typeof xSql){ |
| case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null); |
| case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail); |
| default: |
| return util.sqlite3__wasm_db_error( |
| pDb, capi.SQLITE_MISUSE, |
| "Invalid SQL argument type for sqlite3_prepare_v2/v3()." |
| ); |
| } |
| }; |
| |
| /* 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) |
| : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length); |
| }; |
| |
| }/*sqlite3_prepare_v2/v3()*/ |
| |
| {/*sqlite3_bind_text/blob()*/ |
| const __bindText = wasm.xWrap("sqlite3_bind_text", "int", [ |
| "sqlite3_stmt*", "int", "string", "int", "*" |
| ]); |
| const __bindBlob = wasm.xWrap("sqlite3_bind_blob", "int", [ |
| "sqlite3_stmt*", "int", "*", "int", "*" |
| ]); |
| |
| /** Documented in the capi object's initializer. */ |
| capi.sqlite3_bind_text = function f(pStmt, iCol, text, nText, xDestroy){ |
| if(f.length!==arguments.length){ |
| return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt), |
| "sqlite3_bind_text", f.length); |
| }else if(wasm.isPtr(text) || null===text){ |
| return __bindText(pStmt, iCol, text, nText, xDestroy); |
| }else if(text instanceof ArrayBuffer){ |
| text = new Uint8Array(text); |
| }else if(Array.isArray(pMem)){ |
| text = pMem.join(''); |
| } |
| let p, n; |
| try{ |
| if(util.isSQLableTypedArray(text)){ |
| p = wasm.allocFromTypedArray(text); |
| n = text.byteLength; |
| }else if('string'===typeof text){ |
| [p, n] = wasm.allocCString(text); |
| }else{ |
| return util.sqlite3__wasm_db_error( |
| capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, |
| "Invalid 3rd argument type for sqlite3_bind_text()." |
| ); |
| } |
| return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); |
| }catch(e){ |
| wasm.dealloc(p); |
| return util.sqlite3__wasm_db_error( |
| capi.sqlite3_db_handle(pStmt), e |
| ); |
| } |
| }/*sqlite3_bind_text()*/; |
| |
| /** Documented in the capi object's initializer. */ |
| capi.sqlite3_bind_blob = function f(pStmt, iCol, pMem, nMem, xDestroy){ |
| if(f.length!==arguments.length){ |
| return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt), |
| "sqlite3_bind_blob", f.length); |
| }else if(wasm.isPtr(pMem) || null===pMem){ |
| return __bindBlob(pStmt, iCol, pMem, nMem, xDestroy); |
| }else if(pMem instanceof ArrayBuffer){ |
| pMem = new Uint8Array(pMem); |
| }else if(Array.isArray(pMem)){ |
| pMem = pMem.join(''); |
| } |
| let p, n; |
| try{ |
| if(util.isBindableTypedArray(pMem)){ |
| p = wasm.allocFromTypedArray(pMem); |
| n = nMem>=0 ? nMem : pMem.byteLength; |
| }else if('string'===typeof pMem){ |
| [p, n] = wasm.allocCString(pMem); |
| }else{ |
| return util.sqlite3__wasm_db_error( |
| capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, |
| "Invalid 3rd argument type for sqlite3_bind_blob()." |
| ); |
| } |
| return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); |
| }catch(e){ |
| wasm.dealloc(p); |
| return util.sqlite3__wasm_db_error( |
| capi.sqlite3_db_handle(pStmt), e |
| ); |
| } |
| }/*sqlite3_bind_blob()*/; |
| |
| }/*sqlite3_bind_text/blob()*/ |
| |
| {/* sqlite3_config() */ |
| /** |
| Wraps a small subset of the C API's sqlite3_config() options. |
| Unsupported options trigger the return of capi.SQLITE_NOTFOUND. |
| Passing fewer than 2 arguments triggers return of |
| capi.SQLITE_MISUSE. |
| */ |
| capi.sqlite3_config = function(op, ...args){ |
| if(arguments.length<2) return capi.SQLITE_MISUSE; |
| switch(op){ |
| case capi.SQLITE_CONFIG_COVERING_INDEX_SCAN: // 20 /* int */ |
| case capi.SQLITE_CONFIG_MEMSTATUS:// 9 /* boolean */ |
| case capi.SQLITE_CONFIG_SMALL_MALLOC: // 27 /* boolean */ |
| case capi.SQLITE_CONFIG_SORTERREF_SIZE: // 28 /* int nByte */ |
| case capi.SQLITE_CONFIG_STMTJRNL_SPILL: // 26 /* int nByte */ |
| case capi.SQLITE_CONFIG_URI:// 17 /* int */ |
| return wasm.exports.sqlite3__wasm_config_i(op, args[0]); |
| case capi.SQLITE_CONFIG_LOOKASIDE: // 13 /* int int */ |
| return wasm.exports.sqlite3__wasm_config_ii(op, args[0], args[1]); |
| case capi.SQLITE_CONFIG_MEMDB_MAXSIZE: // 29 /* sqlite3_int64 */ |
| return wasm.exports.sqlite3__wasm_config_j(op, args[0]); |
| case capi.SQLITE_CONFIG_GETMALLOC: // 5 /* sqlite3_mem_methods* */ |
| case capi.SQLITE_CONFIG_GETMUTEX: // 11 /* sqlite3_mutex_methods* */ |
| case capi.SQLITE_CONFIG_GETPCACHE2: // 19 /* sqlite3_pcache_methods2* */ |
| case capi.SQLITE_CONFIG_GETPCACHE: // 15 /* no-op */ |
| case capi.SQLITE_CONFIG_HEAP: // 8 /* void*, int nByte, int min */ |
| case capi.SQLITE_CONFIG_LOG: // 16 /* xFunc, void* */ |
| case capi.SQLITE_CONFIG_MALLOC:// 4 /* sqlite3_mem_methods* */ |
| case capi.SQLITE_CONFIG_MMAP_SIZE: // 22 /* sqlite3_int64, sqlite3_int64 */ |
| case capi.SQLITE_CONFIG_MULTITHREAD: // 2 /* nil */ |
| case capi.SQLITE_CONFIG_MUTEX: // 10 /* sqlite3_mutex_methods* */ |
| case capi.SQLITE_CONFIG_PAGECACHE: // 7 /* void*, int sz, int N */ |
| case capi.SQLITE_CONFIG_PCACHE2: // 18 /* sqlite3_pcache_methods2* */ |
| case capi.SQLITE_CONFIG_PCACHE: // 14 /* no-op */ |
| case capi.SQLITE_CONFIG_PCACHE_HDRSZ: // 24 /* int *psz */ |
| case capi.SQLITE_CONFIG_PMASZ: // 25 /* unsigned int szPma */ |
| case capi.SQLITE_CONFIG_SERIALIZED: // 3 /* nil */ |
| case capi.SQLITE_CONFIG_SINGLETHREAD: // 1 /* nil */: |
| case capi.SQLITE_CONFIG_SQLLOG: // 21 /* xSqllog, void* */ |
| case capi.SQLITE_CONFIG_WIN32_HEAPSIZE: // 23 /* int nByte */ |
| default: |
| /* maintenance note: we specifically do not include |
| SQLITE_CONFIG_ROWID_IN_VIEW here, on the grounds that |
| it's only for legacy support and no apps written with |
| this API require that. */ |
| return capi.SQLITE_NOTFOUND; |
| } |
| }; |
| }/* sqlite3_config() */ |
| |
| {/*auto-extension bindings.*/ |
| const __autoExtFptr = new Set; |
| |
| capi.sqlite3_auto_extension = function(fPtr){ |
| if( fPtr instanceof Function ){ |
| fPtr = wasm.installFunction('i(ppp)', fPtr); |
| }else if( 1!==arguments.length || !wasm.isPtr(fPtr) ){ |
| return capi.SQLITE_MISUSE; |
| } |
| const rc = wasm.exports.sqlite3_auto_extension(fPtr); |
| if( fPtr!==arguments[0] ){ |
| if(0===rc) __autoExtFptr.add(fPtr); |
| else wasm.uninstallFunction(fPtr); |
| } |
| return rc; |
| }; |
| |
| capi.sqlite3_cancel_auto_extension = function(fPtr){ |
| /* We do not do an automatic JS-to-WASM function conversion here |
| because it would be senseless: the converted pointer would |
| never possibly match an already-installed one. */; |
| if(!fPtr || 1!==arguments.length || !wasm.isPtr(fPtr)) return 0; |
| return wasm.exports.sqlite3_cancel_auto_extension(fPtr); |
| /* Note that it "cannot happen" that a client passes a pointer which |
| is in __autoExtFptr because __autoExtFptr only contains automatic |
| conversions created inside sqlite3_auto_extension() and |
| never exposed to the client. */ |
| }; |
| |
| capi.sqlite3_reset_auto_extension = function(){ |
| wasm.exports.sqlite3_reset_auto_extension(); |
| for(const fp of __autoExtFptr) wasm.uninstallFunction(fp); |
| __autoExtFptr.clear(); |
| }; |
| }/* auto-extension */ |
| |
| const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); |
| if( pKvvfs ){/* kvvfs-specific glue */ |
| if(util.isUIThread()){ |
| const kvvfsMethods = new capi.sqlite3_kvvfs_methods( |
| wasm.exports.sqlite3__wasm_kvvfs_methods() |
| ); |
| delete capi.sqlite3_kvvfs_methods; |
| |
| const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack, |
| pstack = wasm.pstack; |
| |
| const kvvfsStorage = (zClass)=> |
| ((115/*=='s'*/===wasm.peek(zClass)) |
| ? sessionStorage : localStorage); |
| |
| /** |
| Implementations for members of the object referred to by |
| sqlite3__wasm_kvvfs_methods(). We swap out the native |
| implementations with these, which use localStorage or |
| sessionStorage for their backing store. |
| */ |
| const kvvfsImpls = { |
| xRead: (zClass, zKey, zBuf, nBuf)=>{ |
| const stack = pstack.pointer, |
| astack = wasm.scopedAllocPush(); |
| try { |
| const zXKey = kvvfsMakeKey(zClass,zKey); |
| if(!zXKey) return -3/*OOM*/; |
| 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 |
| ASCII so that jV.length is equal to the |
| C-string's byte length. */; |
| if(nBuf<=0) return nV; |
| else if(1===nBuf){ |
| 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.poke(zBuf + nBuf - 1, 0); |
| return nBuf - 1; |
| }catch(e){ |
| console.error("kvstorageRead()",e); |
| return -2; |
| }finally{ |
| pstack.restore(stack); |
| wasm.scopedAllocPop(astack); |
| } |
| }, |
| xWrite: (zClass, zKey, zData)=>{ |
| const stack = pstack.pointer; |
| try { |
| const zXKey = kvvfsMakeKey(zClass,zKey); |
| if(!zXKey) return 1/*OOM*/; |
| const jKey = wasm.cstrToJs(zXKey); |
| kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData)); |
| return 0; |
| }catch(e){ |
| console.error("kvstorageWrite()",e); |
| return capi.SQLITE_IOERR; |
| }finally{ |
| pstack.restore(stack); |
| } |
| }, |
| xDelete: (zClass, zKey)=>{ |
| const stack = pstack.pointer; |
| try { |
| const zXKey = kvvfsMakeKey(zClass,zKey); |
| if(!zXKey) return 1/*OOM*/; |
| kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey)); |
| return 0; |
| }catch(e){ |
| console.error("kvstorageDelete()",e); |
| return capi.SQLITE_IOERR; |
| }finally{ |
| pstack.restore(stack); |
| } |
| } |
| }/*kvvfsImpls*/; |
| for(const k of Object.keys(kvvfsImpls)){ |
| kvvfsMethods[kvvfsMethods.memberKey(k)] = |
| wasm.installFunction( |
| kvvfsMethods.memberSignature(k), |
| kvvfsImpls[k] |
| ); |
| } |
| }else{ |
| /* Worker thread: unregister kvvfs to avoid it being used |
| for anything other than local/sessionStorage. It "can" |
| be used that way but it's not really intended to be. */ |
| capi.sqlite3_vfs_unregister(pKvvfs); |
| } |
| }/*pKvvfs*/ |
| |
| /* Warn if client-level code makes use of FuncPtrAdapter. */ |
| wasm.xWrap.FuncPtrAdapter.warnOnUse = true; |
| |
| const StructBinder = sqlite3.StructBinder |
| /* we require a local alias b/c StructBinder is removed from the sqlite3 |
| object during the final steps of the API cleanup. */; |
| /** |
| Installs a StructBinder-bound function pointer member of the |
| given name and function in the given StructBinder.StructType |
| target object. |
| |
| It creates a WASM proxy for the given function and arranges for |
| that proxy to be cleaned up when tgt.dispose() is called. Throws |
| on the slightest hint of error, e.g. tgt is-not-a StructType, |
| name does not map to a struct-bound member, etc. |
| |
| As a special case, if the given function is a pointer, then |
| `wasm.functionEntry()` is used to validate that it is a known |
| function. If so, it is used as-is with no extra level of proxying |
| or cleanup, else an exception is thrown. It is legal to pass a |
| value of 0, indicating a NULL pointer, with the caveat that 0 |
| _is_ a legal function pointer in WASM but it will not be accepted |
| as such _here_. (Justification: the function at address zero must |
| be one which initially came from the WASM module, not a method we |
| want to bind to a virtual table or VFS.) |
| |
| This function returns a proxy for itself which is bound to tgt |
| and takes 2 args (name,func). That function returns the same |
| thing as this one, permitting calls to be chained. |
| |
| If called with only 1 arg, it has no side effects but returns a |
| func with the same signature as described above. |
| |
| ACHTUNG: because we cannot generically know how to transform JS |
| exceptions into result codes, the installed functions do no |
| automatic catching of exceptions. It is critical, to avoid |
| undefined behavior in the C layer, that methods mapped via |
| this function do not throw. The exception, as it were, to that |
| rule is... |
| |
| If applyArgcCheck is true then each JS function (as opposed to |
| function pointers) gets wrapped in a proxy which asserts that it |
| is passed the expected number of arguments, throwing if the |
| argument count does not match expectations. That is only intended |
| for dev-time usage for sanity checking, and may leave the C |
| environment in an undefined state. |
| */ |
| const installMethod = function callee( |
| tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck |
| ){ |
| if(!(tgt instanceof StructBinder.StructType)){ |
| toss("Usage error: target object is-not-a StructType."); |
| }else if(!(func instanceof Function) && !wasm.isPtr(func)){ |
| toss("Usage errror: expecting a Function or WASM pointer to one."); |
| } |
| if(1===arguments.length){ |
| return (n,f)=>callee(tgt, n, f, applyArgcCheck); |
| } |
| if(!callee.argcProxy){ |
| callee.argcProxy = function(tgt, funcName, func,sig){ |
| return function(...args){ |
| if(func.length!==arguments.length){ |
| toss("Argument mismatch for", |
| tgt.structInfo.name+"::"+funcName |
| +": Native signature is:",sig); |
| } |
| return func.apply(this, args); |
| } |
| }; |
| /* An ondispose() callback for use with |
| StructBinder-created types. */ |
| callee.removeFuncList = function(){ |
| if(this.ondispose.__removeFuncList){ |
| this.ondispose.__removeFuncList.forEach( |
| (v,ndx)=>{ |
| if('number'===typeof v){ |
| try{wasm.uninstallFunction(v)} |
| catch(e){/*ignore*/} |
| } |
| /* else it's a descriptive label for the next number in |
| the list. */ |
| } |
| ); |
| delete this.ondispose.__removeFuncList; |
| } |
| }; |
| }/*static init*/ |
| const sigN = tgt.memberSignature(name); |
| if(sigN.length<2){ |
| toss("Member",name,"does not have a function pointer signature:",sigN); |
| } |
| const memKey = tgt.memberKey(name); |
| const fProxy = (applyArgcCheck && !wasm.isPtr(func)) |
| /** This middle-man proxy is only for use during development, to |
| confirm that we always pass the proper number of |
| arguments. We know that the C-level code will always use the |
| correct argument count. */ |
| ? callee.argcProxy(tgt, memKey, func, sigN) |
| : func; |
| if(wasm.isPtr(fProxy)){ |
| if(fProxy && !wasm.functionEntry(fProxy)){ |
| toss("Pointer",fProxy,"is not a WASM function table entry."); |
| } |
| tgt[memKey] = fProxy; |
| }else{ |
| const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); |
| tgt[memKey] = pFunc; |
| if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){ |
| tgt.addOnDispose('ondispose.__removeFuncList handler', |
| callee.removeFuncList); |
| tgt.ondispose.__removeFuncList = []; |
| } |
| tgt.ondispose.__removeFuncList.push(memKey, pFunc); |
| } |
| return (n,f)=>callee(tgt, n, f, applyArgcCheck); |
| }/*installMethod*/; |
| installMethod.installMethodArgcCheck = false; |
| |
| /** |
| Installs methods into the given StructBinder.StructType-type |
| instance. Each entry in the given methods object must map to a |
| known member of the given StructType, else an exception will be |
| triggered. See installMethod() for more details, including the |
| semantics of the 3rd argument. |
| |
| As an exception to the above, if any two or more methods in the |
| 2nd argument are the exact same function, installMethod() is |
| _not_ called for the 2nd and subsequent instances, and instead |
| those instances get assigned the same method pointer which is |
| created for the first instance. This optimization is primarily to |
| accommodate special handling of sqlite3_module::xConnect and |
| xCreate methods. |
| |
| On success, returns its first argument. Throws on error. |
| */ |
| const installMethods = function( |
| structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck |
| ){ |
| const seen = new Map /* map of <Function, memberName> */; |
| for(const k of Object.keys(methods)){ |
| const m = methods[k]; |
| const prior = seen.get(m); |
| if(prior){ |
| const mkey = structInstance.memberKey(k); |
| structInstance[mkey] = structInstance[structInstance.memberKey(prior)]; |
| }else{ |
| installMethod(structInstance, k, m, applyArgcCheck); |
| seen.set(m, k); |
| } |
| } |
| return structInstance; |
| }; |
| |
| /** |
| Equivalent to calling installMethod(this,...arguments) with a |
| first argument of this object. If called with 1 or 2 arguments |
| and the first is an object, it's instead equivalent to calling |
| installMethods(this,...arguments). |
| */ |
| StructBinder.StructType.prototype.installMethod = function callee( |
| name, func, applyArgcCheck = installMethod.installMethodArgcCheck |
| ){ |
| return (arguments.length < 3 && name && 'object'===typeof name) |
| ? installMethods(this, ...arguments) |
| : installMethod(this, ...arguments); |
| }; |
| |
| /** |
| Equivalent to calling installMethods() with a first argument |
| of this object. |
| */ |
| StructBinder.StructType.prototype.installMethods = function( |
| methods, applyArgcCheck = installMethod.installMethodArgcCheck |
| ){ |
| return installMethods(this, methods, applyArgcCheck); |
| }; |
| |
| }); |