# End main Emscripten-based module build
########################################################################
-include kvvfs.make
+########################################################################
+# batch-runner.js...
+dir.sql := sql
+speedtest1 := ../../speedtest1
+speedtest1.c := ../../test/speedtest1.c
+speedtest1.sql := $(dir.sql)/speedtest1.sql
+$(speedtest1):
+ $(MAKE) -C ../.. speedtest1
+$(speedtest1.sql): $(speedtest1)
+ $(speedtest1) --script $@
+batch-runner.list: $(MAKEFILE) $(speedtest1.sql) $(dir.sql)/000-mandelbrot.sql
+ bash split-speedtest1-script.sh $(dir.sql)/speedtest1.sql
+ ls -1 $(dir.sql)/*.sql | grep -v speedtest1.sql | sort > $@
+clean-batch:
+ rm -f batch-runner.list $(dir.sql)/speedtest1*.sql
+# ^^^ we don't do this along with 'clean' because we clean/rebuild on
+# a regular basis with different -Ox flags and rebuilding the batch
+# pieces each time is an unnecessary time sink.
+batch: batch-runner.list
+all: batch
+# end batch-runner.js
+########################################################################
+# speedtest1.js...
+emcc.speedtest1-flags := -g $(emcc_opt)
+ifneq (0,$(ENABLE_WASMFS))
+ emcc.speedtest1-flags += -pthread -sWASMFS -sPTHREAD_POOL_SIZE=2
+ emcc.speedtest1-flags += -DSQLITE_WASM_OPFS
+endif
+emcc.speedtest1-flags += -sINVOKE_RUN=0
+#emcc.speedtest1-flags += --no-entry
+emcc.speedtest1-flags += -flto
+emcc.speedtest1-flags += -sABORTING_MALLOC
+emcc.speedtest1-flags += -sINITIAL_MEMORY=128450560
+emcc.speedtest1-flags += -sSTRICT_JS
+emcc.speedtest1-flags += $(emcc.environment)
+emcc.speedtest1-flags += -sMODULARIZE
+emcc.speedtest1-flags += -sEXPORT_NAME=sqlite3Speedtest1InitModule
+emcc.speedtest1-flags += -Wno-limited-postlink-optimizations
+emcc.speedtest1-flags += -sEXPORTED_FUNCTIONS=_main,_malloc,_free,_sqlite3_wasm_vfs_unlink,_sqlite3_wasm_init_opfs
+emcc.speedtest1-flags += -sDYNAMIC_EXECUTION=0
+emcc.speedtest1-flags += --minify 0
+
+speedtest1.js := speedtest1.js
+speedtest1.wasm := $(subst .js,.wasm,$(speedtest1.js))
+$(speedtest1.js): emcc.cflags+=
+# speedtest1 notes re. sqlite3-wasm.o vs sqlite3-wasm.c: building against
+# the latter (predictably) results in a slightly faster binary, but we're
+# close enough to the target speed requirements that the 500ms makes a
+# difference.
+$(speedtest1.js): $(speedtest1.c) $(sqlite3-wasm.c) $(MAKEFILE)
+ $(emcc.bin) \
+ $(emcc.speedtest1-flags) \
+ -I. -I$(dir.top) \
+ -DSQLITE_THREADSAFE=0 \
+ -DSQLITE_TEMP_STORE=3 \
+ -DSQLITE_OMIT_UTF16 \
+ -DSQLITE_OMIT_DEPRECATED \
+ -DSQLITE_OMIT_SHARED_CACHE \
+ '-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \
+ -DSQLITE_SPEEDTEST1_WASM \
+ -o $@ $(speedtest1.c) $(sqlite3-wasm.c) -lm
+ifneq (,$(wasm-strip))
+ $(wasm-strip) $(speedtest1.wasm)
+endif
+ ls -la $@ $(speedtest1.wasm)
+
+speedtest1: $(speedtest1.js)
+all: $(speedtest1.js)
+CLEAN_FILES += $(speedtest1.js) $(speedtest1.wasm)
+# end speedtest1.js
++########################################################################
+
########################################################################
# fiddle_remote is the remote destination for the fiddle app. It
# must be a [user@]HOST:/path for rsync.
rsync -va fiddle/ $(fiddle_remote)
# end fiddle remote push
########################################################################
++
++include kvvfs.make
***********************************************************************
This file is the tail end of the sqlite3-api.js constellation,
- intended to be appended after all other files so that it can clean
- up any global systems temporarily used for setting up the API's
- various subsystems.
+ intended to be appended after all other sqlite3-api-*.js files so
+ that it can finalize any setup and clean up any global symbols
+ temporarily used for setting up the API's various subsystems.
*/
'use strict';
-self.sqlite3.postInit.forEach(
- self.importScripts/*global is a Worker*/
- ? function(f){
- /** We try/catch/report for the sake of failures which happen in
- a Worker, as those exceptions can otherwise get completely
- swallowed, leading to confusing downstream errors which have
- nothing to do with this failure. */
- try{ f(self, self.sqlite3) }
- catch(e){
- console.error("Error in postInit() function:",e);
- throw e;
- }
- }
- : (f)=>f(self, self.sqlite3)
-);
-delete self.sqlite3.postInit;
-if(self.location && +self.location.port > 1024){
- console.warn("Installing sqlite3 bits as global S for dev-testing purposes.");
- self.S = self.sqlite3;
+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 */;
+
+ /**
+ For current (2022-08-22) purposes, automatically call
+ sqlite3ApiBootstrap(). That decision will be revisited at some
+ point, as we really want client code to be able to call this to
+ configure certain parts. Clients may modify
+ self.sqlite3ApiBootstrap.defaultConfig to tweak the default
+ configuration used by a no-args call to sqlite3ApiBootstrap().
+ */
+ //console.warn("self.sqlite3ApiConfig = ",self.sqlite3ApiConfig);
+ const sqlite3 = self.sqlite3ApiBootstrap();
+ 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;
+ }
+
+ /* Clean up temporary references to our APIs... */
+ delete sqlite3.capi.util /* arguable, but these are (currently) internal-use APIs */;
+ //console.warn("Module.sqlite3 =",Module.sqlite3);
+ Module.sqlite3 = sqlite3 /* Currently needed by test code and sqlite3-worker1.js */;
+}else{
+ console.warn("This is not running in an Emscripten module context, so",
+ "self.sqlite3ApiBootstrap() is _not_ being called due to lack",
+ "of config info for the WASM environment.",
+ "It must be called manually.");
}
-/* Clean up temporary global-scope references to our APIs... */
-self.sqlite3.config.Module.sqlite3 = self.sqlite3
-/* ^^^^ Currently needed by test code and Worker API setup */;
-delete self.sqlite3.capi.util /* arguable, but these are (currently) internal-use APIs */;
-delete self.sqlite3 /* clean up our global-scope reference */;
-//console.warn("Module.sqlite3 =",Module.sqlite3);
this.name = 'SQLite3Error';
}
};
- const toss3 = (...args)=>{throw new SQLite3Error(args)};
+ 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.
not resolve to real filenames, but "" uses an on-storage
temporary database and requires that the VFS support that.
- The db is currently opened with a fixed set of flags:
- (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
- SQLITE_OPEN_EXRESCODE). This API will change in the future
- permit the caller to provide those flags via an additional
- argument.
+ The second argument specifies the open/create mode for the
+ database. It must be string containing a sequence of letters (in
+ any order, but case sensitive) specifying the mode:
+
+ - "c" => create if it does not exist, else fail if it does not
+ exist. Implies the "w" flag.
+
+ - "w" => write. Implies "r": a db cannot be write-only.
+
+ - "r" => read-only if neither "w" nor "c" are provided, else it
+ is ignored.
+
+ If "w" is not provided, the db is implicitly read-only, noting that
+ "rc" is meaningless
+
+ Any other letters are currently ignored. The default is
+ "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, its read-only `pointer` property holds its `sqlite3*`
- pointer value. That property can also be used to check whether
- this DB instance is still open.
+ 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"){
- const DB = function ctor(fn=':memory:'){
-- 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();
- let ptr;
-- try {
-- const ppDb = capi.wasm.scopedAllocPtr() /* output (sqlite3**) arg */;
- const rc = capi.sqlite3_open_v2(fn, ppDb, oflags, null);
- ptr = capi.wasm.getPtrValue(ppDb);
- const rc = capi.sqlite3_open_v2(fn, ppDb, capi.SQLITE_OPEN_READWRITE
- | capi.SQLITE_OPEN_CREATE
- | capi.SQLITE_OPEN_EXRESCODE, null);
- ptr = capi.wasm.getMemValue(ppDb, '*');
-- ctor.checkRc(ptr, rc);
- }catch( e ){
- if( ptr ) capi.sqlite3_close_v2(ptr);
- }catch(e){
- if(ptr) capi.sqlite3_close_v2(ptr);
-- throw e;
- }finally{
- capi.wasm.scopedAllocPop(stack);
-- }
- 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));
};
/**
}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:
};
/**
- Expects to be given a DB instance or an `sqlite3*` pointer, and an
- sqlite3 API result code. If the result code is not falsy, this
- function throws an SQLite3Error with an error message from
- sqlite3_errmsg(), using dbPtr as the db handle. Note that if it's
- passed a non-error code like SQLITE_ROW or SQLITE_DONE, it will
- still throw but the error string might be "Not an error." The
- various non-0 non-error codes need to be checked for in client
- code where they are expected.
+ 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
+ message from sqlite3_errmsg(), using dbPtr as the db handle, or
+ sqlite3_errstr() if dbPtr is falsy. Note that if it's passed a
+ non-error code like SQLITE_ROW or SQLITE_DONE, it will still
+ throw but the error string might be "Not an error." The various
+ 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(
- throw new SQLite3Error([
-- "sqlite result code",sqliteResultCode+":",
- (dbPtr
- ? capi.sqlite3_errmsg(dbPtr)
- : capi.sqlite3_errstr(sqliteResultCode))
- );
- capi.sqlite3_errmsg(dbPtr) || "Unknown db error."
- ].join(' '));
-- }
-- };
++ DB.checkRc = checkSqlite3Rc;
DB.prototype = {
/**
},
DB,
Stmt
- }/*SQLite3 object*/;
-})(self);
+ }/*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;
++ }
+});
+
["sqlite3_total_changes64", "i64", ["sqlite3*"]]
];
- if(pdir && 0===this.wasm.xCallWrapped(
+ /**
+ Functions which are intended solely for API-internal use by the
+ WASM components, not client code. These get installed into
+ capi.wasm.
+ */
+ capi.wasm.bindingSignatures.wasm = [
+ ["sqlite3_wasm_vfs_unlink", "int", "string"]
+ ];
+
+ /** State for sqlite3_web_persistent_dir(). */
+ let __persistentDir;
+ /**
+ An experiment. Do not use.
+
+ If the wasm environment has a persistent storage directory,
+ its path is returned by this function. If it does not then
+ it returns "" (noting that "" is a falsy value).
+
+ The first time this is called, this function inspects the current
+ environment to determine whether persistence filesystem support
+ is available and, if it is, enables it (if needed).
+
+ TODOs and caveats:
+
+ - If persistent storage is available at the root of the virtual
+ filesystem, this interface cannot currently distinguish that
+ from the lack of persistence. That case cannot currently (with
+ WASMFS/OPFS) happen, but it is conceivably possible in future
+ environments or non-browser runtimes (none of which are yet
+ supported targets).
+ */
+ capi.sqlite3_web_persistent_dir = function(){
+ if(undefined !== __persistentDir) return __persistentDir;
+ // If we have no OPFS, there is no persistent dir
+ const pdir = config.persistentDirName;
+ if(!pdir
+ || !self.FileSystemHandle
+ || !self.FileSystemDirectoryHandle
+ || !self.FileSystemFileHandle){
+ return __persistentDir = "";
+ }
+ try{
- }.bind(capi);
++ if(pdir && 0===capi.wasm.xCallWrapped(
+ 'sqlite3_wasm_init_opfs', 'i32', ['string'], pdir
+ )){
+ /** OPFS does not support locking and will trigger errors if
+ we try to lock. We don't _really_ want to
+ _unconditionally_ install a non-locking sqlite3 VFS as the
+ default, but we do so here for simplicy's sake for the
+ time being. That said: locking is a no-op on all of the
+ current WASM storage, so this isn't (currently) as bad as
+ it may initially seem. */
+ const pVfs = sqlite3.capi.sqlite3_vfs_find("unix-none");
+ if(pVfs){
+ capi.sqlite3_vfs_register(pVfs,1);
+ console.warn("Installed 'unix-none' as the default sqlite3 VFS.");
+ }
+ return __persistentDir = pdir;
+ }else{
+ return __persistentDir = "";
+ }
+ }catch(e){
+ // sqlite3_wasm_init_opfs() is not available
+ return __persistentDir = "";
+ }
- const p = this.sqlite3_web_persistent_dir();
++ };
+
+ /**
+ 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.
+ */
+ capi.sqlite3_web_filename_is_persistent = function(name){
- }.bind(capi);
++ const p = capi.sqlite3_web_persistent_dir();
+ 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();
++ }
+
/* The remainder of the API will be set up in later steps. */
- return {
+ const sqlite3 = {
+ WasmAllocError: WasmAllocError,
capi,
- postInit: [
- /* some pieces of the API may install functions into this array,
- and each such function will be called, passed (self,sqlite3),
- at the very end of the API load/init process, where self is
- the current global object and sqlite3 is the object returned
- from sqlite3ApiBootstrap(). This array will be removed at the
- end of the API setup process. */],
- /** Config is needed downstream for gluing pieces together. It
- will be removed at the end of the API setup process. */
config
};
+ sqlite3ApiBootstrap.initializers.forEach((f)=>f(sqlite3));
+ delete sqlite3ApiBootstrap.initializers;
+ sqlite3ApiBootstrap.sqlite3 = sqlite3;
+ return sqlite3;
}/*sqlite3ApiBootstrap()*/;
+/**
+ self.sqlite3ApiBootstrap.initializers is an internal detail used by
+ the various pieces of the sqlite3 API's amalgamation process. It
+ must not be modified by client code except when plugging such code
+ into the amalgamation process.
+
+ Each component of the amalgamation is expected to append a function
+ to this array. When sqlite3ApiBootstrap() is called for the first
+ time, each such function will be called (in their appended order)
+ and passed the sqlite3 namespace object, into which they can install
+ their features (noting that most will also require that certain
+ features alread have been installed). At the end of that process,
+ this array is deleted.
+*/
+self.sqlite3ApiBootstrap.initializers = [];
+/**
+ Client code may assign sqlite3ApiBootstrap.defaultConfig an
+ object-type value before calling sqlite3ApiBootstrap() (without
+ arguments) in order to tell that call to use this object as its
+ default config value. The intention of this is to provide
+ downstream clients with a reasonably flexible approach for plugging in
+ an environment-suitable configuration without having to define a new
+ global-scope symbol.
+*/
+self.sqlite3ApiBootstrap.defaultConfig = Object.create(null);
+/** Placeholder: gets installed by the first call to
+ self.sqlite3ApiBootstrap(). */
+self.sqlite3ApiBootstrap.sqlite3 = undefined;
#undef outf
#undef lenCheck
}
- #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) */
+
+/*
+** 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 function invokes the xDelete method of the default VFS,
+** passing on the given filename. If zName is NULL, no default VFS is
+** found, or it has no xDelete method, SQLITE_MISUSE is returned, else
+** the result of the xDelete() call is returned.
+*/
+WASM_KEEP
+int sqlite3_wasm_vfs_unlink(const char * zName){
+ int rc = SQLITE_MISUSE /* ??? */;
+ sqlite3_vfs * const pVfs = sqlite3_vfs_find(0);
+ if( zName && pVfs && pVfs->xDelete ){
+ rc = pVfs->xDelete(pVfs, zName, 1);
+ }
+ return rc;
+}
+
+#if defined(__EMSCRIPTEN__) && defined(SQLITE_WASM_OPFS)
+#include <emscripten/wasmfs.h>
+#include <emscripten/console.h>
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings, specifically
+** only when building with Emscripten's WASMFS support.
+**
+** This function should only be called if the JS side detects the
+** existence of the Origin-Private FileSystem (OPFS) APIs in the
+** client. The first time it is called, this function instantiates a
+** WASMFS backend impl for OPFS. On success, subsequent calls are
+** no-ops.
+**
+** This function may be passed a "mount point" name, which must have a
+** leading "/" and is currently restricted to a single path component,
+** e.g. "/foo" is legal but "/foo/" and "/foo/bar" are not. If it is
+** NULL or empty, it defaults to "/persistent".
+**
+** Returns 0 on success, SQLITE_NOMEM if instantiation of the backend
+** object fails, SQLITE_IOERR if mkdir() of the zMountPoint dir in
+** the virtual FS fails. In builds compiled without SQLITE_WASM_OPFS
+** defined, SQLITE_NOTFOUND is returned without side effects.
+*/
+WASM_KEEP
+int sqlite3_wasm_init_opfs(const char *zMountPoint){
+ static backend_t pOpfs = 0;
+ if( !zMountPoint || !*zMountPoint ) zMountPoint = "/persistent";
+ if( !pOpfs ){
+ pOpfs = wasmfs_create_opfs_backend();
+ if( pOpfs ){
+ emscripten_console_log("Created WASMFS OPFS backend.");
+ }
+ }
+ /** It's not enough to instantiate the backend. We have to create a
+ mountpoint in the VFS and attach the backend to it. */
+ if( pOpfs && 0!=access(zMountPoint, F_OK) ){
+ /* mkdir() simply hangs when called from fiddle app. Cause is
+ not yet determined but the hypothesis is an init-order
+ issue. */
+ /* Note that this check and is not robust but it will
+ hypothetically suffice for the transient wasm-based virtual
+ filesystem we're currently running in. */
+ const int rc = wasmfs_create_directory(zMountPoint, 0777, pOpfs);
+ emscripten_console_logf("OPFS mkdir(%s) rc=%d", zMountPoint, rc);
+ if(rc) return SQLITE_IOERR;
+ }
+ return pOpfs ? 0 : SQLITE_NOMEM;
+}
+#else
+WASM_KEEP
+int sqlite3_wasm_init_opfs(void){
+ return SQLITE_NOTFOUND;
+}
+#endif /* __EMSCRIPTEN__ && SQLITE_WASM_OPFS */
+
+
+#undef WASM_KEEP
--- /dev/null
+<!doctype html>
+<html lang="en-us">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
+ <link rel="stylesheet" href="common/testing.css"/>
+ <title>sqlite3 WASM Testing Page Index</title>
+ </head>
+ <body>
+ <header id='titlebar'><span>sqlite3 WASM test pages</span></header>
+ <hr>
+ <div>Below is the list of test pages for the sqlite3 WASM
+ builds. All of them require that this directory have been
+ "make"d first. The intent is that <em>this</em> page be run
+ using:</div>
+ <blockquote><pre>althttpd -page index.html</pre></blockquote>
+ <div>and the individual tests be started in their own tab.</div>
+ <div>Warnings and Caveats:
+ <ul class='warning'>
+ <li>Some of these pages require that
+ the web server emit the so-called COOP and COEP headers. The
+ default build of althttpd <em>does not</em>.
+ </li>
+ <li>Whether or not WASMFS/OPFS support is enabled on any given
+ page may depend on build-time options which are <em>off by
+ default</em> because they currently (as of 2022-09-08) break
+ with Worker-based pages.
+ </li>
+ </ul>
+ </div>
+ <div>The tests...
+ <ul id='test-list'>
+ <li><a href='testing1.html'>testing1</a>: sanity tests of the core APIs and surrounding utility code.</li>
+ <li><a href='testing2.html'>testing2</a>: Worker-based test of OO API #1.</li>
+ <li><a href='testing-worker1-promiser.html'>testing-worker1-promiser</a>:
+ tests for the Promise-based wrapper of the Worker-based API.</li>
+ <li><a href='batch-runner.html'>batch-runner</a>: runs batches of SQL exported from speedtest1.</li>
+ <li><a href='speedtest1.html'>speedtest1</a>: a main-thread WASM build of speedtest1.</li>
+ <li><a href='speedtest1-worker.html'>speedtest1-worker</a>: an interactive Worker-thread variant of speedtest1.</li>
+ <li><a href='demo-oo1.html'>demo-oo1</a>: demonstration of the OO API #1.</li>
++ <li><a href='kvvfs1.html'>kvvfs1</a>: very basic demo of using the key-value vfs for storing
++ a persistent db in JS localStorage or sessionStorage.</li>
+ <!--li><a href='x.html'></a></li-->
+ </ul>
+ </div>
+ <style>
+ #test-list { font-size: 120%; }
+ </style>
+ <script>//Assign a distinct target tab name for each test page...
+ document.querySelectorAll('a').forEach(function(e){
+ e.target = e.href;
+ });
+ </script>
+ </body>
+</html>
--- /dev/null
-kvvfs.cflags += -std=c99 -fPIC
+ #!/usr/bin/make
+ #^^^^ help emacs select makefile mode
+ #
+ # This is a sub-make for building a standalone kvvfs-based
+ # sqlite3.wasm. It is intended to be "include"d from the main
+ # GNUMakefile.
+ #
+ # Notable potential TODOs:
+ #
+ # - Trim down a custom sqlite3-api.js for this build. We can elimate
+ # the jaccwabyt dependency, for example, because this build won't
+ # make use of the VFS bits. Similarly, we can eliminate or replace
+ # parts of the OO1 API, or provide a related API which manages
+ # singletons of the localStorage/sessionStorage instances.
+ #
+ ########################################################################
+ MAKEFILE.kvvfs := $(lastword $(MAKEFILE_LIST))
+
+ kvvfs.js := sqlite3-kvvfs.js
+ kvvfs.wasm := sqlite3-kvvfs.wasm
+ kvvfs.wasm.c := $(dir.api)/sqlite3-wasm.c
+
+ CLEAN_FILES += $(kvvfs.js) $(kvvfs.wasm)
+
+ ########################################################################
+ # emcc flags for .c/.o/.wasm.
+ kvvfs.flags =
+ #kvvfs.flags += -v # _very_ loud but also informative about what it's doing
+
+ ########################################################################
+ # emcc flags for .c/.o.
+ kvvfs.cflags :=
++kvvfs.cflags += -std=c99 -fPIC -g
+ kvvfs.cflags += -I. -I$(dir.top)
+ kvvfs.cflags += -DSQLITE_OS_KV=1 $(SQLITE_OPT)
+
+ ########################################################################
+ # emcc flags specific to building the final .js/.wasm file...
+ kvvfs.jsflags := -fPIC
+ kvvfs.jsflags += --no-entry
++kvvfs.jsflags += --minify 0
+ kvvfs.jsflags += -sENVIRONMENT=web
+ kvvfs.jsflags += -sMODULARIZE
+ kvvfs.jsflags += -sSTRICT_JS
+ kvvfs.jsflags += -sDYNAMIC_EXECUTION=0
+ kvvfs.jsflags += -sNO_POLYFILL
+ kvvfs.jsflags += -sEXPORTED_FUNCTIONS=@$(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api
+ kvvfs.jsflags += -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory,allocateUTF8OnStack
+ # wasmMemory ==> for -sIMPORTED_MEMORY
+ # allocateUTF8OnStack ==> kvvfs internals
+ kvvfs.jsflags += -sUSE_CLOSURE_COMPILER=0
+ kvvfs.jsflags += -sIMPORTED_MEMORY
+ #kvvfs.jsflags += -sINITIAL_MEMORY=13107200
+ #kvvfs.jsflags += -sTOTAL_STACK=4194304
+ kvvfs.jsflags += -sEXPORT_NAME=sqlite3InitModule
+ kvvfs.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr.
+ kvvfs.jsflags += --post-js=$(post-js.js)
+ #kvvfs.jsflags += -sFILESYSTEM=0 # only for experimentation. sqlite3 needs the FS API
+ # Perhaps the kvvfs build doesn't?
+ #kvvfs.jsflags += -sABORTING_MALLOC
+ kvvfs.jsflags += -sALLOW_MEMORY_GROWTH
+ kvvfs.jsflags += -sALLOW_TABLE_GROWTH
+ kvvfs.jsflags += -Wno-limited-postlink-optimizations
+ # ^^^^^ it likes to warn when we have "limited optimizations" via the -g3 flag.
+ kvvfs.jsflags += -sERROR_ON_UNDEFINED_SYMBOLS=0
+ kvvfs.jsflags += -sLLD_REPORT_UNDEFINED
+ #kvvfs.jsflags += --import-undefined
+ kvvfs.jsflags += -sMEMORY64=0
+ ifneq (0,$(enable_bigint))
+ kvvfs.jsflags += -sWASM_BIGINT
+ endif
+
+ $(kvvfs.js): $(MAKEFILE) $(MAKEFILE.kvvfs) $(kvvfs.wasm.c) \
+ EXPORTED_FUNCTIONS.api \
+ $(post-js.js)
+ $(emcc.bin) -o $@ $(emcc_opt) $(emcc.flags) $(kvvfs.cflags) $(kvvfs.jsflags) $(kvvfs.wasm.c)
+ chmod -x $(kvvfs.wasm)
+ ifneq (,$(wasm-strip))
+ $(wasm-strip) $(kvvfs.wasm)
+ endif
+ @ls -la $@ $(kvvfs.wasm)
+
+ kvvfs: $(kvvfs.js)
++all: kvvfs
--- /dev/null
- <div>Everything on this page happens in the dev console.</div>
+ <!doctype html>
+ <html lang="en-us">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
+ <link rel="stylesheet" href="common/emscripten.css"/>
+ <link rel="stylesheet" href="common/testing.css"/>
+ <title>sqlite3-kvvfs.js tests</title>
+ </head>
+ <body>
+ <header id='titlebar'><span>sqlite3-kvvfs.js tests</span></header>
+ <!-- emscripten bits -->
+ <figure id="module-spinner">
+ <div class="spinner"></div>
+ <div class='center'><strong>Initializing app...</strong></div>
+ <div class='center'>
+ On a slow internet connection this may take a moment. If this
+ message displays for "a long time", intialization may have
+ failed and the JavaScript console may contain clues as to why.
+ </div>
+ </figure>
+ <div class="emscripten" id="module-status">Downloading...</div>
+ <div class="emscripten">
+ <progress value="0" max="100" id="module-progress" hidden='1'></progress>
+ </div><!-- /emscripten bits -->
++ <div>Everything on this page happens in the dev console. TODOs for this demo include,
++ but are not necessarily limited to:
++
++ <ul>
++ <li>UI controls to switch between localStorage and sessionStorage</li>
++ <li>Button to clear storage.</li>
++ <li>Button to dump the current db contents.</li>
++ <!--li></li-->
++ </ul>
++ </div>
+ <hr>
+ <div id='test-output'></div>
+ <script src="sqlite3-kvvfs.js"></script>
+ <script src="common/SqliteTestUtil.js"></script>
+ <script src="kvvfs1.js"></script>
+ </body>
+ </html>
--- /dev/null
- self.S = sqlite3;
- T.assert(0 === capi.sqlite3_vfs_find(null));
- S.capi.sqlite3_initialize();
- T.assert( Number.isFinite( capi.sqlite3_vfs_find(null) ) );
- const stores = {
- local: localStorage,
- session: sessionStorage
- };
- const cleanupStore = function(n){
- const s = stores[n];
- const isKv = (key)=>key.startsWith('kvvfs-'+n);
- let i, k, toRemove = [];
- for( i = 0; (k = s.key(i)); ++i) {
- if(isKv(k)) toRemove.push(k);
- }
- toRemove.forEach((k)=>s.removeItem(k));
- };
- const dbStorage = 1 ? 'session' : 'local';
- const db = new oo.DB(dbStorage);
+ /*
+ 2022-09-12
+
+ 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.
+
+ ***********************************************************************
+
+ A basic test script for sqlite3-kvvfs.wasm. This file must be run in
+ main JS thread and sqlite3-kvvfs.js must have been loaded before it.
+ */
+ 'use strict';
+ (function(){
+ const T = self.SqliteTestUtil;
+ const toss = function(...args){throw new Error(args.join(' '))};
+ const debug = console.debug.bind(console);
+ const eOutput = document.querySelector('#test-output');
+ const log = console.log.bind(console)
+ const logHtml = function(...args){
+ log.apply(this, args);
+ const ln = document.createElement('div');
+ ln.append(document.createTextNode(args.join(' ')));
+ eOutput.append(ln);
+ };
+
+ const runTests = function(Module){
+ //log("Module",Module);
+ const sqlite3 = Module.sqlite3,
+ capi = sqlite3.capi,
+ oo = sqlite3.oo1,
+ wasm = capi.wasm;
+ log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
+ log("Build options:",wasm.compileOptionUsed());
- log("New db. Populating..");
++ T.assert( 0 !== capi.sqlite3_vfs_find(null) );
++
++ const dbStorage = 1 ? ':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 );
++ log("Storage backend:",db.filename /* note that the name was internally translated */);
+ try {
+ db.exec("create table if not exists t(a)");
+ if(undefined===db.selectValue("select a from t limit 1")){
- const n = db.filename;
++ log("New db. Populating. This DB will persist across page reloads.");
+ db.exec("insert into t(a) values(1),(2),(3)");
+ }else{
+ log("Found existing table data:");
+ db.exec({
+ sql: "select * from t order by a",
+ rowMode: 0,
+ callback: function(v){log(v)}
+ });
+ }
+ }finally{
- //cleanupStore(n);
+ db.close();
-
- log("Init done. Proceed from the dev console.");
+ }
++ log("End of demo.");
+ };
+
+ sqlite3InitModule(self.sqlite3TestModule).then(function(theModule){
+ console.warn("Installing Emscripten module as global EM for dev console access.");
+ self.EM = theModule;
+ runTests(theModule);
+ });
+ })();
- C Minor\scleanups\sand\sdocumentation\sin\sthe\swasm\spieces.
- D 2022-09-11T16:59:40.674
-C Fix\suninitialized\svariable\sin\srollback-journal\sprocessing\sin\sos_kv.c
-D 2022-09-12T15:59:35.676
++C Merge\skv-vfs\sbranch\sinto\sfiddle-opfs\sbranch\sto\sadd\skvvfs-based\swasm\sbuild\sand\sdemo.
++D 2022-09-12T16:09:50.625
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
- F Makefile.in 918d18842ba16dac5ebd5f7913aa8fb4f39879c881deced83362ed44a14ab4c6
-F Makefile.in ee179f405fd5f8845473f888517c4ada46099306c33ae1f27dd1aef53fe8e867
++F Makefile.in 50e421194df031f669667fdb238c54959ecbea5a0b97dd3ed776cffbeea926d5
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
F Makefile.msc d547a2fdba38a1c6cd1954977d0b0cc017f5f8fbfbc65287bf8d335808938016
F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
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 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3
+F ext/wasm/EXPORTED_FUNCTIONS.fiddle db7a4602f043cf4a5e4135be3609a487f9f1c83f05778bfbdf93766be4541b96
F ext/wasm/EXPORTED_RUNTIME_METHODS.fiddle a004bd5eeeda6d3b28d16779b7f1a80305bfe009dfc7f0721b042967f0d39d02
- F ext/wasm/GNUmakefile 18b80a063c684b0ca44b96221fc2c23647d0196f757c83c7a572b853562d8ac9
-F ext/wasm/GNUmakefile 12a672ab9125dc860457c2853f7651b98517e424d7a0e9714c89b28c5ff73800
-F ext/wasm/README.md 4b00ae7c7d93c4591251245f0996a319e2651361013c98d2efb0b026771b7331
-F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api c5eaceabb9e759aaae7d3101a4a3e542f96ab2c99d89a80ce20ec18c23115f33
++F ext/wasm/GNUmakefile 6e642a0dc7ac43d9287e8f31c80ead469ddc7475a6d4ab7ac3b1feefcd4f7279
+F ext/wasm/README.md e1ee1e7c321c6a250bf78a84ca6f5882890a237a450ba5a0649c7a8399194c52
+F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 1dfd067b3cbd9a49cb204097367cf2f8fe71b5a3b245d9d82a24779fd4ac2394
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
-F ext/wasm/api/README.md b6d0fb64bfdf7bf9ce6938ea4104228f6f5bbef600f5d910b2f8c8694195988c
+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 1a12e64060c2cb0defd34656a76a9b1d7ed58459c290249bb31567c806fd44de
-F ext/wasm/api/sqlite3-api-cleanup.js 149fd63a0400cd1d69548887ffde2ed89c13283384a63c2e9fcfc695e38a9e11
-F ext/wasm/api/sqlite3-api-glue.js 82c09f49c69984009ba5af2b628e67cc26c5dd203d383cd3091d40dab4e6514b
-F ext/wasm/api/sqlite3-api-oo1.js e9612cb704c0563c5d71ed2a8dccd95bf6394fa4de3115d1b978dc269c49ab02
-F ext/wasm/api/sqlite3-api-opfs.js c93cdd14f81a26b3a64990515ee05c7e29827fbc8fba4e4c2fef3a37a984db89
-F ext/wasm/api/sqlite3-api-prologue.js 0fb0703d2d8ac89fa2d4dd8f9726b0ea226b8708ac34e5b482df046e147de0eb
-F ext/wasm/api/sqlite3-api-worker.js 1124f404ecdf3c14d9f829425cef778cd683911a9883f0809a463c3c7773c9fd
++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 b06a1ac982c7d433396b8304550ce1493a63671a3bf653c3b5646a9075e0ca41
++F ext/wasm/api/sqlite3-api-oo1.js a9d8892be246548a9978ace506d108954aa13eb5ce25332975c8377953804ff3
+F ext/wasm/api/sqlite3-api-opfs.js 011799db398157cbd254264b6ebae00d7234b93d0e9e810345f213a5774993c0
- F ext/wasm/api/sqlite3-api-prologue.js afd753be49ecba4b5bfc3e54929826a01eaf96ac2d8cec575d683e3c5bcc8464
++F ext/wasm/api/sqlite3-api-prologue.js 9e37ce4dfd74926d0df80dd7e72e33085db4bcee48e2c21236039be416a7dff2
+F ext/wasm/api/sqlite3-api-worker1.js d33062afa045fd4be01ba4abc266801807472558b862b30056211b00c9c347b4
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
- F ext/wasm/api/sqlite3-wasm.c ab8abf26708238a17840cad224bec1511efde6e5b182068f3abf079ba1d83ef6
-F ext/wasm/api/sqlite3-wasm.c 8585793ca8311c7a0618b7e00ed2b3729799c20664a51f196258576e3d475c9e
-F ext/wasm/api/sqlite3-worker.js 1325ca8d40129a82531902a3a077b795db2eeaee81746e5a0c811a04b415fa7f
-F ext/wasm/common/SqliteTestUtil.js e41a1406f18da9224523fad0c48885caf995b56956a5b9852909c0989e687e90
++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/common/SqliteTestUtil.js 529161a624265ba84271a52db58da022649832fa1c71309fb1e02cc037327a2b
F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
-F ext/wasm/common/testing.css 572cf1ffae0b6eb7ca63684d3392bf350217a07b90e7a896e4fa850700c989b0
-F ext/wasm/common/whwasmutil.js 3d9deda1be718e2b10e2b6b474ba6ba857d905be314201ae5b3df5eef79f66aa
+F ext/wasm/common/testing.css 3a5143699c2b73a85b962271e1a9b3241b30d90e30d895e4f55665e648572962
+F ext/wasm/common/whwasmutil.js f7282ef36c9625330d4e6e82d1beec6678cd101e95e7108cd85db587a788c145
+F ext/wasm/demo-oo1.html 75646855b38405d82781246fd08c852a2b3bee05dd9f0fe10ab655a8cffb79aa
+F ext/wasm/demo-oo1.js 477f230cce3455e701431436d892d8c6bfea2bdf1ddcdd32a273e2f4bb339801
F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
-F ext/wasm/fiddle/fiddle-worker.js 88bc2193a6cb6a3f04d8911bed50a4401fe6f277de7a71ba833865ab64a1b4ae
+F ext/wasm/fiddle/fiddle-worker.js bccf46045be8824752876f3eec01c223be0616ccac184bffd0024cfe7a3262b8
F ext/wasm/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08
-F ext/wasm/fiddle/fiddle.js 812f9954cc7c4b191884ad171f36fcf2d0112d0a7ecfdf6087896833a0c079a8
-F ext/wasm/jaccwabyt/jaccwabyt.js 99b424b4d467d4544e82615b58e2fe07532a898540bf9de2a985f3c21e7082b2
+F ext/wasm/fiddle/fiddle.js 4ffcfc9a235beebaddec689a549e9e0dfad6dca5c1f0b41f03468d7e76480686
- F ext/wasm/index.html 4f635f986dbc7518280abe0ef537ba41682e35f160fac35a0745cf6c4d223b62
++F ext/wasm/index.html 5876ae0442bef5b37fec9a45ee0722798e47ef727723ada742d33554845afa6a
+F ext/wasm/jaccwabyt/jaccwabyt.js 0d7f32817456a0f3937fcfd934afeb32154ca33580ab264dab6c285e6dbbd215
F ext/wasm/jaccwabyt/jaccwabyt.md 447cc02b598f7792edaa8ae6853a7847b8178a18ed356afacbdbf312b2588106
F ext/wasm/jaccwabyt/jaccwabyt_test.c 39e4b865a33548f943e2eb9dd0dc8d619a80de05d5300668e9960fff30d0d36f
F ext/wasm/jaccwabyt/jaccwabyt_test.exports 5ff001ef975c426ffe88d7d8a6e96ec725e568d2c2307c416902059339c06f19
-F ext/wasm/kvvfs.make 7cc9cf10e744c3ba523c3eaf5c4af47028f3a5bb76db304ea8044a9b2a9d496f
-F ext/wasm/kvvfs1.html 2acb241a6110a4ec581adbf07a23d5fc2ef9c7142aa9d60856732a102abc5016
-F ext/wasm/kvvfs1.js 46afaf4faba041bf938355627bc529854295e561f49db3a240c914e75a529338
-F ext/wasm/testing1.html 0bf3ff224628c1f1e3ed22a2dc1837c6c73722ad8c0ad9c8e6fb9e6047667231
-F ext/wasm/testing1.js cba7134901a965743fa9289d82447ab71de4690b1ee5d06f6cb83e8b569d7943
-F ext/wasm/testing2.html 73e5048e666fd6fb28b6e635677a9810e1e139c599ddcf28d687c982134b92b8
-F ext/wasm/testing2.js d37433c601f88ed275712c1cfc92d3fb36c7c22e1ed8c7396fb2359e42238ebc
++F ext/wasm/kvvfs.make dba616578bf91a76370a46494dd68a09c6dff5beb6d5561e2db65a27216e9630
++F ext/wasm/kvvfs1.html b8304cd5c7e7ec32c3b15521a95c322d6efdb8d22b3c4156123545dc54e07583
++F ext/wasm/kvvfs1.js a5075f98ffecd7d32348697db991fc61342d89aa20651034d1572af61890fb8b
+F ext/wasm/scratchpad-opfs-main.html 4565cf194e66188190d35f70e82553e2e2d72b9809b73c94ab67b8cfd14d2e0c
+F ext/wasm/scratchpad-opfs-main.js 69e960e9161f6412fd0c30f355d4112f1894d6609eb431e2d16d207d1380518e
+F ext/wasm/scratchpad-opfs-worker.html 66c1d15d678f3bd306373d76b61c6c8aef988f61f4a8dd40185d452f9c6d2bf5
+F ext/wasm/scratchpad-opfs-worker.js 3ec2868c669713145c76eb5877c64a1b20741f741817b87c907a154b676283a9
+F ext/wasm/scratchpad-opfs-worker2.js 5f2237427ac537b8580b1c659ff14ad2621d1694043eaaf41ae18dbfef2e48c0
+F ext/wasm/speedtest1-worker.html 6b5fda04d0b69e8c2651689356cb0c28fd33aa1a82b03dcbc8b0d68fbd7ed57f
+F ext/wasm/speedtest1-worker.js 356b9953add4449acf199793db9b76b11ee016021918d8daffd19f08ec68d305
+F ext/wasm/speedtest1.html 8f61cbe68300acca25dd9fa74dce79b774786e2b4feeb9bcbc46e1cefbfa6262
+F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x
+F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0
+F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5
+F ext/wasm/sqlite3-worker1-promiser.js 92b8da5f38439ffec459a8215775d30fa498bc0f1ab929ff341fc3dd479660b9
+F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416458af84f340a9e
+F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893
+F ext/wasm/testing-worker1-promiser.js c62b5879339eef0b21aebd9d75bc125c86530edc17470afff18077f931cb704a
+F ext/wasm/testing1.html 528001c7e32ee567abc195aa071fd9820cc3c8ffc9c8a39a75e680db05f0c409
+F ext/wasm/testing1.js 2def7a86c52ff28b145cb86188d5c7a49d5993f9b78c50d140e1c31551220955
+F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
+F ext/wasm/testing2.js 25584bcc30f19673ce13a6f301f89f8820a59dfe044e0c4f2913941f4097fe3c
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
F src/os.c 0eb831ba3575af5277e47f4edd14fdfc90025c67eb25ce5cda634518d308d4e9
F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63
F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
- F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
- F src/os_unix.c 102f7e5c5b59c18ea3dbc929dc3be8acb3afc0e0b6ad572e032335c9c27f44f1
-F src/os_kv.c 2b4c04a470c05fe95a84d2ba3a5eb4874f0dbaa12da3a47f221ee3beec7eeda0
++F src/os_kv.c a188e92dac693b1c1b512d93b0c4dc85c1baad11e322b01121f87057996e4d11
+ F src/os_setup.h 0711dbc4678f3ac52d7fe736951b6384a0615387c4ba5135a4764e4e31f4b6a6
+ F src/os_unix.c d6322b78130d995160bb9cfb7850678ad6838b08c1d13915461b33326a406c04
F src/os_win.c e9454cb141908e8eef2102180bad353a36480612d5b736e4c2bd5777d9b25a34
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c 6176d9752eb580419e8fef4592dc417a6b00ddfd43ee22f818819bf8840ceee8
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P cdbf09fa1b0c93aeb3222a157de33a4688ae629c2b829ffff0f1f62364c5ae1c
- R 9d278bdeb3b04c59e81ef94ad61886ea
-P 250a935aeb94d3fadec0d3fe22de85de4e658e2fdb3be3aa9a8bbc8f7b7d8414
-R b0d44b8764d44e0d25567b7049186ee6
-U drh
-Z b805cc7ee3bf0477637e784d78058472
++P 4e6ce329872eb733ba2f7f7879747c52761ae97790fd8ed169a25a79854cc3d9 e49682c5eac91958f143e639c5656ca54560d14f5805d514bf4aa0c206e63844
++R f15733833cf5e73dce86b4365f218581
+U stephan
- Z 1e71f5bdda12caaeb1881e6b3d2fda00
++Z f2a01bf4c99986993a2e00b39e93be73
# Remove this line to create a well-formed Fossil manifest.
- 4e6ce329872eb733ba2f7f7879747c52761ae97790fd8ed169a25a79854cc3d9
-e49682c5eac91958f143e639c5656ca54560d14f5805d514bf4aa0c206e63844
++a7d8b26acd3c1ae344369e4d70804c0cab45272c0983cfd32d616a0a7b28acb9
--- /dev/null
- unsigned int n = 0;
+ /*
+ ** 2022-09-06
+ **
+ ** 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 contains an experimental VFS layer that operates on a
+ ** Key/Value storage engine where both keys and values must be pure
+ ** text.
+ */
+ #include <sqliteInt.h>
+ #if SQLITE_OS_KV
+
+ /*****************************************************************************
+ ** Debugging logic
+ */
+
+ /* SQLITE_KV_TRACE() is used for tracing calls to kvstorage routines. */
+ #if 0
+ #define SQLITE_KV_TRACE(X) printf X;
+ #else
+ #define SQLITE_KV_TRACE(X)
+ #endif
+
+ /* SQLITE_KV_LOG() is used for tracing calls to the VFS interface */
+ #if 0
+ #define SQLITE_KV_LOG(X) printf X;
+ #else
+ #define SQLITE_KV_LOG(X)
+ #endif
+
+
+ /*
+ ** Forward declaration of objects used by this VFS implementation
+ */
+ typedef struct KVVfsFile KVVfsFile;
+
+ /* A single open file. There are only two files represented by this
+ ** VFS - the database and the rollback journal.
+ */
+ struct KVVfsFile {
+ sqlite3_file base; /* IO methods */
+ const char *zClass; /* Storage class */
+ int isJournal; /* True if this is a journal file */
+ unsigned int nJrnl; /* Space allocated for aJrnl[] */
+ char *aJrnl; /* Journal content */
+ int szPage; /* Last known page size */
+ sqlite3_int64 szDb; /* Database file size. -1 means unknown */
+ };
+
+ /*
+ ** Methods for KVVfsFile
+ */
+ static int kvvfsClose(sqlite3_file*);
+ static int kvvfsReadDb(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+ static int kvvfsReadJrnl(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+ static int kvvfsWriteDb(sqlite3_file*,const void*,int iAmt, sqlite3_int64);
+ static int kvvfsWriteJrnl(sqlite3_file*,const void*,int iAmt, sqlite3_int64);
+ static int kvvfsTruncateDb(sqlite3_file*, sqlite3_int64 size);
+ static int kvvfsTruncateJrnl(sqlite3_file*, sqlite3_int64 size);
+ static int kvvfsSyncDb(sqlite3_file*, int flags);
+ static int kvvfsSyncJrnl(sqlite3_file*, int flags);
+ static int kvvfsFileSizeDb(sqlite3_file*, sqlite3_int64 *pSize);
+ static int kvvfsFileSizeJrnl(sqlite3_file*, sqlite3_int64 *pSize);
+ static int kvvfsLock(sqlite3_file*, int);
+ static int kvvfsUnlock(sqlite3_file*, int);
+ static int kvvfsCheckReservedLock(sqlite3_file*, int *pResOut);
+ static int kvvfsFileControl(sqlite3_file*, int op, void *pArg);
+ static int kvvfsSectorSize(sqlite3_file*);
+ static int kvvfsDeviceCharacteristics(sqlite3_file*);
+
+ /*
+ ** Methods for sqlite3_vfs
+ */
+ static int kvvfsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+ static int kvvfsDelete(sqlite3_vfs*, const char *zName, int syncDir);
+ static int kvvfsAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+ static int kvvfsFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
+ static void *kvvfsDlOpen(sqlite3_vfs*, const char *zFilename);
+ static int kvvfsRandomness(sqlite3_vfs*, int nByte, char *zOut);
+ static int kvvfsSleep(sqlite3_vfs*, int microseconds);
+ static int kvvfsCurrentTime(sqlite3_vfs*, double*);
+ static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
+
+ static sqlite3_vfs kvvfs_vfs = {
+ 1, /* iVersion */
+ sizeof(KVVfsFile), /* szOsFile */
+ 1024, /* mxPathname */
+ 0, /* pNext */
+ "kvvfs", /* zName */
+ 0, /* pAppData */
+ kvvfsOpen, /* xOpen */
+ kvvfsDelete, /* xDelete */
+ kvvfsAccess, /* xAccess */
+ kvvfsFullPathname, /* xFullPathname */
+ kvvfsDlOpen, /* xDlOpen */
+ 0, /* xDlError */
+ 0, /* xDlSym */
+ 0, /* xDlClose */
+ kvvfsRandomness, /* xRandomness */
+ kvvfsSleep, /* xSleep */
+ kvvfsCurrentTime, /* xCurrentTime */
+ 0, /* xGetLastError */
+ kvvfsCurrentTimeInt64 /* xCurrentTimeInt64 */
+ };
+
+ /* Methods for sqlite3_file objects referencing a database file
+ */
+ static sqlite3_io_methods kvvfs_db_io_methods = {
+ 1, /* iVersion */
+ kvvfsClose, /* xClose */
+ kvvfsReadDb, /* xRead */
+ kvvfsWriteDb, /* xWrite */
+ kvvfsTruncateDb, /* xTruncate */
+ kvvfsSyncDb, /* xSync */
+ kvvfsFileSizeDb, /* xFileSize */
+ kvvfsLock, /* xLock */
+ kvvfsUnlock, /* xUnlock */
+ kvvfsCheckReservedLock, /* xCheckReservedLock */
+ kvvfsFileControl, /* xFileControl */
+ kvvfsSectorSize, /* xSectorSize */
+ kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */
+ 0, /* xShmMap */
+ 0, /* xShmLock */
+ 0, /* xShmBarrier */
+ 0, /* xShmUnmap */
+ 0, /* xFetch */
+ 0 /* xUnfetch */
+ };
+
+ /* Methods for sqlite3_file objects referencing a rollback journal
+ */
+ static sqlite3_io_methods kvvfs_jrnl_io_methods = {
+ 1, /* iVersion */
+ kvvfsClose, /* xClose */
+ kvvfsReadJrnl, /* xRead */
+ kvvfsWriteJrnl, /* xWrite */
+ kvvfsTruncateJrnl, /* xTruncate */
+ kvvfsSyncJrnl, /* xSync */
+ kvvfsFileSizeJrnl, /* xFileSize */
+ kvvfsLock, /* xLock */
+ kvvfsUnlock, /* xUnlock */
+ kvvfsCheckReservedLock, /* xCheckReservedLock */
+ kvvfsFileControl, /* xFileControl */
+ kvvfsSectorSize, /* xSectorSize */
+ kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */
+ 0, /* xShmMap */
+ 0, /* xShmLock */
+ 0, /* xShmBarrier */
+ 0, /* xShmUnmap */
+ 0, /* xFetch */
+ 0 /* xUnfetch */
+ };
+
+ /****** Storage subsystem **************************************************/
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
+
+ /* Forward declarations for the low-level storage engine
+ */
+ #define KVSTORAGE_KEY_SZ 32
+
+ /* Expand the key name with an appropriate prefix and put the result
+ ** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least
+ ** KVSTORAGE_KEY_SZ bytes.
+ */
+ static void kvstorageMakeKey(
+ const char *zClass,
+ const char *zKeyIn,
+ char *zKeyOut
+ ){
+ sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn);
+ }
+
+ #ifdef __EMSCRIPTEN__
+ /* Provide Emscripten-based impls of kvstorageWrite/Read/Delete()... */
+ #include <emscripten.h>
+ #include <emscripten/console.h>
+
+ /*
+ ** WASM_KEEP is identical to EMSCRIPTEN_KEEPALIVE but is not
+ ** Emscripten-specific. It explicitly includes marked functions for
+ ** export into the target wasm file without requiring explicit listing
+ ** of those functions in Emscripten's -sEXPORTED_FUNCTIONS=... list
+ ** (or equivalent in other build platforms). Any function with neither
+ ** this attribute nor which is listed as an explicit export will not
+ ** be exported from the wasm file (but may still be used internally
+ ** within the wasm file).
+ **
+ ** The functions in this file (sqlite3-wasm.c) which require exporting
+ ** are marked with this flag. They may also be added to any explicit
+ ** build-time export list but need not be. All of these APIs are
+ ** intended for use only within the project's own JS/WASM code, and
+ ** not by client code, so an argument can be made for reducing their
+ ** visibility by not including them in any build-time export lists.
+ **
+ ** 2022-09-11: it's not yet _proven_ that this approach works in
+ ** non-Emscripten builds. If not, such builds will need to export
+ ** those using the --export=... wasm-ld flag (or equivalent). As of
+ ** this writing we are tied to Emscripten for various reasons
+ ** and cannot test the library with other build environments.
+ */
+ #define WASM_KEEP __attribute__((used,visibility("default")))
+ /*
+ ** 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( 0!=zKeyOut ) kvstorageMakeKey(zClass, zKeyIn, zKeyOut);
+ return KVSTORAGE_KEY_SZ;
+ }
+ /*
+ ** 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 (if we don't reference them from exported C functions
+ ** then they get stripped away at build time). It is not part of the
+ ** public API and its signature and semantics may change at any time.
+ ** It's not even part of the private API, for that matter - it's part
+ ** of the Emscripten C/JS/WASM glue.
+ */
+ WASM_KEEP
+ int sqlite3__wasm_emjs_kvvfs(int whichOp){
+ int rc = 0;
+ const char * zClass =
+ "sezzion" /*don't collide with "session" records!*/;
+ 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);
+ break;
+ default: break;
+ }
+ return rc;
+ }
+
+ #undef WASM_KEEP
+ #else /* end ifdef __EMSCRIPTEN__ */
+ /* Forward declarations for the low-level storage engine
+ */
+ static int kvstorageWrite(const char*, const char *zKey, const char *zData);
+ static int kvstorageDelete(const char*, const char *zKey);
+ static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf);
+
+ /* Write content into a key. zClass is the particular namespace of the
+ ** underlying key/value store to use - either "local" or "session".
+ **
+ ** Both zKey and zData are zero-terminated pure text strings.
+ **
+ ** Return the number of errors.
+ */
+ static int kvstorageWrite(
+ const char *zClass,
+ const char *zKey,
+ const char *zData
+ ){
+ FILE *fd;
+ char zXKey[KVSTORAGE_KEY_SZ];
+ kvstorageMakeKey(zClass, zKey, zXKey);
+ fd = fopen(zXKey, "wb");
+ if( fd ){
+ SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey,
+ (int)strlen(zData), zData,
+ strlen(zData)>50 ? "..." : ""));
+ fputs(zData, fd);
+ fclose(fd);
+ return 0;
+ }else{
+ return 1;
+ }
+ }
+
+ /* Delete a key (with its corresponding data) from the key/value
+ ** namespace given by zClass. If the key does not previously exist,
+ ** this routine is a no-op.
+ */
+ static int kvstorageDelete(const char *zClass, const char *zKey){
+ char zXKey[KVSTORAGE_KEY_SZ];
+ kvstorageMakeKey(zClass, zKey, zXKey);
+ unlink(zXKey);
+ SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey));
+ return 0;
+ }
+
+ /* Read the value associated with a zKey from the key/value namespace given
+ ** by zClass and put the text data associated with that key in the first
+ ** nBuf bytes of zBuf[]. The value might be truncated if zBuf is not large
+ ** enough to hold it all. The value put into zBuf must always be zero
+ ** terminated, even if it gets truncated because nBuf is not large enough.
+ **
+ ** Return the total number of bytes in the data, without truncation, and
+ ** not counting the final zero terminator. Return -1 if the key does
+ ** not exist.
+ **
+ ** If nBuf<=0 then this routine simply returns the size of the data without
+ ** actually reading it.
+ */
+ static int kvstorageRead(
+ const char *zClass,
+ const char *zKey,
+ char *zBuf,
+ int nBuf
+ ){
+ FILE *fd;
+ struct stat buf;
+ char zXKey[KVSTORAGE_KEY_SZ];
+ kvstorageMakeKey(zClass, zKey, zXKey);
+ if( access(zXKey, R_OK)!=0
+ || stat(zXKey, &buf)!=0
+ || !S_ISREG(buf.st_mode)
+ ){
+ SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey));
+ return -1;
+ }
+ if( nBuf<=0 ){
+ return (int)buf.st_size;
+ }else if( nBuf==1 ){
+ zBuf[0] = 0;
+ SQLITE_KV_TRACE(("KVVFS-READ %-15s (%d)\n", zXKey,
+ (int)buf.st_size));
+ return (int)buf.st_size;
+ }
+ if( nBuf > buf.st_size + 1 ){
+ nBuf = buf.st_size + 1;
+ }
+ fd = fopen(zXKey, "rb");
+ if( fd==0 ){
+ SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey));
+ return -1;
+ }else{
+ sqlite3_int64 n = fread(zBuf, 1, nBuf-1, fd);
+ fclose(fd);
+ zBuf[n] = 0;
+ SQLITE_KV_TRACE(("KVVFS-READ %-15s (%lld) %.50s%s\n", zXKey,
+ n, zBuf, n>50 ? "..." : ""));
+ return (int)n;
+ }
+ }
+ #endif /* ifdef __EMSCRIPTEN__ */
+
+ /****** Utility subroutines ************************************************/
+
+ /*
+ ** Encode binary into the text encoded used to persist on disk.
+ ** The output text is stored in aOut[], which must be at least
+ ** nData+1 bytes in length.
+ **
+ ** Return the actual length of the encoded text, not counting the
+ ** zero terminator at the end.
+ **
+ ** Encoding format
+ ** ---------------
+ **
+ ** * Non-zero bytes are encoded as upper-case hexadecimal
+ **
+ ** * A sequence of one or more zero-bytes that are not at the
+ ** beginning of the buffer are encoded as a little-endian
+ ** base-26 number using a..z. "a" means 0. "b" means 1,
+ ** "z" means 25. "ab" means 26. "ac" means 52. And so forth.
+ **
+ ** * Because there is no overlap between the encoding characters
+ ** of hexadecimal and base-26 numbers, it is always clear where
+ ** one stops and the next begins.
+ */
+ static int kvvfsEncode(const char *aData, int nData, char *aOut){
+ int i, j;
+ const unsigned char *a = (const unsigned char*)aData;
+ for(i=j=0; i<nData; i++){
+ unsigned char c = a[i];
+ if( c!=0 ){
+ aOut[j++] = "0123456789ABCDEF"[c>>4];
+ aOut[j++] = "0123456789ABCDEF"[c&0xf];
+ }else{
+ /* A sequence of 1 or more zeros is stored as a little-endian
+ ** base-26 number using a..z as the digits. So one zero is "b".
+ ** Two zeros is "c". 25 zeros is "z", 26 zeros is "ab", 27 is "bb",
+ ** and so forth.
+ */
+ int k;
+ for(k=1; i+k<nData && a[i+k]==0; k++){}
+ i += k-1;
+ while( k>0 ){
+ aOut[j++] = 'a'+(k%26);
+ k /= 26;
+ }
+ }
+ }
+ aOut[j] = 0;
+ return j;
+ }
+
+ /* Convert hex to binary */
+ static char kvvfsHexToBinary(char c){
+ if( c>='0' && c<='9' ) return c - '0';
+ if( c>='A' && c<='F' ) return c - 'A' + 10;
+ return 0;
+ }
+
+ /*
+ ** Decode the text encoding back to binary. The binary content is
+ ** written into pOut, which must be at least nOut bytes in length.
+ **
+ ** The return value is the number of bytes actually written into aOut[].
+ */
+ static int kvvfsDecode(const char *aIn, char *aOut, int nOut){
+ int i, j;
+ int c;
+ i = 0;
+ j = 0;
+ while( (c = aIn[i])!=0 ){
+ if( c>='a' ){
+ int n = 0;
+ int mult = 1;
+ while( c>='a' && c<='z' ){
+ n += (c - 'a')*mult;
+ mult *= 26;
+ c = aIn[++i];
+ }
+ if( j+n>nOut ) return -1;
+ while( n-->0 ){
+ aOut[j++] = 0;
+ }
+ }else{
+ if( j>nOut ) return -1;
+ aOut[j] = kvvfsHexToBinary(aIn[i++])<<4;
+ aOut[j++] += kvvfsHexToBinary(aIn[i++]);
+ }
+ }
+ return j;
+ }
+
+ /*
+ ** Decode a complete journal file. Allocate space in pFile->aJrnl
+ ** and store the decoding there. Or leave pFile->aJrnl set to NULL
+ ** if an error is encountered.
+ **
+ ** The first few characters of the text encoding will be a little-endian
+ ** base-26 number (digits a..z) that is the total number of bytes
+ ** in the decoded journal file image. This base-26 number is followed
+ ** by a single space, then the encoding of the journal. The space
+ ** separator is required to act as a terminator for the base-26 number.
+ */
+ static void kvvfsDecodeJournal(
+ KVVfsFile *pFile, /* Store decoding in pFile->aJrnl */
+ const char *zTxt, /* Text encoding. Zero-terminated */
+ int nTxt /* Bytes in zTxt, excluding zero terminator */
+ ){
++ unsigned int n;
+ int c, i, mult;
+ i = 0;
+ mult = 1;
+ while( (c = zTxt[i++])>='a' && c<='z' ){
+ n += (zTxt[i] - 'a')*mult;
+ mult *= 26;
+ }
+ sqlite3_free(pFile->aJrnl);
+ pFile->aJrnl = sqlite3_malloc64( n );
+ if( pFile->aJrnl==0 ){
+ pFile->nJrnl = 0;
+ return;
+ }
+ pFile->nJrnl = n;
+ n = kvvfsDecode(zTxt+i, pFile->aJrnl, pFile->nJrnl);
+ if( n<pFile->nJrnl ){
+ sqlite3_free(pFile->aJrnl);
+ pFile->aJrnl = 0;
+ pFile->nJrnl = 0;
+ }
+ }
+
+ /*
+ ** Read or write the "sz" element, containing the database file size.
+ */
+ static sqlite3_int64 kvvfsReadFileSize(KVVfsFile *pFile){
+ char zData[50];
+ zData[0] = 0;
+ kvstorageRead(pFile->zClass, "sz", zData, sizeof(zData)-1);
+ return strtoll(zData, 0, 0);
+ }
+ static void kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){
+ char zData[50];
+ sqlite3_snprintf(sizeof(zData), zData, "%lld", sz);
+ kvstorageWrite(pFile->zClass, "sz", zData);
+ }
+
+ /****** sqlite3_io_methods methods ******************************************/
+
+ /*
+ ** Close an kvvfs-file.
+ */
+ static int kvvfsClose(sqlite3_file *pProtoFile){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+
+ SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass,
+ pFile->isJournal ? "journal" : "db"));
+ sqlite3_free(pFile->aJrnl);
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Read from the -journal file.
+ */
+ static int kvvfsReadJrnl(
+ sqlite3_file *pProtoFile,
+ void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+ ){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ assert( pFile->isJournal );
+ SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
+ if( pFile->aJrnl==0 ){
+ int szTxt = kvstorageRead(pFile->zClass, "jrnl", 0, 0);
+ char *aTxt;
+ if( szTxt<=4 ){
+ return SQLITE_IOERR;
+ }
+ aTxt = sqlite3_malloc64( szTxt+1 );
+ if( aTxt==0 ) return SQLITE_NOMEM;
+ kvstorageRead(pFile->zClass, "jrnl", aTxt, szTxt+1);
+ kvvfsDecodeJournal(pFile, aTxt, szTxt);
+ sqlite3_free(aTxt);
+ if( pFile->aJrnl==0 ) return SQLITE_IOERR;
+ }
+ if( iOfst+iAmt>pFile->nJrnl ){
+ return SQLITE_IOERR_SHORT_READ;
+ }
+ memcpy(zBuf, pFile->aJrnl+iOfst, iAmt);
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Read from the database file.
+ */
+ static int kvvfsReadDb(
+ sqlite3_file *pProtoFile,
+ void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+ ){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ unsigned int pgno;
+ int got, n;
+ char zKey[30];
+ char aData[131073];
+ assert( iOfst>=0 );
+ assert( iAmt>=0 );
+ SQLITE_KV_LOG(("xRead('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
+ if( iOfst+iAmt>=512 ){
+ if( (iOfst % iAmt)!=0 ){
+ return SQLITE_IOERR_READ;
+ }
+ if( (iAmt & (iAmt-1))!=0 || iAmt<512 || iAmt>65536 ){
+ return SQLITE_IOERR_READ;
+ }
+ pFile->szPage = iAmt;
+ pgno = 1 + iOfst/iAmt;
+ }else{
+ pgno = 1;
+ }
+ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno);
+ got = kvstorageRead(pFile->zClass, zKey, aData, sizeof(aData)-1);
+ if( got<0 ){
+ n = 0;
+ }else{
+ aData[got] = 0;
+ if( iOfst+iAmt<512 ){
+ n = kvvfsDecode(aData, &aData[1000], 1000);
+ if( n>=iOfst+iAmt ){
+ memcpy(zBuf, &aData[1000+iOfst], iAmt);
+ n = iAmt;
+ }else{
+ n = 0;
+ }
+ }else{
+ n = kvvfsDecode(aData, zBuf, iAmt);
+ }
+ }
+ if( n<iAmt ){
+ memset(zBuf+n, 0, iAmt-n);
+ return SQLITE_IOERR_SHORT_READ;
+ }
+ return SQLITE_OK;
+ }
+
+
+ /*
+ ** Write into the -journal file.
+ */
+ static int kvvfsWriteJrnl(
+ sqlite3_file *pProtoFile,
+ const void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+ ){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ sqlite3_int64 iEnd = iOfst+iAmt;
+ SQLITE_KV_LOG(("xWrite('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
+ if( iEnd>=0x10000000 ) return SQLITE_FULL;
+ if( pFile->aJrnl==0 || pFile->nJrnl<iEnd ){
+ char *aNew = sqlite3_realloc(pFile->aJrnl, iEnd);
+ if( aNew==0 ){
+ return SQLITE_IOERR_NOMEM;
+ }
+ pFile->aJrnl = aNew;
+ if( pFile->nJrnl<iOfst ){
+ memset(pFile->aJrnl+pFile->nJrnl, 0, iOfst-pFile->nJrnl);
+ }
+ pFile->nJrnl = iEnd;
+ }
+ memcpy(pFile->aJrnl+iOfst, zBuf, iAmt);
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Write into the database file.
+ */
+ static int kvvfsWriteDb(
+ sqlite3_file *pProtoFile,
+ const void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+ ){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ unsigned int pgno;
+ char zKey[30];
+ char aData[131073];
+ SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
+ assert( iAmt>=512 && iAmt<=65536 );
+ assert( (iAmt & (iAmt-1))==0 );
+ pgno = 1 + iOfst/iAmt;
+ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno);
+ kvvfsEncode(zBuf, iAmt, aData);
+ kvstorageWrite(pFile->zClass, zKey, aData);
+ if( iOfst+iAmt > pFile->szDb ){
+ pFile->szDb = iOfst + iAmt;
+ }
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Truncate an kvvfs-file.
+ */
+ static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size));
+ assert( size==0 );
+ kvstorageDelete(pFile->zClass, "jrnl");
+ sqlite3_free(pFile->aJrnl);
+ pFile->aJrnl = 0;
+ pFile->nJrnl = 0;
+ return SQLITE_OK;
+ }
+ static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ if( pFile->szDb>size
+ && pFile->szPage>0
+ && (size % pFile->szPage)==0
+ ){
+ char zKey[50];
+ unsigned int pgno, pgnoMax;
+ SQLITE_KV_LOG(("xTruncate('%s-db',%lld)\n", pFile->zClass, size));
+ pgno = 1 + size/pFile->szPage;
+ pgnoMax = 2 + pFile->szDb/pFile->szPage;
+ while( pgno<=pgnoMax ){
+ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno);
+ kvstorageDelete(pFile->zClass, zKey);
+ pgno++;
+ }
+ pFile->szDb = size;
+ kvvfsWriteFileSize(pFile, size);
+ return SQLITE_OK;
+ }
+ return SQLITE_IOERR;
+ }
+
+ /*
+ ** Sync an kvvfs-file.
+ */
+ static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){
+ int i, n;
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ char *zOut;
+ SQLITE_KV_LOG(("xSync('%s-journal')\n", pFile->zClass));
+ if( pFile->nJrnl<=0 ){
+ return kvvfsTruncateJrnl(pProtoFile, 0);
+ }
+ zOut = sqlite3_malloc64( pFile->nJrnl*2 + 50 );
+ if( zOut==0 ){
+ return SQLITE_IOERR_NOMEM;
+ }
+ n = pFile->nJrnl;
+ i = 0;
+ do{
+ zOut[i++] = 'a' + (n%26);
+ n /= 26;
+ }while( n>0 );
+ zOut[i++] = ' ';
+ kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]);
+ kvstorageWrite(pFile->zClass, "jrnl", zOut);
+ sqlite3_free(zOut);
+ return SQLITE_OK;
+ }
+ static int kvvfsSyncDb(sqlite3_file *pProtoFile, int flags){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ SQLITE_KV_LOG(("xSync('%s-db')\n", pFile->zClass));
+ if( pFile->szDb>0 ){
+ kvvfsWriteFileSize(pFile, pFile->szDb);
+ }
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Return the current file-size of an kvvfs-file.
+ */
+ static int kvvfsFileSizeJrnl(sqlite3_file *pProtoFile, sqlite_int64 *pSize){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ SQLITE_KV_LOG(("xFileSize('%s-journal')\n", pFile->zClass));
+ *pSize = pFile->nJrnl;
+ return SQLITE_OK;
+ }
+ static int kvvfsFileSizeDb(sqlite3_file *pProtoFile, sqlite_int64 *pSize){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ SQLITE_KV_LOG(("xFileSize('%s-db')\n", pFile->zClass));
+ if( pFile->szDb>=0 ){
+ *pSize = pFile->szDb;
+ }else{
+ *pSize = kvvfsReadFileSize(pFile);
+ }
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Lock an kvvfs-file.
+ */
+ static int kvvfsLock(sqlite3_file *pProtoFile, int eLock){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ assert( !pFile->isJournal );
+ SQLITE_KV_LOG(("xLock(%s,%d)\n", pFile->zClass, eLock));
+
+ if( eLock!=SQLITE_LOCK_NONE ){
+ pFile->szDb = kvvfsReadFileSize(pFile);
+ }
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Unlock an kvvfs-file.
+ */
+ static int kvvfsUnlock(sqlite3_file *pProtoFile, int eLock){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ assert( !pFile->isJournal );
+ SQLITE_KV_LOG(("xUnlock(%s,%d)\n", pFile->zClass, eLock));
+ if( eLock==SQLITE_LOCK_NONE ){
+ pFile->szDb = -1;
+ }
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Check if another file-handle holds a RESERVED lock on an kvvfs-file.
+ */
+ static int kvvfsCheckReservedLock(sqlite3_file *pProtoFile, int *pResOut){
+ SQLITE_KV_LOG(("xCheckReservedLock\n"));
+ *pResOut = 0;
+ return SQLITE_OK;
+ }
+
+ /*
+ ** File control method. For custom operations on an kvvfs-file.
+ */
+ static int kvvfsFileControl(sqlite3_file *pProtoFile, int op, void *pArg){
+ return SQLITE_NOTFOUND;
+ }
+
+ /*
+ ** Return the sector-size in bytes for an kvvfs-file.
+ */
+ static int kvvfsSectorSize(sqlite3_file *pFile){
+ return 512;
+ }
+
+ /*
+ ** Return the device characteristic flags supported by an kvvfs-file.
+ */
+ static int kvvfsDeviceCharacteristics(sqlite3_file *pProtoFile){
+ return 0;
+ }
+
+ /****** sqlite3_vfs methods *************************************************/
+
+ /*
+ ** Open an kvvfs file handle.
+ */
+ static int kvvfsOpen(
+ sqlite3_vfs *pProtoVfs,
+ const char *zName,
+ sqlite3_file *pProtoFile,
+ int flags,
+ int *pOutFlags
+ ){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName));
+ if( strcmp(zName, "local")==0
+ || strcmp(zName, "session")==0
+ ){
+ pFile->isJournal = 0;
+ pFile->base.pMethods = &kvvfs_db_io_methods;
+ }else
+ if( strcmp(zName, "local-journal")==0
+ || strcmp(zName, "session-journal")==0
+ ){
+ pFile->isJournal = 1;
+ pFile->base.pMethods = &kvvfs_jrnl_io_methods;
+ }else{
+ return SQLITE_CANTOPEN;
+ }
+ if( zName[0]=='s' ){
+ pFile->zClass = "session";
+ }else{
+ pFile->zClass = "local";
+ }
+ pFile->aJrnl = 0;
+ pFile->nJrnl = 0;
+ pFile->szPage = -1;
+ pFile->szDb = -1;
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Delete the file located at zPath. If the dirSync argument is true,
+ ** ensure the file-system modifications are synced to disk before
+ ** returning.
+ */
+ static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+ if( strcmp(zPath, "local-journal")==0 ){
+ kvstorageDelete("local", "jrnl");
+ }else
+ if( strcmp(zPath, "session-journal")==0 ){
+ kvstorageDelete("session", "jrnl");
+ }
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Test for access permissions. Return true if the requested permission
+ ** is available, or false otherwise.
+ */
+ static int kvvfsAccess(
+ sqlite3_vfs *pProtoVfs,
+ const char *zPath,
+ int flags,
+ int *pResOut
+ ){
+ SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath));
+ if( strcmp(zPath, "local-journal")==0 ){
+ *pResOut = kvstorageRead("local", "jrnl", 0, 0)>0;
+ }else
+ if( strcmp(zPath, "session-journal")==0 ){
+ *pResOut = kvstorageRead("session", "jrnl", 0, 0)>0;
+ }else
+ if( strcmp(zPath, "local")==0 ){
+ *pResOut = kvstorageRead("local", "sz", 0, 0)>0;
+ }else
+ if( strcmp(zPath, "session")==0 ){
+ *pResOut = kvstorageRead("session", "sz", 0, 0)>0;
+ }else
+ {
+ *pResOut = 0;
+ }
+ SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut));
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Populate buffer zOut with the full canonical pathname corresponding
+ ** to the pathname in zPath. zOut is guaranteed to point to a buffer
+ ** of at least (INST_MAX_PATHNAME+1) bytes.
+ */
+ static int kvvfsFullPathname(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ int nOut,
+ char *zOut
+ ){
+ size_t nPath = strlen(zPath);
+ SQLITE_KV_LOG(("xFullPathname(\"%s\")\n", zPath));
+ if( nOut<nPath+1 ) nPath = nOut - 1;
+ memcpy(zOut, zPath, nPath);
+ zOut[nPath] = 0;
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Open the dynamic library located at zPath and return a handle.
+ */
+ static void *kvvfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+ return 0;
+ }
+
+ /*
+ ** Populate the buffer pointed to by zBufOut with nByte bytes of
+ ** random data.
+ */
+ static int kvvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+ memset(zBufOut, 0, nByte);
+ return nByte;
+ }
+
+ /*
+ ** Sleep for nMicro microseconds. Return the number of microseconds
+ ** actually slept.
+ */
+ static int kvvfsSleep(sqlite3_vfs *pVfs, int nMicro){
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Return the current time as a Julian Day number in *pTimeOut.
+ */
+ static int kvvfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+ *pTimeOut = 2459829.13362986;
+ return SQLITE_OK;
+ }
+ static int kvvfsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
+ *pTimeOut = (sqlite3_int64)(2459829.13362986*86400000.0);
+ return SQLITE_OK;
+ }
+
+ /*
+ ** This routine is called initialize the KV-vfs as the default VFS.
+ */
+ int sqlite3_os_init(void){
+ return sqlite3_vfs_register(&kvvfs_vfs, 1);
+ }
+ int sqlite3_os_end(void){
+ return SQLITE_OK;
+ }
+ #endif /* SQLITE_OS_KV */