diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api index aead79e50d..f03478b17c 100644 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api @@ -25,6 +25,7 @@ _sqlite3_compileoption_used _sqlite3_create_function_v2 _sqlite3_data_count _sqlite3_db_filename +_sqlite3_db_handle _sqlite3_db_name _sqlite3_errmsg _sqlite3_error_offset @@ -33,6 +34,7 @@ _sqlite3_exec _sqlite3_expanded_sql _sqlite3_extended_errcode _sqlite3_extended_result_codes +_sqlite3_file_control _sqlite3_finalize _sqlite3_initialize _sqlite3_interrupt diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js index 1b57cdc5de..0e99edf508 100644 --- a/ext/wasm/api/sqlite3-api-cleanup.js +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -20,11 +20,17 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build /** Install a suitable default configuration for sqlite3ApiBootstrap(). */ - const SABC = self.sqlite3ApiBootstrap.defaultConfig; - SABC.Module = Module /* ==> Currently needs to be exposed here for test code. NOT part - of the public API. */; - SABC.exports = Module['asm']; - SABC.memory = Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */; + const SABC = self.sqlite3ApiConfig || Object.create(null); + if(undefined===SABC.Module){ + SABC.Module = Module /* ==> Currently needs to be exposed here for + test code. NOT part of the public API. */; + } + if(undefined===SABC.exports){ + SABC.exports = Module['asm']; + } + if(undefined===SABC.memory){ + SABC.memory = Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */; + } /** For current (2022-08-22) purposes, automatically call @@ -35,8 +41,15 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build configuration used by a no-args call to sqlite3ApiBootstrap(). */ //console.warn("self.sqlite3ApiConfig = ",self.sqlite3ApiConfig); - const sqlite3 = self.sqlite3ApiBootstrap(); - delete self.sqlite3ApiBootstrap; + const rmApiConfig = (SABC !== self.sqlite3ApiConfig); + self.sqlite3ApiConfig = SABC; + let sqlite3; + try{ + sqlite3 = self.sqlite3ApiBootstrap(); + }finally{ + delete self.sqlite3ApiBootstrap; + if(rmApiConfig) delete self.sqlite3ApiConfig; + } if(self.location && +self.location.port > 1024){ console.warn("Installing sqlite3 bits as global S for local dev/test purposes."); diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 3a9e8803cb..67f9403548 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -55,6 +55,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ const aPtr = wasm.xWrap.argAdapter('*'); wasm.xWrap.argAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr); + wasm.xWrap.resultAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr); /** Populate api object with sqlite3_...() by binding the "raw" wasm @@ -174,7 +175,7 @@ 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', 'flock', 'ioCap', + 'encodings', 'fcntl', 'flock', 'ioCap', 'openFlags', 'prepareFlags', 'resultCodes', 'syncFlags', 'udfFlags', 'version' ]){ diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index 3dfe5bfb05..8280204d62 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -496,6 +496,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ the statement actually produces any result rows. ================================================================== + - `.columnNames`: if this is an array, the column names of the + result set are stored in this array before the callback (if + any) is triggered (regardless of whether the query produces any + result rows). If no statement has result columns, this value is + unchanged. Achtung: an SQL result may have multiple columns + with identical names. + - `.callback` = a function which gets called for each row of the result set, but only if that statement has any result _rows_. The callback's "this" is the options object. The second @@ -523,8 +530,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ as the first argument to the callback: A.1) `'array'` (the default) causes the results of - `stmt.get([])` to be passed to passed on and/or appended to - `resultRows`. + `stmt.get([])` to be passed to the `callback` and/or appended + to `resultRows`. A.2) `'object'` causes the results of `stmt.get(Object.create(null))` to be passed to the @@ -536,7 +543,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ A.3) `'stmt'` causes the current Stmt to be passed to the callback, but this mode will trigger an exception if `resultRows` is an array because appending the statement to - the array would be unhelpful. + the array would be downright unhelpful. B) An integer, indicating a zero-based column in the result row. Only that one single value will be passed on. @@ -545,10 +552,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ':', '$', or '@' will fetch the row as an object, extract that one field, and pass that field's value to the callback. Note that these keys are case-sensitive so must match the case used - in the SQL. e.g. `"select a A from t"` with a `rowMode` of '$A' - would work but '$a' would not. A reference to a column not in - the result set will trigger an exception on the first row (as - the check is not performed until rows are fetched). + in the SQL. e.g. `"select a A from t"` with a `rowMode` of + `'$A'` would work but `'$a'` would not. A reference to a column + not in the result set will trigger an exception on the first + row (as the check is not performed until rows are fetched). + Note also that `$` is a legal identifier character in JS so + need not be quoted. (Design note: those 3 characters were + chosen because they are the characters support for naming bound + parameters.) Any other `rowMode` value triggers an exception. @@ -560,12 +571,17 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ and can be used over a WebWorker-style message interface. exec() throws if `resultRows` is set and `rowMode` is 'stmt'. - - `.columnNames`: if this is an array, the column names of the - result set are stored in this array before the callback (if - any) is triggered (regardless of whether the query produces any - result rows). If no statement has result columns, this value is - unchanged. Achtung: an SQL result may have multiple columns - with identical names. + + Potential TODOs: + + - `.bind`: permit an array of arrays/objects to bind. The first + sub-array would act on the first statement which has bindable + parameters (as it does now). The 2nd would act on the next such + statement, etc. + + - `.callback` and `.resultRows`: permit an array entries with + semantics similar to those described for `.bind` above. + */ exec: function(/*(sql [,obj]) || (obj)*/){ affirmDbOpen(this); @@ -1580,31 +1596,5 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Stmt }/*oo1 object*/; - if( self.window===self && 0!==capi.sqlite3_vfs_find('kvvfs') ){ - /* Features specific to kvvfs... */ - /** - Clears all storage used by the kvvfs DB backend, deleting any - DB(s) stored there. Its argument must be either 'session', - 'local', or ''. In the first two cases, only sessionStorage - resp. localStorage is cleared. If it's an empty string (the - default) then both are cleared. Only storage keys which match - the pattern used by kvvfs are cleared: any other client-side - data are retained. - */ - DB.clearKvvfsStorage = function(which=''){ - const prefix = 'kvvfs-'+which; - const stores = []; - if('session'===which || ''===which) stores.push(sessionStorage); - if('local'===which || ''===which) stores.push(localStorage); - stores.forEach(function(s){ - const toRm = []; - let i = 0, k; - for( i = 0; (k = s.key(i)); ++i ){ - if(k.startsWith(prefix)) toRm.push(k); - } - toRm.forEach((kk)=>s.removeItem(kk)); - }); - }; - }/* main-window-only bits */ }); diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index 17dcd42289..1c22e9ea21 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -43,10 +43,7 @@ - Insofar as possible, support client-side storage using JS filesystem APIs. As of this writing, such things are still very - much TODO. Initial testing with using IndexedDB as backing storage - showed it to work reasonably well, but it's also too easy to - corrupt by using a web page in two browser tabs because IndexedDB - lacks the locking features needed to support that. + much under development. Specific non-goals of this project: @@ -54,8 +51,10 @@ Encodings in that realm, there are no currently plans to support the UTF16-related sqlite3 APIs. They would add a complication to the bindings for no appreciable benefit. Though web-related - implementation details take priority, the lower-level WASM module - "should" work in non-web WASM environments. + implementation details take priority, and the JavaScript + components of the API specifically focus on browser clients, the + lower-level WASM module "should" work in non-web WASM + environments. - Supporting old or niche-market platforms. WASM is built for a modern web and requires modern platforms. @@ -78,17 +77,18 @@ */ /** - sqlite3ApiBootstrap() is the only global symbol exposed by this - API. It is intended to be called one time at the end of the API - amalgamation process, passed configuration details for the current - environment, and then optionally be removed from the global object - using `delete self.sqlite3ApiBootstrap`. + sqlite3ApiBootstrap() is the only global symbol persistently + exposed by this API. It is intended to be called one time at the + end of the API amalgamation process, passed configuration details + for the current environment, and then optionally be removed from + the global object using `delete self.sqlite3ApiBootstrap`. This function expects a configuration object, intended to abstract away details specific to any given WASM environment, primarily so that it can be used without any _direct_ dependency on - Emscripten. The config object is only honored the first time this - is called. Subsequent calls ignore the argument and return the same + Emscripten. (Note the default values for the config object!) The + config object is only honored the first time this is + called. Subsequent calls ignore the argument and return the same (configured) object which gets initialized by the first call. The config object properties include: @@ -133,7 +133,7 @@ */ 'use strict'; self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( - apiConfig = (sqlite3ApiBootstrap.defaultConfig || self.sqlite3ApiConfig) + apiConfig = (self.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig) ){ if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */ console.warn("sqlite3ApiBootstrap() called multiple times.", @@ -567,18 +567,22 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ) ? !!capi.sqlite3_compileoption_used(optName) : false; }/*compileOptionUsed()*/; - capi.wasm.bindingSignatures = [ - /** - Signatures for the WASM-exported C-side functions. Each entry - is an array with 2+ elements: + /** + Signatures for the WASM-exported C-side functions. Each entry + is an array with 2+ elements: - ["c-side name", - "result type" (capi.wasm.xWrap() syntax), - [arg types in xWrap() syntax] - // ^^^ this needn't strictly be an array: it can be subsequent - // elements instead: [x,y,z] is equivalent to x,y,z - ] - */ + [ "c-side name", + "result type" (capi.wasm.xWrap() syntax), + [arg types in xWrap() syntax] + // ^^^ this needn't strictly be an array: it can be subsequent + // elements instead: [x,y,z] is equivalent to x,y,z + ] + + Note that support for the API-specific data types in the + result/argument type strings gets plugged in at a later phase in + the API initialization process. + */ + capi.wasm.bindingSignatures = [ // Please keep these sorted by function name! ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"], ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"], @@ -604,6 +608,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( "sqlite3*", "string", "int", "int", "*", "*", "*", "*", "*"], ["sqlite3_data_count", "int", "sqlite3_stmt*"], ["sqlite3_db_filename", "string", "sqlite3*", "string"], + ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"], ["sqlite3_db_name", "string", "sqlite3*", "int"], ["sqlite3_errmsg", "string", "sqlite3*"], ["sqlite3_error_offset", "int", "sqlite3*"], @@ -614,6 +619,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ["sqlite3_expanded_sql", "string", "sqlite3_stmt*"], ["sqlite3_extended_errcode", "int", "sqlite3*"], ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"], + ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], ["sqlite3_finalize", "int", "sqlite3_stmt*"], ["sqlite3_initialize", undefined], ["sqlite3_interrupt", undefined, "sqlite3*" @@ -740,20 +746,132 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** Returns true if sqlite3.capi.sqlite3_web_persistent_dir() is a - non-empty string and the given name has that string as its - prefix, else returns false. + non-empty string and the given name starts with (that string + + '/'), else returns false. + + Potential (but arguable) TODO: return true if the name is one of + (":localStorage:", "local", ":sessionStorage:", "session") and + kvvfs is available. */ capi.sqlite3_web_filename_is_persistent = function(name){ const p = capi.sqlite3_web_persistent_dir(); - return (p && name) ? name.startsWith(p) : false; + return (p && name) ? name.startsWith(p+'/') : false; }; - + if(0===capi.wasm.exports.sqlite3_vfs_find(0)){ /* Assume that sqlite3_initialize() has not yet been called. This will be the case in an SQLITE_OS_KV build. */ capi.wasm.exports.sqlite3_initialize(); } + if( self.window===self ){ + /* Features specific to the main window thread... */ + + /** + Internal helper for sqlite3_web_kvvfs_clear() and friends. + Its argument should be one of ('local','session',''). + */ + const __kvvfsInfo = function(which){ + const rc = Object.create(null); + rc.prefix = 'kvvfs-'+which; + rc.stores = []; + if('session'===which || ''===which) rc.stores.push(self.sessionStorage); + if('local'===which || ''===which) rc.stores.push(self.localStorage); + return rc; + }; + + /** + Clears all storage used by the kvvfs DB backend, deleting any + DB(s) stored there. Its argument must be either 'session', + 'local', or ''. In the first two cases, only sessionStorage + resp. localStorage is cleared. If it's an empty string (the + default) then both are cleared. Only storage keys which match + the pattern used by kvvfs are cleared: any other client-side + data are retained. + + This function is only available in the main window thread. + + Returns the number of entries cleared. + */ + capi.sqlite3_web_kvvfs_clear = function(which=''){ + let rc = 0; + const kvinfo = __kvvfsInfo(which); + kvinfo.stores.forEach((s)=>{ + const toRm = [] /* keys to remove */; + let i; + for( i = 0; i < s.length; ++i ){ + const k = s.key(i); + if(k.startsWith(kvinfo.prefix)) toRm.push(k); + } + toRm.forEach((kk)=>s.removeItem(kk)); + rc += toRm.length; + }); + return rc; + }; + + /** + This routine guesses the approximate amount of + window.localStorage and/or window.sessionStorage in use by the + kvvfs database backend. Its argument must be one of + ('session', 'local', ''). In the first two cases, only + sessionStorage resp. localStorage is counted. If it's an empty + string (the default) then both are counted. Only storage keys + which match the pattern used by kvvfs are counted. The returned + value is the "length" value of every matching key and value, + noting that the kvvf uses only ASCII keys and values. + + Note that the returned size is not authoritative from the + perspective of how much data can fit into localStorage and + sessionStorage, as the precise algorithms for determining + those limits are unspecified and may include per-entry + overhead invisible to clients. + */ + capi.sqlite3_web_kvvfs_size = function(which=''){ + let sz = 0; + const kvinfo = __kvvfsInfo(which); + kvinfo.stores.forEach((s)=>{ + let i; + for(i = 0; i < s.length; ++i){ + const k = s.key(i); + if(k.startsWith(kvinfo.prefix)){ + sz += k.length; + sz += s.getItem(k).length; + } + } + }); + return sz; + }; + + /** + Given an `sqlite3*`, returns a truthy value (see below) if that + db handle uses the "kvvfs" VFS, else returns false. If pDb is + NULL then this function returns true if the default VFS is + "kvvfs". Results are undefined if pDb is truthy but refers to + an invalid pointer. + + The truthy value it returns is a pointer to the kvvfs + `sqlite3_vfs` object. + */ + capi.sqlite3_web_db_is_kvvfs = function(pDb){ + const pK = capi.sqlite3_vfs_find("kvvfs"); + if(!pK) return false; + else if(!pDb){ + return capi.sqlite3_vfs_find(0) && pK; + } + const scope = capi.wasm.scopedAllocPush(); + try{ + const ppVfs = capi.wasm.scopedAllocPtr(); + return ( + (0===capi.sqlite3_file_control( + pDb, "main", capi.SQLITE_FCNTL_VFS_POINTER, ppVfs + )) && (capi.wasm.getPtrValue(ppVfs) === pK) + ) ? pK : false; + }finally{ + capi.wasm.scopedAllocPop(scope); + } + }; + }/* main-window-only bits */ + /* The remainder of the API will be set up in later steps. */ const sqlite3 = { WasmAllocError: WasmAllocError, @@ -790,6 +908,11 @@ self.sqlite3ApiBootstrap.initializers = []; global-scope symbol. */ self.sqlite3ApiBootstrap.defaultConfig = Object.create(null); -/** Placeholder: gets installed by the first call to - self.sqlite3ApiBootstrap(). */ +/** + Placeholder: gets installed by the first call to + self.sqlite3ApiBootstrap(). However, it is recommended that the + caller of sqlite3ApiBootstrap() capture its return value and delete + self.sqlite3ApiBootstrap after calling it. It returns the same + value which will be stored here. +*/ self.sqlite3ApiBootstrap.sqlite3 = undefined; diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index 2a505f19ab..c072e8c9d4 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -76,7 +76,7 @@ int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ */ WASM_KEEP const char * sqlite3_wasm_enum_json(void){ - static char strBuf[1024 * 8] = {0} /* where the JSON goes */; + static char strBuf[1024 * 12] = {0} /* where the JSON goes */; int n = 0, childCount = 0, structCount = 0 /* output counters for figuring out where commas go */; char * pos = &strBuf[1] /* skip first byte for now to help protect @@ -259,7 +259,8 @@ const char * sqlite3_wasm_enum_json(void){ } _DefGroup; DefGroup(openFlags) { - /* Noting that not all of these will have any effect in WASM-space. */ + /* Noting that not all of these will have any effect in + ** WASM-space. */ DefInt(SQLITE_OPEN_READONLY); DefInt(SQLITE_OPEN_READWRITE); DefInt(SQLITE_OPEN_CREATE); @@ -322,6 +323,49 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_IOCAP_BATCH_ATOMIC); } _DefGroup; + DefGroup(fcntl) { + DefInt(SQLITE_FCNTL_LOCKSTATE); + DefInt(SQLITE_FCNTL_GET_LOCKPROXYFILE); + DefInt(SQLITE_FCNTL_SET_LOCKPROXYFILE); + DefInt(SQLITE_FCNTL_LAST_ERRNO); + DefInt(SQLITE_FCNTL_SIZE_HINT); + DefInt(SQLITE_FCNTL_CHUNK_SIZE); + DefInt(SQLITE_FCNTL_FILE_POINTER); + DefInt(SQLITE_FCNTL_SYNC_OMITTED); + DefInt(SQLITE_FCNTL_WIN32_AV_RETRY); + DefInt(SQLITE_FCNTL_PERSIST_WAL); + DefInt(SQLITE_FCNTL_OVERWRITE); + DefInt(SQLITE_FCNTL_VFSNAME); + DefInt(SQLITE_FCNTL_POWERSAFE_OVERWRITE); + DefInt(SQLITE_FCNTL_PRAGMA); + DefInt(SQLITE_FCNTL_BUSYHANDLER); + DefInt(SQLITE_FCNTL_TEMPFILENAME); + DefInt(SQLITE_FCNTL_MMAP_SIZE); + DefInt(SQLITE_FCNTL_TRACE); + DefInt(SQLITE_FCNTL_HAS_MOVED); + DefInt(SQLITE_FCNTL_SYNC); + DefInt(SQLITE_FCNTL_COMMIT_PHASETWO); + DefInt(SQLITE_FCNTL_WIN32_SET_HANDLE); + DefInt(SQLITE_FCNTL_WAL_BLOCK); + DefInt(SQLITE_FCNTL_ZIPVFS); + DefInt(SQLITE_FCNTL_RBU); + DefInt(SQLITE_FCNTL_VFS_POINTER); + DefInt(SQLITE_FCNTL_JOURNAL_POINTER); + DefInt(SQLITE_FCNTL_WIN32_GET_HANDLE); + DefInt(SQLITE_FCNTL_PDB); + DefInt(SQLITE_FCNTL_BEGIN_ATOMIC_WRITE); + DefInt(SQLITE_FCNTL_COMMIT_ATOMIC_WRITE); + DefInt(SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE); + DefInt(SQLITE_FCNTL_LOCK_TIMEOUT); + DefInt(SQLITE_FCNTL_DATA_VERSION); + DefInt(SQLITE_FCNTL_SIZE_LIMIT); + DefInt(SQLITE_FCNTL_CKPT_DONE); + DefInt(SQLITE_FCNTL_RESERVE_BYTES); + DefInt(SQLITE_FCNTL_CKPT_START); + DefInt(SQLITE_FCNTL_EXTERNAL_READER); + DefInt(SQLITE_FCNTL_CKSM_FILE); + } _DefGroup; + DefGroup(access){ DefInt(SQLITE_ACCESS_EXISTS); DefInt(SQLITE_ACCESS_READWRITE); diff --git a/ext/wasm/batch-runner.html b/ext/wasm/batch-runner.html index 38f38070c0..2a6c1405cf 100644 --- a/ext/wasm/batch-runner.html +++ b/ext/wasm/batch-runner.html @@ -55,7 +55,9 @@
- + + diff --git a/ext/wasm/batch-runner.js b/ext/wasm/batch-runner.js index 437424b48f..9964d747c7 100644 --- a/ext/wasm/batch-runner.js +++ b/ext/wasm/batch-runner.js @@ -17,6 +17,7 @@ (function(){ const toss = function(...args){throw new Error(args.join(' '))}; const warn = console.warn.bind(console); + let sqlite3; const App = { e: { @@ -30,6 +31,7 @@ btnReset: document.querySelector('#db-reset'), cbReverseLog: document.querySelector('#cb-reverse-log-order') }, + db: Object.create(null), cache:{}, metrics:{ /** @@ -61,15 +63,16 @@ }, openDb: function(fn, unlinkFirst=true){ - if(this.db && this.db.ptr){ + if(this.db.ptr){ toss("Already have an opened db."); } const capi = this.sqlite3.capi, wasm = capi.wasm; const stack = wasm.scopedAllocPush(); let pDb = 0; try{ - if(unlinkFirst && fn && ':memory:'!==fn){ - capi.wasm.sqlite3_wasm_vfs_unlink(fn); + if(unlinkFirst && fn){ + if(':'!==fn[0]) capi.wasm.sqlite3_wasm_vfs_unlink(fn); + this.clearStorage(); } const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; const ppDb = wasm.scopedAllocPtr(); @@ -82,7 +85,6 @@ }finally{ wasm.scopedAllocPop(stack); } - this.db = Object.create(null); this.db.filename = fn; this.db.ptr = pDb; this.logHtml("Opened db:",fn); @@ -90,10 +92,13 @@ }, closeDb: function(unlink=false){ - if(this.db && this.db.ptr){ + if(this.db.ptr){ this.sqlite3.capi.sqlite3_close_v2(this.db.ptr); this.logHtml("Closed db",this.db.filename); - if(unlink) capi.wasm.sqlite3_wasm_vfs_unlink(this.db.filename); + if(unlink){ + capi.wasm.sqlite3_wasm_vfs_unlink(this.db.filename); + this.clearStorage(); + } this.db.ptr = this.db.filename = undefined; } }, @@ -329,6 +334,21 @@ return p.catch((e)=>this.logErr("Error via evalFile("+fn+"):",e.message)); }/*evalFile()*/, + clearStorage: function(){ + const sz = sqlite3.capi.sqlite3_web_kvvfs_size(); + const n = sqlite3.capi.sqlite3_web_kvvfs_clear(this.db.filename || ''); + this.logHtml("Cleared kvvfs local/sessionStorage:", + n,"entries totaling approximately",sz,"bytes."); + }, + + resetDb: function(){ + if(this.db.ptr){ + const fn = this.db.filename; + this.closeDb(true); + this.openDb(fn,false); + } + }, + run: function(sqlite3){ delete this.run; this.sqlite3 = sqlite3; @@ -336,9 +356,14 @@ this.logHtml("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); this.logHtml("WASM heap size =",wasm.heap8().length); this.loadSqlList(); - const pDir = capi.sqlite3_web_persistent_dir(); - const dbFile = pDir ? pDir+"/speedtest.db" : ":memory:"; - if(!pDir){ + const pDir = 1 ? '' : capi.sqlite3_web_persistent_dir(); + const dbFile = pDir ? pDir+"/speedtest.db" : ( + sqlite3.capi.sqlite3_vfs_find('kvvfs') ? 'local' : ':memory:' + ); + this.clearStorage(); + if(pDir){ + logHtml("Using persistent storage:",dbFile); + }else{ document.querySelector('#warn-opfs').remove(); } this.openDb(dbFile, !!pDir); @@ -368,11 +393,7 @@ who.evalFile(who.e.selSql.value); }, false); this.e.btnReset.addEventListener('click', function(){ - const fn = who.db.filename; - if(fn){ - who.closeDb(true); - who.openDb(fn,true); - } + who.resetDb(); }, false); this.e.btnExportMetrics.addEventListener('click', function(){ who.logHtml2('warning',"Triggering download of metrics CSV. Check your downloads folder."); @@ -394,12 +415,14 @@ } const timeTotal = performance.now() - timeStart; who.logHtml("Run-remaining time:",timeTotal,"ms ("+(timeTotal/1000/60)+" minute(s))"); + who.clearStorage(); }, false); }/*run()*/ }/*App*/; self.sqlite3TestModule.initSqlite3().then(function(theEmccModule){ self._MODULE = theEmccModule /* this is only to facilitate testing from the console */; + sqlite3 = theEmccModule.sqlite3; App.run(theEmccModule.sqlite3); }); })(); diff --git a/ext/wasm/kvvfs1.html b/ext/wasm/kvvfs1.html index 1a5eb62cf1..5d9f077958 100644 --- a/ext/wasm/kvvfs1.html +++ b/ext/wasm/kvvfs1.html @@ -31,6 +31,7 @@ +
diff --git a/ext/wasm/kvvfs1.js b/ext/wasm/kvvfs1.js index c29426fc63..fd57051a26 100644 --- a/ext/wasm/kvvfs1.js +++ b/ext/wasm/kvvfs1.js @@ -48,35 +48,35 @@ wasm = capi.wasm; log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); T.assert( 0 !== capi.sqlite3_vfs_find(null) ); - if(!oo.DB.clearKvvfsStorage){ + if(!capi.sqlite3_vfs_find('kvvfs')){ warn("This build is not kvvfs-capable."); return; } - const dbStorage = 1 ? ':sessionStorage:' : ':localStorage:'; - const theStore = 's'===dbStorage[1] ? sessionStorage : localStorage; + const dbStorage = 1 ? 'session' : 'local'; + const theStore = 's'===dbStorage[0] ? sessionStorage : localStorage; /** The names ':sessionStorage:' and ':localStorage:' are handled via the DB class constructor, not the C level. In the C API, the names "local" and "session" are the current (2022-09-12) names for those keys, but that is subject to change. */ - const db = new oo.DB( dbStorage ); + const db = new oo.DB( dbStorage, 'c', 'kvvfs' ); document.querySelector('#btn-clear-storage').addEventListener('click',function(){ - oo.DB.clearKvvfsStorage(); - log("kvvfs localStorage and sessionStorage cleared."); + const sz = capi.sqlite3_web_kvvfs_clear(); + log("kvvfs localStorage and sessionStorage cleared:",sz,"entries."); }); document.querySelector('#btn-clear-log').addEventListener('click',function(){ eOutput.innerText = ''; }); document.querySelector('#btn-init-db').addEventListener('click',function(){ - const saveSql = []; try{ + const saveSql = []; db.exec({ - sql:["drop table if exists t;", - "create table if not exists t(a);", - "insert into t(a) values(?),(?),(?)"], + sql: ["drop table if exists t;", + "create table if not exists t(a);", + "insert into t(a) values(?),(?),(?)"], bind: [performance.now() >> 0, (performance.now() * 2) >> 0, (performance.now() / 2) >> 0], @@ -101,6 +101,10 @@ error(e.message); } }); + document.querySelector('#btn-storage-size').addEventListener('click',function(){ + log("sqlite3_web_kvvfs_size(",dbStorage,") says", capi.sqlite3_web_kvvfs_size(dbStorage), + "bytes"); + }); log("Storage backend:",db.filename /* note that the name was internally translated */); if(0===db.selectValue('select count(*) from sqlite_master')){ log("DB is empty. Use the init button to populate it."); diff --git a/ext/wasm/testing1.js b/ext/wasm/testing1.js index 2aadc30c3a..e45b52bfa0 100644 --- a/ext/wasm/testing1.js +++ b/ext/wasm/testing1.js @@ -33,6 +33,8 @@ return v1>=(v2-factor) && v1<=(v2+factor); }; + let sqlite3; + const testBasicSanity = function(db,sqlite3){ const capi = sqlite3.capi; log("Basic sanity tests..."); @@ -1005,10 +1007,17 @@ } }/*testWasmUtil()*/; + const clearKvvfs = function(){ + const sz = sqlite3.capi.sqlite3_web_kvvfs_size(); + const n = sqlite3.capi.sqlite3_web_kvvfs_clear(''); + log("Cleared kvvfs local/sessionStorage:", + n,"entries totaling approximately",sz,"bytes."); + }; + const runTests = function(Module){ //log("Module",Module); - const sqlite3 = Module.sqlite3, - capi = sqlite3.capi, + sqlite3 = Module.sqlite3; + const capi = sqlite3.capi, oo = sqlite3.oo1, wasm = capi.wasm; log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); @@ -1048,14 +1057,15 @@ let dbName = "/testing1.sqlite3"; let vfsName = undefined; - if(oo.DB.clearKvvfsStorage){ + if(capi.sqlite3_web_db_is_kvvfs()){ dbName = "local"; vfsName = 'kvvfs'; logHtml("Found kvvfs. Clearing db(s) from sessionStorage and localStorage", "and selecting kvvfs-friendly db name:",dbName); - oo.DB.clearKvvfsStorage(); + clearKvvfs(); } const db = new oo.DB(dbName,'c',vfsName), startTime = performance.now(); + log("capi.sqlite3_web_db_is_kvvfs() ==",capi.sqlite3_web_db_is_kvvfs(db.pointer)); try { log("db.filename =",db.filename,"db.fileName() =",db.fileName()); const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', @@ -1072,6 +1082,7 @@ }); }finally{ db.close(); + if('kvvfs'===vfsName) clearKvvfs(); } logHtml("Total Test count:",T.counter,"in",(performance.now() - startTime),"ms"); log('capi.wasm.exports',capi.wasm.exports); diff --git a/manifest b/manifest index b104be806b..6fa5a9fc8a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Get\stesting1.js\sworking\swith\sa\skvvfs\sbuild. -D 2022-09-12T22:27:00.037 +C Add/apply\svarious\skvvfs-specific\sutility\sAPIs\sto\sthe\sJS\slayer\sto\sassist\sin\stesting\sand\sanalysis.\sCorrect\sa\sbackwards\sdefault\sarg\scheck\sfor\ssqlite3ApiBootstrap().\sAdd\sexports\sfor\ssqlite3_db_handle(),\ssqlite3_file_control(),\sand\sthe\sSQLITE_FCNTL_xxx\senum. +D 2022-09-13T19:27:03.599 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -476,21 +476,21 @@ F ext/wasm/EXPORTED_FUNCTIONS.fiddle db7a4602f043cf4a5e4135be3609a487f9f1c83f057 F ext/wasm/EXPORTED_RUNTIME_METHODS.fiddle a004bd5eeeda6d3b28d16779b7f1a80305bfe009dfc7f0721b042967f0d39d02 F ext/wasm/GNUmakefile b175a00599c976fe9e7bc02bbc1337b5e3b81d042320c3a0be1621d2c7a21b21 F ext/wasm/README.md e1ee1e7c321c6a250bf78a84ca6f5882890a237a450ba5a0649c7a8399194c52 -F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 1dfd067b3cbd9a49cb204097367cf2f8fe71b5a3b245d9d82a24779fd4ac2394 +F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 150a793a47205b8009ac934f3b6d6ebf67b965c072339aaa25ce808a19e116cc F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 F ext/wasm/api/README.md d876597edd2b9542b6ea031adaaff1c042076fde7b670b1dc6d8a87b28a6631b F ext/wasm/api/post-js-footer.js b64319261d920211b8700004d08b956a6c285f3b0bba81456260a713ed04900c F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a4a06cb776c003880b -F ext/wasm/api/sqlite3-api-cleanup.js 101919ec261644e2f6f0a59952fd9612127b69ea99b493277b2789ea478f9b6b -F ext/wasm/api/sqlite3-api-glue.js 2bf536a38cde324cf352bc2c575f8e22c6d204d667c0eda5a254ba45318914bc -F ext/wasm/api/sqlite3-api-oo1.js b498662748918c132aa59433ea2bd2ebb7e026549fd68506a1ae1ea94736f4f6 +F ext/wasm/api/sqlite3-api-cleanup.js 8564a6077cdcaea9a9f428a019af8a05887f0131e6a2a1e72a7ff1145fadfe77 +F ext/wasm/api/sqlite3-api-glue.js 366d580c8e5bf7fcf4c6dee6f646c31f5549bd417ea03a59a0acca00e8ecce30 +F ext/wasm/api/sqlite3-api-oo1.js 4925f4736eb28fd3a6d4dbbd2c078c38be651aa5996041e7f0b81be36506608e F ext/wasm/api/sqlite3-api-opfs.js 011799db398157cbd254264b6ebae00d7234b93d0e9e810345f213a5774993c0 -F ext/wasm/api/sqlite3-api-prologue.js 9e37ce4dfd74926d0df80dd7e72e33085db4bcee48e2c21236039be416a7dff2 +F ext/wasm/api/sqlite3-api-prologue.js c496adc0cf2ae427ee900aad01c09db97850bd8b6737f2849cab207c8415b839 F ext/wasm/api/sqlite3-api-worker1.js d33062afa045fd4be01ba4abc266801807472558b862b30056211b00c9c347b4 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 -F ext/wasm/api/sqlite3-wasm.c bf4637cf28463cada4b25f09651943c7ece004b253ef39b7ab68eaa60662aa09 -F ext/wasm/batch-runner.html 23209ade7981acce7ecd79d6eff9f4c5a4e8b14ae867ac27cd89b230be640fa6 -F ext/wasm/batch-runner.js 2abd146d3e3a66128ac0a2cc39bfd01e9811c9511fa10ec927d6649795f1ee50 +F ext/wasm/api/sqlite3-wasm.c 7d1760f3a864a9ae16cfb71543829d946a54384bd07af99a3adf9ef32913705f +F ext/wasm/batch-runner.html 2857a6db7292ac83d1581af865d643fd34235db2df830d10b43b01388c599e04 +F ext/wasm/batch-runner.js fb6a338aeef509f181f499700a8595bc578bbc54ef832e0cda7e7e7c10b90a18 F ext/wasm/common/SqliteTestUtil.js 529161a624265ba84271a52db58da022649832fa1c71309fb1e02cc037327a2b F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f F ext/wasm/common/testing.css 3a5143699c2b73a85b962271e1a9b3241b30d90e30d895e4f55665e648572962 @@ -507,8 +507,8 @@ F ext/wasm/jaccwabyt/jaccwabyt.md 447cc02b598f7792edaa8ae6853a7847b8178a18ed356a F ext/wasm/jaccwabyt/jaccwabyt_test.c 39e4b865a33548f943e2eb9dd0dc8d619a80de05d5300668e9960fff30d0d36f F ext/wasm/jaccwabyt/jaccwabyt_test.exports 5ff001ef975c426ffe88d7d8a6e96ec725e568d2c2307c416902059339c06f19 F ext/wasm/kvvfs.make bfa0aaac384d9f200d2c8e31efb3536b40d139667b88e6eba9c0a71e23da6a5c -F ext/wasm/kvvfs1.html 83bac238d1e93ed102a461672fc58fe74e611e426708e9a5c66002c01eadb942 -F ext/wasm/kvvfs1.js 53721a42e0ec45f6978cc723e5de47f882517867d0fcff4c6ff05f4c422e27c4 +F ext/wasm/kvvfs1.html 13bb24190bfb276a57b228499519badcc1bf39ed07e4b37bc2a425ce6418fed1 +F ext/wasm/kvvfs1.js a23ee98a78b9da379eab7a20d7ddf5283a684dd7a91685a84686c05216f96689 F ext/wasm/scratchpad-opfs-main.html 4565cf194e66188190d35f70e82553e2e2d72b9809b73c94ab67b8cfd14d2e0c F ext/wasm/scratchpad-opfs-main.js 69e960e9161f6412fd0c30f355d4112f1894d6609eb431e2d16d207d1380518e F ext/wasm/scratchpad-opfs-worker.html 66c1d15d678f3bd306373d76b61c6c8aef988f61f4a8dd40185d452f9c6d2bf5 @@ -526,7 +526,7 @@ F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416 F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893 F ext/wasm/testing-worker1-promiser.js c62b5879339eef0b21aebd9d75bc125c86530edc17470afff18077f931cb704a F ext/wasm/testing1.html 50575755e43232dbe4c2f97c9086b3118eb91ec2ee1fae931e6d7669fb17fcae -F ext/wasm/testing1.js 735120231d6e55b92964924fcb54a66484d8b2e30fe5a18c71081b3590344a5f +F ext/wasm/testing1.js 7cd8ab255c238b030d928755ae8e91e7d90a12f2ae601b1b8f7827aaa4fb258e F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3 F ext/wasm/testing2.js 25584bcc30f19673ce13a6f301f89f8820a59dfe044e0c4f2913941f4097fe3c F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x @@ -2024,8 +2024,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 8b1608a9a37131121cb3a0cb1e8dbad5e39dc1fc2a341d056f09537f179b2e7a -R 77095a41dfb9b784dcab0078822e6eff +P 333a45725d1708e0fefa559c33ce1c7eeb425cdb04f594b1f2b48166c1478c79 +R fff963b52d5c3a76677d96f35303ab67 U stephan -Z c624cb9efadffe8972e3df15b8dc9939 +Z e8c548c1774e1a39daa4183bad155966 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 69b3e88fd2..e142fcae1f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -333a45725d1708e0fefa559c33ce1c7eeb425cdb04f594b1f2b48166c1478c79 \ No newline at end of file +0d78961870ee9f22f1ba16d423377d28dcc36e04b1e31ffd57f3e2fd51f8f0f2 \ No newline at end of file