diff options
-rw-r--r-- | ext/wasm/GNUmakefile | 2 | ||||
-rw-r--r-- | ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api | 25 | ||||
-rw-r--r-- | ext/wasm/api/README.md | 9 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-glue.js | 188 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-oo1.js | 43 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-prologue.js | 61 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-v-helper.js | 392 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-vfs-helper.js | 221 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-wasm.c | 186 | ||||
-rw-r--r-- | ext/wasm/common/emscripten.css | 2 | ||||
-rw-r--r-- | ext/wasm/common/testing.css | 6 | ||||
-rw-r--r-- | ext/wasm/common/whwasmutil.js | 37 | ||||
-rw-r--r-- | ext/wasm/jaccwabyt/jaccwabyt.js | 137 | ||||
-rw-r--r-- | ext/wasm/jaccwabyt/jaccwabyt.md | 103 | ||||
-rw-r--r-- | ext/wasm/tester1-worker.html | 6 | ||||
-rw-r--r-- | ext/wasm/tester1.c-pp.html | 9 | ||||
-rw-r--r-- | ext/wasm/tester1.c-pp.js | 511 | ||||
-rw-r--r-- | manifest | 44 | ||||
-rw-r--r-- | manifest.uuid | 2 |
19 files changed, 1154 insertions, 830 deletions
diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index 968d4f440..1b1843768 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -289,7 +289,7 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js sqlite3-api.jses += $(sqlite3-api-build-version.js) sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js -sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.js +sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api index 096bf4401..523488cf0 100644 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api @@ -6,12 +6,15 @@ _sqlite3_bind_int64 _sqlite3_bind_null _sqlite3_bind_parameter_count _sqlite3_bind_parameter_index +_sqlite3_bind_pointer _sqlite3_bind_text +_sqlite3_busy_handler _sqlite3_busy_timeout _sqlite3_changes _sqlite3_changes64 _sqlite3_clear_bindings _sqlite3_close_v2 +_sqlite3_collation_needed _sqlite3_column_blob _sqlite3_column_bytes _sqlite3_column_count @@ -24,14 +27,21 @@ _sqlite3_column_text _sqlite3_column_type _sqlite3_compileoption_get _sqlite3_compileoption_used +_sqlite3_complete +_sqlite3_create_collation +_sqlite3_create_collation_v2 _sqlite3_create_function _sqlite3_create_function_v2 +_sqlite3_create_module +_sqlite3_create_module_v2 _sqlite3_create_window_function _sqlite3_data_count _sqlite3_db_filename _sqlite3_db_handle _sqlite3_db_name +_sqlite3_declare_vtab _sqlite3_deserialize +_sqlite3_drop_modules _sqlite3_errmsg _sqlite3_error_offset _sqlite3_errstr @@ -42,6 +52,7 @@ _sqlite3_extended_result_codes _sqlite3_file_control _sqlite3_finalize _sqlite3_free +_sqlite3_get_auxdata _sqlite3_initialize _sqlite3_libversion _sqlite3_libversion_number @@ -50,6 +61,7 @@ _sqlite3_malloc64 _sqlite3_msize _sqlite3_open _sqlite3_open_v2 +_sqlite3_overload_function _sqlite3_prepare_v2 _sqlite3_prepare_v3 _sqlite3_randomness @@ -65,16 +77,20 @@ _sqlite3_result_error_toobig _sqlite3_result_int _sqlite3_result_int64 _sqlite3_result_null +_sqlite3_result_pointer _sqlite3_result_text _sqlite3_result_zeroblob _sqlite3_result_zeroblob64 _sqlite3_serialize +_sqlite3_set_auxdata _sqlite3_shutdown _sqlite3_sourceid _sqlite3_sql _sqlite3_step _sqlite3_strglob +_sqlite3_stricmp _sqlite3_strlike +_sqlite3_strnicmp _sqlite3_total_changes _sqlite3_total_changes64 _sqlite3_trace_v2 @@ -88,11 +104,20 @@ _sqlite3_value_bytes _sqlite3_value_double _sqlite3_value_int _sqlite3_value_int64 +_sqlite3_value_pointer _sqlite3_value_text _sqlite3_value_type _sqlite3_vfs_find _sqlite3_vfs_register _sqlite3_vfs_unregister +_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 _malloc _free _realloc diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md index f9955a895..ce34b26b8 100644 --- a/ext/wasm/api/README.md +++ b/ext/wasm/api/README.md @@ -78,11 +78,10 @@ browser client: a Promise-based interface into the Worker #1 API. This is a far user-friendlier way to interface with databases running in a Worker thread. -- **`sqlite3-vfs-helper.js`**\ - This internal-use-only file installs `sqlite3.VfsHelper` for use by - `sqlite3-*.js` files which create `sqlite3_vfs` implementations. - `sqlite3.VfsHelper` gets removed from the the `sqlite3` object after - the library is finished initializing. +- **`sqlite3-v-helper.js`**\ + Installs `sqlite3.VfsHelper` and `sqlite3.VtabHelper` for use by + downstream code which creates `sqlite3_vfs` and `sqlite3_module` + implementations. - **`sqlite3-vfs-opfs.c-pp.js`**\ is an sqlite3 VFS implementation which supports Google Chrome's Origin-Private FileSystem (OPFS) as a storage layer to provide diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index ac1e9fd6d..0516953f1 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -37,27 +37,44 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }); delete self.Jaccwabyt; - if(0){ - /* "The problem" is that the following isn't even remotely - type-safe. OTOH, nothing about WASM pointers is. */ - const argPointer = wasm.xWrap.argAdapter('*'); - wasm.xWrap.argAdapter('StructType', (v)=>{ - if(v && v.constructor && v instanceof StructBinder.StructType){ - v = v.pointer; - } - return wasm.isPtr(v) - ? argPointer(v) - : toss("Invalid (object) type for StructType-type argument."); - }); - } - {/* Convert Arrays and certain TypedArrays to strings for - 'flexible-string'-type arguments */ + 'string:flexible'-type arguments */ const xString = wasm.xWrap.argAdapter('string'); wasm.xWrap.argAdapter( - 'flexible-string', (v)=>xString(util.flexibleString(v)) + 'string:flexible', (v)=>xString(util.flexibleString(v)) + ); + + /** + The 'string:static' argument adapter treats its argument as + either... + + - WASM pointer: assumed to be a long-lived C-string which gets + returned as-is. + + - Anything else: gets coerced to a JS string for use as a map + key. If a matching entry is found (as described next), it is + returned, else wasm.allocCString() is used to create a a new + string, map its pointer to (''+v) for the remainder of the + application's life, and returns that pointer value for this + call and all future calls which are passed a + string-equivalent argument. + + Use case: sqlite3_bind_pointer() and sqlite3_result_pointer() + call for "a static string and preferably a string + literal". This converter is used to ensure that the string + value seen by those functions is long-lived and behaves as they + need it to. + */ + wasm.xWrap.argAdapter( + 'string:static', + function(v){ + if(wasm.isPtr(v)) return v; + v = ''+v; + let rc = this[v]; + return rc || (rc = this[v] = wasm.allocCString(v)); + }.bind(Object.create(null)) ); - } + }/* special-case string-type argument conversions */ if(1){// WhWasmUtil.xWrap() bindings... /** @@ -68,15 +85,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. */ const aPtr = wasm.xWrap.argAdapter('*'); + const nilType = function(){}; wasm.xWrap.argAdapter('sqlite3_filename', aPtr) ('sqlite3_stmt*', aPtr) ('sqlite3_context*', aPtr) ('sqlite3_value*', aPtr) ('void*', aPtr) - ('sqlite3*', (v)=>{ - if(sqlite3.oo1 && v instanceof sqlite3.oo1.DB) v = v.pointer; - return aPtr(v); - }) + ('sqlite3*', (v)=> + aPtr((v instanceof (sqlite3?.oo1?.DB || nilType)) + ? v.pointer : v)) + ('sqlite3_index_info*', (v)=> + aPtr((v instanceof (capi.sqlite3_index_info || nilType)) + ? v.pointer : v)) + ('sqlite3_module*', (v)=> + aPtr((v instanceof (capi.sqlite3_module || nilType)) + ? v.pointer : v)) /** `sqlite3_vfs*`: @@ -87,14 +110,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ ('sqlite3_vfs*', (v)=>{ if('string'===typeof v){ - const x = capi.sqlite3_vfs_find(v); /* A NULL sqlite3_vfs pointer will be treated as the default VFS in many contexts. We specifically do not want that behavior here. */ - if(!x) sqlite3.SQLite3Error.toss("Unknown sqlite3_vfs name:",v); - return x; - }else if(v instanceof sqlite3.capi.sqlite3_vfs) v = v.pointer; - return aPtr(v); + return capi.sqlite3_vfs_find(v) + || sqlite3.SQLite3Error.toss("Unknown sqlite3_vfs name:",v); + } + return aPtr((v instanceof capi.sqlite3_vfs) ? v.pointer : v); }); wasm.xWrap.resultAdapter('sqlite3*', aPtr) @@ -127,7 +149,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ : fI64Disabled(e[0]); } - /* There's no(?) need to expose bindingSignatures to clients, + /* There's no need to expose bindingSignatures to clients, implicitly making it part of the public interface. */ delete wasm.bindingSignatures; @@ -141,22 +163,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return errCode; }; } - }/*xWrap() bindings*/; /** - When registering a VFS and its related components it may be - necessary to ensure that JS keeps a reference to them to keep - them from getting garbage collected. Simply pass each such value - to this function and a reference will be held to it for the life - of the app. - */ - capi.sqlite3_vfs_register.addReference = function f(...args){ - if(!f._) f._ = []; - f._.push(...args); - }; - - /** Internal helper to assist in validating call argument counts in the hand-written sqlite3_xyz() wrappers. We do this only for consistency with non-special-case wrappings. @@ -167,29 +176,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ (1===n?"":'s')+"."); }; - /** - Helper for flexible-string conversions which require a - byte-length counterpart argument. Passed a value and its - ostensible length, this function returns [V,N], where V - is either v or a transformed copy of v and N is either n, - -1, or the byte length of v (if it's a byte array). - */ - const __flexiString = function(v,n){ - if('string'===typeof v){ - n = -1; - }else if(util.isSQLableTypedArray(v)){ - n = v.byteLength; - v = util.typedArrayToString(v); - }else if(Array.isArray(v)){ - v = v.join(""); - n = -1; - } - return [v, n]; - }; - if(1){/* Special-case handling of sqlite3_exec() */ const __exec = wasm.xWrap("sqlite3_exec", "int", - ["sqlite3*", "flexible-string", "*", "*", "**"]); + ["sqlite3*", "string:flexible", "*", "*", "**"]); /* Documented in the api object's initializer. */ capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){ if(f.length!==arguments.length){ @@ -541,6 +530,26 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if(1){/* Special-case handling of sqlite3_prepare_v2() and sqlite3_prepare_v3() */ /** + Helper for string:flexible conversions which require a + byte-length counterpart argument. Passed a value and its + ostensible length, this function returns [V,N], where V + is either v or a transformed copy of v and N is either n, + -1, or the byte length of v (if it's a byte array). + */ + const __flexiString = (v,n)=>{ + if('string'===typeof v){ + n = -1; + }else if(util.isSQLableTypedArray(v)){ + n = v.byteLength; + v = util.typedArrayToString(v); + }else if(Array.isArray(v)){ + v = v.join(""); + n = -1; + } + return [v, n]; + }; + + /** Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). */ const __prepare = Object.create(null); @@ -570,7 +579,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "int", ["sqlite3*", "*", "int", "int", "**", "**"]); - /* Documented in the api object's initializer. */ + /* Documented in the capi object's initializer. */ capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ if(f.length!==arguments.length){ return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length); @@ -587,7 +596,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } }; - /* Documented in the api object's initializer. */ + /* Documented in the capi object's initializer. */ capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){ return (f.length===arguments.length) ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) @@ -603,13 +612,17 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); - for(const t of ['access', 'blobFinalizers', 'dataTypes', - 'encodings', 'fcntl', 'flock', 'ioCap', - 'limits', - 'openFlags', 'prepareFlags', 'resultCodes', - 'serialize', 'syncFlags', 'trace', 'udfFlags', - 'version' - ]){ + const defineGroups = ['access', 'authorizer', + 'blobFinalizers', 'dataTypes', + 'encodings', 'fcntl', 'flock', 'ioCap', + 'limits', 'openFlags', + 'prepareFlags', 'resultCodes', + 'serialize', 'syncFlags', 'trace', 'udfFlags', + 'version' ]; + if(wasm.bigIntEnabled){ + defineGroups.push('vtab'); + } + for(const t of defineGroups){ for(const e of Object.entries(wasm.ctype[t])){ // ^^^ [k,v] there triggers a buggy code transformation via // one of the Emscripten-driven optimizers. @@ -629,19 +642,37 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; /* Bind all registered C-side structs... */ const notThese = Object.assign(Object.create(null),{ - // Structs NOT to register - WasmTestStruct: true + // For each struct to NOT register, map its name to true: + WasmTestStruct: true, + /* We unregister the kvvfs VFS from Worker threads below. */ + sqlite3_kvvfs_methods: !util.isUIThread(), + /* sqlite3_index_info and friends require int64: */ + sqlite3_index_info: !wasm.bigIntEnabled, + sqlite3_index_constraint: !wasm.bigIntEnabled, + sqlite3_index_orderby: !wasm.bigIntEnabled, + sqlite3_index_constraint_usage: !wasm.bigIntEnabled }); - if(!util.isUIThread()){ - /* We remove the kvvfs VFS from Worker threads below. */ - notThese.sqlite3_kvvfs_methods = true; - } for(const s of wasm.ctype.structs){ if(!notThese[s.name]){ capi[s.name] = sqlite3.StructBinder(s); } } - }/*end C constant imports*/ + if(capi.sqlite3_index_info){ + /* Move these inner structs into sqlite3_index_info. Binding + ** them to WASM requires that we create global-scope structs to + ** model them with, but those are no longer needed after we've + ** passed them to StructBinder. */ + for(const k of ['sqlite3_index_constraint', + 'sqlite3_index_orderby', + 'sqlite3_index_constraint_usage']){ + capi.sqlite3_index_info[k] = capi[k]; + delete capi[k]; + } + capi.sqlite3_vtab_config = + (pDb, op, arg=0)=>wasm.exports.sqlite3_wasm_vtab_config( + wasm.xWrap.argAdapter('sqlite3*')(pDb), op, arg); + }/* end vtab-related setup */ + }/*end C constant and struct imports*/ const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); if( pKvvfs ){/* kvvfs-specific glue */ @@ -652,8 +683,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ delete capi.sqlite3_kvvfs_methods; const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, - pstack = wasm.pstack, - pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc; + pstack = wasm.pstack; const kvvfsStorage = (zClass)=> ((115/*=='s'*/===wasm.getMemValue(zClass)) diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index e077b0c50..6253c659f 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -192,13 +192,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** Sets SQL which should be exec()'d on a DB instance after it is opened with the given VFS pointer. The SQL may be any type - supported by the "flexible-string" function argument - conversion. Alternately, the 2nd argument may be a function, in - which case it is called with (theOo1DbObject,sqlite3Namespace) at - the end of the DB() constructor. The function must throw on - error, in which case the db is closed and the exception is - propagated. This function is intended only for use by DB - subclasses or sqlite3_vfs implementations. + supported by the "string:flexible" function argument conversion. + Alternately, the 2nd argument may be a function, in which case it + is called with (theOo1DbObject,sqlite3Namespace) at the end of + the DB() constructor. The function must throw on error, in which + case the db is closed and the exception is propagated. This + function is intended only for use by DB subclasses or sqlite3_vfs + implementations. */ dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){ __vfsPostOpenSql[pVfs] = sql; @@ -474,6 +474,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; /** + Internal impl of the DB.selectArrays() and + selectObjects() methods. + */ + const __selectAll = + (db, sql, bind, rowMode)=>db.exec({ + sql, bind, rowMode, returnValue: 'resultRows' + }); + + /** Expects to be given a DB instance or an `sqlite3*` pointer (may be null) and an sqlite3 API result code. If the result code is not falsy, this function throws an SQLite3Error with an error @@ -1099,6 +1108,26 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }, /** + Runs the given SQL and returns an array of all results, with + each row represented as an array, as per the 'array' `rowMode` + option to `exec()`. An empty result set resolves + to an empty array. The second argument, if any, is treated as + the 'bind' option to a call to exec(). + */ + selectArrays: function(sql,bind){ + return __selectAll(this, sql, bind, 'array'); + }, + + /** + Works identically to selectArrays() except that each value + in the returned array is an object, as per the 'object' `rowMode` + option to `exec()`. + */ + selectObjects: function(sql,bind){ + return __selectAll(this, sql, bind, 'object'); + }, + + /** Returns the number of currently-opened Stmt handles for this db handle, or 0 if this DB instance is closed. */ diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index 5ebe7af05..558fcda1e 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -602,7 +602,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( If the callback is not a JS function then this binding performs no translation of the callback, but the sql argument is still converted to a WASM string for the call using the - "flexible-string" argument converter. + "string:flexible" argument converter. */ sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/, @@ -884,16 +884,23 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"], ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"], ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"], + ["sqlite3_bind_pointer", "int", + "sqlite3_stmt*", "int", "*", "string:static", "*"], ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int" /* We should arguably create a hand-written binding of bind_text() which does more flexible text conversion, along the lines of sqlite3_prepare_v3(). The slightly problematic part is the final argument (text destructor). */ ], + //["sqlite3_busy_handler","int", "sqlite3*", "*", "*"], + // ^^^^ TODO: custom binding which auto-converts JS function arg + // to a WASM function, noting that calling it multiple times + // would introduce a leak. ["sqlite3_busy_timeout","int", "sqlite3*", "int"], ["sqlite3_close_v2", "int", "sqlite3*"], ["sqlite3_changes", "int", "sqlite3*"], ["sqlite3_clear_bindings","int", "sqlite3_stmt*"], + ["sqlite3_collation_needed", "int", "sqlite3*", "*", "*"/*=>v(ppis)*/], ["sqlite3_column_blob","*", "sqlite3_stmt*", "int"], ["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"], ["sqlite3_column_count", "int", "sqlite3_stmt*"], @@ -904,9 +911,16 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ["sqlite3_column_type","int", "sqlite3_stmt*", "int"], ["sqlite3_compileoption_get", "string", "int"], ["sqlite3_compileoption_used", "int", "string"], + ["sqlite3_complete", "int", "string:flexible"], /* sqlite3_create_function(), sqlite3_create_function_v2(), and sqlite3_create_window_function() use hand-written bindings to simplify handling of their function-type arguments. */ + ["sqlite3_create_collation", "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*"], @@ -928,6 +942,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], ["sqlite3_finalize", "int", "sqlite3_stmt*"], ["sqlite3_free", undefined,"*"], + ["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"], ["sqlite3_initialize", undefined], /*["sqlite3_interrupt", undefined, "sqlite3*" ^^^ we cannot actually currently support this because JS is @@ -945,23 +960,28 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( the range of supported argument types. */ ["sqlite3_realloc", "*","*","int"], ["sqlite3_reset", "int", "sqlite3_stmt*"], - ["sqlite3_result_blob",undefined, "sqlite3_context*", "*", "int", "*"], - ["sqlite3_result_double",undefined, "sqlite3_context*", "f64"], - ["sqlite3_result_error",undefined, "sqlite3_context*", "string", "int"], + ["sqlite3_result_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_text",undefined, "sqlite3_context*", "string", "int", "*"], + ["sqlite3_result_int", undefined, "sqlite3_context*", "int"], + ["sqlite3_result_null", undefined, "sqlite3_context*"], + ["sqlite3_result_pointer", undefined, + "sqlite3_context*", "*", "string:static", "*"], + ["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"], ["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"], ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"], + ["sqlite3_set_auxdata", undefined, "sqlite3_context*", "int", "*", "*"/* => v(*) */], ["sqlite3_shutdown", undefined], ["sqlite3_sourceid", "string"], ["sqlite3_sql", "string", "sqlite3_stmt*"], ["sqlite3_step", "int", "sqlite3_stmt*"], ["sqlite3_strglob", "int", "string","string"], - ["sqlite3_strlike", "int", "string","string","int"], + ["sqlite3_stricmp", "int", "string", "string"], + ["sqlite3_strlike", "int", "string", "string","int"], + ["sqlite3_strnicmp", "int", "string", "string", "int"], ["sqlite3_trace_v2", "int", "sqlite3*", "int", "*", "*"], ["sqlite3_total_changes", "int", "sqlite3*"], /* Note sqlite3_uri_...() has very specific requirements @@ -975,6 +995,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ["sqlite3_value_bytes","int", "sqlite3_value*"], ["sqlite3_value_double","f64", "sqlite3_value*"], ["sqlite3_value_int","int", "sqlite3_value*"], + ["sqlite3_value_pointer", "*", "sqlite3_value*", "string:static"], ["sqlite3_value_text", "string", "sqlite3_value*"], ["sqlite3_value_type", "int", "sqlite3_value*"], ["sqlite3_vfs_find", "*", "string"], @@ -993,19 +1014,40 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( Functions which require BigInt (int64) support are separated from the others because we need to conditionally bind them or apply dummy impls, depending on the capabilities of the environment. + + Note that not all of these functions directly require int64 + but are only for use with APIs which require int64. For example, + the vtab-related functions. */ wasm.bindingSignatures.int64 = [ ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]], ["sqlite3_changes64","i64", ["sqlite3*"]], ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]], + ["sqlite3_create_module", "int", + ["sqlite3*","string","sqlite3_module*","*"]], + ["sqlite3_create_module_v2", "int", + ["sqlite3*","string","sqlite3_module*","*","*"]], + ["sqlite3_declare_vtab", "int", ["sqlite3*", "string:flexible"]], + ["sqlite3_drop_modules", "int", ["sqlite3*", "**"]], ["sqlite3_malloc64", "*","i64"], ["sqlite3_msize", "i64", "*"], + ["sqlite3_overload_function", "int", ["sqlite3*","string","int"]], ["sqlite3_realloc64", "*","*", "i64"], - ["sqlite3_result_int64",undefined, "*", "i64"], + ["sqlite3_result_int64", undefined, "*", "i64"], ["sqlite3_result_zeroblob64", "int", "*", "i64"], ["sqlite3_total_changes64", "i64", ["sqlite3*"]], ["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]], ["sqlite3_value_int64","i64", "sqlite3_value*"], + ["sqlite3_vtab_collation","string","sqlite3_index_info*","int"], + ["sqlite3_vtab_distinct","int", "sqlite3_index_info*"], + ["sqlite3_vtab_in","int", "sqlite3_index_info*", "int", "int"], + ["sqlite3_vtab_in_first", "int", "sqlite3_value*", "**"], + ["sqlite3_vtab_in_next", "int", "sqlite3_value*", "**"], + /*["sqlite3_vtab_config" is variadic and requires a hand-written + proxy.] */ + ["sqlite3_vtab_nochange","int", "sqlite3_context*"], + ["sqlite3_vtab_on_conflict","int", "sqlite3*"], + ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"] ]; /** @@ -1600,7 +1642,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( some initializers. Retain them when running in test mode so that we can add tests for them. */ delete sqlite3.util; - delete sqlite3.VfsHelper; delete sqlite3.StructBinder; } return sqlite3; diff --git a/ext/wasm/api/sqlite3-v-helper.js b/ext/wasm/api/sqlite3-v-helper.js new file mode 100644 index 000000000..84272266e --- /dev/null +++ b/ext/wasm/api/sqlite3-v-helper.js @@ -0,0 +1,392 @@ +/* +** 2022-11-30 +** +** 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 installs sqlite3.VfsHelper, and object which exists to + assist in the creation of JavaScript implementations of sqlite3_vfs, + along with its virtual table counterpart, sqlite3.VtabHelper. +*/ +'use strict'; +self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ + const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss; + const vh = Object.create(null), vt = Object.create(null); + + sqlite3.VfsHelper = vh; + sqlite3.VtabHelper = vt; + + /** + Installs a StructBinder-bound function pointer member of the + given name and function in the given 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. + + Returns a proxy for this function which is bound to tgt and takes + 2 args (name,func). That function returns the same thing, + 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. + + If tgt.ondispose is set before this is called then it _must_ + be an array, to which this function will append entries. + + 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 method 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 will leave the C environment in an undefined + state. For non-dev-time use, it is a given that the C API will + never call one of the generated function wrappers with the wrong + argument count. + */ + vh.installMethod = vt.installMethod = function callee( + tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck + ){ + if(!(tgt instanceof sqlite3.StructBinder.StructType)){ + toss("Usage error: target object is-not-a StructType."); + } + 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 + sqlite3.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," is not a function pointer. Signature =",sigN); + } + const memKey = tgt.memberKey(name); + const fProxy = applyArgcCheck + /** 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; + const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); + tgt[memKey] = pFunc; + if(!tgt.ondispose) tgt.ondispose = []; + if(!tgt.ondispose.__removeFuncList){ + tgt.ondispose.push('ondispose.__removeFuncList handler', + callee.removeFuncList); + tgt.ondispose.__removeFuncList = []; + } + tgt.ondispose.__removeFuncList.push(memKey, pFunc); + return (n,f)=>callee(tgt, n, f, applyArgcCheck); + }/*installMethod*/; + vh.installMethod.installMethodArgcCheck = false; + + /** + Installs methods into the given 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 this object. Throws on error. + */ + vh.installMethods = vt.installMethods = function( + structType, methods, applyArgcCheck = vh.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 = structType.memberKey(k); + structType[mkey] = structType[structType.memberKey(prior)]; + }else{ + vh.installMethod(structType, k, m, applyArgcCheck); + seen.set(m, k); + } + } + return this; + }; + + /** + Uses sqlite3_vfs_register() to register the + sqlite3.capi.sqlite3_vfs-type vfs, which must have already been + filled out properly. If the 2nd argument is truthy, the VFS is + registered as the default VFS, else it is not. + + On success, returns this object. Throws on error. + */ + vh.registerVfs = function(vfs, asDefault=false){ + if(!(vfs instanceof sqlite3.capi.sqlite3_vfs)){ + toss("Expecting a sqlite3_vfs-type argument."); + } + const rc = capi.sqlite3_vfs_register(vfs.pointer, asDefault ? 1 : 0); + if(rc){ + toss("sqlite3_vfs_register(",vfs,") failed with rc",rc); + } + if(vfs.pointer !== capi.sqlite3_vfs_find(vfs.$zName)){ + toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS", + vfs); + } + return this; + }; + + /** + A wrapper for installMethods() or registerVfs() to reduce + installation of a VFS and/or its I/O methods to a single + call. + + Accepts an object which contains the properties "io" and/or + "vfs", each of which is itself an object with following properties: + + - `struct`: an sqlite3.StructType-type struct. This must be a + populated (except for the methods) object of type + sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the + "vfs" entry). + + - `methods`: an object mapping sqlite3_io_methods method names + (e.g. 'xClose') to JS implementations of those methods. The JS + implementations must be call-compatible with their native + counterparts. + + For each of those object, this function passes its (`struct`, + `methods`, (optional) `applyArgcCheck`) properties to + this.installMethods(). + + If the `vfs` entry is set then: + + - Its `struct` property is passed to this.registerVfs(). The + `vfs` entry may optionally have an `asDefault` property, which + gets passed as the 2nd argument to registerVfs(). + + - If `struct.$zName` is falsy and the entry has a string-type + `name` property, `struct.$zName` is set to the C-string form of + that `name` value before registerVfs() is called. + + On success returns this object. Throws on error. + */ + vh.installVfs = function(opt){ + let count = 0; + const propList = ['io','vfs']; + for(const key of propList){ + const o = opt[key]; + if(o){ + ++count; + this.installMethods(o.struct, o.methods, !!o.applyArgcCheck); + if('vfs'===key){ + if(!o.struct.$zName && 'string'===typeof o.name){ + o.struct.$zName = wasm.allocCString(o.name); + /* Note that we leak that C-string. */ + } + this.registerVfs(o.struct, !!o.asDefault); + } + } + } + if(!count) toss("Misuse: installVfs() options object requires at least", + "one of:", propList); + return this; + }; + + /** + Expects to be passed the (argc,argv) arguments of + sqlite3_module::xFilter(), or an equivalent API. This function + transforms the arguments (an array of (sqlite3_value*)) into a JS + array of equivalent JS values. It uses the same type conversions + as sqlite3_create_function_v2() and friends. Throws on error, + e.g. if it cannot figure out a sensible data conversion. + */ + vt.sqlite3ValuesToJs = capi.sqlite3_create_function_v2.udfConvertArgs; + + /** + Factory function for xyz2js() impls. + */ + const __v2jsFactory = function(structType){ + return function(ptr,remove=false){ + if(0===arguments.length) ptr = new structType; + if(ptr instanceof structType){ + //T.assert(!this.has(ptr.pointer)); + this.set(ptr.pointer, ptr); + return ptr; + }else if(!wasm.isPtr(ptr)){ + sqlite3.SQLite3Error.toss("Invalid argument to v2jsFactory"); + } + let rc = this.get(ptr); + if(remove) this.delete(ptr); + /*arguable else if(!rc){ + rc = new structType(ptr); + this.set(ptr, rc); + }*/ + return rc; + }.bind(new Map); + }; + /** + EXPERIMENTAL. DO NOT USE IN CLIENT CODE. + + Has 3 distinct uses: + + - vtab2js() instantiates a new capi.sqlite3_vtab instance, maps + its pointer for later by-pointer lookup, and returns that + object. This is intended to be called from + sqlite3_module::xConnect() or xCreate() implementations. + + - vtab2js(pVtab) accepts a WASM pointer to a C-level + (sqlite3_vtab*) instance and returns the capi.sqlite3_vtab + object created by the first form of this function, or undefined + if that form has not been used. This is intended to be called + from sqlite3_module methods which take a (sqlite3_vtab*) pointer + _except_ for xDisconnect(), in which case use... + + - vtab2js(pVtab,true) as for the previous form, but removes the + pointer-to-object mapping before returning. The caller must + call dispose() on the returned object. This is intended to be + called from sqlite3_module::xDisconnect() implementations or + in error handling of a failed xCreate() or xConnect(). + */ + vt.vtab2js = __v2jsFactory(capi.sqlite3_vtab); + + /** + EXPERIMENTAL. DO NOT USE IN CLIENT CODE. + + Works identically to vtab2js() except that it deals with + sqlite3_cursor objects and pointers instead of sqlite3_vtab. + + - vcur2js() is intended to be called from sqlite3_module::xOpen() + + - vcur2js(pCursor) is intended to be called from all sqlite3_module + methods which take a (sqlite3_vtab_cursor*) _except_ for + xClose(), in which case use... + + - vcur2js(pCursor, true) will remove the m apping of pCursor to a + capi.sqlite3_vtab_cursor object and return that object. The + caller must call dispose() on the returned object. This is + intended to be called form xClose() or in error handling of a + failed xOpen(). + */ + vt.vcur2js = __v2jsFactory(capi.sqlite3_vtab_cursor); + + /** + Given an error object, this function returns + sqlite3.capi.SQLITE_NOMEM if (e instanceof + sqlite3.WasmAllocError), else it returns its + second argument. Its intended usage is in the methods + of a sqlite3_vfs or sqlite3_module: + + ``` + try{ + let rc = ... + return rc; + }catch(e){ + return sqlite3.VtabHelper.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ); + // where SQLITE_XYZ is some call-appropriate result code. + } + ``` + */ + /**vh.exceptionToRc = vt.exceptionToRc = + (e, defaultRc=capi.SQLITE_ERROR)=>( + (e instanceof sqlite3.WasmAllocError) + ? capi.SQLITE_NOMEM + : defaultRc + );*/ + + /** + Given an sqlite3_module method name and error object, this + function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof + sqlite3.WasmAllocError), else it returns its second argument. Its + intended usage is in the methods of a sqlite3_vfs or + sqlite3_module: + + ``` + try{ + let rc = ... + return rc; + }catch(e){ + return sqlite3.VtabHelper.xMethodError( + 'xColumn', e, sqlite3.capi.SQLITE_XYZ); + // where SQLITE_XYZ is some call-appropriate result code + // defaulting to SQLITE_ERROR. + } + ``` + + If xMethodError.errorReporter is a function, it is called in + order to report the error, else the error is not reported. + If that function throws, that exception is ignored. + */ + vt.xMethodError = function f(methodName, err, defaultRc=capi.SQLITE_ERROR){ + if(f.errorReporter instanceof Function){ + try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);} + catch(e){/*ignored*/} + } + return (err instanceof sqlite3.WasmAllocError) + ? capi.SQLITE_NOMEM + : defaultRc; + }; + vt.xMethodError.errorReporter = 1 ? console.error.bind(console) : false; + + /** + "The problem" with this is that it introduces an outer function with + a different arity than the passed-in method callback. That means we + cannot do argc validation on these. Additionally, some methods (namely + xConnect) may have call-specific error handling. It would be a shame to + hard-coded that per-method support in this function. + */ + /** vt.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){ + return function(...args){ + try { method(...args); } + }catch(e){ return vt.xMethodError(methodName, e, defaultRc) } + }; + */ + + /** + A helper for sqlite3_vtab::xRow() implementations. It must be + passed that function's 2nd argument and the value for that + pointer. Returns the same as wasm.setMemValue() and will throw + if the 1st or 2nd arguments are invalid for that function. + */ + vt.setRowId = (ppRowid64, value)=>wasm.setMemValue(ppRowid64, value, 'i64'); +}/*sqlite3ApiBootstrap.initializers.push()*/); diff --git a/ext/wasm/api/sqlite3-vfs-helper.js b/ext/wasm/api/sqlite3-vfs-helper.js deleted file mode 100644 index f9d3c18c7..000000000 --- a/ext/wasm/api/sqlite3-vfs-helper.js +++ /dev/null @@ -1,221 +0,0 @@ -/* -** 2022-11-30 -** -** 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 installs sqlite.VfsHelper, an object which exists - to assist in the creation of JavaScript implementations of - sqlite3_vfs. It is NOT part of the public API, and is an - internal implemenation detail for use in this project's - own development of VFSes. It may be exposed to clients - at some point, provided there is value in doing so. -*/ -'use strict'; -self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss; - const vh = Object.create(null); - - /** - Does nothing more than holds a permanent reference to each - argument. This is useful in some cases to ensure that, e.g., a - custom sqlite3_io_methods instance does not get - garbage-collected. - - Returns this object. - */ - vh.holdReference = function(...args){ - for(const v of args) this.refs.add(v); - return vh; - }.bind({refs: new Set}); - - /** - Installs a StructBinder-bound function pointer member of the - given name and function in the given 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. - - If applyArgcCheck is true then each method 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 recommended for dev-time usage for - sanity checking. Once a VFS implementation is known to be - working, it is a given that the C API will never call it with the - wrong argument count. - - Returns a proxy for this function which is bound to tgt and takes - 2 args (name,func). That function returns the same thing, - 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. - - If tgt.ondispose is set before this is called then it _must_ - be an array, to which this function will append entries. - */ - vh.installMethod = function callee(tgt, name, func, - applyArgcCheck=callee.installMethodArgcCheck){ - if(!(tgt instanceof sqlite3.StructBinder.StructType)){ - toss("Usage error: target object is-not-a StructType."); - } - if(1===arguments.length){ - return (n,f)=>callee(tgt, n, f, applyArgcCheck); - } - if(!callee.argcProxy){ - callee.argcProxy = function(func,sig){ - return function(...args){ - if(func.length!==arguments.length){ - toss("Argument mismatch. Native signature is:",sig); - } - return func.apply(this, args); - } - }; - /* An ondispose() callback for use with - sqlite3.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," is not a function pointer. Signature =",sigN); - } - const memKey = tgt.memberKey(name); - const fProxy = applyArgcCheck - /** 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(func, sigN) - : func; - const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); - tgt[memKey] = pFunc; - if(!tgt.ondispose) tgt.ondispose = []; - if(!tgt.ondispose.__removeFuncList){ - tgt.ondispose.push('ondispose.__removeFuncList handler', - callee.removeFuncList); - tgt.ondispose.__removeFuncList = []; - } - tgt.ondispose.__removeFuncList.push(memKey, pFunc); - return (n,f)=>callee(tgt, n, f, applyArgcCheck); - }/*installMethod*/; - vh.installMethod.installMethodArgcCheck = false; - - /** - Installs methods into the given StructType-type object. 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. - - On success, passes its first argument to holdRefence() and - returns this object. Throws on error. - */ - vh.installMethods = function(structType, methods, - applyArgcCheck=vh.installMethod.installMethodArgcCheck){ - for(const k of Object.keys(methods)){ - vh.installMethod(structType, k, methods[k], applyArgcCheck); - } - return vh.holdReference(structType); - }; - - /** - Uses sqlite3_vfs_register() to register the - sqlite3.capi.sqlite3_vfs-type vfs, which must have already been - filled out properly. If the 2nd argument is truthy, the VFS is - registered as the default VFS, else it is not. - - On success, passes its first argument to this.holdReference() and - returns this object. Throws on error. - */ - vh.registerVfs = function(vfs, asDefault=false){ - if(!(vfs instanceof sqlite3.capi.sqlite3_vfs)){ - toss("Expecting a sqlite3_vfs-type argument."); - } - const rc = capi.sqlite3_vfs_register(vfs.pointer, asDefault ? 1 : 0); - if(rc){ - toss("sqlite3_vfs_register(",vfs,") failed with rc",rc); - } - if(vfs.pointer !== capi.sqlite3_vfs_find(vfs.$zName)){ - toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS", - vfs); - } - return vh.holdReference(vfs); - }; - - /** - A wrapper for installMethods() or registerVfs() to reduce - installation of a VFS and/or its I/O methods to a single - call. - - Accepts an object which contains the properties "io" and/or - "vfs", each of which is itself an object with following properties: - - - `struct`: an sqlite3.StructType-type struct. This must be a - populated (except for the methods) object of type - sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the - "vfs" entry). - - - `methods`: an object mapping sqlite3_io_methods method names - (e.g. 'xClose') to JS implementations of those methods. - - For each of those object, this function passes its (`struct`, - `methods`, (optional) `applyArgcCheck`) properties to - this.installMethods(). - - If the `vfs` entry is set then: - - - Its `struct` property is passed to this.registerVfs(). The - `vfs` entry may optionally have an `asDefault` property, which - gets passed as the 2nd argument to registerVfs(). - - - If `struct.$zName` is falsy and the entry has a string-type - `name` property, `struct.$zName` is set to the C-string form of - that `name` value before registerVfs() is called. - - On success returns this object. Throws on error. - */ - vh.installVfs = function(opt){ - let count = 0; - const propList = ['io','vfs']; - for(const key of propList){ - const o = opt[key]; - if(o){ - ++count; - this.installMethods(o.struct, o.methods, !!o.applyArgcCheck); - if('vfs'===key){ - if(!o.struct.$zName && 'string'===typeof o.name){ - o.struct.$zName = wasm.allocCString(o.name); - /* Note that we leak that C-string. */ - } - this.registerVfs(o.struct, !!o.asDefault); - } - } - } - if(!count) toss("Misuse: installVfs() options object requires at least", - "one of:", propList); - return this; - }; - - sqlite3.VfsHelper = vh; -}/*sqlite3ApiBootstrap.initializers.push()*/); diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index f6499243a..96234bca3 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -368,7 +368,7 @@ void sqlite3_wasm_test_struct(WasmTestStruct * s){ */ SQLITE_WASM_KEEP const char * sqlite3_wasm_enum_json(void){ - static char aBuffer[1024 * 12] = {0} /* where the JSON goes */; + static char aBuffer[1024 * 16] = {0} /* where the JSON goes */; int n = 0, nChildren = 0, nStruct = 0 /* output counters for figuring out where commas go */; char * zPos = &aBuffer[1] /* skip first byte for now to help protect @@ -410,6 +410,12 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/; } _DefGroup; + /* TODO? Authorizer... */ + DefGroup(authorizer){ + DefInt(SQLITE_DENY); + DefInt(SQLITE_IGNORE); + } _DefGroup; + DefGroup(blobFinalizers) { /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as ** integers to avoid casting-related warnings. */ @@ -681,7 +687,36 @@ const char * sqlite3_wasm_enum_json(void){ DefStr(SQLITE_VERSION); DefStr(SQLITE_SOURCE_ID); } _DefGroup; - + + DefGroup(vtab) { + DefInt(SQLITE_INDEX_SCAN_UNIQUE); + DefInt(SQLITE_INDEX_CONSTRAINT_EQ); + DefInt(SQLITE_INDEX_CONSTRAINT_GT); + DefInt(SQLITE_INDEX_CONSTRAINT_LE); + DefInt(SQLITE_INDEX_CONSTRAINT_LT); + DefInt(SQLITE_INDEX_CONSTRAINT_GE); + DefInt(SQLITE_INDEX_CONSTRAINT_MATCH); + DefInt(SQLITE_INDEX_CONSTRAINT_LIKE); + DefInt(SQLITE_INDEX_CONSTRAINT_GLOB); + DefInt(SQLITE_INDEX_CONSTRAINT_REGEXP); + DefInt(SQLITE_INDEX_CONSTRAINT_NE); + DefInt(SQLITE_INDEX_CONSTRAINT_ISNOT); + DefInt(SQLITE_INDEX_CONSTRAINT_ISNOTNULL); + DefInt(SQLITE_INDEX_CONSTRAINT_ISNULL); + DefInt(SQLITE_INDEX_CONSTRAINT_IS); + DefInt(SQLITE_INDEX_CONSTRAINT_LIMIT); + DefInt(SQLITE_INDEX_CONSTRAINT_OFFSET); + DefInt(SQLITE_INDEX_CONSTRAINT_FUNCTION); + DefInt(SQLITE_VTAB_CONSTRAINT_SUPPORT); + DefInt(SQLITE_VTAB_INNOCUOUS); + DefInt(SQLITE_VTAB_DIRECTONLY); + DefInt(SQLITE_ROLLBACK); + //DefInt(SQLITE_IGNORE); // Also used by sqlite3_authorizer() callback + DefInt(SQLITE_FAIL); + //DefInt(SQLITE_ABORT); // Also an error code + DefInt(SQLITE_REPLACE); + } _DefGroup; + #undef DefGroup #undef DefStr #undef DefInt @@ -793,6 +828,128 @@ const char * sqlite3_wasm_enum_json(void){ } _StructBinder; #undef CurrentStruct + +#define CurrentStruct sqlite3_vtab + StructBinder { + M(pModule, "p"); + M(nRef, "i"); + M(zErrMsg, "p"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_vtab_cursor + StructBinder { + M(pVtab, "p"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_module + StructBinder { + M(iVersion, "i"); + M(xCreate, "i(ppippp)"); + M(xConnect, "i(ppippp)"); + M(xBestIndex, "i(pp)"); + M(xDisconnect, "i(p)"); + M(xDestroy, "i(p)"); + M(xOpen, "i(pp)"); + M(xClose, "i(p)"); + M(xFilter, "i(pisip)"); + M(xNext, "i(p)"); + M(xEof, "i(p)"); + M(xColumn, "i(ppi)"); + M(xRowid, "i(pp)"); + M(xUpdate, "i(pipp)"); + M(xBegin, "i(p)"); + M(xSync, "i(p)"); + M(xCommit, "i(p)"); + M(xRollback, "i(p)"); + M(xFindFunction, "i(pispp)"); + M(xRename, "i(ps)"); + // ^^^ v1. v2+ follows... + M(xSavepoint, "i(pi)"); + M(xRelease, "i(pi)"); + M(xRollbackTo, "i(pi)"); + // ^^^ v2. v3+ follows... + M(xShadowName, "i(s)"); + } _StructBinder; +#undef CurrentStruct + + /** + ** Workaround: in order to map the various inner structs from + ** sqlite3_index_info, we have to uplift those into constructs we + ** can access by type name. These structs _must_ match their + ** in-sqlite3_index_info counterparts byte for byte. + */ + typedef struct { + int iColumn; + unsigned char op; + unsigned char usable; + int iTermOffset; + } sqlite3_index_constraint; + typedef struct { + int iColumn; + unsigned char desc; + } sqlite3_index_orderby; + typedef struct { + int argvIndex; + unsigned char omit; + } sqlite3_index_constraint_usage; + { /* 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; +#define IndexSzCheck(T,M) \ + (sizeof(T) == sizeof(*siiCheck.M)) + if(!IndexSzCheck(sqlite3_index_constraint,aConstraint) + || !IndexSzCheck(sqlite3_index_orderby,aOrderBy) + || !IndexSzCheck(sqlite3_index_constraint_usage,aConstraintUsage)){ + assert(!"sizeof mismatch in sqlite3_index_... struct(s)"); + return 0; + } +#undef IndexSzCheck + } + +#define CurrentStruct sqlite3_index_constraint + StructBinder { + M(iColumn, "i"); + M(op, "C"); + M(usable, "C"); + M(iTermOffset, "i"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_index_orderby + StructBinder { + M(iColumn, "i"); + M(desc, "C"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_index_constraint_usage + StructBinder { + M(argvIndex, "i"); + M(omit, "C"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_index_info + StructBinder { + M(nConstraint, "i"); + M(aConstraint, "p"); + M(nOrderBy, "i"); + M(aOrderBy, "p"); + M(aConstraintUsage, "p"); + M(idxNum, "i"); + M(idxStr, "p"); + M(needToFreeIdxStr, "i"); + M(orderByConsumed, "i"); + M(estimatedCost, "d"); + M(estimatedRows, "j"); + M(idxFlags, "i"); + M(colUsed, "j"); + } _StructBinder; +#undef CurrentStruct + #if SQLITE_WASM_TESTS #define CurrentStruct WasmTestStruct StructBinder { @@ -1109,6 +1266,31 @@ sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ return &sqlite3KvvfsMethods; } +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** This is a proxy for the variadic sqlite3_vtab_config() which passes +** its argument on, or not, to sqlite3_vtab_config(), depending on the +** value of its 2nd argument. Returns the result of +** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a +** valid value. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){ + switch(op){ + case SQLITE_VTAB_DIRECTONLY: + case SQLITE_VTAB_INNOCUOUS: + return sqlite3_vtab_config(pDb, op); + case SQLITE_VTAB_CONSTRAINT_SUPPORT: + return sqlite3_vtab_config(pDb, op, arg); + default: + return SQLITE_MISUSE; + } + +} + + #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) #include <emscripten/wasmfs.h> diff --git a/ext/wasm/common/emscripten.css b/ext/wasm/common/emscripten.css index 7e3dc811d..d8f82c73b 100644 --- a/ext/wasm/common/emscripten.css +++ b/ext/wasm/common/emscripten.css @@ -1,4 +1,4 @@ -/* emcscript-related styling, used during the module load/intialization processes... */ +/* emscripten-related styling, used during the module load/intialization processes... */ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } div.emscripten { text-align: center; } div.emscripten_border { border: 1px solid black; } diff --git a/ext/wasm/common/testing.css b/ext/wasm/common/testing.css index 9438b330c..fb44f1d61 100644 --- a/ext/wasm/common/testing.css +++ b/ext/wasm/common/testing.css @@ -61,3 +61,9 @@ span.labeled-input { flex-direction: column-reverse; } label[for] { cursor: pointer } + +h1 { + border-radius: 0.25em; + padding: 0.15em 0.25em; +} +h1:first-of-type {margin: 0 0 0.5em 0;} diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index 8cb483324..9bce82f1a 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -725,11 +725,12 @@ self.WhWasmUtilInstaller = function(target){ Expects ptr to be a pointer into the WASM heap memory which refers to a NUL-terminated C-style string encoded as UTF-8. Returns the length, in bytes, of the string, as for `strlen(3)`. - As a special case, if !ptr then it it returns `null`. Throws if - ptr is out of range for target.heap8u(). + As a special case, if !ptr or if it's not a pointer then it + returns `null`. Throws if ptr is out of range for + target.heap8u(). */ target.cstrlen = function(ptr){ - if(!ptr) return null; + if(!ptr || !target.isPtr(ptr)) return null; const h = heapWrappers().HEAP8U; let pos = ptr; for( ; h[pos] !== 0; ++pos ){} @@ -753,7 +754,7 @@ self.WhWasmUtilInstaller = function(target){ refers to a NUL-terminated C-style string encoded as UTF-8. This function counts its byte length using cstrlen() then returns a JS-format string representing its contents. As a special case, if - ptr is falsy, `null` is returned. + ptr is falsy or not a pointer, `null` is returned. */ target.cstringToJs = function(ptr){ const n = target.cstrlen(ptr); @@ -1081,10 +1082,9 @@ self.WhWasmUtilInstaller = function(target){ // impl for allocMainArgv() and scopedAllocMainArgv(). const __allocMainArgv = function(isScoped, list){ - if(!list.length) toss("Cannot allocate empty array."); const pList = target[ isScoped ? 'scopedAlloc' : 'alloc' - ](list.length * target.ptrSizeof); + ]((list.length + 1) * target.ptrSizeof); let i = 0; list.forEach((e)=>{ target.setPtrValue(pList + (target.ptrSizeof * i++), @@ -1092,26 +1092,33 @@ self.WhWasmUtilInstaller = function(target){ isScoped ? 'scopedAllocCString' : 'allocCString' ](""+e)); }); + target.setPtrValue(pList + (target.ptrSizeof * i), 0); return pList; }; /** Creates an array, using scopedAlloc(), suitable for passing to a C-level main() routine. The input is a collection with a length - property and a forEach() method. A block of memory list.length - entries long is allocated and each pointer-sized block of that - memory is populated with a scopedAllocCString() conversion of the - (""+value) of each element. Returns a pointer to the start of the - list, suitable for passing as the 2nd argument to a C-style - main() function. - - Throws if list.length is falsy or scopedAllocPush() is not active. + property and a forEach() method. A block of memory + (list.length+1) entries long is allocated and each pointer-sized + block of that memory is populated with a scopedAllocCString() + conversion of the (""+value) of each element, with the exception + that the final entry is a NULL pointer. Returns a pointer to the + start of the list, suitable for passing as the 2nd argument to a + C-style main() function. + + Throws if scopedAllocPush() is not active. + + Design note: the returned array is allocated with an extra NULL + pointer entry to accommodate certain APIs, but client code which + does not need that functionality should treat the returned array + as list.length entries long. */ target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list); /** Identical to scopedAllocMainArgv() but uses alloc() instead of - scopedAllocMainArgv + scopedAlloc(). */ target.allocMainArgv = (list)=>__allocMainArgv(false, list); diff --git a/ext/wasm/jaccwabyt/jaccwabyt.js b/ext/wasm/jaccwabyt/jaccwabyt.js index dee7258c5..1fa6baf15 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.js +++ b/ext/wasm/jaccwabyt/jaccwabyt.js @@ -121,6 +121,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ at SIG s[0]. Throws for an unknown SIG. */ const sigIR = function(s){ switch(sigLetter(s)){ + case 'c': case 'C': return 'i8'; case 'i': return 'i32'; case 'p': case 'P': case 's': return ptrIR; case 'j': return 'i64'; @@ -129,33 +130,9 @@ self.Jaccwabyt = function StructBinderFactory(config){ } toss("Unhandled signature IR:",s); }; - /** Returns the sizeof value for the given SIG. Throws for an - unknown SIG. */ - const sigSizeof = function(s){ - switch(sigLetter(s)){ - case 'i': return 4; - case 'p': case 'P': case 's': return ptrSizeof; - case 'j': return 8; - case 'f': return 4 /* C-side floats, not JS-side */; - case 'd': return 8; - } - toss("Unhandled signature sizeof:",s); - }; + const affirmBigIntArray = BigInt64Array ? ()=>true : ()=>toss('BigInt64Array is not available.'); - /** Returns the (signed) TypedArray associated with the type - described by the given SIG. Throws for an unknown SIG. */ - /********** - const sigTypedArray = function(s){ - switch(sigIR(s)) { - case 'i32': return Int32Array; - case 'i64': return affirmBigIntArray() && BigInt64Array; - case 'float': return Float32Array; - case 'double': return Float64Array; - } - toss("Unhandled signature TypedArray:",s); - }; - **************/ /** Returns the name of a DataView getter method corresponding to the given SIG. */ const sigDVGetter = function(s){ @@ -168,6 +145,8 @@ self.Jaccwabyt = function StructBinderFactory(config){ break; } case 'i': return 'getInt32'; + case 'c': return 'getInt8'; + case 'C': return 'getUint8'; case 'j': return affirmBigIntArray() && 'getBigInt64'; case 'f': return 'getFloat32'; case 'd': return 'getFloat64'; @@ -186,6 +165,8 @@ self.Jaccwabyt = function StructBinderFactory(config){ break; } case 'i': return 'setInt32'; + case 'c': return 'setInt8'; + case 'C': return 'setUint8'; case 'j': return affirmBigIntArray() && 'setBigInt64'; case 'f': return 'setFloat32'; case 'd': return 'setFloat64'; @@ -199,7 +180,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ */ const sigDVSetWrapper = function(s){ switch(sigLetter(s)) { - case 'i': case 'f': case 'd': return Number; + case 'i': case 'f': case 'c': case 'C': case 'd': return Number; case 'j': return affirmBigIntArray() && BigInt; case 'p': case 'P': case 's': switch(ptrSizeof){ @@ -211,6 +192,8 @@ self.Jaccwabyt = function StructBinderFactory(config){ toss("Unhandled DataView set wrapper for signature:",s); }; + /** Returns the given struct and member name in a form suitable for + debugging and error output. */ const sPropName = (s,k)=>s+'::'+k; const __propThrowOnSet = function(structName,propName){ @@ -218,30 +201,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ }; /** - When C code passes a pointer of a bound struct to back into - a JS function via a function pointer struct member, it - arrives in JS as a number (pointer). - StructType.instanceForPointer(ptr) can be used to get the - instance associated with that pointer, and __ptrBacklinks - holds that mapping. WeakMap keys must be objects, so we - cannot use a weak map to map pointers to instances. We use - the StructType constructor as the WeakMap key, mapped to a - plain, prototype-less Object which maps the pointers to - struct instances. That arrangement gives us a - per-StructType type-safe way to resolve pointers. - */ - const __ptrBacklinks = new WeakMap(); - /** - Similar to __ptrBacklinks but is scoped at the StructBinder - level and holds pointer-to-object mappings for all struct - instances created by any struct from any StructFactory - which this specific StructBinder has created. The intention - of this is to help implement more transparent handling of - pointer-type property resolution. - */ - const __ptrBacklinksGlobal = Object.create(null); - - /** In order to completely hide StructBinder-bound struct pointers from JS code, we store them in a scope-local WeakMap which maps the struct-bound objects to their WASM @@ -272,6 +231,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ obj.ondispose.forEach(function(x){ try{ if(x instanceof Function) x.call(obj); + else if(x instanceof StructType) x.dispose(); else if('number' === typeof x) dealloc(x); // else ignore. Strings are permitted to annotate entries // to assist in debugging. @@ -282,8 +242,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ }); } delete obj.ondispose; - delete __ptrBacklinks.get(ctor)[m]; - delete __ptrBacklinksGlobal[m]; __instancePointerMap.delete(obj); if(ctor.debugFlags.__flags.dealloc){ log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""), @@ -316,8 +274,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ } if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof); __instancePointerMap.set(obj, m); - __ptrBacklinks.get(ctor)[m] = obj; - __ptrBacklinksGlobal[m] = obj; }catch(e){ __freeStruct(ctor, obj, m); throw e; @@ -361,21 +317,11 @@ self.Jaccwabyt = function StructBinderFactory(config){ framework's native format or in Emscripten format. */ const __memberSignature = function f(obj,memberName,emscriptenFormat=false){ - if(!f._) f._ = (x)=>x.replace(/[^vipPsjrd]/g,"").replace(/[pPs]/g,'i'); + if(!f._) f._ = (x)=>x.replace(/[^vipPsjrdcC]/g,"").replace(/[pPscC]/g,'i'); const m = __lookupMember(obj.structInfo, memberName, true); return emscriptenFormat ? f._(m.signature) : m.signature; }; - /** - Returns the instanceForPointer() impl for the given - StructType constructor. - */ - const __instanceBacklinkFactory = function(ctor){ - const b = Object.create(null); - __ptrBacklinks.set(ctor, b); - return (ptr)=>b[ptr]; - }; - const __ptrPropDescriptor = { configurable: false, enumerable: false, get: function(){return __instancePointerMap.get(this)}, @@ -550,7 +496,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ */ Object.defineProperties(StructType, { allocCString: rop(__allocCString), - instanceForPointer: rop((ptr)=>__ptrBacklinksGlobal[ptr]), isA: rop((v)=>v instanceof StructType), hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]), memberKey: __memberKeyProp @@ -570,7 +515,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ /*cache all available getters/setters/set-wrappers for direct reuse in each accessor function. */ f._ = {getters: {}, setters: {}, sw:{}}; - const a = ['i','p','P','s','f','d','v()']; + const a = ['i','c','C','p','P','s','f','d','v()']; if(bigIntEnabled) a.push('j'); a.forEach(function(v){ //const ir = sigIR(v); @@ -579,8 +524,8 @@ self.Jaccwabyt = function StructBinderFactory(config){ f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values for conversion */; }); - const rxSig1 = /^[ipPsjfd]$/, - rxSig2 = /^[vipPsjfd]\([ipPsjfd]*\)$/; + const rxSig1 = /^[ipPsjfdcC]$/, + rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/; f.sigCheck = function(obj, name, key,sig){ if(Object.prototype.hasOwnProperty.call(obj, key)){ toss(obj.structName,'already has a property named',key+'.'); @@ -594,7 +539,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ f.sigCheck(ctor.prototype, name, key, descr.signature); descr.key = key; descr.name = name; - const sizeOf = sigSizeof(descr.signature); const sigGlyph = sigLetter(descr.signature); const xPropName = sPropName(ctor.prototype.structName,key); const dbg = ctor.prototype.debugFlags.__flags; @@ -610,16 +554,12 @@ self.Jaccwabyt = function StructBinderFactory(config){ prop.get = function(){ if(dbg.getter){ log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), - xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf); + xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof); } let rc = ( - new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) + new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof) )[f._.getters[sigGlyph]](0, isLittleEndian); if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); - if(rc && isAutoPtrSig(descr.signature)){ - rc = StructType.instanceForPointer(rc) || rc; - if(dbg.getter) log("debug.getter:",xPropName,"resolved =",rc); - } return rc; }; if(descr.readOnly){ @@ -628,7 +568,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ prop.set = function(v){ if(dbg.setter){ log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), - xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf, v); + xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v); } if(!this.pointer){ toss("Cannot set struct property on disposed instance."); @@ -644,7 +584,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ toss("Invalid value for pointer-type",xPropName+'.'); } ( - new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) + new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof) )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); }; } @@ -665,13 +605,25 @@ self.Jaccwabyt = function StructBinderFactory(config){ if(!structName) toss("Struct name is required."); let lastMember = false; Object.keys(structInfo.members).forEach((k)=>{ + // Sanity checks of sizeof/offset info... const m = structInfo.members[k]; if(!m.sizeof) toss(structName,"member",k,"is missing sizeof."); - else if(0!==(m.sizeof%4)){ - toss(structName,"member",k,"sizeof is not aligned."); - } - else if(0!==(m.offset%4)){ - toss(structName,"member",k,"offset is not aligned."); + else if(m.sizeof===1){ + (m.signature === 'c' || m.signature === 'C') || + toss("Unexpected sizeof==1 member", + sPropName(structInfo.name,k), + "with signature",m.signature); + }else{ + // sizes and offsets of size-1 members may be odd values, but + // others may not. + if(0!==(m.sizeof%4)){ + console.warn("Invalid struct member description =",m,"from",structInfo); + toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof); + } + if(0!==(m.offset%4)){ + console.warn("Invalid struct member description =",m,"from",structInfo); + toss(structName,"member",k,"offset is not aligned. offset="+m.offset); + } } if(!lastMember || lastMember.offset < m.offset) lastMember = m; }); @@ -697,27 +649,9 @@ self.Jaccwabyt = function StructBinderFactory(config){ }; Object.defineProperties(StructCtor,{ debugFlags: debugFlags, - disposeAll: rop(function(){ - const map = __ptrBacklinks.get(StructCtor); - Object.keys(map).forEach(function(ptr){ - const b = map[ptr]; - if(b) __freeStruct(StructCtor, b, ptr); - }); - __ptrBacklinks.set(StructCtor, Object.create(null)); - return StructCtor; - }), - instanceForPointer: rop(__instanceBacklinkFactory(StructCtor)), isA: rop((v)=>v instanceof StructCtor), memberKey: __memberKeyProp, memberKeys: __structMemberKeys, - resolveToInstance: rop(function(v, throwIfNot=false){ - if(!(v instanceof StructCtor)){ - v = Number.isSafeInteger(v) - ? StructCtor.instanceForPointer(v) : undefined; - } - if(!v && throwIfNot) toss("Value is-not-a",StructCtor.structName); - return v; - }), methodInfoForKey: rop(function(mKey){ }), structInfo: rop(structInfo), @@ -735,7 +669,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ ); return StructCtor; }; - StructBinder.instanceForPointer = StructType.instanceForPointer; StructBinder.StructType = StructType; StructBinder.config = config; StructBinder.allocCString = __allocCString; diff --git a/ext/wasm/jaccwabyt/jaccwabyt.md b/ext/wasm/jaccwabyt/jaccwabyt.md index edcba260a..17ada1edf 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.md +++ b/ext/wasm/jaccwabyt/jaccwabyt.md @@ -281,21 +281,29 @@ supported letters are: signature entry. - **`f`** = `float` (4 bytes) - **`d`** = `double` (8 bytes) -- **`p`** = `int32` (but see below!) +- **`c`** = `int8` (1 byte) char - see notes below! +- **`C`** = `uint8` (1 byte) unsigned char - see notes below! +- **`p`** = `int32` (see notes below!) - **`P`** = Like `p` but with extra handling. Described below. -- **`s`** = like `int32` but is a _hint_ that it's a pointer to a string - so that _some_ (very limited) contexts may treat it as such, noting - such algorithms must, for lack of information to the contrary, - assume both that the encoding is UTF-8 and that the pointer's member - is NUL-terminated. If that is _not_ the case for a given string - member, do not use `s`: use `i` or `p` instead and do any string - handling yourself. +- **`s`** = like `int32` but is a _hint_ that it's a pointer to a + string so that _some_ (very limited) contexts may treat it as such, + noting that such algorithms must, for lack of information to the + contrary, assume both that the encoding is UTF-8 and that the + pointer's member is NUL-terminated. If that is _not_ the case for a + given string member, do not use `s`: use `i` or `p` instead and do + any string handling yourself. Noting that: -- All of these types are numeric. Attempting to set any struct-bound - property to a non-numeric value will trigger an exception except in - cases explicitly noted otherwise. +- **All of these types are numeric**. Attempting to set any + struct-bound property to a non-numeric value will trigger an + exception except in cases explicitly noted otherwise. +- **"Char" types**: WASM does not define an `int8` type, nor does it + distinguish between signed and unsigned. This API treats `c` as + `int8` and `C` as `uint8` for purposes of getting and setting values + when using the `DataView` class. It is _not_ recommended that client + code use these types in new WASM-capable code, but they were added + for the sake of binding some immutable legacy code to WASM. > Sidebar: Emscripten's public docs do not mention `p`, but their generated code includes `p` as an alias for `i`, presumably to mean @@ -317,12 +325,12 @@ Signatures in the form `x(...)` denote function-pointer members and form `x()`. For function-type signatures, the strings are formulated such that they can be passed to Emscripten's `addFunction()` after stripping out the `(` and `)` characters. For good measure, to match -the public Emscripten docs, `p` should also be replaced with `i`. In -JavaScript that might look like: +the public Emscripten docs, `p`, `c`, and `C`, should also be replaced +with `i`. In JavaScript that might look like: > ``` -signature.replace(/[^vipPsjfd]/g,'').replace(/[pPs]/g,'i'); +signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i'); ``` <a name='step-2-pvsp'></a> @@ -337,12 +345,6 @@ special use of unsigned numbers). A capital `P` changes the semantics of plain member pointers (but not, as of this writing, function pointer members) as follows: -- When a `P`-type member is **fetched** via `myStruct.x` and its value is - a non-0 integer, [`StructBinder.instanceForPointer()`][StructBinder] - is used to try to map that pointer to a struct instance. If a match - is found, the "get" operation returns that instance instead of the - integer. If no match is found, it behaves exactly as for `p`, returning - the integer value. - When a `P`-type member is **set** via `myStruct.x=y`, if [`(y instanceof StructType)`][StructType] then the value of `y.pointer` is stored in `myStruct.x`. If `y` is neither a number nor @@ -388,14 +390,11 @@ It is important to understand that creating a new instance allocates memory on the WASM heap. We must not simply rely on garbage collection to clean up the instances because doing so will not free up the WASM heap memory. The correct way to free up that memory is to use the -object's `dispose()` method. Alternately, there is a "nuclear option": -`MyBinder.disposeAll()` will free the memory allocated for _all_ -instances which have not been manually disposed. +object's `dispose()` method. The following usage pattern offers one way to easily ensure proper cleanup of struct instances: - > ```javascript const my = new MyStruct(); @@ -409,11 +408,6 @@ try { from the byte array. */ // Pass the struct to C code which takes a MyStruct pointer: aCFunction( my.pointer ); - // Type-safely check if a pointer returned from C is a MyStruct: - const x = MyStruct.instanceForPointer( anotherCFunction() ); - // If it is a MyStruct, x now refers to that object. Note, however, - // that this only works for instances created in JS, as the - // pointer mapping only exists in JS space. } finally { my.dispose(); } @@ -426,6 +420,16 @@ to use `try`/`finally` without a `catch`, and doing so is an ideal match for the memory management requirements of Jaccwaby-bound struct instances. +It is often useful to wrap an existing instance of a C-side struct +without taking over ownership of its memory. That can be achieved by +simply passing a pointer to the constructor. For example: + +```js +const m = new MyStruct( functionReturningASharedPtr() ); +// calling m.dispose() will _not_ free the wrapped C-side instance +// but will trigger any ondispose handler. +``` + Now that we have struct instances, there are a number of things we can do with them, as covered in the rest of this document. @@ -549,20 +553,6 @@ The Struct Binder has the following members: any of its "significant" configuration values may have undefined results. -- `instanceForPointer(pointer)` - Given a pointer value relative to `config.memory`, if that pointer - resolves to a struct of _any type_ generated via the same Struct - Binder, this returns the struct instance associated with it, or - `undefined` if no struct object is mapped to that pointer. This - differs from the struct-type-specific member of the same name in - that this one is not "type-safe": it does not know the type of the - returned object (if any) and may return a struct of any - [StructType][] for which this Struct Binder has created a - constructor. It cannot return instances created via a different - [StructBinderFactory][] because each factory can hypothetically have - a different memory heap. - - <a name='api-structtype'></a> API: Struct Type ------------------------------------------------------------ @@ -591,9 +581,6 @@ individual instances via `theInstance.constructor`.): [struct's constructor][StructCtors]. If true, the memory is owned by someone other than the object and must outlive the object. -- `instanceForPointer(pointer)` - Works identically to the [StructBinder][] method of the same name. - - `isA(value)` Returns true if its argument is a StructType instance _from the same [StructBinder][]_ as this StructType. @@ -738,21 +725,6 @@ pointer can be taken over using something like These constructors have the following "static" members: -- `disposeAll()` - For each instance of this struct, the equivalent of its `dispose()` - method is called. This frees all WASM-allocated memory associated - with _all_ instances and clears the `instanceForPointer()` - mappings. Returns `this`. - -- `instanceForPointer(pointer)` - Given a pointer value (accessible via the `pointer` property of all - struct instances) which ostensibly refers to an instance of this - class, this returns the instance associated with it, or `undefined` - if no object _of this specific struct type_ is mapped to that - pointer. When C-side code calls back into JS code and passes a - pointer to an object, this function can be used to type-safely - "cast" that pointer back to its original object. - - `isA(value)` Returns true if its argument was created by this constructor. @@ -762,15 +734,6 @@ These constructors have the following "static" members: - `memberKeys(string)` Works exactly as documented for [StructType][]. -- `resolveToInstance(value [,throwIfNot=false])` - Works like `instanceForPointer()` but accepts either an instance - of this struct type or a pointer which resolves to one. - It returns an instance of this struct type on success. - By default it returns a falsy value if its argument is not, - or does not resolve to, an instance of this struct type, - but if passed a truthy second argument then it will throw - instead. - - `structInfo` The structure description passed to [StructBinder][] when this constructor was generated. diff --git a/ext/wasm/tester1-worker.html b/ext/wasm/tester1-worker.html index eba3fdeb4..a9c12d72c 100644 --- a/ext/wasm/tester1-worker.html +++ b/ext/wasm/tester1-worker.html @@ -7,11 +7,7 @@ <link rel="stylesheet" href="../common/emscripten.css"/> <link rel="stylesheet" href="../common/testing.css"/> <title>sqlite3 tester #1: Worker thread</title> - <style> - body { - font-family: monospace; - } - </style> + <style></style> </head> <body> <h1 id='color-target'>sqlite3 tester #1: Worker thread</h1> diff --git a/ext/wasm/tester1.c-pp.html b/ext/wasm/tester1.c-pp.html index b1b68e486..535e58962 100644 --- a/ext/wasm/tester1.c-pp.html +++ b/ext/wasm/tester1.c-pp.html @@ -13,14 +13,9 @@ ES6 Module in UI thread UI thread //#endif </title> - <style> - body { - font-family: monospace; - } - </style> + <style></style> </head> - <body> - <h1 id='color-target'></h1> + <body><h1 id='color-target'></h1> <div>See <a href='tester1-worker.html' target='tester1-worker.html'>tester1-worker.html</a> for the Worker-thread variant.</div> <div class='input-wrapper'> diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index 0aaee9769..2866d34f5 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -419,6 +419,7 @@ self.sqlite3InitModule = sqlite3InitModule; //////////////////////////////////////////////////////////////////// T.g('C/WASM Utilities') .t('sqlite3.wasm namespace', function(sqlite3){ + // TODO: break this into smaller individual test functions. const w = wasm; const chr = (x)=>x.charCodeAt(0); //log("heap getters..."); @@ -660,6 +661,26 @@ self.sqlite3InitModule = sqlite3InitModule; T.assert(rc>0 && Number.isFinite(rc)); rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8'); T.assert('string'===typeof rc).assert(rc.length>300); + + + { // 'string:static' argAdapter() sanity checks... + let argAd = w.xWrap.argAdapter('string:static'); + let p0 = argAd('foo'), p1 = argAd('bar'); + T.assert(w.isPtr(p0) && w.isPtr(p1)) + .assert(p0 !== p1) + .assert(p0 === argAd('foo')) + .assert(p1 === argAd('bar')); + } + + // 'string:flexible' argAdapter() sanity checks... + w.scopedAllocCall(()=>{ + const argAd = w.xWrap.argAdapter('string:flexible'); + const cj = (v)=>w.cstringToJs(argAd(v)); + T.assert('Hi' === cj('Hi')) + .assert('hi' === cj(['h','i'])) + .assert('HI' === cj(new Uint8Array([72, 73]))); + }); + if(haveWasmCTests()){ if(!sqlite3.config.useStdAlloc){ fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:dealloc',['i32']); @@ -720,9 +741,6 @@ self.sqlite3InitModule = sqlite3InitModule; assert(undefined === K.prototype.lookupMember('nope',false)). assert(k1 instanceof StructType). assert(StructType.isA(k1)). - assert(K.resolveToInstance(k1.pointer)===k1). - mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/). - assert(k1 === StructType.instanceForPointer(k1.pointer)). mustThrowMatching(()=>k1.$ro = 1, /read-only/); Object.keys(MyStructDef.members).forEach(function(key){ key = K.memberKey(key); @@ -732,8 +750,7 @@ self.sqlite3InitModule = sqlite3InitModule; " from "+k1.memoryDump()); }); T.assert('number' === typeof k1.pointer). - mustThrowMatching(()=>k1.pointer = 1, /pointer/). - assert(K.instanceForPointer(k1.pointer) === k1); + mustThrowMatching(()=>k1.pointer = 1, /pointer/); k1.$p4 = 1; k1.$pP = 2; T.assert(1 === k1.$p4).assert(2 === k1.$pP); if(MyStructDef.members.$p8){ @@ -748,22 +765,13 @@ self.sqlite3InitModule = sqlite3InitModule; assert('number' === typeof k1.$cstr). assert('A C-string.' === k1.memberToJsString('cstr')); k1.$pP = k2; - T.assert(k1.$pP === k2); + T.assert(k1.$pP === k2.pointer); k1.$pP = null/*null is special-cased to 0.*/; T.assert(0===k1.$pP); let ptr = k1.pointer; k1.dispose(); T.assert(undefined === k1.pointer). - assert(undefined === K.instanceForPointer(ptr)). mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/); - const k3 = new K(); - ptr = k3.pointer; - T.assert(k3 === K.instanceForPointer(ptr)); - K.disposeAll(); - T.assert(ptr). - assert(undefined === k2.pointer). - assert(undefined === k3.pointer). - assert(undefined === K.instanceForPointer(ptr)); }finally{ k1.dispose(); k2.dispose(); @@ -793,10 +801,8 @@ self.sqlite3InitModule = sqlite3InitModule; assert(wts instanceof WTStruct). assert(wts instanceof StructType). assert(StructType.isA(wts)). - assert(wts === StructType.instanceForPointer(wts.pointer)); - T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8). - assert(0===wts.$ppV).assert(0===wts.$xFunc). - assert(WTStruct.instanceForPointer(wts.pointer) === wts); + assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8). + assert(0===wts.$ppV).assert(0===wts.$xFunc); const testFunc = W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/); let counter = 0; @@ -805,7 +811,6 @@ self.sqlite3InitModule = sqlite3InitModule; /*log("This from a JS function called from C, "+ "which itself was called from JS. arg =",arg);*/ ++counter; - T.assert(WTStruct.instanceForPointer(arg) === wts); if(3===counter){ tossQuietly("Testing exception propagation."); } @@ -829,7 +834,7 @@ self.sqlite3InitModule = sqlite3InitModule; testFunc(wts.pointer); //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV); T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8) - .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer)) + .assert(wts.$ppV === wts.pointer) .assert('string' === typeof wts.memberToJsString('cstr')) .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr')) .mustThrowMatching(()=>wts.memberToJsString('xFunc'), @@ -837,280 +842,31 @@ self.sqlite3InitModule = sqlite3InitModule; ; testFunc(wts.pointer); T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8) - .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer)); + .assert(wts.$ppV === wts.pointer); /** The 3rd call to wtsFunc throw from JS, which is called from C, which is called from JS. Let's ensure that that exception propagates back here... */ T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/); W.uninstallFunction(wts.$xFunc); wts.$xFunc = 0; - if(autoResolvePtr){ - wts.$ppV = 0; - T.assert(!wts.$ppV); - //WTStruct.debugFlags(0x03); - wts.$ppV = wts; - T.assert(wts === wts.$ppV) - //WTStruct.debugFlags(0); - } + wts.$ppV = 0; + T.assert(!wts.$ppV); + //WTStruct.debugFlags(0x03); + wts.$ppV = wts; + T.assert(wts.pointer === wts.$ppV) wts.setMemberCString('cstr', "A C-string."); T.assert(Array.isArray(wts.ondispose)). assert(wts.ondispose[0] === wts.$cstr). assert('A C-string.' === wts.memberToJsString('cstr')); const ptr = wts.pointer; wts.dispose(); - T.assert(ptr).assert(undefined === wts.pointer). - assert(undefined === WTStruct.instanceForPointer(ptr)) + T.assert(ptr).assert(undefined === wts.pointer); }finally{ wts.dispose(); } }/*StructBinder*/) //////////////////////////////////////////////////////////////////// - .t('sqlite3.StructBinder part 2', function(sqlite3){ - // https://www.sqlite.org/c3ref/vfs.html - // https://www.sqlite.org/c3ref/io_methods.html - const sqlite3_io_methods = capi.sqlite3_io_methods, - sqlite3_vfs = capi.sqlite3_vfs, - sqlite3_file = capi.sqlite3_file; - //log("struct sqlite3_file", sqlite3_file.memberKeys()); - //log("struct sqlite3_vfs", sqlite3_vfs.memberKeys()); - //log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys()); - const installMethod = function callee(tgt, name, func){ - if(1===arguments.length){ - return (n,f)=>callee(tgt,n,f); - } - if(!callee.argcProxy){ - callee.argcProxy = function(func,sig){ - return function(...args){ - if(func.length!==arguments.length){ - toss("Argument mismatch. Native signature is:",sig); - } - return func.apply(this, args); - } - }; - callee.ondisposeRemoveFunc = function(){ - if(this.__ondispose){ - const who = this; - this.__ondispose.forEach( - (v)=>{ - if('number'===typeof v){ - try{wasm.uninstallFunction(v)} - catch(e){/*ignore*/} - }else{/*wasm function wrapper property*/ - delete who[v]; - } - } - ); - delete this.__ondispose; - } - }; - }/*static init*/ - const sigN = tgt.memberSignature(name), - memKey = tgt.memberKey(name); - //log("installMethod",tgt, name, sigN); - if(!tgt.__ondispose){ - T.assert(undefined === tgt.ondispose); - tgt.ondispose = [callee.ondisposeRemoveFunc]; - tgt.__ondispose = []; - } - const fProxy = callee.argcProxy(func, sigN); - const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); - tgt[memKey] = pFunc; - /** - ACHTUNG: function pointer IDs are from a different pool than - allocation IDs, starting at 1 and incrementing in steps of 1, - so if we set tgt[memKey] to those values, we'd very likely - later misinterpret them as plain old pointer addresses unless - unless we use some silly heuristic like "all values <5k are - presumably function pointers," or actually perform a function - lookup on every pointer to first see if it's a function. That - would likely work just fine, but would be kludgy. - - It turns out that "all values less than X are functions" is - essentially how it works in wasm: a function pointer is - reported to the client as its index into the - __indirect_function_table. - - So... once jaccwabyt can be told how to access the - function table, it could consider all pointer values less - than that table's size to be functions. As "real" pointer - values start much, much higher than the function table size, - that would likely work reasonably well. e.g. the object - pointer address for sqlite3's default VFS is (in this local - setup) 65104, whereas the function table has fewer than 600 - entries. - */ - const wrapperKey = '$'+memKey; - tgt[wrapperKey] = fProxy; - tgt.__ondispose.push(pFunc, wrapperKey); - //log("tgt.__ondispose =",tgt.__ondispose); - return (n,f)=>callee(tgt, n, f); - }/*installMethod*/; - - const installIOMethods = function instm(iom){ - (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type."); - if(!instm._requireFileArg){ - instm._requireFileArg = function(arg,methodName){ - arg = capi.sqlite3_file.resolveToInstance(arg); - if(!arg){ - err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file."); - } - return arg; - }; - instm._methods = { - // https://sqlite.org/c3ref/io_methods.html - xClose: /*i(P)*/function(f){ - /* int (*xClose)(sqlite3_file*) */ - log("xClose(",f,")"); - if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE; - f.dispose(/*noting that f has externally-owned memory*/); - return 0; - }, - xRead: /*i(Ppij)*/function(f,dest,n,offset){ - /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */ - log("xRead(",arguments,")"); - if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE; - wasm.heap8().fill(0, dest + offset, n); - return 0; - }, - xWrite: /*i(Ppij)*/function(f,dest,n,offset){ - /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */ - log("xWrite(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE; - return 0; - }, - xTruncate: /*i(Pj)*/function(f){ - /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */ - log("xTruncate(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE; - return 0; - }, - xSync: /*i(Pi)*/function(f){ - /* int (*xSync)(sqlite3_file*, int flags) */ - log("xSync(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE; - return 0; - }, - xFileSize: /*i(Pp)*/function(f,pSz){ - /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */ - log("xFileSize(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE; - wasm.setMemValue(pSz, 0/*file size*/); - return 0; - }, - xLock: /*i(Pi)*/function(f){ - /* int (*xLock)(sqlite3_file*, int) */ - log("xLock(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE; - return 0; - }, - xUnlock: /*i(Pi)*/function(f){ - /* int (*xUnlock)(sqlite3_file*, int) */ - log("xUnlock(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE; - return 0; - }, - xCheckReservedLock: /*i(Pp)*/function(){ - /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */ - log("xCheckReservedLock(",arguments,")"); - return 0; - }, - xFileControl: /*i(Pip)*/function(){ - /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */ - log("xFileControl(",arguments,")"); - return capi.SQLITE_NOTFOUND; - }, - xSectorSize: /*i(P)*/function(){ - /* int (*xSectorSize)(sqlite3_file*) */ - log("xSectorSize(",arguments,")"); - return 0/*???*/; - }, - xDeviceCharacteristics:/*i(P)*/function(){ - /* int (*xDeviceCharacteristics)(sqlite3_file*) */ - log("xDeviceCharacteristics(",arguments,")"); - return 0; - } - }; - }/*static init*/ - iom.$iVersion = 1; - Object.keys(instm._methods).forEach( - (k)=>installMethod(iom, k, instm._methods[k]) - ); - }/*installIOMethods()*/; - - const iom = new sqlite3_io_methods, sfile = new sqlite3_file; - const err = console.error.bind(console); - try { - const IOM = sqlite3_io_methods, S3F = sqlite3_file; - //log("iom proto",iom,iom.constructor.prototype); - //log("sfile",sfile,sfile.constructor.prototype); - T.assert(0===sfile.$pMethods).assert(iom.pointer > 0); - //log("iom",iom); - sfile.$pMethods = iom.pointer; - T.assert(iom.pointer === sfile.$pMethods) - .assert(IOM.resolveToInstance(iom)) - .assert(undefined ===IOM.resolveToInstance(sfile)) - .mustThrow(()=>IOM.resolveToInstance(0,true)) - .assert(S3F.resolveToInstance(sfile.pointer)) - .assert(undefined===S3F.resolveToInstance(iom)) - .assert(iom===IOM.resolveToInstance(sfile.$pMethods)); - T.assert(0===iom.$iVersion); - installIOMethods(iom); - T.assert(1===iom.$iVersion); - //log("iom.__ondispose",iom.__ondispose); - T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10); - }finally{ - iom.dispose(); - T.assert(undefined === iom.__ondispose); - } - - const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null)); - try { - const SB = sqlite3.StructBinder; - T.assert(dVfs instanceof SB.StructType) - .assert(dVfs.pointer) - .assert('sqlite3_vfs' === dVfs.structName) - .assert(!!dVfs.structInfo) - .assert(SB.StructType.hasExternalPointer(dVfs)) - .assert(dVfs.$iVersion>0) - .assert('number'===typeof dVfs.$zName) - .assert('number'===typeof dVfs.$xSleep) - .assert(wasm.functionEntry(dVfs.$xOpen)) - .assert(dVfs.memberIsString('zName')) - .assert(dVfs.memberIsString('$zName')) - .assert(!dVfs.memberIsString('pAppData')) - .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'), - /Invalid member type signature for C-string/) - .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/) - .assert('string' === typeof dVfs.memberToJsString('zName')) - .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName')) - ; - //log("Default VFS: @",dVfs.pointer); - Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){ - const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname], - addr = dVfs[mk], prefix = 'defaultVfs.'+mname; - if(1===mbr.signature.length){ - let sep = '?', val = undefined; - switch(mbr.signature[0]){ - // TODO: move this into an accessor, e.g. getPreferredValue(member) - case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break - case 'p': case 'P': sep = '@'; val = dVfs[mk]; break; - case 's': sep = '='; - val = dVfs.memberToJsString(mname); - break; - } - //log(prefix, sep, val); - }else{ - //log(prefix," = funcptr @",addr, wasm.functionEntry(addr)); - } - }); - }finally{ - dVfs.dispose(); - T.assert(undefined===dVfs.pointer); - } - }/*StructBinder part 2*/) - - //////////////////////////////////////////////////////////////////// .t('sqlite3.wasm.pstack', function(sqlite3){ const P = wasm.pstack; const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError; @@ -1219,7 +975,7 @@ self.sqlite3InitModule = sqlite3InitModule; .t('Create db', function(sqlite3){ const dbFile = '/tester1.db'; wasm.sqlite3_wasm_vfs_unlink(0, dbFile); - const db = this.db = new sqlite3.oo1.DB(dbFile); + const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c'); T.assert(Number.isInteger(db.pointer)) .mustThrowMatching(()=>db.pointer=1, /read-only/) .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1)) @@ -1442,6 +1198,29 @@ self.sqlite3InitModule = sqlite3InitModule; rc = db.selectArray('select a, b from t where b=-1'); T.assert(undefined === rc); }) + //////////////////////////////////////////////////////////////////////// + .t('selectArrays/Objects()', function(sqlite3){ + const db = this.db; + const sql = 'select a, b from t where a=? or b=? order by a'; + let rc = db.selectArrays(sql, [1, 4]); + T.assert(Array.isArray(rc)) + .assert(2===rc.length) + .assert(2===rc[0].length) + .assert(1===rc[0][0]) + .assert(2===rc[0][1]) + .assert(3===rc[1][0]) + .assert(4===rc[1][1]) + rc = db.selectArrays(sql, [99,99]); + T.assert(Array.isArray(rc)).assert(0===rc.length); + rc = db.selectObjects(sql, [1,4]); + T.assert(Array.isArray(rc)) + .assert(2===rc.length) + .assert('object' === typeof rc[1]) + .assert(1===rc[0].a) + .assert(2===rc[0].b) + .assert(3===rc[1].a) + .assert(4===rc[1].b); + }) //////////////////////////////////////////////////////////////////////// .t({ @@ -1748,10 +1527,178 @@ self.sqlite3InitModule = sqlite3InitModule; T.mustThrow(()=>db.exec("select * from foo.bar")); }) + //////////////////////////////////////////////////////////////////////// + .t({ + name: 'Custom virtual tables', + predicate: ()=>wasm.bigIntEnabled, + test: function(sqlite3){ + warn("The vtab/module JS bindings are experimental and subject to change."); + const vth = sqlite3.VtabHelper; + const tmplCols = Object.assign(Object.create(null),{ + A: 0, B: 1 + }); + /** + The vtab demonstrated here is a JS-ification of + ext/misc/templatevtab.c. + */ + const tmplMethods = { + xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){ + try{ + const rc = capi.sqlite3_declare_vtab( + pDb, "CREATE TABLE ignored(a,b)" + ); + if(0===rc){ + const t = vth.vtab2js(); + wasm.setPtrValue(ppVtab, t.pointer); + T.assert(t === vth.vtab2js(wasm.getPtrValue(ppVtab))); + } + return rc; + }catch(e){ + if(!(e instanceof sqlite3.WasmAllocError)){ + wasm.setPtrValue(pzErr, wasm.allocCString(e.message)); + } + return vth.xMethodError('xConnect',e); + } + }, + xDisconnect: function(pVtab){ + try { + const t = vth.vtab2js(pVtab, true); + t.dispose(); + return 0; + }catch(e){ + return vth.xMethodError('xDisconnect',e); + } + }, + xOpen: function(pVtab, ppCursor){ + try{ + const t = vth.vtab2js(pVtab), c = vth.vcur2js(); + T.assert(t instanceof capi.sqlite3_vtab); + T.assert(c instanceof capi.sqlite3_vtab_cursor); + wasm.setPtrValue(ppCursor, c.pointer); + c._rowId = 0; + return 0; + }catch(e){ + return vth.xMethodError('xOpen',e); + } + }, + xClose: function(pCursor){ + try{ + const c = vth.vcur2js(pCursor,true); + T.assert(c instanceof capi.sqlite3_vtab_cursor) + .assert(!vth.vcur2js(pCursor)); + c.dispose(); + return 0; + }catch(e){ + return vth.xMethodError('xClose',e); + } + }, + xNext: function(pCursor){ + try{ + const c = vth.vcur2js(pCursor); + ++c._rowId; + return 0; + }catch(e){ + return vth.xMethodError('xNext',e); + } + + }, + xColumn: function(pCursor, pCtx, iCol){ + try{ + const c = vth.vcur2js(pCursor); + switch(iCol){ + case tmplCols.A: + capi.sqlite3_result_int(pCtx, 1000 + c._rowId); + break; + case tmplCols.B: + capi.sqlite3_result_int(pCtx, 2000 + c._rowId); + break; + default: sqlite3.SQLite3Error.toss("Invalid column id",iCol); + } + return 0; + }catch(e){ + return vth.xMethodError('xColumn',e); + } + }, + xRowid: function(pCursor, ppRowid64){ + try{ + const c = vth.vcur2js(pCursor); + vth.setRowId(ppRowid64, c._rowId); + return 0; + }catch(e){ + return vth.xMethodError('xRowid',e); + } + }, + xEof: function(pCursor){ + const c = vth.vcur2js(pCursor); + return c._rowId>=10; + }, + xFilter: function(pCursor, idxNum, idxCStr, + argc, argv/* [sqlite3_value* ...] */){ + try{ + const c = vth.vcur2js(pCursor); + c._rowId = 0; + return 0; + }catch(e){ + return vth.xMethodError('xFilter',e); + } + }, + xBestIndex: function(pVtab, pIdxInfo){ + try{ + const t = vth.vtab2js(pVtab); + const pii = new capi.sqlite3_index_info(pIdxInfo); + pii.$estimatedRows = 10; + pii.$estimatedCost = 10.0; + pii.dispose(); + return 0; + }catch(e){ + return vth.xMethodError('xBestIndex',e); + } + } + }; + /** + The vtab API places relevance on whether xCreate and + xConnect are exactly the same function (same pointer + address). Two JS-side references to the same method will + end up, without acrobatics to counter it, being compiled as + two different WASM-side bindings, i.e. two different + pointers. + + In order to account for this, VtabHelper.installMethods() + checks for duplicate function entries and maps them to the + same WASM-compiled instance. + */ + if(1){ + tmplMethods.xCreate = tmplMethods.xConnect; + } + + const tmplMod = new sqlite3.capi.sqlite3_module(); + tmplMod.ondispose = []; + tmplMod.$iVersion = 0; + vth.installMethods(tmplMod, tmplMethods, true); + if(tmplMethods.xCreate){ + T.assert(tmplMod.$xCreate) + .assert(tmplMod.$xCreate === tmplMod.$xConnect, + "installMethods() must avoid re-compiling identical functions"); + tmplMod.$xCreate = 0; + } + let rc = capi.sqlite3_create_module( + this.db, "testvtab", tmplMod, 0 + ); + this.db.checkRc(rc); + + const list = this.db.selectArrays( + "SELECT a,b FROM testvtab order by a" + ); + T.assert(10===list.length) + .assert(1000===list[0][0]) + .assert(2009===list[list.length-1][1]) + } + })/*vtab sanity checks*/ + //////////////////////////////////////////////////////////////////// .t({ - name: 'C-side WASM tests (if compiled in)', - predicate: haveWasmCTests, + name: 'C-side WASM tests', + predicate: ()=>(haveWasmCTests() || "Not compiled in."), test: function(){ const w = wasm, db = this.db; const stack = w.scopedAllocPush(); @@ -1835,8 +1782,8 @@ self.sqlite3InitModule = sqlite3InitModule; }/* jaccwabyt-specific tests */) .t('Close db', function(){ - T.assert(this.db).assert(Number.isInteger(this.db.pointer)); - wasm.exports.sqlite3_wasm_db_reset(this.db.pointer); + T.assert(this.db).assert(wasm.isPtr(this.db.pointer)); + wasm.sqlite3_wasm_db_reset(this.db); this.db.close(); T.assert(!this.db.pointer); }) @@ -1,5 +1,5 @@ -C Add\soptional\sfeature:\sA\sCLI\scontinuation\sprompt\swhich\sreflects\sopen\slexemes\sand\sparens,\ssimilarly\sto\sPG\sshell. -D 2022-12-06T05:31:20.264 +C Merge\strunk\sinto\swasm-vtab\sbranch. +D 2022-12-06T06:21:02.190 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -491,37 +491,37 @@ F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile bfa47f169468ca9db031105b0e336db29a88e93c3abd217d0bbb2b8731fa5413 +F ext/wasm/GNUmakefile 54c0db93a5493f625c0a993c12aee5d83951440eee03b2aecfc8aeb998182998 F ext/wasm/README-dist.txt 2d670b426fc7c613b90a7d2f2b05b433088fe65181abead970980f0a4a75ea20 F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9 -F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 89af0612bad5c651f69e629c7e9689be6d3c8a92a9010da5dd90a87c1d86817a +F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api ffa70413409e922ce0f761779787a1d9100b34b43c8e3106bb7ccf2786a41326 F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 -F ext/wasm/api/README.md 20a256f4aaae80035d2bb1c9e3e0a125570313a8d137d427471d7be10edde87a +F ext/wasm/api/README.md 17fb1e10335cc87e366dec496c5b17b061f3f75cdf216e825258de34d97a3e53 F ext/wasm/api/extern-post-js.c-pp.js 8923f76c3d2213159e12d641dc750523ead5c848185dc4996fae5cc12397f88d F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1 F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 F ext/wasm/api/pre-js.c-pp.js b88499dc303c21fc3f55f2c364a0f814f587b60a95784303881169f9e91c1d5f F ext/wasm/api/sqlite3-api-cleanup.js 680d5ccfff54459db136a49b2199d9f879c8405d9c99af1dda0cc5e7c29056f4 -F ext/wasm/api/sqlite3-api-glue.js 6fe39964605fda3b699f69365eed565b5172d29cab2c49bc057a43f9a93f9f36 -F ext/wasm/api/sqlite3-api-oo1.js 91a7d7b9203fb0f031e6ba380a644a7f871e1798b388de399c01ed4087bac9e0 -F ext/wasm/api/sqlite3-api-prologue.js 697a5989ad52a9ba7bc60b5436589bd05885ee2201d84c38c5e9af3876af3ba4 +F ext/wasm/api/sqlite3-api-glue.js c3a11e1d0e6fd381f68f9e76ad01f3616a6b809fbf9f5aa8e323955c128a6811 +F ext/wasm/api/sqlite3-api-oo1.js b970787aaf0bdd3a3df59cf66aeb84d0decaaa0529aed7eaf45121087181245f +F ext/wasm/api/sqlite3-api-prologue.js 31cffd8ce212fad8d316a08decd864b0f614c5fce3686153707dffe40f8279e8 F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3 F ext/wasm/api/sqlite3-opfs-async-proxy.js f79dd8d98ef3e0b55c10bb2bee7a3840fa967318e1f577c156aafc34664271d1 -F ext/wasm/api/sqlite3-vfs-helper.js 4ad4faf02e1524bf0296be8452c00b5708dce6faf649468d0377e26a0b299263 +F ext/wasm/api/sqlite3-v-helper.js 4451763a0cd85734f0afe18b48918cb3c88ca99cef399b7c5f12119281e7b6a8 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 29d6487a26b2fb6a471cde52c37ffee7c27ed6a91914b308c247e0706f454ffb F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 -F ext/wasm/api/sqlite3-wasm.c b0babf8435f31d21f28454fb81433aa538c68b23d0a4a251f0666fdec4e71f59 +F ext/wasm/api/sqlite3-wasm.c 723522a6c2a2463884a83fa1cc7ae5770deaaf0856a1058cc1023b2bfa1c898b F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 F ext/wasm/batch-runner.js 49609e89aaac9989d6c1ad3fae268e4878e1ad7bc5fd3e5c2f44959660780b2e F ext/wasm/c-pp.c 92285f7bce67ed7b7020b40fde8ed0982c442b63dc33df9dfd4b658d4a6c0779 F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b -F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f -F ext/wasm/common/testing.css 35889709547d89a6109ff83b25c11bbc91d8dd43aab8722e428655ca98880a06 -F ext/wasm/common/whwasmutil.js c1bc5715cd96728929cc31d788b16152ccbd6b2e111d2e88fbc9725247e67b4f +F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 +F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f +F ext/wasm/common/whwasmutil.js 0de1e72494d52185d518892a3ac95d38b8e295d3699b64ddb36a3d46c11c8346 F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6 @@ -539,8 +539,8 @@ F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5 F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2 F ext/wasm/index-dist.html c806b6005145b71d64240606e9c6e0bf56878ee8829c66fe7486cebf34b0e6b1 F ext/wasm/index.html f151b7c7b5cfdc066567d556acd168e769efd4e982286dc5f849a5ee69ecd0ff -F ext/wasm/jaccwabyt/jaccwabyt.js 95f573de1826474c9605dda620ee622fcb1673ae74f191eb324c0853aa4dcb66 -F ext/wasm/jaccwabyt/jaccwabyt.md 9aa6951b529a8b29f578ec8f0355713c39584c92cf1708f63ba0cf917cb5b68e +F ext/wasm/jaccwabyt/jaccwabyt.js f4fc93375e9c40ef60f56cbecca1b4dc8bf4f53fab2c3abc860ed34890c5d32d +F ext/wasm/jaccwabyt/jaccwabyt.md 4bf62f7519857cdd674594aba7436cc4fae177eefbfaabc00740e16d9a828bee F ext/wasm/module-symbols.html 980680c8acfa3c8ae6a5aa223512d1b8e78040ced20f8ba2c382129bc73ec028 F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06 F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18 @@ -553,9 +553,9 @@ F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d826 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac -F ext/wasm/tester1-worker.html 29b1d87f7d51f70d61645719fee657f3787fe939bb695f27034c75404e8f1e6f -F ext/wasm/tester1.c-pp.html 74aa9b31c75f12490653f814b53c3dd39f40cd3f70d6a53a716f4e8587107399 -F ext/wasm/tester1.c-pp.js d096a8fadfd27caa680a4311b1d529551f8fe885a63dd27457c87b6008c64632 +F ext/wasm/tester1-worker.html d43f3c131d88f10d00aff3e328fed13c858d674ea2ff1ff90225506137f85aa9 +F ext/wasm/tester1.c-pp.html d34bef3d48e5cbc1c7c06882ad240fec49bf88f5f65696cc2c72c416933aa406 +F ext/wasm/tester1.c-pp.js 411a120a4e57b5f43c3f7448d72c48b4fedb7b4b62efe0c617dd412c192c2810 F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4893b4e3eafc7c9c22b24717f90a585862203f987cf108b079ce6e946093e675 dac2ddc287db7a68d0cd49b785060f62290868fbb1aa2ee09e54d3b1acfbf55f -R 4e6f769112d568da8b23927a4847535b -U larrybr -Z 19f7409ace4582d7233d6e9f94f36495 +P f902f3b2c79d6c699ead1efeb1426e1e0f4ac709afdff88be1de62f34f3d5ccc f41f18b1c3c950565ee3c237aebb51cfc3241813c6425813a8217e07aa0153a6 +R 8bcaf9d3b9f238bc8e19a8bbe1bb65e6 +U stephan +Z 78f5f82441cc43a02424eee016e46cb3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 02dae66a8..1b960ebdc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f41f18b1c3c950565ee3c237aebb51cfc3241813c6425813a8217e07aa0153a6
\ No newline at end of file +d106edb956bbe22bddc2c6e5bcdf4fb8f797794c725c2e359e3fa4b31d881843
\ No newline at end of file |