aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/api')
-rw-r--r--ext/wasm/api/sqlite3-api-cleanup.js2
-rw-r--r--ext/wasm/api/sqlite3-api-oo1.js143
-rw-r--r--ext/wasm/api/sqlite3-api-prologue.js14
-rw-r--r--ext/wasm/api/sqlite3-wasm.c221
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