diff options
Diffstat (limited to 'ext/wasm/api')
-rw-r--r-- | ext/wasm/api/sqlite3-api-cleanup.js | 2 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-oo1.js | 143 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-prologue.js | 14 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-wasm.c | 221 |
4 files changed, 113 insertions, 267 deletions
diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js index 01aba213e..1b57cdc5d 100644 --- a/ext/wasm/api/sqlite3-api-cleanup.js +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -39,7 +39,7 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build delete self.sqlite3ApiBootstrap; if(self.location && +self.location.port > 1024){ - console.warn("Installing sqlite3 bits as global S for dev-testing purposes."); + console.warn("Installing sqlite3 bits as global S for local dev/test purposes."); self.S = sqlite3; } diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index aafc04a2d..af179d1fe 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -70,6 +70,74 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const toss3 = (...args)=>{throw new SQLite3Error(...args)}; sqlite3.SQLite3Error = SQLite3Error; + // Documented in DB.checkRc() + const checkSqlite3Rc = function(dbPtr, sqliteResultCode){ + if(sqliteResultCode){ + if(dbPtr instanceof DB) dbPtr = dbPtr.pointer; + throw new SQLite3Error( + "sqlite result code",sqliteResultCode+":", + (dbPtr + ? capi.sqlite3_errmsg(dbPtr) + : capi.sqlite3_errstr(sqliteResultCode)) + ); + } + }; + + /** + A proxy for DB class constructors. It must be called with the + being-construct DB object as its "this". + */ + const dbCtorHelper = function ctor(fn=':memory:', flags='c', vfsName){ + if(!ctor._name2vfs){ + // Map special filenames which we handle here (instead of in C) + // to some helpful metadata... + ctor._name2vfs = Object.create(null); + const isWorkerThread = (self.window===self /*===running in main window*/) + ? false + : (n)=>toss3("The VFS for",n,"is only available in the main window thread.") + ctor._name2vfs[':localStorage:'] = { + vfs: 'kvvfs', + filename: isWorkerThread || (()=>'local') + }; + ctor._name2vfs[':sessionStorage:'] = { + vfs: 'kvvfs', + filename: isWorkerThread || (()=>'session') + }; + } + if('string'!==typeof fn){ + toss3("Invalid filename for DB constructor."); + } + const vfsCheck = ctor._name2vfs[fn]; + if(vfsCheck){ + vfsName = vfsCheck.vfs; + fn = vfsCheck.filename(fn); + } + let ptr, oflags = 0; + if( flags.indexOf('c')>=0 ){ + oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; + } + if( flags.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE; + if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY; + oflags |= capi.SQLITE_OPEN_EXRESCODE; + const stack = capi.wasm.scopedAllocPush(); + try { + const ppDb = capi.wasm.scopedAllocPtr() /* output (sqlite3**) arg */; + const pVfsName = vfsName ? capi.wasm.scopedAllocCString(vfsName) : 0; + const rc = capi.sqlite3_open_v2(fn, ppDb, oflags, pVfsName); + ptr = capi.wasm.getPtrValue(ppDb); + checkSqlite3Rc(ptr, rc); + }catch( e ){ + if( ptr ) capi.sqlite3_close_v2(ptr); + throw e; + }finally{ + capi.wasm.scopedAllocPop(stack); + } + this.filename = fn; + __ptrMap.set(this, ptr); + __stmtMap.set(this, Object.create(null)); + __udfMap.set(this, Object.create(null)); + }; + /** The DB class provides a high-level OO wrapper around an sqlite3 db handle. @@ -102,42 +170,29 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "c". These modes are ignored for the special ":memory:" and "" names. - The final argument is currently unimplemented but will eventually - be used to specify an optional sqlite3 VFS implementation name, - as for the final argument to sqlite3_open_v2(). + The final argument is analogous to the final argument of + sqlite3_open_v2(): the name of an sqlite3 VFS. Pass a falsy value, + or not at all, to use the default. If passed a value, it must + be the string name of a VFS For purposes of passing a DB instance to C-style sqlite3 functions, the DB object's read-only `pointer` property holds its `sqlite3*` pointer value. That property can also be used to check whether this DB instance is still open. + + + EXPERIMENTAL: in the main window thread, the filenames + ":localStorage:" and ":sessionStorage:" are special: they cause + the db to use either localStorage or sessionStorage for storing + the database. In this mode, only a single database is permitted + in each storage object. This feature is experimental and subject + to any number of changes (including outright removal). This + support requires a specific build of sqlite3, the existence of + which can be determined at runtime by checking for a non-0 return + value from sqlite3.capi.sqlite3_vfs_find("kvvfs"). */ - const DB = function ctor(fn=':memory:', flags='c', vtab="not yet implemented"){ - if('string'!==typeof fn){ - toss3("Invalid filename for DB constructor."); - } - let ptr, oflags = 0; - if( flags.indexOf('c')>=0 ){ - oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; - } - if( flags.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE; - if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY; - oflags |= capi.SQLITE_OPEN_EXRESCODE; - const stack = capi.wasm.scopedAllocPush(); - try { - const ppDb = capi.wasm.scopedAllocPtr() /* output (sqlite3**) arg */; - const rc = capi.sqlite3_open_v2(fn, ppDb, oflags, null); - ptr = capi.wasm.getPtrValue(ppDb); - ctor.checkRc(ptr, rc); - }catch( e ){ - if( ptr ) capi.sqlite3_close_v2(ptr); - throw e; - }finally{ - capi.wasm.scopedAllocPop(stack); - } - this.filename = fn; - __ptrMap.set(this, ptr); - __stmtMap.set(this, Object.create(null)); - __udfMap.set(this, Object.create(null)); + const DB = function ctor(fn=':memory:', flags='c', vfsName){ + dbCtorHelper.apply(this, Array.prototype.slice.call(arguments)); }; /** @@ -232,6 +287,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if(args[0] && 'object'===typeof args[0]){ out.opt = args[0]; out.sql = out.opt.sql; + }else if(Array.isArray(args[0])){ + out.sql = args[0]; } break; case 2: @@ -295,17 +352,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ non-0 non-error codes need to be checked for in client code where they are expected. */ - DB.checkRc = function(dbPtr, sqliteResultCode){ - if(sqliteResultCode){ - if(dbPtr instanceof DB) dbPtr = dbPtr.pointer; - throw new SQLite3Error( - "sqlite result code",sqliteResultCode+":", - (dbPtr - ? capi.sqlite3_errmsg(dbPtr) - : capi.sqlite3_errstr(sqliteResultCode)) - ); - } - }; + DB.checkRc = checkSqlite3Rc; DB.prototype = { /** @@ -1532,5 +1579,19 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ DB, Stmt }/*oo1 object*/; + + if( self.window===self && 0!==capi.sqlite3_vfs_find('kvvfs') ){ + /* In the main window thread, add a couple of convenience proxies + for localStorage and sessionStorage DBs... */ + let klass = sqlite3.oo1.LocalStorageDb = function(){ + dbCtorHelper.call(this, 'local', 'c', 'kvvfs'); + }; + klass.prototype = DB.prototype; + + klass = sqlite3.oo1.SessionStorageDb = function(){ + dbCtorHelper.call(this, 'session', 'c', 'kvvfs'); + }; + klass.prototype = DB.prototype; + } }); diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index 7959d047c..17dcd4228 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -713,7 +713,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return __persistentDir = ""; } try{ - if(pdir && 0===this.wasm.xCallWrapped( + if(pdir && 0===capi.wasm.xCallWrapped( 'sqlite3_wasm_init_opfs', 'i32', ['string'], pdir )){ /** OPFS does not support locking and will trigger errors if @@ -736,7 +736,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( // sqlite3_wasm_init_opfs() is not available return __persistentDir = ""; } - }.bind(capi); + }; /** Returns true if sqlite3.capi.sqlite3_web_persistent_dir() is a @@ -744,9 +744,15 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( prefix, else returns false. */ capi.sqlite3_web_filename_is_persistent = function(name){ - const p = this.sqlite3_web_persistent_dir(); + const p = capi.sqlite3_web_persistent_dir(); return (p && name) ? name.startsWith(p) : false; - }.bind(capi); + }; + + 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(); + } /* The remainder of the API will be set up in later steps. */ const sqlite3 = { diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index df60a4a24..2a505f19a 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -523,226 +523,5 @@ int sqlite3_wasm_init_opfs(void){ } #endif /* __EMSCRIPTEN__ && SQLITE_WASM_OPFS */ -#if defined(__EMSCRIPTEN__) // && defined(SQLITE_OS_KV) -#include <emscripten.h> -#include <emscripten/console.h> - -#ifndef KVSTORAGE_KEY_SZ -/* We can remove this once kvvfs and this bit is merged. */ -# define KVSTORAGE_KEY_SZ 32 -static void kvstorageMakeKey( - const char *zClass, - const char *zKeyIn, - char *zKeyOut -){ - sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn); -} -#endif - -/* -** An internal level of indirection for accessing the static -** kvstorageMakeKey() from EM_JS()-generated functions. This must be -** made available for export via Emscripten but is not intended to be -** used from client code. If called with a NULL zKeyOut it is a no-op. -** It returns KVSTORAGE_KEY_SZ, so JS code (which cannot see that -** constant) may call it with NULL arguments to get the size of the -** allocation they'll need for a kvvfs key. -** -** Maintenance reminder: Emscripten will install this in the Module -** init scope and will prefix its name with "_". -*/ -WASM_KEEP -int sqlite3_wasm__kvvfsMakeKey(const char *zClass, - const char *zKeyIn, - char *zKeyOut){ - if(zKeyOut) kvstorageMakeKey(zClass, zKeyIn, zKeyOut); - return KVSTORAGE_KEY_SZ; -} - -#if 0 -/* -** Alternately, we can implement kvstorageMakeKey() in JS in such a -** way that it's visible to kvstorageWrite/Delete/Read() but not the -** rest of the world. This impl is considerably more verbose than the -** C impl because writing directly to memory requires more code in -** JS. Though more verbose, this approach enables removal of -** sqlite3_wasm__kvvfsMakeKey(). The only catch is that the -** KVSTORAGE_KEY_SZ constant has to be hard-coded into this function. -*/ -EM_JS(void, kvstorageMakeKeyJS, - (const char *zClass, const char *zKeyIn, char *zKeyOut),{ - const max = 32; - if(!arguments.length) return max; - let n = 0, i = 0, ch = 0; - // Write key prefix to dest... - if(0){ - const prefix = "kvvfs-"; - for(i in prefix) setValue(zKeyOut+(n++), prefix.charCodeAt(i)); - }else{ - // slightly optimized but less readable... - setValue(zKeyOut + (n++), 107/*'k'*/); - setValue(zKeyOut + (n++), 118/*'v'*/); - setValue(zKeyOut + (n++), 118/*'v'*/); - setValue(zKeyOut + (n++), 102/*'f'*/); - setValue(zKeyOut + (n++), 115/*'s'*/); - setValue(zKeyOut + (n++), 45/*'-'*/); - } - // Write zClass to dest... - for(i = 0; n < max && (ch = getValue(zClass+i)); ++n, ++i){ - setValue(zKeyOut + n, ch); - } - // Write "-" separator to dest... - if(n<max) setValue(zKeyOut + (n++), 45/* == '-'*/); - // Write zKeyIn to dest... - for(i = 0; n < max && (ch = getValue(zKeyIn+i)); ++n, ++i){ - setValue(zKeyOut + n, ch); - } - // NUL terminate... - if(n<max) setValue(zKeyOut + n, 0); -}); -#endif - -/* -** Internal helper for kvstorageWrite/Read/Delete() which creates a -** storage key for the given zClass/zKeyIn combination. Returns a -** pointer to the key: a C string allocated on the WASM stack, or 0 if -** allocation fails. It is up to the caller to save/restore the stack -** before/after this operation. -*/ -EM_JS(const char *, kvstorageMakeKeyOnJSStack, - (const char *zClass, const char *zKeyIn),{ - if( 0==zClass || 0==zKeyIn) return 0; - const zXKey = stackAlloc(_sqlite3_wasm__kvvfsMakeKey(0,0,0)); - if(zXKey) _sqlite3_wasm__kvvfsMakeKey(zClass, zKeyIn, zXKey); - return zXKey; -}); - -/* -** JS impl of kvstorageWrite(). Main docs are in the C impl. This impl -** writes zData to the global sessionStorage (if zClass starts with -** 's') or localStorage, using a storage key derived from zClass and -** zKey. -*/ -EM_JS(int, kvstorageWrite, - (const char *zClass, const char *zKey, const char *zData),{ - const stack = stackSave(); - try { - const zXKey = kvstorageMakeKeyOnJSStack(zClass,zKey); - if(!zXKey) return 1/*OOM*/; - const jKey = UTF8ToString(zXKey); - /** - We could simplify this function and eliminate the - kvstorageMakeKey() symbol acrobatics if we'd simply hard-code - the key algo into the 3 functions which need it: - - const jKey = "kvvfs-"+UTF8ToString(zClass)+"-"+UTF8ToString(zKey); - */ - ((115/*=='s'*/===getValue(zClass)) - ? sessionStorage : localStorage).setItem(jKey, UTF8ToString(zData)); - }catch(e){ - console.error("kvstorageWrite()",e); - return 1; // Can't access SQLITE_xxx from here - }finally{ - stackRestore(stack); - } - return 0; -}); - -/* -** JS impl of kvstorageDelete(). Main docs are in the C impl. This -** impl generates a key derived from zClass and zKey, and removes the -** matching entry (if any) from global sessionStorage (if zClass -** starts with 's') or localStorage. -*/ -EM_JS(int, kvstorageDelete, - (const char *zClass, const char *zKey),{ - const stack = stackSave(); - try { - const zXKey = kvstorageMakeKeyOnJSStack(zClass,zKey); - if(!zXKey) return 1/*OOM*/; - _sqlite3_wasm__kvvfsMakeKey(zClass, zKey, zXKey); - const jKey = UTF8ToString(zXKey); - ((115/*=='s'*/===getValue(zClass)) - ? sessionStorage : localStorage).removeItem(jKey); - }catch(e){ - console.error("kvstorageDelete()",e); - return 1; - }finally{ - stackRestore(stack); - } - return 0; -}); - -/* -** JS impl of kvstorageRead(). Main docs are in the C impl. This impl -** reads its data from the global sessionStorage (if zClass starts -** with 's') or localStorage, using a storage key derived from zClass -** and zKey. -*/ -EM_JS(int, kvstorageRead, - (const char *zClass, const char *zKey, char *zBuf, int nBuf),{ - const stack = stackSave(); - try { - const zXKey = kvstorageMakeKeyOnJSStack(zClass,zKey); - if(!zXKey) return -3/*OOM*/; - const jKey = UTF8ToString(zXKey); - const jV = ((115/*=='s'*/===getValue(zClass)) - ? sessionStorage : localStorage).getItem(jKey); - if(!jV) return -1; - const nV = jV.length /* Note that we are relying 100% on v being - ASCII so that jV.length is equal to the - C-string's byte length. */; - if(nBuf<=0) return nV; - else if(1===nBuf){ - setValue(zBuf, 0); - return nV; - } - const zV = allocateUTF8OnStack(jV); - if(nBuf > nV + 1) nBuf = nV + 1; - HEAPU8.copyWithin(zBuf, zV, zV + nBuf - 1); - setValue( zBuf + nBuf - 1, 0 ); - return nBuf - 1; - }catch(e){ - console.error("kvstorageRead()",e); - return -2; - }finally{ - stackRestore(stack); - } -}); - -/* -** This function exists for (1) WASM testing purposes and (2) as a -** hook to get Emscripten to export several EM_JS()-generated -** functions. It is not part of the public API and its signature -** and semantics may change at any time. -*/ -WASM_KEEP -int sqlite3_wasm__emjs_keep(int whichOp){ - int rc = 0; - const char * zClass = "session"; - const char * zKey = "hello"; - switch( whichOp ){ - case 0: break; - case 1: - kvstorageWrite(zClass, zKey, "world"); - break; - case 2: { - char buffer[128] = {0}; - char * zBuf = &buffer[0]; - rc = kvstorageRead(zClass, zKey, zBuf, (int)sizeof(buffer)); - emscripten_console_logf("kvstorageRead()=%d %s\n", rc, zBuf); - break; - } - case 3: - kvstorageDelete(zClass, zKey); - break; - case 4: - kvstorageMakeKeyOnJSStack(0,0) /* force Emscripten to include this */; - break; - default: break; - } - return rc; -} -#endif /* ifdef __EMSCRIPTEN__ (kvvfs method impls) */ #undef WASM_KEEP |