diff options
Diffstat (limited to 'ext/wasm/api')
20 files changed, 644 insertions, 354 deletions
diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-auth b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-auth deleted file mode 100644 index 085090821..000000000 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-auth +++ /dev/null @@ -1 +0,0 @@ -_sqlite3_set_authorizer diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core index db47ee7db..2578002ce 100644 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core @@ -41,7 +41,6 @@ _sqlite3_create_collation _sqlite3_create_collation_v2 _sqlite3_create_function _sqlite3_create_function_v2 -_sqlite3_create_window_function _sqlite3_data_count _sqlite3_db_filename _sqlite3_db_handle @@ -49,7 +48,6 @@ _sqlite3_db_name _sqlite3_db_readonly _sqlite3_db_status _sqlite3_deserialize -_sqlite3_drop_modules _sqlite3_errcode _sqlite3_errmsg _sqlite3_error_offset @@ -81,7 +79,6 @@ _sqlite3_open_v2 _sqlite3_overload_function _sqlite3_prepare_v2 _sqlite3_prepare_v3 -_sqlite3_progress_handler _sqlite3_randomness _sqlite3_realloc _sqlite3_realloc64 diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-session b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras index 5b7b53f95..01dad072e 100644 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-session +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras @@ -1,3 +1,27 @@ +_sqlite3_column_database_name +_sqlite3_column_origin_name +_sqlite3_column_table_name +_sqlite3_create_module +_sqlite3_create_module_v2 +_sqlite3_create_window_function +_sqlite3_declare_vtab +_sqlite3_drop_modules +_sqlite3_preupdate_blobwrite +_sqlite3_preupdate_count +_sqlite3_preupdate_depth +_sqlite3_preupdate_hook +_sqlite3_preupdate_new +_sqlite3_preupdate_old +_sqlite3_progress_handler +_sqlite3_set_authorizer +_sqlite3_vtab_collation +_sqlite3_vtab_distinct +_sqlite3_vtab_in +_sqlite3_vtab_in_first +_sqlite3_vtab_in_next +_sqlite3_vtab_nochange +_sqlite3_vtab_on_conflict +_sqlite3_vtab_rhs_value _sqlite3changegroup_add _sqlite3changegroup_add_strm _sqlite3changegroup_delete diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-preupdate b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-preupdate deleted file mode 100644 index 5c57a76b6..000000000 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-preupdate +++ /dev/null @@ -1,6 +0,0 @@ -_sqlite3_preupdate_blobwrite -_sqlite3_preupdate_count -_sqlite3_preupdate_depth -_sqlite3_preupdate_hook -_sqlite3_preupdate_new -_sqlite3_preupdate_old diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-vtab b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-vtab deleted file mode 100644 index 1f6de9682..000000000 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-vtab +++ /dev/null @@ -1,11 +0,0 @@ -_sqlite3_create_module -_sqlite3_create_module_v2 -_sqlite3_declare_vtab -_sqlite3_vtab_collation -_sqlite3_vtab_distinct -_sqlite3_vtab_in -_sqlite3_vtab_in_first -_sqlite3_vtab_in_next -_sqlite3_vtab_nochange -_sqlite3_vtab_on_conflict -_sqlite3_vtab_rhs_value diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md index ebd4aaacb..ce7e58ebe 100644 --- a/ext/wasm/api/README.md +++ b/ext/wasm/api/README.md @@ -85,17 +85,18 @@ browser client: Installs the `sqlite3.vtab` namespace, which contain helpers for use by downstream code which creates `sqlite3_module` implementations. - **`sqlite3-vfs-opfs.c-pp.js`**\ - is an sqlite3 VFS implementation which supports the Origin-Private - FileSystem (OPFS) as a storage layer to provide persistent storage - for database files in a browser. It requires... + is an sqlite3 VFS implementation which supports the [Origin-Private + FileSystem (OPFS)][OPFS] as a storage layer to provide persistent + storage for database files in a browser. It requires... - **`sqlite3-opfs-async-proxy.js`**\ - is the asynchronous backend part of the OPFS proxy. It speaks - directly to the (async) OPFS API and channels those results back - to its synchronous counterpart. This file, because it must be - started in its own Worker, is not part of the amalgamation. + is the asynchronous backend part of the [OPFS][] proxy. It + speaks directly to the (async) OPFS API and channels those + results back to its synchronous counterpart. This file, because + it must be started in its own Worker, is not part of the + amalgamation. - **`sqlite3-vfs-opfs-sahpool.c-pp.js`**\ - is another sqlite3 VFS supporting the OPFS, but uses a completely - different approach that the above-listed one. + is another sqlite3 VFS supporting the [OPFS][], but uses a + completely different approach that the above-listed one. - **`sqlite3-api-cleanup.js`**\ The previous files do not immediately extend the library. Instead they add callback functions to be called during its @@ -152,7 +153,7 @@ into the build-generated `sqlite3.js` along with `sqlite3-api.js`. flag. This file overwrites the Emscripten-installed `sqlite3InitModule()` function with one which, after the module is loaded, also initializes the asynchronous parts of the sqlite3 - module. For example, the OPFS VFS support. + module. For example, the [OPFS][] VFS support. <a id='c-pp'></a> Preprocessing of Source Files @@ -164,3 +165,6 @@ builds. The preprocessor application itself is in [`c-pp.c`](/file/ext/wasm/c-pp.c) and the complete technical details of such preprocessing are maintained in [`GNUMakefile`](/file/ext/wasm/GNUmakefile). + + +[OPFS]: https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system diff --git a/ext/wasm/api/extern-post-js.c-pp.js b/ext/wasm/api/extern-post-js.c-pp.js index 63e55051c..3598c0d6f 100644 --- a/ext/wasm/api/extern-post-js.c-pp.js +++ b/ext/wasm/api/extern-post-js.c-pp.js @@ -12,6 +12,7 @@ const toExportForESM = //#endif (function(){ + //console.warn("this is extern-post-js"); /** In order to hide the sqlite3InitModule()'s resulting Emscripten module from downstream clients (and simplify our @@ -62,6 +63,16 @@ const toExportForESM = globalThis.sqlite3InitModule = function ff(...args){ //console.warn("Using replaced sqlite3InitModule()",globalThis.location); return originalInit(...args).then((EmscriptenModule)=>{ + //console.warn("originalInit() then() arg =",EmscriptenModule); + //console.warn("initModuleState =",initModuleState); + EmscriptenModule.runSQLite3PostLoadInit(EmscriptenModule); + const s = EmscriptenModule.sqlite3; + s.scriptInfo = initModuleState; + //console.warn("sqlite3.scriptInfo =",s.scriptInfo); + if(ff.__isUnderTest) s.__isUnderTest = true; + const f = s.asyncPostInit; + delete s.asyncPostInit; + const rv = f(); //#if wasmfs if('undefined'!==typeof WorkerGlobalScope && EmscriptenModule['ENVIRONMENT_IS_PTHREAD']){ @@ -74,14 +85,7 @@ const toExportForESM = return EmscriptenModule; } //#endif - //console.warn("sqlite3InitModule() returning sqlite3 object."); - const s = EmscriptenModule.sqlite3; - s.scriptInfo = initModuleState; - //console.warn("sqlite3.scriptInfo =",s.scriptInfo); - if(ff.__isUnderTest) s.__isUnderTest = true; - const f = s.asyncPostInit; - delete s.asyncPostInit; - return f(); + return rv; }).catch((e)=>{ console.error("Exception loading sqlite3 module:",e); throw e; diff --git a/ext/wasm/api/post-js-footer.js b/ext/wasm/api/post-js-footer.js index 58882cbd9..7af12cbe3 100644 --- a/ext/wasm/api/post-js-footer.js +++ b/ext/wasm/api/post-js-footer.js @@ -1,4 +1,6 @@ /* The current function scope was opened via post-js-header.js, which gets prepended to this at build-time. This file closes that scope. */ -})/*postRun.push(...)*/; +//console.warn("This is the end of the Module.runSQLite3PostLoadInit handler."); +}/*Module.runSQLite3PostLoadInit()*/; +//console.warn("This is the end of the setup of the (pending) Module.runSQLite3PostLoadInit"); diff --git a/ext/wasm/api/post-js-header.js b/ext/wasm/api/post-js-header.js index 7fd82a7d6..77e3cd227 100644 --- a/ext/wasm/api/post-js-header.js +++ b/ext/wasm/api/post-js-header.js @@ -7,22 +7,27 @@ installs will be run after the WASM module is loaded, at which point the sqlite3 JS API bits will get set up. */ -if(!Module.postRun) Module.postRun = []; -Module.postRun.push(function(Module/*the Emscripten-style module object*/){ +Module.runSQLite3PostLoadInit = function(EmscriptenModule/*the Emscripten-style module object*/){ + /** ^^^ Don't use Module.postRun, as that runs a different time + depending on whether this file is built with emcc 3.1.x or + 4.0.x. This function name is intentionally obnoxiously verbose to + ensure that we don't collide with current and future Emscripten + symbol names. */ 'use strict'; + //console.warn("This is the start of Module.runSQLite3PostLoadInit()"); /* This function will contain at least the following: - - post-js-header.js (this file) + - post-js-header.js => this file - sqlite3-api-prologue.js => Bootstrapping bits to attach the rest to - common/whwasmutil.js => Replacements for much of Emscripten's glue - - jaccwaby/jaccwabyt.js => Jaccwabyt (C/JS struct binding) + - jaccwabyt/jaccwabyt.js => Jaccwabyt (C/JS struct binding) - sqlite3-api-glue.js => glues previous parts together - - sqlite3-api-oo.js => SQLite3 OO API #1 + - sqlite3-api-oo1.js => SQLite3 OO API #1 - sqlite3-api-worker1.js => Worker-based API - sqlite3-vfs-helper.c-pp.js => Utilities for VFS impls - sqlite3-vtab-helper.c-pp.js => Utilities for virtual table impls - - sqlite3-vfs-opfs.c-pp.js => OPFS VFS + - sqlite3-vfs-opfs.c-pp.js => OPFS VFS - sqlite3-vfs-opfs-sahpool.c-pp.js => OPFS SAHPool VFS - sqlite3-api-cleanup.js => final API cleanup - - post-js-footer.js => closes this postRun() function + - post-js-footer.js => closes this function */ diff --git a/ext/wasm/api/pre-js.c-pp.js b/ext/wasm/api/pre-js.c-pp.js index 878f3e054..4045fb11c 100644 --- a/ext/wasm/api/pre-js.c-pp.js +++ b/ext/wasm/api/pre-js.c-pp.js @@ -48,7 +48,7 @@ Module['locateFile'] = function(path, prefix) { }else{ theFile = prefix + path; } - sqlite3InitModuleState.debugModule( + this.debugModule( "locateFile(",arguments[0], ',', arguments[1],")", 'sqlite3InitModuleState.scriptDir =',this.scriptDir, 'up.entries() =',Array.from(up.entries()), @@ -59,6 +59,7 @@ Module['locateFile'] = function(path, prefix) { }.bind(sqlite3InitModuleState); //#endif ifnot target=es6-bundler-friendly +//#if custom-Module.instantiateModule /** Bug warning: a custom Module.instantiateWasm() does not work in WASMFS builds: @@ -67,7 +68,15 @@ Module['locateFile'] = function(path, prefix) { In such builds we must disable this. */ -const xNameOfInstantiateWasm = false +const xNameOfInstantiateWasm = +//#if wasmfs + false +//#else + true /* This works, but it does not have the testing coverage in the + wild which Emscripten's default impl does, so we'll save + this option until we really need a custom + Module.instantiateWasm() */ +//#endif ? 'instantiateWasm' : 'emscripten-bug-17951'; Module[xNameOfInstantiateWasm] = function callee(imports,onSuccess){ @@ -80,6 +89,7 @@ Module[xNameOfInstantiateWasm] = function callee(imports,onSuccess){ sqlite3InitModuleState.debugModule( "instantiateWasm() uri =", uri ); + //console.warn("Custom instantiateModule",uri); const wfetch = ()=>fetch(uri, {credentials: 'same-origin'}); const loadWasm = WebAssembly.instantiateStreaming ? async ()=>{ @@ -105,6 +115,7 @@ Module[xNameOfInstantiateWasm] = function callee(imports,onSuccess){ scripts. */ Module[xNameOfInstantiateWasm].uri = 'sqlite3.wasm'; +//#endif custom-Module.instantiateModule /* END FILE: api/pre-js.js, noting that the build process may add a line after this one to change the above .uri to a build-specific one. */ diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js index 65dbb4eb6..7fb29e648 100644 --- a/ext/wasm/api/sqlite3-api-cleanup.js +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -14,6 +14,9 @@ intended to be appended after all other sqlite3-api-*.js files so that it can finalize any setup and clean up any global symbols temporarily used for setting up the API's various subsystems. + + In Emscripten builds it's run in the context of a Module.postRun + handler. */ 'use strict'; if('undefined' !== typeof Module){ // presumably an Emscripten build diff --git a/ext/wasm/api/sqlite3-api-glue.c-pp.js b/ext/wasm/api/sqlite3-api-glue.c-pp.js index e5eb0cfeb..8d2d4a589 100644 --- a/ext/wasm/api/sqlite3-api-glue.c-pp.js +++ b/ext/wasm/api/sqlite3-api-glue.c-pp.js @@ -20,7 +20,6 @@ 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; @@ -136,20 +135,12 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["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_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*"], @@ -211,14 +202,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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: (argv,argIndex)=>argv[0/* sqlite3* */] - }), "*" - ]], ["sqlite3_realloc", "*","*","int"], ["sqlite3_reset", "int", "sqlite3_stmt*"], /* sqlite3_reset_auto_extension() has a hand-written binding. */ @@ -244,13 +227,49 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }), '*' ]], + /** + We do not have a way to automatically clean up destructors + which are automatically converted from JS functions via the + final argument to sqlite3_set_auxdata(). Because of that, + automatic function conversion is not supported for this + function. Clients should use wasm.installFunction() to create + such callbacks, then pass that pointer to + sqlite3_set_auxdata(). Relying on automated conversions here + would lead to leaks of JS/WASM proxy functions because + sqlite3_set_auxdata() is frequently called in UDFs. + + The sqlite3.oo1.DB class's onclose handlers can be used for this + purpose. For example: + + const pAuxDtor = wasm.installFunction('v(p)', function(ptr){ + //free ptr + }); + myDb.onclose = { + after: ()=>{ + wasm.uninstallFunction(pAuxDtor); + } + }; + + Then pass pAuxDtor as the final argument to appropriate + sqlite3_set_auxdata() calls. + + Note that versions prior to 3.49.0 ostensibly had automatic + function conversion here but a typo prevented it from + working. Rather than fix it, it was removed because testing the + fix brought the huge potential for memory leaks to the + forefront. + */ ["sqlite3_set_auxdata", undefined, [ "sqlite3_context*", "int", "*", - new wasm.xWrap.FuncPtrAdapter({ - name: 'xDestroyAuxData', - signature: 'v(*)', - contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */] - }) + true + ? "*" + : new wasm.xWrap.FuncPtrAdapter({ + /* If we can find a way to automate their cleanup, JS functions can + be auto-converted with this. */ + name: 'xDestroyAuxData', + signature: 'v(p)', + contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */] + }) ]], ["sqlite3_shutdown", undefined], ["sqlite3_sourceid", "string"], @@ -303,6 +322,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["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"], @@ -335,8 +367,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ); }/* sqlite3_set_authorizer() */ + if( !!wasm.exports.sqlite3_column_origin_name ){ + wasm.bindingSignatures.push( + ["sqlite3_column_database_name","string", "sqlite3_stmt*", "int"], + ["sqlite3_column_origin_name","string", "sqlite3_stmt*", "int"], + ["sqlite3_column_table_name","string", "sqlite3_stmt*", "int"] + ); + } + if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ - /* ^^^ "the problem" is that this is an option feature and the + /* ^^^ "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*"]); @@ -385,7 +425,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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_drop_modules", "int", ["sqlite3*", "**"]], ["sqlite3_last_insert_rowid", "i64", ["sqlite3*"]], ["sqlite3_malloc64", "*","i64"], ["sqlite3_msize", "i64", "*"], @@ -422,6 +461,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["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"], @@ -950,7 +990,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** Code duplication reducer for functions which take an encoding argument and require SQLITE_UTF8. Sets the db error code to - SQLITE_FORMAT and returns that code. */ + 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." @@ -1000,11 +1041,13 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ this._addUDF(pDb, name, arity, m.udf); }; - __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); - }; + 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(), @@ -1047,6 +1090,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 'sqlite3_set_authorizer', 'sqlite3_trace_v2', 'sqlite3_update_hook' + /* + We do not yet have a way to clean up automatically-converted + sqlite3_set_auxdata() finalizers. + */ ]) { const x = wasm.exports[name]; if( !x ){ @@ -1273,17 +1320,20 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ] ); - const __sqlite3CreateWindowFunction = 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}) - ] - ); + 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( @@ -1328,61 +1378,71 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; /* Documented in the api object's initializer. */ - 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); + 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); } - 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); - } - }; + 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_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_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_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*/; @@ -1782,7 +1842,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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."); + toss("Usage error: expecting a Function or WASM pointer to one."); } if(1===arguments.length){ return (n,f)=>callee(tgt, n, f, applyArgcCheck); diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index 3d6a24c77..06f916002 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -16,7 +16,6 @@ and it installs its deliverable as globalThis.sqlite3.oo1. */ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - const toss = (...args)=>{throw new Error(args.join(' '))}; const toss3 = (...args)=>{throw new sqlite3.SQLite3Error(...args)}; const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util; @@ -1061,18 +1060,18 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const cbArgCache = Object.create(null) /* 2nd arg for arg.cbArg, used by (at least) row-to-object converter */; - for(; stmt.step(); stmt._lockedByExec = false){ + for( ; stmt.step(); __execLock.delete(stmt) ){ if(0===gotColNames++){ stmt.getColumnNames(cbArgCache.columnNames = (opt.columnNames || [])); } - stmt._lockedByExec = true; + __execLock.add(stmt); const row = arg.cbArg(stmt,cbArgCache); if(resultRows) resultRows.push(row); if(callback && false === callback.call(opt, row, stmt)){ break; } } - stmt._lockedByExec = false; + __execLock.delete(stmt); } if(0===gotColNames){ /* opt.columnNames was provided but we visited no result rows */ @@ -1094,7 +1093,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }*/finally{ wasm.scopedAllocPop(stack); if(stmt){ - delete stmt._lockedByExec; + __execLock.delete(stmt); stmt.finalize(); } } @@ -1388,7 +1387,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** Starts a transaction, calls the given callback, and then either - rolls back or commits the savepoint, depending on whether the + rolls back or commits the transaction, depending on whether the callback throws. The callback is passed this db object as its only argument. On success, returns the result of the callback. Throws on error. @@ -1511,7 +1510,30 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; /** - If stmt._lockedByExec is truthy, this throws an exception + Each Stmt object which is "locked" by DB.exec() gets an entry + here to note that "lock". + + The reason this is in place is because exec({callback:...})'s + callback gets access to the Stmt objects created internally by + exec() but it must not use certain Stmt APIs. + */ + const __execLock = new Set(); + /** + This is a Stmt.get() counterpart of __execLock. Each time + Stmt.step() returns true, the statement is added to this set, + indicating that Stmt.get() is legal. Stmt APIs which invalidate + that status remove the Stmt object from this set, which will + cause Stmt.get() to throw with a descriptive error message + instead of a more generic "API misuse" if we were to allow that + call to reach the C API. + */ + const __stmtMayGet = new Set(); + + /** + Stmt APIs which are prohibited on locked objects must call + affirmNotLockedByExec() before doing any work. + + If __execLock.has(stmt) is truthy, this throws an exception complaining that the 2nd argument (an operation name, e.g. "bind()") is not legal while the statement is "locked". Locking happens before an exec()-like callback is passed a @@ -1519,7 +1541,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ finalize the statement. If it does not throw, it returns stmt. */ const affirmNotLockedByExec = function(stmt,currentOpName){ - if(stmt._lockedByExec){ + if(__execLock.has(stmt)){ toss3("Operation is illegal when statement is locked:",currentOpName); } return stmt; @@ -1604,7 +1626,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ toss3("Unsupported bind() argument type: "+(typeof val)); } if(rc) DB.checkRc(stmt.db.pointer, rc); - stmt._mayGet = false; return stmt; }; @@ -1627,9 +1648,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const rc = capi.sqlite3_finalize(this.pointer); delete __stmtMap.get(this.db)[this.pointer]; __ptrMap.delete(this); - delete this._mayGet; + __execLock.delete(this); + __stmtMayGet.delete(this); delete this.parameterCount; - delete this._lockedByExec; delete this.db; return rc; } @@ -1643,7 +1664,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ clearBindings: function(){ affirmNotLockedByExec(affirmStmtOpen(this), 'clearBindings()') capi.sqlite3_clear_bindings(this.pointer); - this._mayGet = false; + __stmtMayGet.delete(this); return this; }, /** @@ -1669,7 +1690,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ affirmNotLockedByExec(this,'reset()'); if(alsoClearBinds) this.clearBindings(); const rc = capi.sqlite3_reset(affirmStmtOpen(this).pointer); - this._mayGet = false; + __stmtMayGet.delete(this); checkSqlite3Rc(this.db, rc); return this; }, @@ -1756,7 +1777,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if(!this.parameterCount){ toss3("This statement has no bindable parameters."); } - this._mayGet = false; + __stmtMayGet.delete(this); if(null===arg){ /* bind NULL */ return bindOne(this, ndx, BindTypes.null, arg); @@ -1821,14 +1842,18 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ affirmNotLockedByExec(this, 'step()'); const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer); switch(rc){ - case capi.SQLITE_DONE: return this._mayGet = false; - case capi.SQLITE_ROW: return this._mayGet = true; - default: - this._mayGet = false; - sqlite3.config.warn("sqlite3_step() rc=",rc, - capi.sqlite3_js_rc_str(rc), - "SQL =", capi.sqlite3_sql(this.pointer)); - DB.checkRc(this.db.pointer, rc); + case capi.SQLITE_DONE: + __stmtMayGet.delete(this); + return false; + case capi.SQLITE_ROW: + __stmtMayGet.add(this); + return true; + default: + __stmtMayGet.delete(this); + sqlite3.config.warn("sqlite3_step() rc=",rc, + capi.sqlite3_js_rc_str(rc), + "SQL =", capi.sqlite3_sql(this.pointer)); + DB.checkRc(this.db.pointer, rc); } }, /** @@ -1913,7 +1938,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ getJSON() can be used for that. */ get: function(ndx,asType){ - if(!affirmStmtOpen(this)._mayGet){ + if(!__stmtMayGet.has(affirmStmtOpen(this))){ toss3("Stmt.step() has not (recently) returned true."); } if(Array.isArray(ndx)){ diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index c8db3698c..7e128a3fa 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -12,12 +12,12 @@ This file is intended to be combined at build-time with other related code, most notably a header and footer which wraps this - whole file into an Emscripten Module.postRun() handler. The sqlite3 - JS API has no hard requirements on Emscripten and does not expose - any Emscripten APIs to clients. It is structured such that its build - can be tweaked to include it in arbitrary WASM environments which - can supply the necessary underlying features (e.g. a POSIX file I/O - layer). + whole file into a single callback which can be run after Emscripten + loads the corresponding WASM module. The sqlite3 JS API has no hard + requirements on Emscripten and does not expose any Emscripten APIs + to clients. It is structured such that its build can be tweaked to + include it in arbitrary WASM environments which can supply the + necessary underlying features (e.g. a POSIX file I/O layer). Main project home page: https://sqlite.org @@ -124,7 +124,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( apiConfig = (globalThis.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig) ){ - if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */ + if(sqlite3ApiBootstrap.sqlite3){ /* already initialized */ (sqlite3ApiBootstrap.sqlite3.config || console).warn( "sqlite3ApiBootstrap() called multiple times.", "Config and external initializers are ignored on calls after the first." @@ -195,7 +195,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as possible, identically to the C-native counterparts, as documented at: - https://www.sqlite.org/c3ref/intro.html + https://sqlite.org/c3ref/intro.html A very few exceptions require an additional level of proxy function or may otherwise require special attention in the WASM @@ -663,7 +663,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( free those pointers (using `wasm.uninstallFunction()` or equivalent). - C reference: https://www.sqlite.org/c3ref/create_function.html + C reference: https://sqlite.org/c3ref/create_function.html Maintenance reminder: the ability to add new WASM-accessible functions to the runtime requires that the @@ -869,7 +869,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( Emscripten -sWASM_BIGINT flag, else false. When enabled, certain 64-bit sqlite3 APIs are enabled which are not otherwise enabled due to JS/WASM int64 - impedence mismatches. + impedance mismatches. */ bigIntEnabled: !!config.bigIntEnabled, /** @@ -880,7 +880,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** When Emscripten compiles with `-sIMPORTED_MEMORY`, it - initalizes the heap and imports it into wasm, as opposed to + initializes the heap and imports it into wasm, as opposed to the other way around. In this case, the memory is not available via this.exports.memory. */ @@ -1453,7 +1453,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( creates (or overwrites) the given file using those APIs. This is primarily intended for use in Emscripten-based builds where the POSIX APIs are transparently proxied by an in-memory virtual filesystem. - It may behave diffrently in other environments. + It may behave differently in other environments. The first argument must be either a JS string or WASM C-string holding the filename. Note that this routine does _not_ create @@ -1555,7 +1555,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( - "memdb": results are undefined. - "kvvfs": will fail with an I/O error due to strict internal - requirments of that VFS's xTruncate(). + requirements of that VFS's xTruncate(). - "unix" and related: will use the WASM build's equivalent of the POSIX I/O APIs. This will work so long as neither a specific @@ -1712,41 +1712,48 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( missing or falsy pointer argument as 0. */ capi.sqlite3_db_config = function(pDb, op, ...args){ - if(!this.s){ - this.s = wasm.xWrap('sqlite3__wasm_db_config_s','int', - ['sqlite3*', 'int', 'string:static'] - /* MAINDBNAME requires a static string */); - this.pii = wasm.xWrap('sqlite3__wasm_db_config_pii', 'int', - ['sqlite3*', 'int', '*','int', 'int']); - this.ip = wasm.xWrap('sqlite3__wasm_db_config_ip','int', - ['sqlite3*', 'int', 'int','*']); - } switch(op){ - case capi.SQLITE_DBCONFIG_ENABLE_FKEY: - case capi.SQLITE_DBCONFIG_ENABLE_TRIGGER: - case capi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: - case capi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: - case capi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: - case capi.SQLITE_DBCONFIG_ENABLE_QPSG: - case capi.SQLITE_DBCONFIG_TRIGGER_EQP: - case capi.SQLITE_DBCONFIG_RESET_DATABASE: - case capi.SQLITE_DBCONFIG_DEFENSIVE: - case capi.SQLITE_DBCONFIG_WRITABLE_SCHEMA: - case capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: - case capi.SQLITE_DBCONFIG_DQS_DML: - case capi.SQLITE_DBCONFIG_DQS_DDL: - case capi.SQLITE_DBCONFIG_ENABLE_VIEW: - case capi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: - case capi.SQLITE_DBCONFIG_TRUSTED_SCHEMA: - case capi.SQLITE_DBCONFIG_STMT_SCANSTATUS: - case capi.SQLITE_DBCONFIG_REVERSE_SCANORDER: - return this.ip(pDb, op, args[0], args[1] || 0); - case capi.SQLITE_DBCONFIG_LOOKASIDE: - return this.pii(pDb, op, args[0], args[1], args[2]); - case capi.SQLITE_DBCONFIG_MAINDBNAME: - return this.s(pDb, op, args[0]); - default: - return capi.SQLITE_MISUSE; + case capi.SQLITE_DBCONFIG_ENABLE_FKEY: + case capi.SQLITE_DBCONFIG_ENABLE_TRIGGER: + case capi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: + case capi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: + case capi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: + case capi.SQLITE_DBCONFIG_ENABLE_QPSG: + case capi.SQLITE_DBCONFIG_TRIGGER_EQP: + case capi.SQLITE_DBCONFIG_RESET_DATABASE: + case capi.SQLITE_DBCONFIG_DEFENSIVE: + case capi.SQLITE_DBCONFIG_WRITABLE_SCHEMA: + case capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: + case capi.SQLITE_DBCONFIG_DQS_DML: + case capi.SQLITE_DBCONFIG_DQS_DDL: + case capi.SQLITE_DBCONFIG_ENABLE_VIEW: + case capi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: + case capi.SQLITE_DBCONFIG_TRUSTED_SCHEMA: + case capi.SQLITE_DBCONFIG_STMT_SCANSTATUS: + case capi.SQLITE_DBCONFIG_REVERSE_SCANORDER: + case capi.SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE: + case capi.SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE: + case capi.SQLITE_DBCONFIG_ENABLE_COMMENTS: + if( !this.ip ){ + this.ip = wasm.xWrap('sqlite3__wasm_db_config_ip','int', + ['sqlite3*', 'int', 'int', '*']); + } + return this.ip(pDb, op, args[0], args[1] || 0); + case capi.SQLITE_DBCONFIG_LOOKASIDE: + if( !this.pii ){ + this.pii = wasm.xWrap('sqlite3__wasm_db_config_pii', 'int', + ['sqlite3*', 'int', '*', 'int', 'int']); + } + return this.pii(pDb, op, args[0], args[1], args[2]); + case capi.SQLITE_DBCONFIG_MAINDBNAME: + if(!this.s){ + this.s = wasm.xWrap('sqlite3__wasm_db_config_s','int', + ['sqlite3*', 'int', 'string:static'] + /* MAINDBNAME requires a static string */); + } + return this.s(pDb, op, args[0]); + default: + return capi.SQLITE_MISUSE; } }.bind(Object.create(null)); diff --git a/ext/wasm/api/sqlite3-api-worker1.c-pp.js b/ext/wasm/api/sqlite3-api-worker1.c-pp.js index 991862545..5e088f438 100644 --- a/ext/wasm/api/sqlite3-api-worker1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-worker1.c-pp.js @@ -279,11 +279,11 @@ The arguments are in the same form accepted by oo1.DB.exec(), with the exceptions noted below. - If the `countChanges` arguments property (added in version 3.43) is - truthy then the `result` property contained by the returned object - will have a `changeCount` property which holds the number of changes - made by the provided SQL. Because the SQL may contain an arbitrary - number of statements, the `changeCount` is calculated by calling + If `args.countChanges` (added in version 3.43) is truthy then the + `result` property contained by the returned object will have a + `changeCount` property which holds the number of changes made by the + provided SQL. Because the SQL may contain an arbitrary number of + statements, the `changeCount` is calculated by calling `sqlite3_total_changes()` before and after the SQL is evaluated. If the value of `countChanges` is 64 then the `changeCount` property will be returned as a 64-bit integer in the form of a BigInt (noting @@ -292,6 +292,15 @@ calling `sqlite3_total_changes64()` before and after the SQL is evaluated. + If the `args.lastInsertRowId` (added in version 3.50.0) is truthy + then the `result` property contained by the returned object will + have a `lastInsertRowId` will hold a BigInt-type value corresponding + to the result of sqlite3_last_insert_rowid(). This value is only + fetched once, after the SQL is run, regardless of how many + statements the SQL contains. This API has no idea whether the SQL + contains any INSERTs, so it is up to the client to apply/rely on + this property only when it makes sense to do so. + A function-type args.callback property cannot cross the window/Worker boundary, so is not useful here. If args.callback is a string then it is assumed to be a @@ -542,6 +551,12 @@ sqlite3.initWorker1API = function(){ if(undefined !== changeCount){ rc.changeCount = db.changes(true,64===rc.countChanges) - changeCount; } + const lastInsertRowId = !!rc.lastInsertRowId + ? sqlite3.capi.sqlite3_last_insert_rowid(db) + : undefined; + if( undefined!==lastInsertRowId ){ + rc.lastInsertRowId = lastInsertRowId; + } if(rc.callback instanceof Function){ rc.callback = theCallback; /* Post a sentinel message to tell the client that the end diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js index ca5583971..e10d0dd50 100644 --- a/ext/wasm/api/sqlite3-opfs-async-proxy.js +++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js @@ -61,7 +61,7 @@ const installAsyncProxy = function(){ } /** - Will hold state copied to this object from the syncronous side of + Will hold state copied to this object from the synchronous side of this API. */ const state = Object.create(null); @@ -506,8 +506,7 @@ const installAsyncProxy = function(){ dirHandle: hDir, fileHandle: hFile, sabView: state.sabFileBufView, - readOnly: create - ? false : (state.sq3Codes.SQLITE_OPEN_READONLY & flags), + readOnly: !create && !!(state.sq3Codes.SQLITE_OPEN_READONLY & flags), deleteOnClose: !!(state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags) }); fh.releaseImplicitLocks = @@ -723,7 +722,7 @@ const installAsyncProxy = function(){ https://github.com/tomayac/sqlite-wasm/issues/12 - is reporting that this occassionally, under high loads, + is reporting that this occasionally, under high loads, returns 'ok', which leads to the whichOp being 0 (which isn't a valid operation ID and leads to an exception, along with a corresponding ugly console log diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js index d423bb0bb..73426f203 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js @@ -57,7 +57,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 'use strict'; const toss = sqlite3.util.toss; const toss3 = sqlite3.util.toss3; - const initPromises = Object.create(null); + const initPromises = Object.create(null) /* cache of (name:result) of VFS init results */; const capi = sqlite3.capi; const util = sqlite3.util; const wasm = sqlite3.wasm; @@ -79,6 +79,48 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ capi.SQLITE_OPEN_MAIN_JOURNAL | capi.SQLITE_OPEN_SUPER_JOURNAL | capi.SQLITE_OPEN_WAL; + const FLAG_COMPUTE_DIGEST_V2 = capi.SQLITE_OPEN_MEMORY + /* Part of the fix for + https://github.com/sqlite/sqlite-wasm/issues/97 + + Summary: prior to version 3.50.0 computeDigest() always computes + a value of [0,0] due to overflows, so it does not do anything + useful. Fixing it invalidates old persistent files, so we + instead only fix it for files created or updated since the bug + was discovered and fixed. + + This flag determines whether we use the broken legacy + computeDigest() or the v2 variant. We only use this flag for + newly-created/overwritten files. Pre-existing files have the + broken digest stored in them so need to continue to use that. + + What this means, in terms of db file compatibility between + versions: + + - DBs created with versions older than this fix (<3.50.0) + can be read by post-fix versions. Such DBs which are written + to in-place (not replaced) by newer versions can still be read + by older versions, as the affected digest is only modified + when the SAH slot is assigned to a given filename. + + - DBs created with post-fix versions will, when read by a pre-fix + version, be seen as having a "bad digest" and will be + unceremoniously replaced by that pre-fix version. When swapping + back to a post-fix version, that version will see that the file + entry is missing the FLAG_COMPUTE_DIGEST_V2 bit so will treat it + as a legacy file. + + This flag is stored in the same memory as the various + SQLITE_OPEN_... flags and we must be careful here to not use a + flag bit which is otherwise relevant for the VFS. + SQLITE_OPEN_MEMORY is handled by sqlite3_open_v2() and friends, + not the VFS, so we'll repurpose that one. If we take a + currently-unused bit and it ends up, at some later point, being + used, we would have to invalidate existing VFS files in order to + move to another bit. Similarly, if the SQLITE_OPEN_MEMORY bit + were ever reassigned (which it won't be!), we'd invalidate all + VFS-side files. + */; /** Subdirectory of the VFS's space where "opaque" (randomly-named) files are stored. Changing this effectively invalidates the data @@ -329,6 +371,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){ const pool = getPoolForVfs(pVfs); try{ + flags &= ~FLAG_COMPUTE_DIGEST_V2; pool.log(`xOpen ${wasm.cstrToJs(zName)} ${flags}`); // First try to open a path that already exists in the file system. const path = (zName && wasm.peek8(zName)) @@ -501,22 +544,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ currently-opened client-specified filenames. */ getFileNames(){ const rc = []; - const iter = this.#mapFilenameToSAH.keys(); - for(const n of iter) rc.push(n); + for(const n of this.#mapFilenameToSAH.keys()) rc.push(n); return rc; } -// #createFileObject(sah,clientName,opaqueName){ -// const f = Object.assign(Object.create(null),{ -// clientName, opaqueName -// }); -// this.#mapSAHToMeta.set(sah, f); -// return f; -// } -// #unmapFileObject(sah){ -// this.#mapSAHToMeta.delete(sah); -// } - /** Adds n files to the pool's capacity. This change is persistent across settings. Returns a Promise which resolves @@ -557,8 +588,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } /** - Releases all currently-opened SAHs. The only legal - operation after this is acquireAccessHandles(). + Releases all currently-opened SAHs. The only legal operation + after this is acquireAccessHandles() or (if this is called from + pauseVfs()) either of isPaused() or unpauseVfs(). */ releaseAccessHandles(){ for(const ah of this.#mapSAHToName.keys()) ah.close(); @@ -568,17 +600,21 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } /** - Opens all files under this.vfsDir/this.#dhOpaque and acquires - a SAH for each. returns a Promise which resolves to no value - but completes once all SAHs are acquired. If acquiring an SAH - throws, SAHPool.$error will contain the corresponding - exception. + Opens all files under this.vfsDir/this.#dhOpaque and acquires a + SAH for each. Returns a Promise which resolves to no value but + completes once all SAHs are acquired. If acquiring an SAH + throws, this.$error will contain the corresponding Error + object. + + If it throws, it releases any SAHs which it may have + acquired before the exception was thrown, leaving the VFS in a + well-defined but unusable state. If clearFiles is true, the client-stored state of each file is cleared when its handle is acquired, including its name, flags, and any data stored after the metadata block. */ - async acquireAccessHandles(clearFiles){ + async acquireAccessHandles(clearFiles=false){ const files = []; for await (const [name,h] of this.#dhOpaque){ if('file'===h.kind){ @@ -631,7 +667,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const fileDigest = new Uint32Array(HEADER_DIGEST_SIZE / 4); sah.read(fileDigest, {at: HEADER_OFFSET_DIGEST}); - const compDigest = this.computeDigest(this.#apBody); + const compDigest = this.computeDigest(this.#apBody, flags); + //warn("getAssociatedPath() flags",'0x'+flags.toString(16), "compDigest", compDigest); if(fileDigest.every((v,i) => v===compDigest[i])){ // Valid digest const pathBytes = this.#apBody.findIndex((v)=>0===v); @@ -640,6 +677,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ // leaving stale db data laying around. sah.truncate(HEADER_OFFSET_DATA); } + //warn("getAssociatedPath() flags",'0x'+flags.toString(16), "compDigest", compDigest,"pathBytes",pathBytes); return pathBytes ? textDecoder.decode(this.#apBody.subarray(0,pathBytes)) : ''; @@ -662,10 +700,17 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if(HEADER_MAX_PATH_SIZE <= enc.written + 1/*NUL byte*/){ toss("Path too long:",path); } + if(path && flags){ + /* When creating or re-writing files, update their digest, if + needed, to v2. We continue to use v1 for the (!path) case + (empty files) because there's little reason not to use a + digest of 0 for empty entries. */ + flags |= FLAG_COMPUTE_DIGEST_V2; + } this.#apBody.fill(0, enc.written, HEADER_MAX_PATH_SIZE); this.#dvBody.setUint32(HEADER_OFFSET_FLAGS, flags); - - const digest = this.computeDigest(this.#apBody); + const digest = this.computeDigest(this.#apBody, flags); + //console.warn("setAssociatedPath(",path,") digest",digest); sah.write(this.#apBody, {at: 0}); sah.write(digest, {at: HEADER_OFFSET_DIGEST}); sah.flush(); @@ -686,15 +731,22 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ metadata for each file as a validation check. Changing this algorithm invalidates all existing databases for this VFS, so don't do that. + + See the docs for FLAG_COMPUTE_DIGEST_V2 for more details. */ - computeDigest(byteArray){ - let h1 = 0xdeadbeef; - let h2 = 0x41c6ce57; - for(const v of byteArray){ - h1 = 31 * h1 + (v * 307); - h2 = 31 * h2 + (v * 307); + computeDigest(byteArray, fileFlags){ + if( fileFlags & FLAG_COMPUTE_DIGEST_V2 ){ + let h1 = 0xdeadbeef; + let h2 = 0x41c6ce57; + for(const v of byteArray){ + h1 = Math.imul(h1 ^ v, 2654435761); + h2 = Math.imul(h2 ^ v, 104729); + } + return new Uint32Array([h1>>>0, h2>>>0]); + }else{ + /* this is what the buggy legacy computation worked out to */ + return new Uint32Array([0,0]); } - return new Uint32Array([h1>>>0, h2>>>0]); } /** @@ -832,17 +884,24 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Removes this object's sqlite3_vfs registration and shuts down this object, releasing all handles, mappings, and whatnot, including deleting its data directory. There is currently no - way to "revive" the object and reaquire its resources. + way to "revive" the object and reaquire its + resources. Similarly, there is no recovery strategy if removal + of any given SAH fails, so such errors are ignored by this + function. This function is intended primarily for testing. Resolves to true if it did its job, false if the VFS has already been shut down. + + @see pauseVfs() + @see unpauseVfs() */ async removeVfs(){ if(!this.#cVfs.pointer || !this.#dhOpaque) return false; capi.sqlite3_vfs_unregister(this.#cVfs.pointer); this.#cVfs.dispose(); + delete initPromises[this.vfsName]; try{ this.releaseAccessHandles(); await this.#dhVfsRoot.removeEntry(OPAQUE_DIR_NAME, {recursive: true}); @@ -852,13 +911,77 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ); this.#dhVfsRoot = this.#dhVfsParent = undefined; }catch(e){ - sqlite3.config.error(this.vfsName,"removeVfs() failed:",e); + sqlite3.config.error(this.vfsName,"removeVfs() failed with no recovery strategy:",e); /*otherwise ignored - there is no recovery strategy*/ } return true; } + /** + "Pauses" this VFS by unregistering it from SQLite and + relinquishing all open SAHs, leaving the associated files + intact. If this object is already paused, this is a + no-op. Returns this object. + + This function throws if SQLite has any opened file handles + hosted by this VFS, as the alternative would be to invoke + Undefined Behavior by closing file handles out from under the + library. Similarly, automatically closing any database handles + opened by this VFS would invoke Undefined Behavior in + downstream code which is holding those pointers. + + If this function throws due to open file handles then it has + no side effects. If the OPFS API throws while closing handles + then the VFS is left in an undefined state. + + @see isPaused() + @see unpauseVfs() + */ + pauseVfs(){ + if(this.#mapS3FileToOFile_.size>0){ + sqlite3.SQLite3Error.toss( + capi.SQLITE_MISUSE, "Cannot pause VFS", + this.vfsName,"because it has opened files." + ); + } + if(this.#mapSAHToName.size>0){ + capi.sqlite3_vfs_unregister(this.vfsName); + this.releaseAccessHandles(); + } + return this; + } + + /** + Returns true if this pool is currently paused else false. + + @see pauseVfs() + @see unpauseVfs() + */ + isPaused(){ + return 0===this.#mapSAHToName.size; + } + + /** + "Unpauses" this VFS, reacquiring all SAH's and (if successful) + re-registering it with SQLite. This is a no-op if the VFS is + not currently paused. + + The returned Promise resolves to this object. See + acquireAccessHandles() for how it behaves if it throws due to + SAH acquisition failure. + + @see isPaused() + @see pauseVfs() + */ + async unpauseVfs(){ + if(0===this.#mapSAHToName.size){ + return this.acquireAccessHandles(false). + then(()=>capi.sqlite3_vfs_register(this.#cVfs, 0),this); + } + return this; + } + //! Documented elsewhere in this file. exportFile(name){ const sah = this.#mapFilenameToSAH.get(name) || toss("File not found:",name); @@ -983,6 +1106,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ async removeVfs(){ return this.#p.removeVfs() } + pauseVfs(){ this.#p.pauseVfs(); return this; } + async unpauseVfs(){ return this.#p.unpauseVfs().then(()=>this); } + isPaused(){ return this.#p.isPaused() } + }/* class OpfsSAHPoolUtil */; /** @@ -1037,7 +1164,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - `clearOnInit`: (default=false) if truthy, contents and filename mapping are removed from each SAH it is acquired during - initalization of the VFS, leaving the VFS's storage in a pristine + initialization of the VFS, leaving the VFS's storage in a pristine state. Use this only for databases which need not survive a page reload. @@ -1164,7 +1291,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ VFS-hosted database file. The result of the resolved Promise when called this way is the size of the resulting database. - On succes this routine rewrites the database header bytes in the + On success this routine rewrites the database header bytes in the output file (not the input array) to force disabling of WAL mode. On a write error, the handle is removed from the pool and made @@ -1216,6 +1343,41 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Clears all client-defined state of all SAHs and makes all of them available for re-use by the pool. Results are undefined if any such handles are currently in use, e.g. by an sqlite3 db. + + APIs specific to the "pause" capability (added in version 3.49): + + Summary: "pausing" the VFS disassociates it from SQLite and + relinquishes its SAHs so that they may be opened by another + instance of this VFS (running in a separate tab/page or Worker). + "Unpausing" it takes back control, if able. + + - pauseVfs() + + "Pauses" this VFS by unregistering it from SQLite and + relinquishing all open SAHs, leaving the associated files intact. + This enables pages/tabs to coordinate semi-concurrent usage of + this VFS. If this object is already paused, this is a + no-op. Returns this object. Throws if SQLite has any opened file + handles hosted by this VFS. If this function throws due to open + file handles then it has no side effects. If the OPFS API throws + while closing handles then the VFS is left in an undefined state. + + - isPaused() + + Returns true if this VFS is paused, else false. + + - [async] unpauseVfs() + + Restores the VFS to an active state after having called + pauseVfs() on it. This is a no-op if the VFS is not paused. The + returned Promise resolves to this object on success. A rejected + Promise means there was a problem reacquiring the SAH handles + (possibly because they're in use by another instance or have + since been removed). Generically speaking, there is no recovery + strategy for that type of error, but if the problem is simply + that the OPFS files are locked, then a later attempt to unpause + it, made after the concurrent instance releases the SAHs, may + recover from the situation. */ sqlite3.installOpfsSAHPoolVfs = async function(options=Object.create(null)){ options = Object.assign(Object.create(null), optionDefaults, (options||{})); diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index 2d11b3583..9cacae788 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -459,7 +459,7 @@ const installOpfsVfs = function callee(options){ Runs the given operation (by name) in the async worker counterpart, waits for its response, and returns the result which the async worker writes to SAB[state.opIds.rc]. The - 2nd and subsequent arguments must be the aruguments for the + 2nd and subsequent arguments must be the arguments for the async op. */ const opRun = (op,...args)=>{ @@ -922,6 +922,8 @@ const installOpfsVfs = function callee(options){ fh.filename = zName; fh.sab = new SharedArrayBuffer(state.fileBufferSize); fh.flags = flags; + fh.readOnly = !(sqlite3.SQLITE_OPEN_CREATE & flags) + && !!(flags & capi.SQLITE_OPEN_READONLY); const rc = opRun('xOpen', pFile, zName, flags, opfsFlags); if(!rc){ /* Recall that sqlite3_vfs::xClose() will be called, even on diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index 7f7e69689..ee8a10209 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -14,16 +14,17 @@ */ #define SQLITE_WASM #ifdef SQLITE_WASM_ENABLE_C_TESTS +# undef SQLITE_WASM_ENABLE_C_TESTS +# define SQLITE_WASM_ENABLE_C_TESTS 1 /* -** Code blocked off by SQLITE_WASM_TESTS is intended solely for use in -** unit/regression testing. They may be safely omitted from +** Code blocked off by SQLITE_WASM_ENABLE_C_TESTS is intended solely +** for use in unit/regression testing. They may be safely omitted from ** client-side builds. The main unit test script, tester1.js, will ** skip related tests if it doesn't find the corresponding functions ** in the WASM exports. */ -# define SQLITE_WASM_TESTS 1 #else -# define SQLITE_WASM_TESTS 0 +# define SQLITE_WASM_ENABLE_C_TESTS 0 #endif /* @@ -92,60 +93,18 @@ #undef SQLITE_ENABLE_API_ARMOR #define SQLITE_ENABLE_API_ARMOR 1 -#ifndef SQLITE_ENABLE_BYTECODE_VTAB -# define SQLITE_ENABLE_BYTECODE_VTAB 1 -#endif -#ifndef SQLITE_ENABLE_DBPAGE_VTAB -# define SQLITE_ENABLE_DBPAGE_VTAB 1 -#endif -#ifndef SQLITE_ENABLE_DBSTAT_VTAB -# define SQLITE_ENABLE_DBSTAT_VTAB 1 -#endif -#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS -# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 -#endif -#ifndef SQLITE_ENABLE_FTS5 -# define SQLITE_ENABLE_FTS5 1 -#endif -#ifndef SQLITE_ENABLE_MATH_FUNCTIONS -# define SQLITE_ENABLE_MATH_FUNCTIONS 1 -#endif -#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC -# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 -#endif -#ifndef SQLITE_ENABLE_PREUPDATE_HOOK -# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ -#endif -#ifndef SQLITE_ENABLE_RTREE -# define SQLITE_ENABLE_RTREE 1 -#endif -#ifndef SQLITE_ENABLE_SESSION -# define SQLITE_ENABLE_SESSION 1 -#endif -#ifndef SQLITE_ENABLE_STMTVTAB -# define SQLITE_ENABLE_STMTVTAB 1 -#endif -#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION -# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION -#endif - /**********************************************************************/ /* SQLITE_O... */ -#ifndef SQLITE_OMIT_DEPRECATED -# define SQLITE_OMIT_DEPRECATED 1 -#endif -#ifndef SQLITE_OMIT_LOAD_EXTENSION -# define SQLITE_OMIT_LOAD_EXTENSION 1 -#endif -#ifndef SQLITE_OMIT_SHARED_CACHE -# define SQLITE_OMIT_SHARED_CACHE 1 -#endif -#ifndef SQLITE_OMIT_UTF16 -# define SQLITE_OMIT_UTF16 1 -#endif -#ifndef SQLITE_OS_KV_OPTIONAL -# define SQLITE_OS_KV_OPTIONAL 1 -#endif +#undef SQLITE_OMIT_DEPRECATED +#define SQLITE_OMIT_DEPRECATED 1 +#undef SQLITE_OMIT_LOAD_EXTENSION +#define SQLITE_OMIT_LOAD_EXTENSION 1 +#undef SQLITE_OMIT_SHARED_CACHE +#define SQLITE_OMIT_SHARED_CACHE 1 +#undef SQLITE_OMIT_UTF16 +#define SQLITE_OMIT_UTF16 1 +#undef SQLITE_OS_KV_OPTIONAL +#define SQLITE_OS_KV_OPTIONAL 1 /**********************************************************************/ /* SQLITE_S... */ @@ -169,49 +128,73 @@ #endif #ifdef SQLITE_WASM_EXTRA_INIT -# define SQLITE_EXTRA_INIT sqlite3_wasm_extra_init +/* SQLITE_EXTRA_INIT vs SQLITE_EXTRA_INIT_MUTEXED: +** see https://sqlite.org/forum/forumpost/14183b98fc0b1dea */ +# define SQLITE_EXTRA_INIT_MUTEXED sqlite3_wasm_extra_init #endif /* -** If SQLITE_WASM_MINIMAL is defined, undefine most of the ENABLE -** macros. +** If SQLITE_WASM_BARE_BONES is defined, undefine most of the ENABLE +** macros. This will, when using the canonical makefile, also elide +** any C functions from the WASM exports which are listed in +** ./EXPORT_FUNCTIONS.sqlite3-extras. */ -#ifdef SQLITE_WASM_MINIMAL -# undef SQLITE_ENABLE_DBPAGE_VTAB -# undef SQLITE_ENABLE_DBSTAT_VTAB -# undef SQLITE_ENABLE_EXPLAIN_COMMENTS -# undef SQLITE_ENABLE_FTS5 -# undef SQLITE_ENABLE_OFFSET_SQL_FUNC -# undef SQLITE_ENABLE_PREUPDATE_HOOK -# undef SQLITE_ENABLE_RTREE -# undef SQLITE_ENABLE_SESSION -# undef SQLITE_ENABLE_STMTVTAB -# undef SQLITE_OMIT_AUTHORIZATION +#ifdef SQLITE_WASM_BARE_BONES +# undef SQLITE_ENABLE_COLUMN_METADATA +# undef SQLITE_ENABLE_DBPAGE_VTAB +# undef SQLITE_ENABLE_DBSTAT_VTAB +# undef SQLITE_ENABLE_EXPLAIN_COMMENTS +# undef SQLITE_ENABLE_FTS5 +# undef SQLITE_ENABLE_OFFSET_SQL_FUNC +# undef SQLITE_ENABLE_PREUPDATE_HOOK +# undef SQLITE_ENABLE_RTREE +# undef SQLITE_ENABLE_SESSION +# undef SQLITE_ENABLE_STMTVTAB +# undef SQLITE_OMIT_AUTHORIZATION # define SQLITE_OMIT_AUTHORIZATION -/*Reminder re. custom sqlite3.c: +# undef SQLITE_OMIT_GET_TABLE +# define SQLITE_OMIT_GET_TABLE +# undef SQLITE_OMIT_INCRBLOB +# define SQLITE_OMIT_INCRBLOB +# undef SQLITE_OMIT_INTROSPECTION_PRAGMAS +# define SQLITE_OMIT_INTROSPECTION_PRAGMAS +# undef SQLITE_OMIT_JSON +# define SQLITE_OMIT_JSON +# undef SQLITE_OMIT_PROGRESS_CALLBACK +# define SQLITE_OMIT_PROGRESS_CALLBACK +# undef SQLITE_OMIT_WAL +# define SQLITE_OMIT_WAL +/* + The following OMITs do not work with the standard amalgamation, so + require a custom build: fossil clean -x ./configure - OPTS='-DSQLITE_OMIT_VIRTUALTABLE -DSQLITE_OMIT_EXPLAIN -DSQLITE_OMIT_TRIGGER' make -e sqlite3 -*/ -/*Requires a custom sqlite3.c -# undef SQLITE_OMIT_TRIGGER -# define SQLITE_OMIT_TRIGGER -*/ -/*TODO (requires build tweaks) -# undef SQLITE_OMIT_WINDOWFUNC -# define SQLITE_OMIT_WINDOWFUNC -*/ -/*Requires a custom sqlite3.c + OPTS='...' make -e sqlite3 + + where ... has a -D... for each of the following OMIT flags: + # undef SQLITE_OMIT_EXPLAIN # define SQLITE_OMIT_EXPLAIN -*/ -/*Requires a custom sqlite3.c + +# undef SQLITE_OMIT_TRIGGER +# define SQLITE_OMIT_TRIGGER + # undef SQLITE_OMIT_VIRTUALTABLE # define SQLITE_OMIT_VIRTUALTABLE + +# undef SQLITE_OMIT_WINDOWFUNC +# define SQLITE_OMIT_WINDOWFUNC + + As of this writing (2024-07-25), such a build fails in various ways + for as-yet-unknown reasons. */ -# undef SQLITE_OMIT_JSON -# define SQLITE_OMIT_JSON +#endif + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_WASM_BARE_BONES) +# define SQLITE_WASM_HAS_VTAB 1 +#else +# define SQLITE_WASM_HAS_VTAB 0 #endif #include <assert.h> @@ -264,10 +247,6 @@ #undef INC__STRINGIFY #undef SQLITE_C -#if defined(__EMSCRIPTEN__) -# include <emscripten/console.h> -#endif - #if 0 /* ** An EXPERIMENT in implementing a stack-based allocator analog to @@ -310,7 +289,7 @@ SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_alloc(int n){ /* ** State for the "pseudo-stack" allocator implemented in ** sqlite3__wasm_pstack_xyz(). In order to avoid colliding with -** Emscripten-controled stack space, it carves out a bit of stack +** Emscripten-controlled stack space, it carves out a bit of stack ** memory to use for that purpose. This memory ends up in the ** WASM-managed memory, such that routines which manipulate the wasm ** heap can also be used to manipulate this memory. @@ -337,7 +316,7 @@ SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_ptr(void){ return PStack.pPos; } /* -** Sets the pstack position poitner to p. Results are undefined if the +** Sets the pstack position pointer to p. Results are undefined if the ** given value did not come from sqlite3__wasm_pstack_ptr(). */ SQLITE_WASM_EXPORT void sqlite3__wasm_pstack_restore(unsigned char * p){ @@ -357,7 +336,6 @@ SQLITE_WASM_EXPORT void sqlite3__wasm_pstack_restore(unsigned char * p){ */ SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_alloc(int n){ if( n<=0 ) return 0; - //if( n & 0x7 ) n += 8 - (n & 0x7) /* align to 8-byte boundary */; n = (n + 7) & ~7 /* align to 8-byte boundary */; if( PStack.pBegin + n > PStack.pPos /*not enough space left*/ || PStack.pBegin + n <= PStack.pBegin /*overflow*/ ) return 0; @@ -412,7 +390,7 @@ int sqlite3__wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ return err_code; } -#if SQLITE_WASM_TESTS +#if SQLITE_WASM_ENABLE_C_TESTS struct WasmTestStruct { int v4; void * ppV; @@ -432,7 +410,7 @@ void sqlite3__wasm_test_struct(WasmTestStruct * s){ } return; } -#endif /* SQLITE_WASM_TESTS */ +#endif /* SQLITE_WASM_ENABLE_C_TESTS */ /* ** This function is NOT part of the sqlite3 public API. It is strictly @@ -623,6 +601,9 @@ const char * sqlite3__wasm_enum_json(void){ DefInt(SQLITE_DBCONFIG_TRUSTED_SCHEMA); DefInt(SQLITE_DBCONFIG_STMT_SCANSTATUS); DefInt(SQLITE_DBCONFIG_REVERSE_SCANORDER); + DefInt(SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE); + DefInt(SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE); + DefInt(SQLITE_DBCONFIG_ENABLE_COMMENTS); DefInt(SQLITE_DBCONFIG_MAX); } _DefGroup; @@ -967,7 +948,7 @@ const char * sqlite3__wasm_enum_json(void){ } _DefGroup; DefGroup(vtab) { -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_WASM_MINIMAL) +#if SQLITE_WASM_HAS_VTAB DefInt(SQLITE_INDEX_SCAN_UNIQUE); DefInt(SQLITE_INDEX_CONSTRAINT_EQ); DefInt(SQLITE_INDEX_CONSTRAINT_GT); @@ -995,7 +976,7 @@ const char * sqlite3__wasm_enum_json(void){ DefInt(SQLITE_FAIL); //DefInt(SQLITE_ABORT); // Also an error code DefInt(SQLITE_REPLACE); -#endif /*!SQLITE_OMIT_VIRTUALTABLE*/ +#endif /*SQLITE_WASM_HAS_VTAB*/ } _DefGroup; #undef DefGroup @@ -1004,7 +985,7 @@ const char * sqlite3__wasm_enum_json(void){ #undef _DefGroup /* - ** Emit an array of "StructBinder" struct descripions, which look + ** Emit an array of "StructBinder" struct descriptions, which look ** like: ** ** { @@ -1110,6 +1091,7 @@ const char * sqlite3__wasm_enum_json(void){ #undef CurrentStruct +#if SQLITE_WASM_HAS_VTAB #define CurrentStruct sqlite3_vtab StructBinder { M(pModule, "p"); @@ -1155,7 +1137,6 @@ const char * sqlite3__wasm_enum_json(void){ } _StructBinder; #undef CurrentStruct -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_WASM_MINIMAL) /** ** Workaround: in order to map the various inner structs from ** sqlite3_index_info, we have to uplift those into constructs we @@ -1179,7 +1160,7 @@ const char * sqlite3__wasm_enum_json(void){ { /* Validate that the above struct sizeof()s match ** expectations. We could improve upon this by ** checking the offsetof() for each member. */ - const sqlite3_index_info siiCheck; + const sqlite3_index_info siiCheck = {0}; #define IndexSzCheck(T,M) \ (sizeof(T) == sizeof(*siiCheck.M)) if(!IndexSzCheck(sqlite3_index_constraint,aConstraint) @@ -1232,9 +1213,9 @@ const char * sqlite3__wasm_enum_json(void){ } _StructBinder; #undef CurrentStruct -#endif /*!SQLITE_OMIT_VIRTUALTABLE*/ +#endif /*SQLITE_WASM_HAS_VTAB*/ -#if SQLITE_WASM_TESTS +#if SQLITE_WASM_ENABLE_C_TESTS #define CurrentStruct WasmTestStruct StructBinder { M(v4, "i"); @@ -1244,7 +1225,7 @@ const char * sqlite3__wasm_enum_json(void){ M(xFunc, "v(p)"); } _StructBinder; #undef CurrentStruct -#endif +#endif /*SQLITE_WASM_ENABLE_C_TESTS*/ } out( "]"/*structs*/); @@ -1441,7 +1422,7 @@ int sqlite3__wasm_db_serialize( sqlite3 *pDb, const char *zSchema, ** NULL), or nData is negative, SQLITE_MISUSE are returned. ** ** On success, it creates a new file with the given name, populated -** with the fist nData bytes of pData. If pData is NULL, the file is +** with the first nData bytes of pData. If pData is NULL, the file is ** created and/or truncated to nData bytes. ** ** Whether or not directory components of zFilename are created @@ -1603,7 +1584,7 @@ sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){ return &sqlite3KvvfsMethods; } -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_WASM_MINIMAL) +#if SQLITE_WASM_HAS_VTAB /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. @@ -1626,7 +1607,7 @@ int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){ return SQLITE_MISUSE; } } -#endif /*!SQLITE_OMIT_VIRTUALTABLE*/ +#endif /*SQLITE_WASM_HAS_VTAB*/ /* ** This function is NOT part of the sqlite3 public API. It is strictly @@ -1656,6 +1637,9 @@ int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ case SQLITE_DBCONFIG_TRUSTED_SCHEMA: case SQLITE_DBCONFIG_STMT_SCANSTATUS: case SQLITE_DBCONFIG_REVERSE_SCANORDER: + case SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE: + case SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE: + case SQLITE_DBCONFIG_ENABLE_COMMENTS: return sqlite3_db_config(pDb, op, arg1, pArg2); default: return SQLITE_MISUSE; } @@ -1750,6 +1734,7 @@ char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){ } #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) +#include <emscripten/console.h> #include <emscripten/wasmfs.h> /* @@ -1801,7 +1786,7 @@ int sqlite3__wasm_init_wasmfs(const char *zUnused){ } #endif /* __EMSCRIPTEN__ && SQLITE_ENABLE_WASMFS */ -#if SQLITE_WASM_TESTS +#if SQLITE_WASM_ENABLE_C_TESTS SQLITE_WASM_EXPORT int sqlite3__wasm_test_intptr(int * p){ @@ -1967,6 +1952,9 @@ int sqlite3__wasm_SQLTester_strglob(const char *zGlob, const char *z){ return !sqlite3__wasm_SQLTester_strnotglob(zGlob, z); } -#endif /* SQLITE_WASM_TESTS */ +#endif /* SQLITE_WASM_ENABLE_C_TESTS */ #undef SQLITE_WASM_EXPORT +#undef SQLITE_WASM_HAS_VTAB +#undef SQLITE_WASM_BARE_BONES +#undef SQLITE_WASM_ENABLE_C_TESTS diff --git a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js index 55e497ead..c043fd148 100644 --- a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -335,8 +335,8 @@ sqlite3Worker1Promiser.v2 = function(config){ /** When built as a module, we export sqlite3Worker1Promiser.v2() instead of sqlite3Worker1Promise() because (A) its interface is more - conventional for ESM usage and (B) the ESM option export option for - this API did not exist until v2 was created, so there's no backwards + conventional for ESM usage and (B) the ESM export option for this + API did not exist until v2 was created, so there's no backwards incompatibility. */ export default sqlite3Worker1Promiser.v2; |