]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Merge kv-vfs branch into fiddle-opfs branch to add kvvfs-based wasm build and demo.
authorstephan <stephan@noemail.net>
Mon, 12 Sep 2022 16:09:50 +0000 (16:09 +0000)
committerstephan <stephan@noemail.net>
Mon, 12 Sep 2022 16:09:50 +0000 (16:09 +0000)
FossilOrigin-Name: a7d8b26acd3c1ae344369e4d70804c0cab45272c0983cfd32d616a0a7b28acb9

13 files changed:
1  2 
Makefile.in
ext/wasm/GNUmakefile
ext/wasm/api/sqlite3-api-cleanup.js
ext/wasm/api/sqlite3-api-oo1.js
ext/wasm/api/sqlite3-api-prologue.js
ext/wasm/api/sqlite3-wasm.c
ext/wasm/index.html
ext/wasm/kvvfs.make
ext/wasm/kvvfs1.html
ext/wasm/kvvfs1.js
manifest
manifest.uuid
src/os_kv.c

diff --cc Makefile.in
Simple merge
index dccbbbe5d1fb0baa388b698a5e363c0c727fd7bc,3133d1230bfd3930adf8f52d65e480f06005d9a2..fd95b013ca655fddade1555f77f0de1ae89b123a
@@@ -310,76 -262,8 +310,78 @@@ wasm: $(sqlite3.js
  # 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.
@@@ -402,3 -286,3 +404,5 @@@ push-fiddle: $(fiddle_files
        rsync -va fiddle/ $(fiddle_remote)
  # end fiddle remote push
  ########################################################################
++
++include kvvfs.make
index 01aba213ed8f25a0d9f32f7844a2e0d0d5e97063,a2f921a5d7bb94113d077201739e92f0e4dcb978..1b57cdc5de829afcb26d841d7dd75d64181d67cc
    ***********************************************************************
  
    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);
index aafc04a2d5b7dd8177a80c4040bd4601b6e49b4f,9e547339668aee13dc37f6fdc44b823db85b2160..af179d1fe1c77aa3d9394236991c2747f46056d0
@@@ -67,9 -64,9 +67,77 @@@ self.sqlite3ApiBootstrap.initializers.p
        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;
++  }
 +});
 +
index 7959d047c971ea7baf4c851b5cc9c873a582ad3b,60ed61477eef8b3bf8f5e58c8c7a0bdf89643124..17dcd4228986e0fdea549e0bdda1125a797e72f2
@@@ -671,119 -576,18 +671,125 @@@ self.sqlite3ApiBootstrap = function sql
        ["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;
index df60a4a24caa28e079b060fa89369209b69b336c,6a81da3e5fb9333d6a78b57399d3d443a9619b41..2a505f19abfbe79fd87dbcc152b5a1cf79b3ba40
@@@ -446,303 -411,3 +446,82 @@@ const char * sqlite3_wasm_enum_json(voi
  #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
index def70cce03d091d14a57062cde56137c7a82eaf3,0000000000000000000000000000000000000000..435040434ff0386db7c245d045d646520507a881
mode 100644,000000..100644
--- /dev/null
@@@ -1,54 -1,0 +1,56 @@@
 +<!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>
index 0000000000000000000000000000000000000000,83a2691370e5336f7a43e61ae367388b68939f63..a65f2faf2650234e41dcb0fb12981fcb1d1993ec
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,82 +1,84 @@@
 -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
index 0000000000000000000000000000000000000000,0657920c530c107bdc5a55e4b573eea8ddc51ad5..773de0a6031b6165fdded59c5e3da72025826144
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,34 +1,43 @@@
 -    <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>
index 0000000000000000000000000000000000000000,f56f4874e0b3ff50c4363b2434155ac1f38c15d5..169fcc8bdf4764b01ba28c44fd772c1be3dc91a4
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,84 +1,73 @@@
 -    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);
+   });
+ })();
diff --cc manifest
index cdac3ba49ca70499ca3834d3f7de3b2c28298b79,601e86a73d57290032f96f2e278992acd8ffa489..c182819a9753c3f0924db9bd0fcb2b9c46d0ea6a
+++ b/manifest
@@@ -1,9 -1,9 +1,9 @@@
- 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
@@@ -472,59 -472,43 +472,62 @@@ F ext/session/test_session.c f433f68a8a
  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
@@@ -590,8 -574,9 +593,9 @@@ F src/notify.c 89a97dc854c3aa62ad5f384e
  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
@@@ -2019,8 -2004,8 +2023,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  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.
diff --cc manifest.uuid
index 0742ec86436b5f773143d198c9d592160e00c726,383958616569b0a783b8427ad706d5cbd0d9f38c..bde2102af8a926efcd231c86ad33ab0824cdae51
@@@ -1,1 -1,1 +1,1 @@@
- 4e6ce329872eb733ba2f7f7879747c52761ae97790fd8ed169a25a79854cc3d9
 -e49682c5eac91958f143e639c5656ca54560d14f5805d514bf4aa0c206e63844
++a7d8b26acd3c1ae344369e4d70804c0cab45272c0983cfd32d616a0a7b28acb9
diff --cc src/os_kv.c
index 0000000000000000000000000000000000000000,9260852dbe8636e76ebf368e881046bf4406215c..a14dc5c543603053d8c83505344e5395197a0359
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1077 +1,1077 @@@
 -  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 */