diff --git a/ext/wasm/SQLTester/GNUmakefile b/ext/wasm/SQLTester/GNUmakefile
new file mode 100644
index 0000000000..8fa1247138
--- /dev/null
+++ b/ext/wasm/SQLTester/GNUmakefile
@@ -0,0 +1,55 @@
+#!/this/is/make
+#
+# This makefile compiles SQLTester test files into something
+# we can readily import into JavaScript.
+all:
+
+SHELL := $(shell which bash 2>/dev/null)
+MAKEFILE := $(lastword $(MAKEFILE_LIST))
+CLEAN_FILES :=
+DISTCLEAN_FILES := ./--dummy-- *~
+
+test-list.mjs := test-list.mjs
+test-list.mjs.gz := $(test-list.mjs).gz
+CLEAN_FILES += $(test-list.mjs)
+
+tests.dir := $(firstword $(wildcard tests ../../jni/src/tests))
+$(info test script dir=$(tests.dir))
+
+tests.all := $(wildcard $(tests.dir)/*.test)
+
+bin.touint8array := ./touint8array
+$(bin.touint8array): $(bin.touint8array).c $(MAKEFILE)
+	$(CC) -o $@ $<
+CLEAN_FILES += $(bin.touint8array)
+
+ifneq (,$(tests.all))
+$(test-list.mjs): $(bin.touint8array) $(tests.all) $(MAKEFILE)
+	@{\
+		echo 'export default ['; \
+		sep=''; \
+		for f in $(sort $(tests.all)); do \
+			echo -en $$sep'{"name": "'$${f##*/}'", "content":'; \
+			$(bin.touint8array) < $$f; \
+			echo -n '}'; \
+			sep=',\n'; \
+		done; \
+	echo '];'; \
+	} > $@
+	@echo "Created $@"
+$(test-list.mjs.gz): $(test-list.mjs)
+	gzip -c $< > $@
+CLEAN_FILES += $(test-list.mjs.gz)
+all: $(test-list.mjs.gz)
+else
+	@echo "Cannot build $(test-list.mjs) for lack of input test files."; \
+		echo "Symlink ./tests to a directory containing SQLTester-format "; \
+		echo "test scripts named *.test, then try again"; \
+		exit 1
+endif
+
+.PHONY: clean distclean
+clean:
+	-rm -f $(CLEAN_FILES)
+distclean: clean
+	-rm -f $(DISTCLEAN_FILES)
diff --git a/ext/wasm/SQLTester/SQLTester.mjs b/ext/wasm/SQLTester/SQLTester.mjs
new file mode 100644
index 0000000000..a72399aefc
--- /dev/null
+++ b/ext/wasm/SQLTester/SQLTester.mjs
@@ -0,0 +1,1339 @@
+/*
+** 2023-08-29
+**
+** 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 the main application entry pointer for the JS
+** implementation of the SQLTester framework.
+**
+** This version is not well-documented because it's a direct port of
+** the Java immplementation, which is documented: in the main SQLite3
+** source tree, see ext/jni/src/org/sqlite/jni/tester/SQLite3Tester.java.
+*/
+
+import sqlite3ApiInit from '/jswasm/sqlite3.mjs';
+
+const sqlite3 = await sqlite3ApiInit();
+
+const log = (...args)=>{
+  console.log('SQLTester:',...args);
+};
+
+/**
+   Try to install vfsName as the new default VFS. Once this succeeds
+   (returns true) then it becomes a no-op on future calls. Throws if
+   vfs registration as the default VFS fails but has no side effects
+   if vfsName is not currently registered.
+*/
+const tryInstallVfs = function f(vfsName){
+  if(f.vfsName) return false;
+  const pVfs = sqlite3.capi.sqlite3_vfs_find(vfsName);
+  if(pVfs){
+    log("Installing",'"'+vfsName+'"',"as default VFS.");
+    const rc = sqlite3.capi.sqlite3_vfs_register(pVfs, 1);
+    if(rc){
+      sqlite3.SQLite3Error.toss(rc,"While trying to register",vfsName,"vfs.");
+    }
+    f.vfsName = vfsName;
+  }
+  return !!pVfs;
+};
+tryInstallVfs.vfsName = undefined;
+
+if( 0 && globalThis.WorkerGlobalScope ){
+  // Try OPFS storage, if available...
+  if( 0 && sqlite3.oo1.OpfsDb ){
+    /* Really slow with these tests */
+    tryInstallVfs("opfs");
+  }
+  if( sqlite3.installOpfsSAHPoolVfs ){
+    await sqlite3.installOpfsSAHPoolVfs({
+      clearOnInit: true,
+      initialCapacity: 15,
+      name: 'opfs-SQLTester'
+    }).then(pool=>{
+      tryInstallVfs(pool.vfsName);
+    }).catch(e=>{
+      log("OpfsSAHPool could not load:",e);
+    });
+  }
+}
+
+const wPost = (function(){
+  return (('undefined'===typeof WorkerGlobalScope)
+          ? ()=>{}
+          : (type, payload)=>{
+            postMessage({type, payload});
+          });
+})();
+//log("WorkerGlobalScope",globalThis.WorkerGlobalScope);
+
+// Return a new enum entry value
+const newE = ()=>Object.create(null);
+
+const newObj = (props)=>Object.assign(newE(), props);
+
+/**
+   Modes for how to escape (or not) column values and names from
+   SQLTester.execSql() to the result buffer output.
+*/
+const ResultBufferMode = Object.assign(Object.create(null),{
+  //! Do not append to result buffer
+  NONE: newE(),
+  //! Append output escaped.
+  ESCAPED: newE(),
+  //! Append output as-is
+  ASIS: newE()
+});
+
+/**
+   Modes to specify how to emit multi-row output from
+   SQLTester.execSql() to the result buffer.
+*/
+const ResultRowMode = newObj({
+  //! Keep all result rows on one line, space-separated.
+  ONLINE: newE(),
+  //! Add a newline between each result row.
+  NEWLINE: newE()
+});
+
+class SQLTesterException extends globalThis.Error {
+  constructor(testScript, ...args){
+    if(testScript){
+      super( [testScript.getOutputPrefix()+": ", ...args].join('') );
+    }else{
+      super( args.join('') );
+    }
+    this.name = 'SQLTesterException';
+  }
+  isFatal() { return false; }
+}
+
+SQLTesterException.toss = (...args)=>{
+  throw new SQLTesterException(...args);
+}
+
+class DbException extends SQLTesterException {
+  constructor(testScript, pDb, rc, closeDb=false){
+    super(testScript, "DB error #"+rc+": "+sqlite3.capi.sqlite3_errmsg(pDb));
+    this.name = 'DbException';
+    if( closeDb ) sqlite3.capi.sqlite3_close_v2(pDb);
+  }
+  isFatal() { return true; }
+}
+
+class TestScriptFailed extends SQLTesterException {
+  constructor(testScript, ...args){
+    super(testScript,...args);
+    this.name = 'TestScriptFailed';
+  }
+  isFatal() { return true; }
+}
+
+class UnknownCommand extends SQLTesterException {
+  constructor(testScript, cmdName){
+    super(testScript, cmdName);
+    this.name = 'UnknownCommand';
+  }
+  isFatal() { return true; }
+}
+
+class IncompatibleDirective extends SQLTesterException {
+  constructor(testScript, ...args){
+    super(testScript,...args);
+    this.name = 'IncompatibleDirective';
+  }
+}
+
+//! For throwing where an expression is required.
+const toss = (errType, ...args)=>{
+  throw new errType(...args);
+};
+
+const __utf8Decoder = new TextDecoder();
+const __utf8Encoder = new TextEncoder('utf-8');
+//! Workaround for Util.utf8Decode()
+const __SAB = ('undefined'===typeof globalThis.SharedArrayBuffer)
+      ? function(){} : globalThis.SharedArrayBuffer;
+
+
+/* Frequently-reused regexes. */
+const Rx = newObj({
+  requiredProperties: / REQUIRED_PROPERTIES:[ \t]*(\S.*)\s*$/,
+  scriptModuleName: / SCRIPT_MODULE_NAME:[ \t]*(\S+)\s*$/,
+  mixedModuleName: / ((MIXED_)?MODULE_NAME):[ \t]*(\S+)\s*$/,
+  command: /^--(([a-z-]+)( .*)?)$/,
+  //! "Special" characters - we have to escape output if it contains any.
+  special: /[\x00-\x20\x22\x5c\x7b\x7d]/,
+  squiggly: /[{}]/
+});
+
+const Util = newObj({
+  toss,
+
+  unlink: function(fn){
+    return 0==sqlite3.wasm.sqlite3_wasm_vfs_unlink(0,fn);
+  },
+
+  argvToString: (list)=>{
+    const m = [...list];
+    m.shift() /* strip command name */;
+    return m.join(" ")
+  },
+
+  utf8Decode: function(arrayBuffer, begin, end){
+    return __utf8Decoder.decode(
+      (arrayBuffer.buffer instanceof __SAB)
+        ? arrayBuffer.slice(begin, end)
+        : arrayBuffer.subarray(begin, end)
+    );
+  },
+
+  utf8Encode: (str)=>__utf8Encoder.encode(str),
+
+  strglob: sqlite3.wasm.xWrap('sqlite3_wasm_SQLTester_strglob','int',
+                              ['string','string'])
+})/*Util*/;
+
+class Outer {
+  #lnBuf = [];
+  #verbosity = 0;
+  #logger = console.log.bind(console);
+
+  constructor(func){
+    if(func) this.setFunc(func);
+  }
+
+  logger(...args){
+    if(args.length){
+      this.#logger = args[0];
+      return this;
+    }
+    return this.#logger;
+  }
+
+  out(...args){
+    if( this.getOutputPrefix && !this.#lnBuf.length ){
+      this.#lnBuf.push(this.getOutputPrefix());
+    }
+    this.#lnBuf.push(...args);
+    return this;
+  }
+
+  #outlnImpl(vLevel, ...args){
+    if( this.getOutputPrefix && !this.#lnBuf.length ){
+      this.#lnBuf.push(this.getOutputPrefix());
+    }
+    this.#lnBuf.push(...args,'\n');
+    const msg = this.#lnBuf.join('');
+    this.#lnBuf.length = 0;
+    this.#logger(msg);
+    return this;
+  }
+
+  outln(...args){
+    return this.#outlnImpl(0,...args);
+  }
+
+  outputPrefix(){
+    if( 0==arguments.length ){
+      return (this.getOutputPrefix
+              ? (this.getOutputPrefix() ?? '') : '');
+    }else{
+      this.getOutputPrefix = arguments[0];
+      return this;
+    }
+  }
+
+  static #verboseLabel = ["πŸ”ˆ",/*"πŸ”‰",*/"πŸ”Š","πŸ“’"];
+  verboseN(lvl, args){
+    if( this.#verbosity>=lvl ){
+      this.#outlnImpl(lvl, Outer.#verboseLabel[lvl-1],': ',...args);
+    }
+  }
+  verbose1(...args){ return this.verboseN(1,args); }
+  verbose2(...args){ return this.verboseN(2,args); }
+  verbose3(...args){ return this.verboseN(3,args); }
+
+  verbosity(){
+    const rc = this.#verbosity;
+    if(arguments.length) this.#verbosity = +arguments[0];
+    return rc;
+  }
+
+}/*Outer*/
+
+class SQLTester {
+
+  //! Console output utility.
+  #outer = new Outer().outputPrefix( ()=>'SQLTester: ' );
+  //! List of input scripts.
+  #aScripts = [];
+  //! Test input buffer.
+  #inputBuffer = [];
+  //! Test result buffer.
+  #resultBuffer = [];
+  //! Output representation of SQL NULL.
+  #nullView;
+  metrics = newObj({
+    //! Total tests run
+    nTotalTest: 0,
+    //! Total test script files run
+    nTestFile: 0,
+    //! Test-case count for to the current TestScript
+    nTest: 0,
+    //! Names of scripts which were aborted.
+    failedScripts: []
+  });
+  #emitColNames = false;
+  //! True to keep going regardless of how a test fails.
+  #keepGoing = false;
+  #db = newObj({
+    //! The list of available db handles.
+    list: new Array(7),
+    //! Index into this.list of the current db.
+    iCurrentDb: 0,
+    //! Name of the default db, re-created for each script.
+    initialDbName: "test.db",
+    //! Buffer for REQUIRED_PROPERTIES pragmas.
+    initSql: ['select 1;'],
+    //! (sqlite3*) to the current db.
+    currentDb: function(){
+      return this.list[this.iCurrentDb];
+    }
+  });
+
+  constructor(){
+    this.reset();
+  }
+
+  outln(...args){ return this.#outer.outln(...args); }
+  out(...args){ return this.#outer.out(...args); }
+  outer(...args){
+    if(args.length){
+      this.#outer = args[0];
+      return this;
+    }
+    return this.#outer;
+  }
+  verbose1(...args){ return this.#outer.verboseN(1,args); }
+  verbose2(...args){ return this.#outer.verboseN(2,args); }
+  verbose3(...args){ return this.#outer.verboseN(3,args); }
+  verbosity(...args){
+    const rc = this.#outer.verbosity(...args);
+    return args.length ? this : rc;
+  }
+  setLogger(func){
+    this.#outer.logger(func);
+    return this;
+  }
+
+  incrementTestCounter(){
+    ++this.metrics.nTotalTest;
+    ++this.metrics.nTest;
+  }
+
+  reset(){
+    this.clearInputBuffer();
+    this.clearResultBuffer();
+    this.#clearBuffer(this.#db.initSql);
+    this.closeAllDbs();
+    this.metrics.nTest = 0;
+    this.#nullView = "nil";
+    this.emitColNames = false;
+    this.#db.iCurrentDb = 0;
+    //this.#db.initSql.push("SELECT 1;");
+  }
+
+  appendInput(line, addNL){
+    this.#inputBuffer.push(line);
+    if( addNL ) this.#inputBuffer.push('\n');
+  }
+  appendResult(line, addNL){
+    this.#resultBuffer.push(line);
+    if( addNL ) this.#resultBuffer.push('\n');
+  }
+  appendDbInitSql(sql){
+    this.#db.initSql.push(sql);
+    if( this.currentDb() ){
+      this.execSql(null, true, ResultBufferMode.NONE, null, sql);
+    }
+  }
+
+  #runInitSql(pDb){
+    let rc = 0;
+    for(const sql of this.#db.initSql){
+      this.#outer.verbose2("RUNNING DB INIT CODE: ",sql);
+      rc = this.execSql(pDb, false, ResultBufferMode.NONE, null, sql);
+      if( rc ) break;
+    }
+    return rc;
+  }
+
+#clearBuffer(buffer){
+    buffer.length = 0;
+    return buffer;
+  }
+
+  clearInputBuffer(){ return this.#clearBuffer(this.#inputBuffer); }
+  clearResultBuffer(){return this.#clearBuffer(this.#resultBuffer); }
+
+  getInputText(){ return this.#inputBuffer.join(''); }
+  getResultText(){ return this.#resultBuffer.join(''); }
+
+  #takeBuffer(buffer){
+    const s = buffer.join('');
+    buffer.length = 0;
+    return s;
+  }
+
+  takeInputBuffer(){
+    return this.#takeBuffer(this.#inputBuffer);
+  }
+  takeResultBuffer(){
+    return this.#takeBuffer(this.#resultBuffer);
+  }
+
+  nullValue(){
+    return (0==arguments.length)
+      ? this.#nullView
+      : (this.#nullView = ''+arguments[0]);
+  }
+
+  outputColumnNames(){
+    return (0==arguments.length)
+      ? this.#emitColNames
+      : (this.#emitColNames = !!arguments[0]);
+  }
+
+  currentDbId(){
+    return (0==arguments.length)
+      ? this.#db.iCurrentDb
+      : (this.#affirmDbId(arguments[0]).#db.iCurrentDb = arguments[0]);
+  }
+
+  #affirmDbId(id){
+    if(id<0 || id>=this.#db.list.length){
+      toss(SQLTesterException, "Database index ",id," is out of range.");
+    }
+    return this;
+  }
+
+  currentDb(...args){
+    if( 0!=args.length ){
+      this.#affirmDbId(id).#db.iCurrentDb = id;
+    }
+    return this.#db.currentDb();
+  }
+
+  getDbById(id){
+    return this.#affirmDbId(id).#db.list[id];
+  }
+
+  getCurrentDb(){ return this.#db.list[this.#db.iCurrentDb]; }
+
+
+  closeDb(id) {
+    if( 0==arguments.length ){
+      id = this.#db.iCurrentDb;
+    }
+    const pDb = this.#affirmDbId(id).#db.list[id];
+    if( pDb ){
+      sqlite3.capi.sqlite3_close_v2(pDb);
+      this.#db.list[id] = null;
+    }
+  }
+
+  closeAllDbs(){
+    for(let i = 0; i<this.#db.list.length; ++i){
+      if(this.#db.list[i]){
+        sqlite3.capi.sqlite3_close_v2(this.#db.list[i]);
+        this.#db.list[i] = null;
+      }
+    }
+    this.#db.iCurrentDb = 0;
+  }
+
+  openDb(name, createIfNeeded){
+    if( 3===arguments.length ){
+      const slot = arguments[0];
+      this.#affirmDbId(slot).#db.iCurrentDb = slot;
+      name = arguments[1];
+      createIfNeeded = arguments[2];
+    }
+    this.closeDb();
+    const capi = sqlite3.capi, wasm = sqlite3.wasm;
+    let pDb = 0;
+    let flags = capi.SQLITE_OPEN_READWRITE;
+    if( createIfNeeded ) flags |= capi.SQLITE_OPEN_CREATE;
+    try{
+      let rc;
+      wasm.pstack.call(function(){
+        let ppOut = wasm.pstack.allocPtr();
+        rc = sqlite3.capi.sqlite3_open_v2(name, ppOut, flags, null);
+        pDb = wasm.peekPtr(ppOut);
+      });
+      let sql;
+      if( 0==rc && this.#db.initSql.length > 0){
+        rc = this.#runInitSql(pDb);
+      }
+      if( 0!=rc ){
+        sqlite3.SQLite3Error.toss(
+          rc,
+          "sqlite3 result code",rc+":",
+          (pDb ? sqlite3.capi.sqlite3_errmsg(pDb)
+           : sqlite3.capi.sqlite3_errstr(rc))
+        );
+      }
+      return this.#db.list[this.#db.iCurrentDb] = pDb;
+    }catch(e){
+      sqlite3.capi.sqlite3_close_v2(pDb);
+      throw e;
+    }
+  }
+
+  addTestScript(ts){
+    if( 2===arguments.length ){
+      ts = new TestScript(arguments[0], arguments[1]);
+    }else if(ts instanceof Uint8Array){
+      ts = new TestScript('<unnamed>', ts);
+    }else if('string' === typeof arguments[1]){
+      ts = new TestScript('<unnamed>', Util.utf8Encode(arguments[1]));
+    }
+    if( !(ts instanceof TestScript) ){
+      Util.toss(SQLTesterException, "Invalid argument type for addTestScript()");
+    }
+    this.#aScripts.push(ts);
+    return this;
+  }
+
+  runTests(){
+    const tStart = (new Date()).getTime();
+    let isVerbose = this.verbosity();
+    this.metrics.failedScripts.length = 0;
+    this.metrics.nTotalTest = 0;
+    this.metrics.nTestFile = 0;
+    for(const ts of this.#aScripts){
+      this.reset();
+      ++this.metrics.nTestFile;
+      let threw = false;
+      const timeStart = (new Date()).getTime();
+      let msgTail = '';
+      try{
+        ts.run(this);
+      }catch(e){
+        if(e instanceof SQLTesterException){
+          threw = true;
+          this.outln("πŸ”₯EXCEPTION: ",e);
+          this.metrics.failedScripts.push({script: ts.filename(), message:e.toString()});
+          if( this.#keepGoing ){
+            this.outln("Continuing anyway because of the keep-going option.");
+          }else if( e.isFatal() ){
+            throw e;
+          }
+        }else{
+          throw e;
+        }
+      }finally{
+        const timeEnd = (new Date()).getTime();
+        this.out("🏁", (threw ? "❌" : "βœ…"), " ",
+                 this.metrics.nTest, " test(s) in ",
+                 (timeEnd-timeStart),"ms. ");
+        const mod = ts.moduleName();
+        if( mod ){
+          this.out( "[",mod,"] " );
+        }
+        this.outln(ts.filename());
+      }
+    }
+    const tEnd = (new Date()).getTime();
+    Util.unlink(this.#db.initialDbName);
+    this.outln("Took ",(tEnd-tStart),"ms. Test count = ",
+               this.metrics.nTotalTest,", script count = ",
+               this.#aScripts.length,(
+                 this.metrics.failedScripts.length
+                   ? ", failed scripts = "+this.metrics.failedScripts.length
+                   : ""
+               )
+              );
+    return this;
+  }
+
+  #setupInitialDb(){
+    if( !this.#db.list[0] ){
+      Util.unlink(this.#db.initialDbName);
+      this.openDb(0, this.#db.initialDbName, true);
+    }else{
+      this.#outer.outln("WARNING: setupInitialDb() was unexpectedly ",
+                        "triggered while it is opened.");
+    }
+  }
+
+  #escapeSqlValue(v){
+    if( !v ) return "{}";
+    if( !Rx.special.test(v) ){
+      return v  /* no escaping needed */;
+    }
+    if( !Rx.squiggly.test(v) ){
+      return "{"+v+"}";
+    }
+    const sb = ["\""];
+    const n = v.length;
+    for(let i = 0; i < n; ++i){
+      const ch = v.charAt(i);
+      switch(ch){
+        case '\\': sb.push("\\\\"); break;
+        case '"': sb.push("\\\""); break;
+        default:{
+          //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch));
+          const ccode = ch.charCodeAt(i);
+          if( ccode < 32 ) sb.push('\\',ccode.toString(8),'o');
+          else sb.push(ch);
+          break;
+        }
+      }
+    }
+    sb.append("\"");
+    return sb.join('');
+  }
+
+  #appendDbErr(pDb, sb, rc){
+    sb.push(sqlite3.capi.sqlite3_js_rc_str(rc), ' ');
+    const msg = this.#escapeSqlValue(sqlite3.capi.sqlite3_errmsg(pDb));
+    if( '{' === msg.charAt(0) ){
+      sb.push(msg);
+    }else{
+      sb.push('{', msg, '}');
+    }
+  }
+
+  #checkDbRc(pDb,rc){
+    sqlite3.oo1.DB.checkRc(pDb, rc);
+  }
+
+  execSql(pDb, throwOnError, appendMode, rowMode, sql){
+    if( !pDb && !this.#db.list[0] ){
+      this.#setupInitialDb();
+    }
+    if( !pDb ) pDb = this.#db.currentDb();
+    const wasm = sqlite3.wasm, capi = sqlite3.capi;
+    sql = (sql instanceof Uint8Array)
+      ? sql
+      : Util.utf8Encode(capi.sqlite3_js_sql_to_string(sql));
+    const self = this;
+    const sb = (ResultBufferMode.NONE===appendMode) ? null : this.#resultBuffer;
+    let rc = 0;
+    wasm.scopedAllocCall(function(){
+      let sqlByteLen = sql.byteLength;
+      const ppStmt = wasm.scopedAlloc(
+        /* output (sqlite3_stmt**) arg and pzTail */
+        (2 * wasm.ptrSizeof) + (sqlByteLen + 1/* SQL + NUL */)
+      );
+      const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */;
+      let pSql = pzTail + wasm.ptrSizeof;
+      const pSqlEnd = pSql + sqlByteLen;
+      wasm.heap8().set(sql, pSql);
+      wasm.poke8(pSql + sqlByteLen, 0/*NUL terminator*/);
+      let pos = 0, n = 1, spacing = 0;
+      while( pSql && wasm.peek8(pSql) ){
+        wasm.pokePtr([ppStmt, pzTail], 0);
+        rc = capi.sqlite3_prepare_v3(
+          pDb, pSql, sqlByteLen, 0, ppStmt, pzTail
+        );
+        if( 0!==rc ){
+          if(throwOnError){
+            throw new DbException(self, pDb, rc);
+          }else if( sb ){
+            self.#appendDbErr(pDb, sb, rc);
+          }
+          break;
+        }
+        const pStmt = wasm.peekPtr(ppStmt);
+        pSql = wasm.peekPtr(pzTail);
+        sqlByteLen = pSqlEnd - pSql;
+        if(!pStmt) continue /* only whitespace or comments */;
+        if( sb ){
+          const nCol = capi.sqlite3_column_count(pStmt);
+          let colName, val;
+          while( capi.SQLITE_ROW === (rc = capi.sqlite3_step(pStmt)) ) {
+            for( let i=0; i < nCol; ++i ){
+              if( spacing++ > 0 ) sb.push(' ');
+              if( self.#emitColNames ){
+                colName = capi.sqlite3_column_name(pStmt, i);
+                switch(appendMode){
+                  case ResultBufferMode.ASIS: sb.push( colName ); break;
+                  case ResultBufferMode.ESCAPED:
+                    sb.push( self.#escapeSqlValue(colName) );
+                    break;
+                  default:
+                    self.toss("Unhandled ResultBufferMode.");
+                }
+                sb.push(' ');
+              }
+              val = capi.sqlite3_column_text(pStmt, i);
+              if( null===val ){
+                sb.push( self.#nullView );
+                continue;
+              }
+              switch(appendMode){
+                case ResultBufferMode.ASIS: sb.push( val ); break;
+                case ResultBufferMode.ESCAPED:
+                  sb.push( self.#escapeSqlValue(val) );
+                  break;
+              }
+            }/* column loop */
+          }/* row loop */
+          if( ResultRowMode.NEWLINE === rowMode ){
+            spacing = 0;
+            sb.push('\n');
+          }
+        }else{ // no output but possibly other side effects
+          while( capi.SQLITE_ROW === (rc = capi.sqlite3_step(pStmt)) ) {}
+        }
+        capi.sqlite3_finalize(pStmt);
+        if( capi.SQLITE_ROW===rc || capi.SQLITE_DONE===rc) rc = 0;
+        else if( rc!=0 ){
+          if( sb ){
+            self.#appendDbErr(db, sb, rc);
+          }
+          break;
+        }
+      }/* SQL script loop */;
+    })/*scopedAllocCall()*/;
+    return rc;
+  }
+
+}/*SQLTester*/
+
+class Command {
+  constructor(){
+  }
+
+  process(sqlTester,testScript,argv){
+    SQLTesterException.toss("process() must be overridden");
+  }
+
+  argcCheck(testScript,argv,min,max){
+    const argc = argv.length-1;
+    if(argc<min || (max>=0 && argc>max)){
+      if( min==max ){
+        testScript.toss(argv[0]," requires exactly ",min," argument(s)");
+      }else if(max>0){
+        testScript.toss(argv[0]," requires ",min,"-",max," arguments.");
+      }else{
+        testScript.toss(argv[0]," requires at least ",min," arguments.");
+      }
+    }
+  }
+}
+
+class Cursor {
+  src;
+  sb = [];
+  pos = 0;
+  //! Current line number. Starts at 0 for internal reasons and will
+  // line up with 1-based reality once parsing starts.
+  lineNo = 0 /* yes, zero */;
+  //! Putback value for this.pos.
+  putbackPos = 0;
+  //! Putback line number
+  putbackLineNo = 0;
+  //! Peeked-to pos, used by peekLine() and consumePeeked().
+  peekedPos = 0;
+  //! Peeked-to line number.
+  peekedLineNo = 0;
+
+  constructor(){
+  }
+
+  //! Restore parsing state to the start of the stream.
+  rewind(){
+    this.sb.length = this.pos = this.lineNo
+      = this.putbackPos = this.putbackLineNo
+      = this.peekedPos = this.peekedLineNo = 0;
+  }
+}
+
+class TestScript {
+  #cursor = new Cursor();
+  #moduleName = null;
+  #filename = null;
+  #testCaseName = null;
+  #outer = new Outer().outputPrefix( ()=>this.getOutputPrefix()+': ' );
+
+  constructor(...args){
+    let content, filename;
+    if( 2 == args.length ){
+      filename = args[0];
+      content = args[1];
+    }else if( 1 == args.length ){
+      if(args[0] instanceof Object){
+        const o = args[0];
+        filename = o.name;
+        content = o.content;
+      }else{
+        content = args[0];
+      }
+    }
+    if(!(content instanceof Uint8Array)){
+      if('string' === typeof content){
+        content = Util.utf8Encode(content);
+      }else if((content instanceof ArrayBuffer)
+               ||(content instanceof Array)){
+        content = new Uint8Array(content);
+      }else{
+        toss(Error, "Invalid content type for TestScript constructor.");
+      }
+    }
+    this.#filename = filename;
+    this.#cursor.src = content;
+  }
+
+  moduleName(){
+    return (0==arguments.length)
+      ? this.#moduleName : (this.#moduleName = arguments[0]);
+  }
+
+  testCaseName(){
+    return (0==arguments.length)
+      ? this.#testCaseName : (this.#testCaseName = arguments[0]);
+  }
+  filename(){
+    return (0==arguments.length)
+      ? this.#filename : (this.#filename = arguments[0]);
+  }
+
+  getOutputPrefix() {
+    let rc =  "["+(this.#moduleName || '<unnamed>')+"]";
+    if( this.#testCaseName ) rc += "["+this.#testCaseName+"]";
+    if( this.#filename ) rc += '['+this.#filename+']';
+    return rc + " line "+ this.#cursor.lineNo;
+  }
+
+  reset(){
+    this.#testCaseName = null;
+    this.#cursor.rewind();
+    return this;
+  }
+
+  toss(...args){
+    throw new TestScriptFailed(this,...args);
+  }
+
+  verbose1(...args){ return this.#outer.verboseN(1,args); }
+  verbose2(...args){ return this.#outer.verboseN(2,args); }
+  verbose3(...args){ return this.#outer.verboseN(3,args); }
+  verbosity(...args){
+    const rc = this.#outer.verbosity(...args);
+    return args.length ? this : rc;
+  }
+
+  #checkRequiredProperties(tester, props){
+    if(true) return false;
+    let nOk = 0;
+    for(const rp of props){
+      this.verbose2("REQUIRED_PROPERTIES: ",rp);
+      switch(rp){
+        case "RECURSIVE_TRIGGERS":
+          tester.appendDbInitSql("pragma recursive_triggers=on;");
+          ++nOk;
+          break;
+        case "TEMPSTORE_FILE":
+          /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+             which we just happen to know is the case */
+          tester.appendDbInitSql("pragma temp_store=1;");
+          ++nOk;
+          break;
+        case "TEMPSTORE_MEM":
+          /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+             which we just happen to know is the case */
+          tester.appendDbInitSql("pragma temp_store=0;");
+          ++nOk;
+          break;
+        case "AUTOVACUUM":
+          tester.appendDbInitSql("pragma auto_vacuum=full;");
+          ++nOk;
+          break;
+        case "INCRVACUUM":
+          tester.appendDbInitSql("pragma auto_vacuum=incremental;");
+          ++nOk;
+        default:
+          break;
+      }
+    }
+    return props.length == nOk;
+  }
+
+  #checkForDirective(tester,line){
+    if(line.startsWith("#")){
+      throw new IncompatibleDirective(this, "C-preprocessor input: "+line);
+    }else if(line.startsWith("---")){
+      throw new IncompatibleDirective(this, "triple-dash: ",line);
+    }
+    let m = Rx.scriptModuleName.exec(line);
+    if( m ){
+      this.#moduleName = m[1];
+      return;
+    }
+    m = Rx.requiredProperties.exec(line);
+    if( m ){
+      const rp = m[1];
+      if( !this.#checkRequiredProperties( tester, rp.split(/\s+/).filter(v=>!!v) ) ){
+        throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
+      }
+    }
+
+    m = Rx.mixedModuleName.exec(line);
+    if( m ){
+      throw new IncompatibleDirective(this, m[1]+": "+m[3]);
+    }
+    if( line.indexOf("\n|")>=0 ){
+      throw new IncompatibleDirective(this, "newline-pipe combination.");
+    }
+
+  }
+
+  #getCommandArgv(line){
+    const m = Rx.command.exec(line);
+    return m ? m[1].trim().split(/\s+/) : null;
+  }
+
+
+  #isCommandLine(line, checkForImpl){
+    let m = Rx.command.exec(line);
+    if( m && checkForImpl ){
+      m = !!CommandDispatcher.getCommandByName(m[2]);
+    }
+    return !!m;
+  }
+
+  fetchCommandBody(tester){
+    const sb = [];
+    let line;
+    while( (null !== (line = this.peekLine())) ){
+      this.#checkForDirective(tester, line);
+      if( this.#isCommandLine(line, true) ) break;
+      sb.push(line,"\n");
+      this.consumePeeked();
+    }
+    line = sb.join('');
+    return !!line.trim() ? line : null;
+  }
+
+  run(tester){
+    this.reset();
+    this.#outer.verbosity( tester.verbosity() );
+    this.#outer.logger( tester.outer().logger() );
+    let line, directive, argv = [];
+    while( null != (line = this.getLine()) ){
+      this.verbose3("run() input line: ",line);
+      this.#checkForDirective(tester, line);
+      argv = this.#getCommandArgv(line);
+      if( argv ){
+        this.#processCommand(tester, argv);
+        continue;
+      }
+      tester.appendInput(line,true);
+    }
+    return true;
+  }
+
+  #processCommand(tester, argv){
+    this.verbose2("processCommand(): ",argv[0], " ", Util.argvToString(argv));
+    if(this.#outer.verbosity()>1){
+      const input = tester.getInputText();
+      this.verbose3("processCommand() input buffer = ",input);
+    }
+    CommandDispatcher.dispatch(tester, this, argv);
+  }
+
+  getLine(){
+    const cur = this.#cursor;
+    if( cur.pos==cur.src.byteLength ){
+      return null/*EOF*/;
+    }
+    cur.putbackPos = cur.pos;
+    cur.putbackLineNo = cur.lineNo;
+    cur.sb.length = 0;
+    let b = 0, prevB = 0, i = cur.pos;
+    let doBreak = false;
+    let nChar = 0 /* number of bytes in the aChar char */;
+    const end = cur.src.byteLength;
+    for(; i < end && !doBreak; ++i){
+      b = cur.src[i];
+      switch( b ){
+        case 13/*CR*/: continue;
+        case 10/*NL*/:
+          ++cur.lineNo;
+          if(cur.sb.length>0) doBreak = true;
+          // Else it's an empty string
+          break;
+        default:{
+          /* Multi-byte chars need to be gathered up and appended at
+             one time so that we can get them as string objects. */
+          nChar = 1;
+          switch( b & 0xF0 ){
+            case 0xC0: nChar = 2; break;
+            case 0xE0: nChar = 3; break;
+            case 0xF0: nChar = 4; break;
+            default:
+              if( b > 127 ) this.toss("Invalid character (#"+b+").");
+              break;
+          }
+          if( 1==nChar ){
+            cur.sb.push(String.fromCharCode(b));
+          }else{
+            const aChar = [] /* multi-byte char buffer */;
+            for(let x = 0; (x < nChar) && (i+x < end); ++x) aChar[x] = cur.src[i+x];
+            cur.sb.push(
+              Util.utf8Decode( new Uint8Array(aChar) )
+            );
+            i += nChar-1;
+          }
+          break;
+        }
+      }
+    }
+    cur.pos = i;
+    const rv = cur.sb.join('');
+    if( i==cur.src.byteLength && 0==rv.length ){
+      return null /* EOF */;
+    }
+    return rv;
+  }/*getLine()*/
+
+  /**
+     Fetches the next line then resets the cursor to its pre-call
+     state. consumePeeked() can be used to consume this peeked line
+     without having to re-parse it.
+  */
+  peekLine(){
+    const cur = this.#cursor;
+    const oldPos = cur.pos;
+    const oldPB = cur.putbackPos;
+    const oldPBL = cur.putbackLineNo;
+    const oldLine = cur.lineNo;
+    try {
+      return this.getLine();
+    }finally{
+      cur.peekedPos = cur.pos;
+      cur.peekedLineNo = cur.lineNo;
+      cur.pos = oldPos;
+      cur.lineNo = oldLine;
+      cur.putbackPos = oldPB;
+      cur.putbackLineNo = oldPBL;
+    }
+  }
+
+
+  /**
+     Only valid after calling peekLine() and before calling getLine().
+     This places the cursor to the position it would have been at had
+     the peekLine() had been fetched with getLine().
+  */
+  consumePeeked(){
+    const cur = this.#cursor;
+    cur.pos = cur.peekedPos;
+    cur.lineNo = cur.peekedLineNo;
+  }
+
+  /**
+     Restores the cursor to the position it had before the previous
+     call to getLine().
+  */
+  putbackLine(){
+    const cur = this.#cursor;
+    cur.pos = cur.putbackPos;
+    cur.lineNo = cur.putbackLineNo;
+  }
+
+}/*TestScript*/;
+
+//! --close command
+class CloseDbCommand extends Command {
+  process(t, ts, argv){
+    this.argcCheck(ts,argv,0,1);
+    let id;
+    if(argv.length>1){
+      const arg = argv[1];
+      if( "all" === arg ){
+        t.closeAllDbs();
+        return;
+      }
+      else{
+        id = parseInt(arg);
+      }
+    }else{
+      id = t.currentDbId();
+    }
+    t.closeDb(id);
+  }
+}
+
+//! --column-names command
+class ColumnNamesCommand extends Command {
+  process( st, ts, argv ){
+    this.argcCheck(ts,argv,1);
+    st.outputColumnNames( !!parseInt(argv[1]) );
+  }
+}
+
+//! --db command
+class DbCommand extends Command {
+  process(t, ts, argv){
+    this.argcCheck(ts,argv,1);
+    t.currentDbId( parseInt(argv[1]) );
+  }
+}
+
+//! --glob command
+class GlobCommand extends Command {
+  #negate = false;
+  constructor(negate=false){
+    super();
+    this.#negate = negate;
+  }
+
+  process(t, ts, argv){
+    this.argcCheck(ts,argv,1,-1);
+    t.incrementTestCounter();
+    const sql = t.takeInputBuffer();
+    let rc = t.execSql(null, true, ResultBufferMode.ESCAPED,
+                       ResultRowMode.ONELINE, sql);
+    const result = t.getResultText();
+    const sArgs = Util.argvToString(argv);
+    //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs);
+    const glob = Util.argvToString(argv);
+    rc = Util.strglob(glob, result);
+    if( (this.#negate && 0===rc) || (!this.#negate && 0!==rc) ){
+      ts.toss(argv[0], " mismatch: ", glob," vs input: ",result);
+    }
+  }
+}
+
+//! --notglob command
+class NotGlobCommand extends GlobCommand {
+  constructor(){super(true);}
+}
+
+//! --open command
+class OpenDbCommand extends Command {
+  #createIfNeeded = false;
+  constructor(createIfNeeded=false){
+    super();
+    this.#createIfNeeded = createIfNeeded;
+  }
+  process(t, ts, argv){
+    this.argcCheck(ts,argv,1);
+    t.openDb(argv[1], this.#createIfNeeded);
+  }
+}
+
+//! --new command
+class NewDbCommand extends OpenDbCommand {
+  constructor(){ super(true); }
+}
+
+//! Placeholder dummy/no-op commands
+class NoopCommand extends Command {
+  process(t, ts, argv){}
+}
+
+//! --null command
+class NullCommand extends Command {
+  process(st, ts, argv){
+    this.argcCheck(ts,argv,1);
+    st.nullValue( argv[1] );
+  }
+}
+
+//! --print command
+class PrintCommand extends Command {
+  process(st, ts, argv){
+    st.out(ts.getOutputPrefix(),': ');
+    if( 1==argv.length ){
+      st.out( st.getInputText() );
+    }else{
+      st.outln( Util.argvToString(argv) );
+    }
+  }
+}
+
+//! --result command
+class ResultCommand extends Command {
+  #bufferMode;
+  constructor(resultBufferMode = ResultBufferMode.ESCAPED){
+    super();
+    this.#bufferMode = resultBufferMode;
+  }
+  process(t, ts, argv){
+    this.argcCheck(ts,argv,0,-1);
+    t.incrementTestCounter();
+    const sql = t.takeInputBuffer();
+    //ts.verbose2(argv[0]," SQL =\n",sql);
+    t.execSql(null, false, this.#bufferMode, ResultRowMode.ONELINE, sql);
+    const result = t.getResultText().trim();
+    const sArgs = argv.length>1 ? Util.argvToString(argv) : "";
+    if( result !== sArgs ){
+      t.outln(argv[0]," FAILED comparison. Result buffer:\n",
+              result,"\nExpected result:\n",sArgs);
+      ts.toss(argv[0]+" comparison failed.");
+    }
+  }
+}
+
+//! --json command
+class JsonCommand extends ResultCommand {
+  constructor(){ super(ResultBufferMode.ASIS); }
+}
+
+//! --run command
+class RunCommand extends Command {
+  process(t, ts, argv){
+    this.argcCheck(ts,argv,0,1);
+    const pDb = (1==argv.length)
+      ? t.currentDb() : t.getDbById( parseInt(argv[1]) );
+    const sql = t.takeInputBuffer();
+    const rc = t.execSql(pDb, false, ResultBufferMode.NONE,
+                       ResultRowMode.ONELINE, sql);
+    if( 0!==rc && t.verbosity()>0 ){
+      const msg = sqlite3.capi.sqlite3_errmsg(pDb);
+      ts.verbose2(argv[0]," non-fatal command error #",rc,": ",
+                  msg,"\nfor SQL:\n",sql);
+    }
+  }
+}
+
+//! --tableresult command
+class TableResultCommand extends Command {
+  #jsonMode;
+  constructor(jsonMode=false){
+    super();
+    this.#jsonMode = jsonMode;
+  }
+  process(t, ts, argv){
+    this.argcCheck(ts,argv,0);
+    t.incrementTestCounter();
+    let body = ts.fetchCommandBody(t);
+    if( null===body ) ts.toss("Missing ",argv[0]," body.");
+    body = body.trim();
+    if( !body.endsWith("\n--end") ){
+      ts.toss(argv[0], " must be terminated with --end\\n");
+    }else{
+      body = body.substring(0, body.length-6);
+    }
+    const globs = body.split(/\s*\n\s*/);
+    if( globs.length < 1 ){
+      ts.toss(argv[0], " requires 1 or more ",
+              (this.#jsonMode ? "json snippets" : "globs"),".");
+    }
+    const sql = t.takeInputBuffer();
+    t.execSql(null, true,
+              this.#jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED,
+              ResultRowMode.NEWLINE, sql);
+    const rbuf = t.getResultText().trim();
+    const res = rbuf.split(/\r?\n/);
+    if( res.length !== globs.length ){
+      ts.toss(argv[0], " failure: input has ", res.length,
+              " row(s) but expecting ",globs.length);
+    }
+    for(let i = 0; i < res.length; ++i){
+      const glob = globs[i].replaceAll(/\s+/g," ").trim();
+      //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>");
+      if( this.#jsonMode ){
+        if( glob!==res[i] ){
+          ts.toss(argv[0], " json <<",glob, ">> does not match: <<",
+                  res[i],">>");
+        }
+      }else if( 0!=Util.strglob(glob, res[i]) ){
+        ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>");
+      }
+    }
+  }
+}
+
+//! --json-block command
+class JsonBlockCommand extends TableResultCommand {
+  constructor(){ super(true); }
+}
+
+//! --testcase command
+class TestCaseCommand extends Command {
+  process(tester, script, argv){
+    this.argcCheck(script, argv,1);
+    script.testCaseName(argv[1]);
+    tester.clearResultBuffer();
+    tester.clearInputBuffer();
+  }
+}
+
+
+//! --verbosity command
+class VerbosityCommand extends Command {
+  process(t, ts, argv){
+    this.argcCheck(ts,argv,1);
+    ts.verbosity( parseInt(argv[1]) );
+  }
+}
+
+class CommandDispatcher {
+  static map = newObj();
+
+  static getCommandByName(name){
+    let rv = CommandDispatcher.map[name];
+    if( rv ) return rv;
+    switch(name){
+      case "close":        rv = new CloseDbCommand(); break;
+      case "column-names": rv = new ColumnNamesCommand(); break;
+      case "db":           rv = new DbCommand(); break;
+      case "glob":         rv = new GlobCommand(); break;
+      case "json":         rv = new JsonCommand(); break;
+      case "json-block":   rv = new JsonBlockCommand(); break;
+      case "new":          rv = new NewDbCommand(); break;
+      case "notglob":      rv = new NotGlobCommand(); break;
+      case "null":         rv = new NullCommand(); break;
+      case "oom":          rv = new NoopCommand(); break;
+      case "open":         rv = new OpenDbCommand(); break;
+      case "print":        rv = new PrintCommand(); break;
+      case "result":       rv = new ResultCommand(); break;
+      case "run":          rv = new RunCommand(); break;
+      case "tableresult":  rv = new TableResultCommand(); break;
+      case "testcase":     rv = new TestCaseCommand(); break;
+      case "verbosity":    rv = new VerbosityCommand(); break;
+    }
+    if( rv ){
+      CommandDispatcher.map[name] = rv;
+    }
+    return rv;
+  }
+
+  static dispatch(tester, testScript, argv){
+    const cmd = CommandDispatcher.getCommandByName(argv[0]);
+    if( !cmd ){
+      toss(UnknownCommand,testScript,argv[0]);
+    }
+    cmd.process(tester, testScript, argv);
+  }
+}/*CommandDispatcher*/
+
+const namespace = newObj({
+  Command,
+  DbException,
+  IncompatibleDirective,
+  Outer,
+  SQLTester,
+  SQLTesterException,
+  TestScript,
+  TestScriptFailed,
+  UnknownCommand,
+  Util,
+  sqlite3
+});
+
+export {namespace as default};
diff --git a/ext/wasm/SQLTester/SQLTester.run.mjs b/ext/wasm/SQLTester/SQLTester.run.mjs
new file mode 100644
index 0000000000..735fe4dcd8
--- /dev/null
+++ b/ext/wasm/SQLTester/SQLTester.run.mjs
@@ -0,0 +1,148 @@
+/*
+** 2023-08-29
+**
+** 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 a test application for SQLTester.js.
+*/
+import {default as ns} from './SQLTester.mjs';
+import {default as allTests} from './test-list.mjs';
+
+globalThis.sqlite3 = ns.sqlite3;
+const log = function f(...args){
+  console.log('SQLTester.run:',...args);
+  return f;
+};
+
+const out = function f(...args){ return f.outer.out(...args) };
+out.outer = new ns.Outer();
+out.outer.getOutputPrefix = ()=>'SQLTester.run: ';
+const outln = (...args)=>{ return out.outer.outln(...args) };
+
+const affirm = function(expr, msg){
+  if( !expr ){
+    throw new Error(arguments[1]
+                    ? ("Assertion failed: "+arguments[1])
+                    : "Assertion failed");
+  }
+}
+
+let ts = new ns.TestScript('/foo.test',`
+/*
+** This is a comment. There are many like it but this one is mine.
+**
+** SCRIPT_MODULE_NAME:      sanity-check-0
+** xMIXED_MODULE_NAME:       mixed-module
+** xMODULE_NAME:             module-name
+** xREQUIRED_PROPERTIES:      small fast reliable
+** xREQUIRED_PROPERTIES:      RECURSIVE_TRIGGERS
+** xREQUIRED_PROPERTIES:      TEMPSTORE_FILE TEMPSTORE_MEM
+** xREQUIRED_PROPERTIES:      AUTOVACUUM INCRVACUUM
+**
+*/
+/* --verbosity 3 */
+/* ---must-fail */
+/* # must fail */
+/* --verbosity 0 */
+--print Hello, world.
+--close all
+--oom
+--db 0
+--new my.db
+--null zilch
+--testcase 1.0
+SELECT 1, null;
+--result 1 zilch
+--glob *zil*
+--notglob *ZIL*
+SELECT 1, 2;
+intentional error;
+--run
+/* ---intentional-failure */
+--testcase json-1
+SELECT json_array(1,2,3)
+--json [1,2,3]
+--testcase tableresult-1
+  select 1, 'a';
+  select 2, 'b';
+--tableresult
+  # [a-z]
+  2 b
+--end
+--testcase json-block-1
+  select json_array(1,2,3);
+  select json_object('a',1,'b',2);
+--json-block
+  [1,2,3]
+  {"a":1,"b":2}
+--end
+--testcase col-names-on
+--column-names 1
+  select 1 as 'a', 2 as 'b';
+--result a 1 b 2
+--testcase col-names-off
+--column-names 0
+  select 1 as 'a', 2 as 'b';
+--result 1 2
+--close
+--print Until next time
+`);
+
+const sqt = new ns.SQLTester()
+      .setLogger(console.log.bind(console))
+      .verbosity(1)
+      .addTestScript(ts);
+sqt.outer().outputPrefix('');
+
+const runTests = function(){
+  try{
+    if( 0 ){
+      affirm( !sqt.getCurrentDb(), 'sqt.getCurrentDb()' );
+      sqt.openDb('/foo.db', true);
+      affirm( !!sqt.getCurrentDb(),'sqt.getCurrentDb()' );
+      affirm( 'zilch' !== sqt.nullValue() );
+      ts.run(sqt);
+      affirm( 'zilch' === sqt.nullValue() );
+      sqt.addTestScript(ts);
+      sqt.runTests();
+    }else{
+      for(const t of allTests){
+        sqt.addTestScript( new ns.TestScript(t) );
+      }
+      allTests.length = 0;
+      sqt.runTests();
+    }
+  }finally{
+    //log( "Metrics:", sqt.metrics );
+    sqt.reset();
+  }
+};
+
+if( globalThis.WorkerGlobalScope ){
+  const wPost = (type,payload)=>globalThis.postMessage({type, payload});
+  globalThis.onmessage = function({data}){
+    switch(data.type){
+      case 'run-tests':{
+        try{ runTests(); }
+        finally{ wPost('tests-end', sqt.metrics); }
+        break;
+      }
+      default:
+        log("unhandled onmessage: ",data);
+        break;
+    }
+  };
+  sqt.setLogger((msg)=>{
+    wPost('stdout', {message: msg});
+  });
+  wPost('is-ready');
+  //globalThis.onmessage({data:{type:'run-tests'}});
+}else{
+  runTests();
+}
diff --git a/ext/wasm/SQLTester/index.html b/ext/wasm/SQLTester/index.html
new file mode 100644
index 0000000000..1dffad63e2
--- /dev/null
+++ b/ext/wasm/SQLTester/index.html
@@ -0,0 +1,127 @@
+<!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>SQLTester</title>
+  </head>
+  <style>
+    fieldset {
+        display: flex;
+        flex-direction: row;
+        padding-right: 1em;
+    }
+    fieldset > :not(.legend) {
+        display: flex;
+        flex-direction: row;
+        padding-right: 1em;
+    }
+  </style>
+  <body>
+    <h1>SQLTester for JS/WASM</h1>
+    <p>This app reads in a build-time-defined set of SQLTester test
+      scripts and runs them through the test suite.
+    </p>
+    <fieldset>
+      <legend>Options</legend>
+      <span class='input-wrapper'>
+        <input type='checkbox' id='cb-log-reverse' checked>
+        <label for='cb-log-reverse'>Reverse log order?</label>
+      </span>
+      <input type='button' id='btn-run-tests' value='Run tests'/>
+    </fieldset>
+    <div id='test-output'>Test output will go here.</div>
+    <!--script src='SQLTester.run.mjs' type='module'></script-->
+    <script>
+      (async function(){
+        const W = new Worker('SQLTester.run.mjs',{
+          type: 'module'
+        });
+        const wPost = (type,payload)=>W.postMessage({type,payload});
+        const mapToString = (v)=>{
+          switch(typeof v){
+            case 'string': return v;
+            case 'number': case 'boolean':
+            case 'undefined': case 'bigint':
+              return ''+v;
+            default: break;
+          }
+          if(null===v) return 'null';
+          if(v instanceof Error){
+            v = {
+              message: v.message,
+              stack: v.stack,
+              errorClass: v.name
+            };
+          }
+          return JSON.stringify(v,undefined,2);
+        };
+        const normalizeArgs = (args)=>args.map(mapToString);
+        const logTarget = document.querySelector('#test-output');
+        const logClass = function(cssClass,...args){
+          const ln = document.createElement('div');
+          if(cssClass){
+            for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){
+              ln.classList.add(c);
+            }
+          }
+          ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
+          logTarget.append(ln);
+        };
+        {
+          const cbReverse = document.querySelector('#cb-log-reverse');
+          const cbReverseKey = 'SQLTester:cb-log-reverse';
+          const cbReverseIt = ()=>{
+            logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse');
+          };
+          cbReverse.addEventListener('change', cbReverseIt, true);
+          cbReverseIt();
+        }
+
+        const btnRun = document.querySelector('#btn-run-tests');
+        const runTests = ()=>{
+          btnRun.setAttribute('disabled','disabled');
+          wPost('run-tests');
+          logTarget.innerText = 'Running tests...';
+        }
+        btnRun.addEventListener('click', runTests);
+        const log2 = function f(...args){
+          logClass('', ...args);
+          return f;
+        };
+        const log = function f(...args){
+          logClass('','index.html:',...args);
+          return f;
+        };
+
+        const timerId = setTimeout( ()=>{
+          logClass('error',"The SQLTester module is taking an unusually ",
+                   "long time to load. More information may be available",
+                   "in the dev console.");
+        }, 3000 /* assuming localhost */ );
+
+        W.onmessage = function({data}){
+          switch(data.type){
+            case 'stdout': log2(data.payload.message); break;
+            case 'tests-end':
+              btnRun.removeAttribute('disabled');
+              delete data.payload.nTest;
+              log("test results:",data.payload);
+              break;
+            case 'is-ready':
+              clearTimeout(timerId);
+              runTests(); break;
+            default:
+              log("unhandled onmessage",data);
+              break;
+          }
+        };
+        //runTests()
+        /* Inexplicably, */
+      })();
+    </script>
+  </body>
+</html>
diff --git a/ext/wasm/SQLTester/touint8array.c b/ext/wasm/SQLTester/touint8array.c
new file mode 100644
index 0000000000..b03ad42f03
--- /dev/null
+++ b/ext/wasm/SQLTester/touint8array.c
@@ -0,0 +1,29 @@
+/*
+** 2023-08-29
+**
+** 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 a tool for writing out the contents of stdin as
+** a comma-separated list of numbers, one per byte.
+*/
+
+#include <stdio.h>
+int main(int argc, char const **argv){
+  int i;
+  int rc = 0, colWidth = 30;
+  int ch;
+  printf("[");
+  for( i=0; EOF!=(ch = fgetc(stdin)); ++i ){
+    if( 0!=i ) printf(",");
+    if( i && 0==(i%colWidth) ) puts("");
+    printf("%d",ch);
+  }
+  printf("]");
+  return rc;
+}
diff --git a/libsql-sqlite3/Makefile.in b/libsql-sqlite3/Makefile.in
index 5ad94a00f0..8b7321561f 100644
--- a/libsql-sqlite3/Makefile.in
+++ b/libsql-sqlite3/Makefile.in
@@ -57,6 +57,7 @@ LIBTCL = @TCL_LIB_SPEC@
 #
 READLINE_FLAGS = -DHAVE_READLINE=@TARGET_HAVE_READLINE@ @TARGET_READLINE_INC@
 READLINE_FLAGS += -DHAVE_EDITLINE=@TARGET_HAVE_EDITLINE@
+READLINE_FLAGS += -DHAVE_LINENOISE=@TARGET_HAVE_LINENOISE@
 
 # The library that programs using readline() must link against.
 #
@@ -753,6 +754,9 @@ fuzzcheck$(TEXE):	$(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP)
 fuzzcheck-asan$(TEXE):	$(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP)
 	$(LTLINK) -o $@ -fsanitize=address $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS) $(OPT_STATIC_LIBLIBSQL_WASM)
 
+fuzzcheck-ubsan$(TEXE):	$(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP)
+	$(LTLINK) -o $@ -fsanitize=undefined $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS)
+
 ossshell$(TEXE):	$(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h
 	$(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/ossshell.c \
              $(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS) $(OPT_STATIC_LIBLIBSQL_WASM)
@@ -812,13 +816,22 @@ mptest:	mptester$(TEXE)
 	$(MPTEST2) --journalmode DELETE
 
 
+has_tclsh84:
+	sh $(TOP)/tool/cktclsh.sh 8.4 $(TCLSH_CMD)
+	touch has_tclsh84
+
+has_tclsh85:
+	sh $(TOP)/tool/cktclsh.sh 8.5 $(TCLSH_CMD)
+	touch has_tclsh85
+
+
 # This target creates a directory named "tsrc" and fills it with
 # copies of all of the C source code and header files needed to
 # build on the target system.  Some of the C source code and header
 # files are automatically generated.  This target takes care of
 # all that automatic generation.
 #
-.target_source:	$(SRC) $(TOP)/tool/vdbe-compress.tcl fts5.c
+.target_source:	$(SRC) $(TOP)/tool/vdbe-compress.tcl has_tclsh84 fts5.c
 	rm -rf tsrc
 	mkdir tsrc
 	cp -f $(SRC) tsrc
@@ -839,15 +852,15 @@ testlibsql: sqlite3.h
 		cargo test --all --doc --all-features &&\
 		cargo install cargo-hack && cargo hack check --each-feature --no-dev-deps
 
-sqlite3.c:	.target_source $(TOP)/tool/mksqlite3c.tcl src-verify
+sqlite3.c:	.target_source $(TOP)/tool/mksqlite3c.tcl src-verify has_tclsh84
 	$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS)
 	cp tsrc/sqlite3ext.h .
 	cp $(TOP)/ext/session/sqlite3session.h .
 
-sqlite3r.h: sqlite3.h
+sqlite3r.h: sqlite3.h has_tclsh84
 	$(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover >sqlite3r.h
 
-sqlite3r.c: sqlite3.c sqlite3r.h
+sqlite3r.c: sqlite3.c sqlite3r.h has_tclsh84
 	cp $(TOP)/ext/recover/sqlite3recover.c tsrc/
 	cp $(TOP)/ext/recover/sqlite3recover.h tsrc/
 	cp $(TOP)/ext/recover/dbdata.c tsrc/
@@ -862,7 +875,7 @@ tclsqlite3.c:	sqlite3.c
 	echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c
 	cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c
 
-sqlite3-all.c:	sqlite3.c $(TOP)/tool/split-sqlite3c.tcl
+sqlite3-all.c:	sqlite3.c $(TOP)/tool/split-sqlite3c.tcl has_tclsh84
 	$(TCLSH_CMD) $(TOP)/tool/split-sqlite3c.tcl
 
 # Rule to build the amalgamation
@@ -1150,10 +1163,10 @@ tclsqlite3$(TEXE):	tclsqlite-shell.lo libsqlite3.la
 
 # Rules to build opcodes.c and opcodes.h
 #
-opcodes.c:	opcodes.h $(TOP)/tool/mkopcodec.tcl
+opcodes.c:	opcodes.h $(TOP)/tool/mkopcodec.tcl has_tclsh84
 	$(TCLSH_CMD) $(TOP)/tool/mkopcodec.tcl opcodes.h >opcodes.c
 
-opcodes.h:	parse.h $(TOP)/src/vdbe.c $(TOP)/tool/mkopcodeh.tcl
+opcodes.h:	parse.h $(TOP)/src/vdbe.c $(TOP)/tool/mkopcodeh.tcl has_tclsh84
 	cat parse.h $(TOP)/src/vdbe.c | $(TCLSH_CMD) $(TOP)/tool/mkopcodeh.tcl >opcodes.h
 
 # Rules to build parse.c and parse.h - the outputs of lemon.
@@ -1164,10 +1177,10 @@ parse.c:	$(TOP)/src/parse.y lemon$(BEXE)
 	cp $(TOP)/src/parse.y .
 	./lemon$(BEXE) $(OPT_FEATURE_FLAGS) $(OPTS) -S parse.y
 
-sqlite3.h:	$(TOP)/src/sqlite.h.in $(TOP)/manifest mksourceid$(BEXE) $(TOP)/VERSION
+sqlite3.h:	$(TOP)/src/sqlite.h.in $(TOP)/manifest mksourceid$(BEXE) $(TOP)/VERSION has_tclsh84
 	$(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) >sqlite3.h
 
-sqlite3rc.h:	$(TOP)/src/sqlite3.rc $(TOP)/VERSION
+sqlite3rc.h:	$(TOP)/src/sqlite3.rc $(TOP)/VERSION has_tclsh84
 	echo '#ifndef SQLITE_RESOURCE_VERSION' >$@
 	echo -n '#define SQLITE_RESOURCE_VERSION ' >>$@
 	cat $(TOP)/VERSION | $(TCLSH_CMD) $(TOP)/tool/replace.tcl exact . , >>$@
@@ -1203,7 +1216,7 @@ SHELL_SRC = \
 	$(TOP)/ext/recover/sqlite3recover.h \
         $(TOP)/src/test_windirent.c
 
-shell.c:	$(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
+shell.c:	$(SHELL_SRC) $(TOP)/tool/mkshellc.tcl has_tclsh84
 	$(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c
 
 
@@ -1294,7 +1307,7 @@ fts5parse.c:	$(TOP)/ext/fts5/fts5parse.y lemon$(BEXE)
 
 fts5parse.h: fts5parse.c
 
-fts5.c: $(FTS5_SRC)
+fts5.c: $(FTS5_SRC) has_tclsh84
 	$(TCLSH_CMD) $(TOP)/ext/fts5/tool/mkfts5c.tcl
 	cp $(TOP)/ext/fts5/fts5.h .
 
@@ -1328,7 +1341,7 @@ TESTFIXTURE_SRC1 = sqlite3.c
 TESTFIXTURE_SRC = $(TESTSRC) $(TOP)/src/tclsqlite.c
 TESTFIXTURE_SRC += $(TESTFIXTURE_SRC$(USE_AMALGAMATION))
 
-testfixture$(TEXE):	$(TESTFIXTURE_SRC)
+testfixture$(TEXE):	has_tclsh85 $(TESTFIXTURE_SRC)
 	$(LTLINK) -DSQLITE_NO_SYNC=1 $(TEMP_STORE) $(TESTFIXTURE_FLAGS) \
 		-o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS)
 
@@ -1352,11 +1365,17 @@ fulltestonly:	$(TESTPROGS) fuzztest
 	./testfixture$(TEXE) $(TOP)/test/full.test
 
 # Fuzz testing
-fuzztest:	fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db
+#
+# WARNING: When the "fuzztest" target is run by the testrunner.tcl script,
+# it does not actually run this code. Instead, it schedules equivalent 
+# commands. Therefore, if this target is updated, then code in
+# testrunner_data.tcl (search for "trd_fuzztest_data") must also be updated.
+#
+fuzztest:	fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE)
 	./fuzzcheck$(TEXE) $(FUZZDATA)
 	./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db
 
-valgrindfuzz:	fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db
+valgrindfuzz:	fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE)
 	valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M $(FUZZDATA)
 	valgrind ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db
 
@@ -1373,17 +1392,23 @@ testrunner:	testfixture$(TEXE)
 
 # Runs both fuzztest and testrunner, consecutively.
 #
-devtest:	testfixture$(TEXE) fuzztest testrunner
+devtest:	srctree-check testfixture$(TEXE) fuzztest testrunner
 
-mdevtest:
+mdevtest: srctree-check has_tclsh85
 	$(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest
 
-sdevtest:
+sdevtest: has_tclsh85
 	$(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest
 
+# Validate that various generated files in the source tree
+# are up-to-date.
+#
+srctree-check:	$(TOP)/tool/srctree-check.tcl
+	$(TCLSH_CMD) $(TOP)/tool/srctree-check.tcl
+
 # Testing for a release
 #
-releasetest: testfixture$(TEXE)
+releasetest: srctree-check testfixture$(TEXE)
 	./testfixture$(TEXE) $(TOP)/test/testrunner.tcl release
 
 # Minimal testing that runs in less than 3 minutes
@@ -1402,7 +1427,7 @@ rusttestwasm:	sqlite3.h liblibsql.la liblibsql_wasm
 # This is the common case.  Run many tests that do not take too long,
 # including fuzzcheck, sqlite3_analyzer, and sqldiff tests.
 #
-test:	fuzztest sourcetest $(TESTPROGS) tcltest
+test:	srctree-check fuzztest sourcetest $(TESTPROGS) tcltest
 
 # Run a test using valgrind.  This can take a really long time
 # because valgrind is so much slower than a native machine.
@@ -1420,13 +1445,13 @@ smoketest:	$(TESTPROGS) fuzzcheck$(TEXE)
 shelltest: $(TESTPROGS)
 	./testfixture$(TEXT) $(TOP)/test/permutations.test shell
 
-sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in
+sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in has_tclsh85
 	$(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in >sqlite3_analyzer.c
 
 sqlite3_analyzer$(TEXE): sqlite3_analyzer.c
 	$(LTLINK) sqlite3_analyzer.c -o $@ $(LIBTCL) $(TLIBS)
 
-sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl $(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in
+sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl $(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in has_tclsh85
 	$(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c
 
 sqltclsh$(TEXE): sqltclsh.c
@@ -1445,7 +1470,7 @@ CHECKER_DEPS =\
   $(TOP)/ext/misc/btreeinfo.c \
   $(TOP)/ext/repair/sqlite3_checker.c.in
 
-sqlite3_checker.c:	$(CHECKER_DEPS)
+sqlite3_checker.c:	$(CHECKER_DEPS) has_tclsh85
 	$(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/ext/repair/sqlite3_checker.c.in >$@
 
 sqlite3_checker$(TEXE):	sqlite3_checker.c
@@ -1531,6 +1556,11 @@ amalgamation-tarball: sqlite3.c sqlite3rc.h
 snapshot-tarball: sqlite3.c sqlite3rc.h
 	TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh --snapshot
 
+# Build a ZIP archive containing various command-line tools.
+#
+tool-zip:	testfixture sqlite3 sqldiff sqlite3_analyzer $(TOP)/tool/mktoolzip.tcl
+	./testfixture $(TOP)/tool/mktoolzip.tcl
+
 # The next two rules are used to support the "threadtest" target. Building
 # threadtest runs a few thread-safety tests that are implemented in C. This
 # target is invoked by the releasetest.tcl script.
@@ -1620,6 +1650,7 @@ clean:
 	rm -f bindings.rs
 	rm -f src-verify
 	rm -f custom.rws
+	rm -f has_tclsh84 has_tclsh85
 
 distclean:	clean
 	rm -f sqlite_cfg.h config.log config.status libtool Makefile sqlite3.pc libsql.pc sqlite3session.h \
@@ -1664,7 +1695,7 @@ fiddle: sqlite3.c shell.c
 	@echo 'Updating custom dictionary from tool/custom.txt'
 	aspell --lang=en create master ./custom.rws < $<
 
-misspell: ./custom.rws
+misspell: ./custom.rws has_tclsh84
 	$(TCLSH_CMD) ./tool/spellsift.tcl ./src/*.c ./src/*.h ./src/*.in
 
 #
diff --git a/libsql-sqlite3/Makefile.msc b/libsql-sqlite3/Makefile.msc
index 3179e301ce..4339be891f 100644
--- a/libsql-sqlite3/Makefile.msc
+++ b/libsql-sqlite3/Makefile.msc
@@ -52,8 +52,8 @@ MINIMAL_AMALGAMATION = 0
 USE_STDCALL = 0
 !ENDIF
 
-# Set this non-0 to use structured exception handling (SEH) for WAL mode
-# in the core library.
+# Use the USE_SEH=0 option on the nmake command line to omit structured
+# exception handling (SEH) support.  SEH is on by default.
 #
 !IFNDEF USE_SEH
 USE_SEH = 1
@@ -374,6 +374,7 @@ SQLITE_TCL_DEP =
 !IFNDEF OPT_FEATURE_FLAGS
 !IF $(MINIMAL_AMALGAMATION)==0
 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1
 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1
 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_GEOPOLY=1
 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1
@@ -403,10 +404,11 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1
 !ENDIF
 
 # Should structured exception handling (SEH) be enabled for WAL mode in
-# the core library?
+# the core library?  It is on by default.  Only omit it if the
+# USE_SEH=0 option is provided on the nmake command-line.
 #
-!IF $(USE_SEH)!=0
-OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1
+!IF $(USE_SEH)==0
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_OMIT_SEH=1
 !ENDIF
 
 # These are the "extended" SQLite compilation options used when compiling for
@@ -1592,8 +1594,7 @@ TESTEXT = \
   $(TOP)\ext\rtree\test_rtreedoc.c \
   $(TOP)\ext\recover\sqlite3recover.c \
   $(TOP)\ext\recover\test_recover.c \
-  $(TOP)\ext\recover\dbdata.c \
-  fts5.c
+  $(TOP)\ext\recover\dbdata.c 
 
 # If use of zlib is enabled, add the "zipfile.c" source file.
 #
@@ -1609,7 +1610,8 @@ TESTSRC2 = \
   $(SRC01) \
   $(SRC07) \
   $(SRC10) \
-  $(TOP)\ext\async\sqlite3async.c
+  $(TOP)\ext\async\sqlite3async.c \
+  fts5.c
 
 # Header files used by all library source files.
 #
@@ -1688,6 +1690,8 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_DQS=0
 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1
 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1
 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1
+SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1
+SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1
 !ENDIF
 
 # <<mark>>
@@ -1819,8 +1823,8 @@ $(SQLITE3EXE):	shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT
 		/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
 
 # <<mark>>
-sqldiff.exe:	$(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H)
-	$(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
+sqldiff.exe:	$(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
+	$(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS)
 
 dbhash.exe:	$(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H)
 	$(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
@@ -2466,6 +2470,9 @@ extensiontest:	testfixture.exe testloadext.dll
 	@set PATH=$(LIBTCLPATH);$(PATH)
 	.\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS)
 
+tool-zip:	testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe $(TOP)\tool\mktoolzip.tcl
+	.\testfixture.exe $(TOP)\tool\mktoolzip.tcl
+
 coretestprogs:	$(TESTPROGS)
 
 testprogs:	coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe
diff --git a/libsql-sqlite3/VERSION b/libsql-sqlite3/VERSION
index a9184766ba..faf0dcbb0e 100644
--- a/libsql-sqlite3/VERSION
+++ b/libsql-sqlite3/VERSION
@@ -1 +1 @@
-3.43.0
+3.44.0
diff --git a/libsql-sqlite3/autoconf/Makefile.msc b/libsql-sqlite3/autoconf/Makefile.msc
index 13663d8777..280bb95deb 100644
--- a/libsql-sqlite3/autoconf/Makefile.msc
+++ b/libsql-sqlite3/autoconf/Makefile.msc
@@ -52,8 +52,8 @@ MINIMAL_AMALGAMATION = 0
 USE_STDCALL = 0
 !ENDIF
 
-# Set this non-0 to use structured exception handling (SEH) for WAL mode
-# in the core library.
+# Use the USE_SEH=0 option on the nmake command line to omit structured
+# exception handling (SEH) support.  SEH is on by default.
 #
 !IFNDEF USE_SEH
 USE_SEH = 1
@@ -296,6 +296,7 @@ SQLITE3EXEPDB = /pdb:sqlite3sh.pdb
 !IFNDEF OPT_FEATURE_FLAGS
 !IF $(MINIMAL_AMALGAMATION)==0
 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1
 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1
 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_GEOPOLY=1
 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1
@@ -325,10 +326,11 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1
 !ENDIF
 
 # Should structured exception handling (SEH) be enabled for WAL mode in
-# the core library?
+# the core library?  It is on by default.  Only omit it if the
+# USE_SEH=0 option is provided on the nmake command-line.
 #
-!IF $(USE_SEH)!=0
-OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1
+!IF $(USE_SEH)==0
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_OMIT_SEH=1
 !ENDIF
 
 # These are the "extended" SQLite compilation options used when compiling for
@@ -986,6 +988,8 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_DQS=0
 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1
 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1
 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1
+SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1
+SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1
 !ENDIF
 
 
diff --git a/libsql-sqlite3/autoconf/tea/configure.ac b/libsql-sqlite3/autoconf/tea/configure.ac
index 093c99d67d..e34bda9f2e 100644
--- a/libsql-sqlite3/autoconf/tea/configure.ac
+++ b/libsql-sqlite3/autoconf/tea/configure.ac
@@ -19,7 +19,7 @@ dnl	to configure the system for the local environment.
 # so that we create the export library with the dll.
 #-----------------------------------------------------------------------
 
-AC_INIT([sqlite],[3.43.0])
+AC_INIT([sqlite],[3.44.0])
 
 #--------------------------------------------------------------------
 # Call TEA_INIT as the first TEA_ macro to set up initial vars.
diff --git a/libsql-sqlite3/configure b/libsql-sqlite3/configure
index d4ce2f4392..ab7afb4826 100755
--- a/libsql-sqlite3/configure
+++ b/libsql-sqlite3/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.71 for sqlite 3.43.0.
+# Generated by GNU Autoconf 2.71 for sqlite 3.44.0.
 #
 #
 # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,
@@ -757,8 +757,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='sqlite'
 PACKAGE_TARNAME='sqlite'
-PACKAGE_VERSION='3.43.0'
-PACKAGE_STRING='sqlite 3.43.0'
+PACKAGE_VERSION='3.44.0'
+PACKAGE_STRING='sqlite 3.44.0'
 PACKAGE_BUGREPORT=''
 PACKAGE_URL=''
 
@@ -805,6 +805,7 @@ CARGO_BIN
 HAVE_ZLIB
 USE_AMALGAMATION
 TARGET_DEBUG
+TARGET_HAVE_LINENOISE
 TARGET_HAVE_EDITLINE
 TARGET_HAVE_READLINE
 TARGET_READLINE_INC
@@ -932,6 +933,7 @@ enable_editline
 enable_readline
 with_readline_lib
 with_readline_inc
+with_linenoise
 enable_debug
 enable_amalgamation
 enable_load_extension
@@ -1509,7 +1511,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures sqlite 3.43.0 to adapt to many kinds of systems.
+\`configure' configures sqlite 3.44.0 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1575,7 +1577,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of sqlite 3.43.0:";;
+     short | recursive ) echo "Configuration of sqlite 3.44.0:";;
    esac
   cat <<\_ACEOF
 
@@ -1633,6 +1635,7 @@ Optional Packages:
                           (tclConfig.sh)
   --with-readline-lib     specify readline library
   --with-readline-inc     specify readline include paths
+  --with-linenoise=DIR    source directory for linenoise library
 
 Some influential environment variables:
   CC          C compiler command
@@ -1711,7 +1714,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-sqlite configure 3.43.0
+sqlite configure 3.44.0
 generated by GNU Autoconf 2.71
 
 Copyright (C) 2021 Free Software Foundation, Inc.
@@ -1986,7 +1989,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by sqlite $as_me 3.43.0, which was
+It was created by sqlite $as_me 3.44.0, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   $ $0$ac_configure_args_raw
@@ -4473,13 +4476,13 @@ then :
 else $as_nop
   lt_cv_nm_interface="BSD nm"
   echo "int some_variable = 0;" > conftest.$ac_ext
-  (eval echo "\"\$as_me:4476: $ac_compile\"" >&5)
+  (eval echo "\"\$as_me:4479: $ac_compile\"" >&5)
   (eval "$ac_compile" 2>conftest.err)
   cat conftest.err >&5
-  (eval echo "\"\$as_me:4479: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
+  (eval echo "\"\$as_me:4482: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
   (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
   cat conftest.err >&5
-  (eval echo "\"\$as_me:4482: output\"" >&5)
+  (eval echo "\"\$as_me:4485: output\"" >&5)
   cat conftest.out >&5
   if $GREP 'External.*some_variable' conftest.out > /dev/null; then
     lt_cv_nm_interface="MS dumpbin"
@@ -5730,7 +5733,7 @@ ia64-*-hpux*)
   ;;
 *-*-irix6*)
   # Find out which ABI we are using.
-  echo '#line 5733 "configure"' > conftest.$ac_ext
+  echo '#line 5736 "configure"' > conftest.$ac_ext
   if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
   (eval $ac_compile) 2>&5
   ac_status=$?
@@ -7073,11 +7076,11 @@ else $as_nop
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:7076: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:7079: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:7080: \$? = $ac_status" >&5
+   echo "$as_me:7083: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -7413,11 +7416,11 @@ else $as_nop
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:7416: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:7419: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:7420: \$? = $ac_status" >&5
+   echo "$as_me:7423: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -7520,11 +7523,11 @@ else $as_nop
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:7523: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:7526: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>out/conftest.err)
    ac_status=$?
    cat out/conftest.err >&5
-   echo "$as_me:7527: \$? = $ac_status" >&5
+   echo "$as_me:7530: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s out/conftest2.$ac_objext
    then
      # The compiler can only warn and ignore the option if not recognized
@@ -7576,11 +7579,11 @@ else $as_nop
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:7579: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:7582: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>out/conftest.err)
    ac_status=$?
    cat out/conftest.err >&5
-   echo "$as_me:7583: \$? = $ac_status" >&5
+   echo "$as_me:7586: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s out/conftest2.$ac_objext
    then
      # The compiler can only warn and ignore the option if not recognized
@@ -9964,7 +9967,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 9967 "configure"
+#line 9970 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -10061,7 +10064,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 10064 "configure"
+#line 10067 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -11776,6 +11779,28 @@ fi
 	fi
 fi
 
+# Check whether --with-linenoise was given.
+if test ${with_linenoise+y}
+then :
+  withval=$with_linenoise; with_linenoise=$withval
+else $as_nop
+  with_linenoise="no"
+fi
+
+if test "x$with_linenoise" != "xno"; then
+   TARGET_HAVE_READLINE=0
+   TARGET_HAVE_EDITLINE=0
+   TARGET_HAVE_LINENOISE=1
+   TARGET_READLINE_INC="-I${with_linenoise}"
+   TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c"
+   echo "using linenoise source code at ${with_linenoise}"
+else
+   TARGET_HAVE_LINENOISE=0
+   echo "not using linenoise"
+fi
+
+
+
 
 
 
@@ -11856,7 +11881,7 @@ fi
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking build type" >&5
 printf %s "checking build type... " >&6; }
 if test "${enable_debug}" = "yes" ; then
-  TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0"
+  TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall"
   { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: debug" >&5
 printf "%s\n" "debug" >&6; }
 else
@@ -13165,7 +13190,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by sqlite $as_me 3.43.0, which was
+This file was extended by sqlite $as_me 3.44.0, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -13233,7 +13258,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config='$ac_cs_config_escaped'
 ac_cs_version="\\
-sqlite config.status 3.43.0
+sqlite config.status 3.44.0
 configured by $0, generated by GNU Autoconf 2.71,
   with options \\"\$ac_cs_config\\"
 
diff --git a/libsql-sqlite3/configure.ac b/libsql-sqlite3/configure.ac
index eb8c8bfd77..f785963bff 100644
--- a/libsql-sqlite3/configure.ac
+++ b/libsql-sqlite3/configure.ac
@@ -601,11 +601,28 @@ if test x"$with_readline" != xno; then
 		TARGET_HAVE_READLINE=1
 	fi
 fi
+AC_ARG_WITH([linenoise],
+            [AS_HELP_STRING([--with-linenoise=DIR],[source directory for linenoise library])],
+            [with_linenoise=$withval],
+            [with_linenoise="no"])
+if test "x$with_linenoise" != "xno"; then
+   TARGET_HAVE_READLINE=0
+   TARGET_HAVE_EDITLINE=0
+   TARGET_HAVE_LINENOISE=1
+   TARGET_READLINE_INC="-I${with_linenoise}"
+   TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c"
+   echo "using linenoise source code at ${with_linenoise}"
+else
+   TARGET_HAVE_LINENOISE=0
+   echo "not using linenoise"
+fi
 
 AC_SUBST(TARGET_READLINE_LIBS)
 AC_SUBST(TARGET_READLINE_INC)
 AC_SUBST(TARGET_HAVE_READLINE)
 AC_SUBST(TARGET_HAVE_EDITLINE)
+AC_SUBST(TARGET_HAVE_LINENOISE)
+
 
 ##########
 # Figure out what C libraries are required to compile programs
@@ -618,7 +635,7 @@ AC_SEARCH_LIBS(fdatasync, [rt])
 AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[enable debugging & verbose explain]))
 AC_MSG_CHECKING([build type])
 if test "${enable_debug}" = "yes" ; then
-  TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0"
+  TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall"
   AC_MSG_RESULT([debug])
 else
   TARGET_DEBUG="-DNDEBUG"
diff --git a/libsql-sqlite3/doc/compile-for-windows.md b/libsql-sqlite3/doc/compile-for-windows.md
index 0bd39d21f1..20f79afa93 100644
--- a/libsql-sqlite3/doc/compile-for-windows.md
+++ b/libsql-sqlite3/doc/compile-for-windows.md
@@ -16,7 +16,7 @@ canonical source on a new Windows 11 PC, as of 2023-08-16:
       a 32-bit build.)  The subsequent steps will not work in a vanilla
       DOS prompt.  Nor will they work in PowerShell.
 
-  3.  Install TCL development libraries.  This note assumes that you wil
+  3.  Install TCL development libraries.  This note assumes that you will
       install the TCL development libraries in the "`c:\Tcl`" directory.
       Make adjustments
       if you want TCL installed somewhere else.  SQLite needs both the
@@ -83,3 +83,54 @@ following minor changes:
       <ul>
       <li>  `set PATH=c:\tcl32\bin;%PATH%`
       </ul>
+
+## Statically Linking The TCL Library
+
+Some utility programs associated with SQLite need to be linked
+with TCL in order to function.  The [sqlite3_analyzer.exe program](https://sqlite.org/sqlanalyze.html)
+is an example.  You can build as described above, and then
+enter:
+
+> ~~~~
+nmake /f Makefile.msc sqlite3_analyzer.exe
+~~~~
+
+And you will end up with a working executable.  However, that executable
+will depend on having the "tcl86.dll" library somewhere on your %PATH%.
+Use the following steps to build an executable that has the TCL library
+statically linked so that it does not depend on separate DLL:
+
+  1.  Use the appropriate "Command Prompt" window - either x86 or
+      x64, depending on whether you want a 32-bit or 64-bit executable.
+
+  2.  Untar the TCL source tarball into a fresh directory.  CD into
+      the "win/" subfolder.
+
+  3.  Run: `nmake /f makefile.vc OPTS=nothreads,static shell`
+
+
+  4.  CD into the "Release*" subfolder that is created (note the
+      wildcard - the full name of the directory might vary).  There
+      you will find the "tcl86s.lib" file.  Copy this file into the
+      same directory that you put the "tcl86.lib" on your initial
+      installation.  (In this document, that directory is
+      "`C:\Tcl32\lib`" for 32-bit builds and
+      "`C:\Tcl\lib`" for 64-bit builds.)
+
+  5.  CD into your SQLite source code directory and build the desired
+      utility program, but add the following extra arguments to the
+      nmake command line:
+      <blockquote><pre>
+      CCOPTS="-DSTATIC_BUILD" LIBTCL="tcl86s.lib netapi32.lib user32.lib"
+      </pre></blockquote>
+      <p>So, for example, to build a statically linked version of
+      sqlite3_analyzer.exe, you might type:
+      <blockquote><pre>
+      nmake /f Makefile.msc CCOPTS="-DSTATIC_BUILD" LIBTCL="tcl86s.lib netapi32.lib user32.lib" sqlite3_analyzer.exe
+      </pre></blockquote>
+
+  6.  After your executable is built, you can verify that it does not
+      depend on the TCL DLL by running:
+      <blockquote><pre>
+      dumpbin /dependents sqlite3_analyzer.exe
+      </pre></blockquote>
diff --git a/libsql-sqlite3/doc/testrunner.md b/libsql-sqlite3/doc/testrunner.md
new file mode 100644
index 0000000000..d828fd76d8
--- /dev/null
+++ b/libsql-sqlite3/doc/testrunner.md
@@ -0,0 +1,284 @@
+
+
+# The testrunner.tcl Script
+
+# 1. Overview
+
+testrunner.tcl is a Tcl script used to run multiple SQLite tests using 
+multiple jobs. It supports the following types of tests:
+
+  *  Tcl test scripts.
+
+  *  Tests run with [make] commands. Specifically, at time of writing, 
+     [make fuzztest], [make mptest], [make sourcetest] and [make threadtest].
+
+testrunner.tcl pipes the output of all tests and builds run into log file
+**testrunner.log**, created in the cwd directory. Searching this file for
+"failed" is a good way to find the output of a failed test.
+
+testrunner.tcl also populates SQLite database **testrunner.db**. This database
+contains details of all tests run, running and to be run. A useful query
+might be:
+
+```
+  SELECT * FROM script WHERE state='failed'
+```
+
+Running the command:
+
+```
+  ./testfixture $(TESTDIR)/testrunner.tcl status
+```
+
+in the directory containing the testrunner.db database runs various queries
+to produce a succinct report on the state of a running testrunner.tcl script.
+Running:
+
+```
+  watch ./testfixture $(TESTDIR)/testrunner.tcl status
+```
+
+in another terminal is a good way to keep an eye on a long running test.
+
+Sometimes testrunner.tcl uses the [testfixture] binary that it is run with
+to run tests (see "Binary Tests" below). Sometimes it builds testfixture and
+other binaries in specific configurations to test (see "Source Tests").
+
+# 2. Binary Tests
+
+The commands described in this section all run various combinations of the Tcl
+test scripts using the [testfixture] binary used to run the testrunner.tcl
+script (i.e. they do not invoke the compiler to build new binaries, or the
+[make] command to run tests that are not Tcl scripts). The procedure to run
+these tests is therefore:
+
+  1. Build the "testfixture" (or "testfixture.exe" for windows) binary using
+     whatever method seems convenient.
+
+  2. Test the binary built in step 1 by running testrunner.tcl with it, 
+     perhaps with various options.
+
+The following sub-sections describe the various options that can be
+passed to testrunner.tcl to test binary testfixture builds.
+
+## 2.1. Organization of Tcl Tests
+
+Tcl tests are stored in files that match the pattern *\*.test*. They are
+found in both the $TOP/test/ directory, and in the various sub-directories
+of the $TOP/ext/ directory of the source tree. Not all *\*.test* files
+contain Tcl tests - a handful are Tcl scripts designed to invoke other
+*\*.test* files.
+
+The **veryquick** set of tests is a subset of all Tcl test scripts in the
+source tree. In includes most tests, but excludes some that are very slow.
+Almost all fault-injection tests (those that test the response of the library
+to OOM or IO errors) are excluded. It is defined in source file 
+*test/permutations.test*.
+
+The **full** set of tests includes all Tcl test scripts in the source tree.
+To run a "full" test is to run all Tcl test scripts that can be found in the
+source tree.
+
+File *permutations.test* defines various test "permutations". A permutation
+consists of:
+
+  *  A subset of Tcl test scripts, and 
+
+  *  Runtime configuration to apply before running each test script 
+     (e.g. enabling auto-vacuum, or disable lookaside).
+
+Running **all** tests is to run all tests in the full test set, plus a dozen
+or so permutations. The specific permutations that are run as part of "all"
+are defined in file *testrunner_data.tcl*.
+
+## 2.2. Commands to Run Tests
+
+To run the "veryquick" test set, use either of the following:
+
+```
+  ./testfixture $TESTDIR/testrunner.tcl
+  ./testfixture $TESTDIR/testrunner.tcl veryquick
+```
+
+To run the "full" test suite:
+
+```
+  ./testfixture $TESTDIR/testrunner.tcl full
+```
+
+To run the subset of the "full" test suite for which the test file name matches
+a specified pattern (e.g. all tests that start with "fts5"), either of:
+
+```
+  ./testfixture $TESTDIR/testrunner.tcl fts5%
+  ./testfixture $TESTDIR/testrunner.tcl 'fts5*'
+```
+
+To run "all" tests (full + permutations):
+
+```
+  ./testfixture $TESTDIR/testrunner.tcl all
+```
+
+<a name=binary_test_failures></a>
+## 2.3. Investigating Binary Test Failures
+
+If a test fails, testrunner.tcl reports name of the Tcl test script and, if
+applicable, the name of the permutation, to stdout. This information can also
+be retrieved from either *testrunner.log* or *testrunner.db*.
+
+If there is no permutation, the individual test script may be run with:
+
+```
+  ./testfixture $PATH_TO_SCRIPT
+```
+
+Or, if the failure occured as part of a permutation:
+
+```
+  ./testfixture $TESTDIR/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT
+```
+
+TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT?
+
+# 3. Source Code Tests
+
+The commands described in this section invoke the C compiler to build 
+binaries from the source tree, then use those binaries to run Tcl and
+other tests. The advantages of this are that:
+
+  *  it is possible to test multiple build configurations with a single
+     command, and 
+
+  *  it ensures that tests are always run using binaries created with the
+     same set of compiler options.
+
+The testrunner.tcl commands described in this section may be run using
+either a *testfixture* (or testfixture.exe) build, or with any other Tcl
+shell that supports SQLite 3.31.1 or newer via "package require sqlite3".
+
+TODO: ./configure + Makefile.msc build systems.
+
+## Commands to Run SQLite Tests
+
+The **mdevtest** command is equivalent to running the veryquick tests and
+the [make fuzztest] target once for each of two --enable-all builds - one 
+with debugging enabled and one without:
+
+```
+  tclsh $TESTDIR/testrunner.tcl mdevtest
+```
+
+In other words, it is equivalent to running:
+
+```
+  $TOP/configure --enable-all --enable-debug
+  make fuzztest
+  make testfixture
+  ./testfixture $TOP/test/testrunner.tcl veryquick
+
+  # Then, after removing files created by the tests above:
+  $TOP/configure --enable-all OPTS="-O0"
+  make fuzztest
+  make testfixture
+  ./testfixture $TOP/test/testrunner.tcl veryquick
+```
+
+The **sdevtest** command is identical to the mdevtest command, except that the
+second of the two builds is a sanitizer build. Specifically, this means that
+OPTS="-fsanitize=address,undefined" is specified instead of OPTS="-O0":
+
+```
+  tclsh $TESTDIR/testrunner.tcl sdevtest
+```
+
+The **release** command runs lots of tests under lots of builds. It runs
+different combinations of builds and tests depending on whether it is run
+on Linux, Windows or OSX. Refer to *testrunner\_data.tcl* for the details
+of the specific tests run.
+
+```
+  tclsh $TESTDIR/testrunner.tcl release
+```
+
+## Running ZipVFS Tests
+
+testrunner.tcl can build a zipvfs-enabled testfixture and use it to run
+tests from the Zipvfs project with the following command:
+
+```
+  tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS
+```
+
+This can be combined with any of "mdevtest", "sdevtest" or "release" to
+test both SQLite and Zipvfs with a single command:
+
+```
+  tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest
+```
+
+## Investigating Source Code Test Failures
+
+Investigating a test failure that occurs during source code testing is a
+two step process:
+
+  1. Recreating the build configuration in which the test failed, and
+
+  2. Re-running the actual test.
+
+To recreate a build configuration, use the testrunner.tcl **script** command
+to create a build script. A build script is a bash script on Linux or OSX, or
+a dos \*.bat file on windows. For example:
+
+```
+  # Create a script that recreates build configuration "Device-One" on 
+  # Linux or OSX:
+  tclsh $TESTDIR/testrunner.tcl script Device-One > make.sh 
+
+  # Create a script that recreates build configuration "Have-Not" on Windows:
+  tclsh $TESTDIR/testrunner.tcl script Have-Not > make.bat 
+```
+
+The generated bash or \*.bat file script accepts a single argument - a makefile
+target to build. This may be used either to run a [make] command test directly,
+or else to build a testfixture (or testfixture.exe) binary with which to
+run a Tcl test script, as <a href=#binary_test_failures>described above</a>.
+
+
+
+# 4. Controlling CPU Core Utilization
+
+When running either binary or source code tests, testrunner.tcl reports the
+number of jobs it intends to use to stdout. e.g.
+
+```
+  $ ./testfixture $TESTDIR/testrunner.tcl
+  splitting work across 16 jobs
+  ... more output ...
+```
+
+By default, testfixture.tcl attempts to set the number of jobs to the number 
+of real cores on the machine. This can be overridden using the "--jobs" (or -j)
+switch:
+
+```
+  $ ./testfixture $TESTDIR/testrunner.tcl --jobs 8
+  splitting work across 8 jobs
+  ... more output ...
+```
+
+The number of jobs may also be changed while an instance of testrunner.tcl is
+running by exucuting the following command from the directory containing the
+testrunner.log and testrunner.db files:
+
+```
+  $ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS
+```
+
+
+
+
+
+
+
+
diff --git a/libsql-sqlite3/ext/expert/expert1.test b/libsql-sqlite3/ext/expert/expert1.test
index dee4eb9ec0..453334234d 100644
--- a/libsql-sqlite3/ext/expert/expert1.test
+++ b/libsql-sqlite3/ext/expert/expert1.test
@@ -464,4 +464,23 @@ do_execsql_test 5.3 {
   t2 t2_idx_0001295b {100 20 5}
 }
 
+if 0 {
+do_test expert1-6.0 {
+  catchcmd :memory: {
+.expert
+select base64('');
+.expert
+select name from pragma_collation_list order by name collate uint;
+}
+} {0 {(no new indexes)
+
+SCAN CONSTANT ROW
+
+(no new indexes)
+
+SCAN pragma_collation_list VIRTUAL TABLE INDEX 0:
+USE TEMP B-TREE FOR ORDER BY
+}}
+}
+
 finish_test
diff --git a/libsql-sqlite3/ext/expert/sqlite3expert.c b/libsql-sqlite3/ext/expert/sqlite3expert.c
index c01feff58c..33d62226f0 100644
--- a/libsql-sqlite3/ext/expert/sqlite3expert.c
+++ b/libsql-sqlite3/ext/expert/sqlite3expert.c
@@ -32,7 +32,7 @@
 #endif /* !defined(SQLITE_AMALGAMATION) */
 
 
-#ifndef SQLITE_OMIT_VIRTUALTABLE 
+#ifndef SQLITE_OMIT_VIRTUALTABLE
 
 typedef sqlite3_int64 i64;
 typedef sqlite3_uint64 u64;
@@ -662,6 +662,7 @@ static int idxRegisterVtab(sqlite3expert *p){
     0,                            /* xRelease */
     0,                            /* xRollbackTo */
     0,                            /* xShadowName */
+    0,                            /* xIntegrity */
   };
 
   return sqlite3_create_module(p->dbv, "expert", &expertModule, (void*)p);
@@ -1818,6 +1819,88 @@ static int idxPopulateStat1(sqlite3expert *p, char **pzErr){
   return rc;
 }
 
+/*
+** Define and possibly pretend to use a useless collation sequence.
+** This pretense allows expert to accept SQL using custom collations.
+*/
+int dummyCompare(void *up1, int up2, const void *up3, int up4, const void *up5){
+  (void)up1;
+  (void)up2;
+  (void)up3;
+  (void)up4;
+  (void)up5;
+  assert(0); /* VDBE should never be run. */
+  return 0;
+}
+/* And a callback to register above upon actual need */
+void useDummyCS(void *up1, sqlite3 *db, int etr, const char *zName){
+  (void)up1;
+  sqlite3_create_collation_v2(db, zName, etr, 0, dummyCompare, 0);
+}
+
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) \
+  && !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+/*
+** dummy functions for no-op implementation of UDFs during expert's work
+*/
+void dummyUDF(sqlite3_context *up1, int up2, sqlite3_value **up3){
+  (void)up1;
+  (void)up2;
+  (void)up3;
+  assert(0); /* VDBE should never be run. */
+}
+void dummyUDFvalue(sqlite3_context *up1){
+  (void)up1;
+  assert(0); /* VDBE should never be run. */
+}
+
+/*
+** Register UDFs from user database with another.
+*/
+int registerUDFs(sqlite3 *dbSrc, sqlite3 *dbDst){
+  sqlite3_stmt *pStmt;
+  int rc = sqlite3_prepare_v2(dbSrc,
+            "SELECT name,type,enc,narg,flags "
+            "FROM pragma_function_list() "
+            "WHERE builtin==0", -1, &pStmt, 0);
+  if( rc==SQLITE_OK ){
+    while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
+      int nargs = sqlite3_column_int(pStmt,3);
+      int flags = sqlite3_column_int(pStmt,4);
+      const char *name = (char*)sqlite3_column_text(pStmt,0);
+      const char *type = (char*)sqlite3_column_text(pStmt,1);
+      const char *enc = (char*)sqlite3_column_text(pStmt,2);
+      if( name==0 || type==0 || enc==0 ){
+        /* no-op.  Only happens on OOM */
+      }else{
+        int ienc = SQLITE_UTF8;
+        int rcf = SQLITE_ERROR;
+        if( strcmp(enc,"utf16le")==0 ) ienc = SQLITE_UTF16LE;
+        else if( strcmp(enc,"utf16be")==0 ) ienc = SQLITE_UTF16BE;
+        ienc |= (flags & (SQLITE_DETERMINISTIC|SQLITE_DIRECTONLY));
+        if( strcmp(type,"w")==0 ){
+          rcf = sqlite3_create_window_function(dbDst,name,nargs,ienc,0,
+                                               dummyUDF,dummyUDFvalue,0,0,0);
+        }else if( strcmp(type,"a")==0 ){
+          rcf = sqlite3_create_function(dbDst,name,nargs,ienc,0,
+                                        0,dummyUDF,dummyUDFvalue);
+        }else if( strcmp(type,"s")==0 ){
+          rcf = sqlite3_create_function(dbDst,name,nargs,ienc,0,
+                                        dummyUDF,0,0);
+        }
+        if( rcf!=SQLITE_OK ){
+          rc = rcf;
+          break;
+        }
+      }
+    }
+    sqlite3_finalize(pStmt);
+    if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+  }
+  return rc;
+}
+#endif
+
 /*
 ** Allocate a new sqlite3expert object.
 */
@@ -1844,7 +1927,21 @@ sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){
       sqlite3_db_config(pNew->dbm, SQLITE_DBCONFIG_TRIGGER_EQP, 1, (int*)0);
     }
   }
-  
+
+  /* Allow custom collations to be dealt with through prepare. */
+  if( rc==SQLITE_OK ) rc = sqlite3_collation_needed(pNew->dbm,0,useDummyCS);
+  if( rc==SQLITE_OK ) rc = sqlite3_collation_needed(pNew->dbv,0,useDummyCS);
+
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) \
+  && !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+  /* Register UDFs from database [db] with [dbm] and [dbv]. */
+  if( rc==SQLITE_OK ){
+    rc = registerUDFs(pNew->db, pNew->dbm);
+  }
+  if( rc==SQLITE_OK ){
+    rc = registerUDFs(pNew->db, pNew->dbv);
+  }
+#endif
 
   /* Copy the entire schema of database [db] into [dbm]. */
   if( rc==SQLITE_OK ){
@@ -1920,6 +2017,10 @@ int sqlite3_expert_sql(
 
   while( rc==SQLITE_OK && zStmt && zStmt[0] ){
     sqlite3_stmt *pStmt = 0;
+    /* Ensure that the provided statement compiles against user's DB. */
+    rc = idxPrepareStmt(p->db, &pStmt, pzErr, zStmt);
+    if( rc!=SQLITE_OK ) break;
+    sqlite3_finalize(pStmt);
     rc = sqlite3_prepare_v2(p->dbv, zStmt, -1, &pStmt, &zStmt);
     if( rc==SQLITE_OK ){
       if( pStmt ){
diff --git a/libsql-sqlite3/ext/fts3/fts3.c b/libsql-sqlite3/ext/fts3/fts3.c
index 43a9daf60d..65852804a1 100644
--- a/libsql-sqlite3/ext/fts3/fts3.c
+++ b/libsql-sqlite3/ext/fts3/fts3.c
@@ -640,6 +640,7 @@ static void fts3DeclareVtab(int *pRc, Fts3Table *p){
 
     zLanguageid = (p->zLanguageid ? p->zLanguageid : "__langid");
     sqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+    sqlite3_vtab_config(p->db, SQLITE_VTAB_INNOCUOUS);
 
     /* Create a list of user columns for the virtual table */
     zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]);
@@ -3889,6 +3890,8 @@ static int fts3RenameMethod(
     rc = sqlite3Fts3PendingTermsFlush(p);
   }
 
+  p->bIgnoreSavepoint = 1;
+
   if( p->zContentTbl==0 ){
     fts3DbExec(&rc, db,
       "ALTER TABLE %Q.'%q_content'  RENAME TO '%q_content';",
@@ -3916,6 +3919,8 @@ static int fts3RenameMethod(
     "ALTER TABLE %Q.'%q_segdir'   RENAME TO '%q_segdir';",
     p->zDb, p->zName, zName
   );
+
+  p->bIgnoreSavepoint = 0;
   return rc;
 }
 
@@ -3926,12 +3931,28 @@ static int fts3RenameMethod(
 */
 static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
   int rc = SQLITE_OK;
-  UNUSED_PARAMETER(iSavepoint);
-  assert( ((Fts3Table *)pVtab)->inTransaction );
-  assert( ((Fts3Table *)pVtab)->mxSavepoint <= iSavepoint );
-  TESTONLY( ((Fts3Table *)pVtab)->mxSavepoint = iSavepoint );
-  if( ((Fts3Table *)pVtab)->bIgnoreSavepoint==0 ){
-    rc = fts3SyncMethod(pVtab);
+  Fts3Table *pTab = (Fts3Table*)pVtab;
+  assert( pTab->inTransaction );
+  assert( pTab->mxSavepoint<=iSavepoint );
+  TESTONLY( pTab->mxSavepoint = iSavepoint );
+
+  if( pTab->bIgnoreSavepoint==0 ){
+    if( fts3HashCount(&pTab->aIndex[0].hPending)>0 ){
+      char *zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')",
+          pTab->zDb, pTab->zName, pTab->zName
+          );
+      if( zSql ){
+        pTab->bIgnoreSavepoint = 1;
+        rc = sqlite3_exec(pTab->db, zSql, 0, 0, 0);
+        pTab->bIgnoreSavepoint = 0;
+        sqlite3_free(zSql);
+      }else{
+        rc = SQLITE_NOMEM;
+      }
+    }
+    if( rc==SQLITE_OK ){
+      pTab->iSavepoint = iSavepoint+1;
+    }
   }
   return rc;
 }
@@ -3942,12 +3963,11 @@ static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
 ** This is a no-op.
 */
 static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
-  TESTONLY( Fts3Table *p = (Fts3Table*)pVtab );
-  UNUSED_PARAMETER(iSavepoint);
-  UNUSED_PARAMETER(pVtab);
-  assert( p->inTransaction );
-  assert( p->mxSavepoint >= iSavepoint );
-  TESTONLY( p->mxSavepoint = iSavepoint-1 );
+  Fts3Table *pTab = (Fts3Table*)pVtab;
+  assert( pTab->inTransaction );
+  assert( pTab->mxSavepoint >= iSavepoint );
+  TESTONLY( pTab->mxSavepoint = iSavepoint-1 );
+  pTab->iSavepoint = iSavepoint;
   return SQLITE_OK;
 }
 
@@ -3957,11 +3977,13 @@ static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
 ** Discard the contents of the pending terms table.
 */
 static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
-  Fts3Table *p = (Fts3Table*)pVtab;
+  Fts3Table *pTab = (Fts3Table*)pVtab;
   UNUSED_PARAMETER(iSavepoint);
-  assert( p->inTransaction );
-  TESTONLY( p->mxSavepoint = iSavepoint );
-  sqlite3Fts3PendingTermsClear(p);
+  assert( pTab->inTransaction );
+  TESTONLY( pTab->mxSavepoint = iSavepoint );
+  if( (iSavepoint+1)<=pTab->iSavepoint ){
+    sqlite3Fts3PendingTermsClear(pTab);
+  }
   return SQLITE_OK;
 }
 
@@ -3980,8 +4002,49 @@ static int fts3ShadowName(const char *zName){
   return 0;
 }
 
+/*
+** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual
+** table.
+*/
+static int fts3Integrity(
+  sqlite3_vtab *pVtab,      /* The virtual table to be checked */
+  const char *zSchema,      /* Name of schema in which pVtab lives */
+  const char *zTabname,     /* Name of the pVTab table */
+  int isQuick,              /* True if this is a quick_check */
+  char **pzErr              /* Write error message here */
+){
+  Fts3Table *p = (Fts3Table*)pVtab;
+  char *zSql;
+  int rc;
+  char *zErr = 0;
+
+  assert( pzErr!=0 );
+  assert( *pzErr==0 );
+  UNUSED_PARAMETER(isQuick);
+  zSql = sqlite3_mprintf(
+            "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
+            zSchema, zTabname, zTabname);
+  if( zSql==0 ){
+    return SQLITE_NOMEM;
+  }
+  rc = sqlite3_exec(p->db, zSql, 0, 0, &zErr);
+  sqlite3_free(zSql);
+  if( (rc&0xff)==SQLITE_CORRUPT ){
+    *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s",
+                p->bFts4 ? 4 : 3, zSchema, zTabname);
+  }else if( rc!=SQLITE_OK ){
+    *pzErr = sqlite3_mprintf("unable to validate the inverted index for"
+                             " FTS%d table %s.%s: %s",
+                p->bFts4 ? 4 : 3, zSchema, zTabname, zErr);
+  }
+  sqlite3_free(zErr);
+  return SQLITE_OK;
+}
+
+
+
 static const sqlite3_module fts3Module = {
-  /* iVersion      */ 3,
+  /* iVersion      */ 4,
   /* xCreate       */ fts3CreateMethod,
   /* xConnect      */ fts3ConnectMethod,
   /* xBestIndex    */ fts3BestIndexMethod,
@@ -4005,6 +4068,7 @@ static const sqlite3_module fts3Module = {
   /* xRelease      */ fts3ReleaseMethod,
   /* xRollbackTo   */ fts3RollbackToMethod,
   /* xShadowName   */ fts3ShadowName,
+  /* xIntegrity    */ fts3Integrity,
 };
 
 /*
diff --git a/libsql-sqlite3/ext/fts3/fts3Int.h b/libsql-sqlite3/ext/fts3/fts3Int.h
index 3a8a884f92..5a5b123b4c 100644
--- a/libsql-sqlite3/ext/fts3/fts3Int.h
+++ b/libsql-sqlite3/ext/fts3/fts3Int.h
@@ -265,6 +265,7 @@ struct Fts3Table {
   int nPgsz;                      /* Page size for host database */
   char *zSegmentsTbl;             /* Name of %_segments table */
   sqlite3_blob *pSegments;        /* Blob handle open on %_segments table */
+  int iSavepoint;
 
   /* 
   ** The following array of hash tables is used to buffer pending index 
diff --git a/libsql-sqlite3/ext/fts3/fts3_aux.c b/libsql-sqlite3/ext/fts3/fts3_aux.c
index d3b194c942..439d579366 100644
--- a/libsql-sqlite3/ext/fts3/fts3_aux.c
+++ b/libsql-sqlite3/ext/fts3/fts3_aux.c
@@ -545,7 +545,8 @@ int sqlite3Fts3InitAux(sqlite3 *db){
      0,                           /* xSavepoint    */
      0,                           /* xRelease      */
      0,                           /* xRollbackTo   */
-     0                            /* xShadowName   */
+     0,                           /* xShadowName   */
+     0                            /* xIntegrity    */
   };
   int rc;                         /* Return code */
 
diff --git a/libsql-sqlite3/ext/fts3/fts3_term.c b/libsql-sqlite3/ext/fts3/fts3_term.c
index 47e244e22c..f3a9746a09 100644
--- a/libsql-sqlite3/ext/fts3/fts3_term.c
+++ b/libsql-sqlite3/ext/fts3/fts3_term.c
@@ -362,7 +362,8 @@ int sqlite3Fts3InitTerm(sqlite3 *db){
      0,                           /* xSavepoint    */
      0,                           /* xRelease      */
      0,                           /* xRollbackTo   */
-     0                            /* xShadowName   */
+     0,                           /* xShadowName   */
+     0                            /* xIntegrity    */
   };
   int rc;                         /* Return code */
 
diff --git a/libsql-sqlite3/ext/fts3/fts3_tokenize_vtab.c b/libsql-sqlite3/ext/fts3/fts3_tokenize_vtab.c
index 65d7eef4c3..7e8d09bd48 100644
--- a/libsql-sqlite3/ext/fts3/fts3_tokenize_vtab.c
+++ b/libsql-sqlite3/ext/fts3/fts3_tokenize_vtab.c
@@ -445,7 +445,8 @@ int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash, void(*xDestroy)(void*)){
      0,                           /* xSavepoint    */
      0,                           /* xRelease      */
      0,                           /* xRollbackTo   */
-     0                            /* xShadowName   */
+     0,                           /* xShadowName   */
+     0                            /* xIntegrity    */
   };
   int rc;                         /* Return code */
 
diff --git a/libsql-sqlite3/ext/fts3/fts3_write.c b/libsql-sqlite3/ext/fts3/fts3_write.c
index 32b483b349..0f894943dd 100644
--- a/libsql-sqlite3/ext/fts3/fts3_write.c
+++ b/libsql-sqlite3/ext/fts3/fts3_write.c
@@ -3325,7 +3325,6 @@ int sqlite3Fts3PendingTermsFlush(Fts3Table *p){
     rc = fts3SegmentMerge(p, p->iPrevLangid, i, FTS3_SEGCURSOR_PENDING);
     if( rc==SQLITE_DONE ) rc = SQLITE_OK;
   }
-  sqlite3Fts3PendingTermsClear(p);
 
   /* Determine the auto-incr-merge setting if unknown.  If enabled,
   ** estimate the number of leaf blocks of content to be written
@@ -3347,6 +3346,10 @@ int sqlite3Fts3PendingTermsFlush(Fts3Table *p){
       rc = sqlite3_reset(pStmt);
     }
   }
+
+  if( rc==SQLITE_OK ){
+    sqlite3Fts3PendingTermsClear(p);
+  }
   return rc;
 }
 
@@ -3978,6 +3981,8 @@ static int fts3AppendToNode(
 
   blobGrowBuffer(pPrev, nTerm, &rc);
   if( rc!=SQLITE_OK ) return rc;
+  assert( pPrev!=0 );
+  assert( pPrev->a!=0 );
 
   nPrefix = fts3PrefixCompress(pPrev->a, pPrev->n, zTerm, nTerm);
   nSuffix = nTerm - nPrefix;
@@ -4034,9 +4039,13 @@ static int fts3IncrmergeAppend(
   nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist;
 
   /* If the current block is not empty, and if adding this term/doclist
-  ** to the current block would make it larger than Fts3Table.nNodeSize
-  ** bytes, write this block out to the database. */
-  if( pLeaf->block.n>0 && (pLeaf->block.n + nSpace)>p->nNodeSize ){
+  ** to the current block would make it larger than Fts3Table.nNodeSize bytes,
+  ** and if there is still room for another leaf page, write this block out to
+  ** the database. */
+  if( pLeaf->block.n>0 
+   && (pLeaf->block.n + nSpace)>p->nNodeSize 
+   && pLeaf->iBlock < (pWriter->iStart + pWriter->nLeafEst)
+  ){
     rc = fts3WriteSegment(p, pLeaf->iBlock, pLeaf->block.a, pLeaf->block.n);
     pWriter->nWork++;
 
@@ -4368,7 +4377,7 @@ static int fts3IncrmergeLoad(
               rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock,0);
               blobGrowBuffer(&pNode->block, 
                   MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc
-                  );
+              );
               if( rc==SQLITE_OK ){
                 memcpy(pNode->block.a, aBlock, nBlock);
                 pNode->block.n = nBlock;
@@ -5218,7 +5227,7 @@ static u64 fts3ChecksumIndex(
   int rc;
   u64 cksum = 0;
 
-  assert( *pRc==SQLITE_OK );
+  if( *pRc ) return 0;
 
   memset(&filter, 0, sizeof(filter));
   memset(&csr, 0, sizeof(csr));
@@ -5433,8 +5442,11 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){
     rc = fts3DoIncrmerge(p, &zVal[6]);
   }else if( nVal>10 && 0==sqlite3_strnicmp(zVal, "automerge=", 10) ){
     rc = fts3DoAutoincrmerge(p, &zVal[10]);
+  }else if( nVal==5 && 0==sqlite3_strnicmp(zVal, "flush", 5) ){
+    rc = sqlite3Fts3PendingTermsFlush(p);
+  }
 #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
-  }else{
+  else{
     int v;
     if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
       v = atoi(&zVal[9]);
@@ -5452,8 +5464,8 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){
       if( v>=4 && v<=FTS3_MERGE_COUNT && (v&1)==0 ) p->nMergeCount = v;
       rc = SQLITE_OK;
     }
-#endif
   }
+#endif
   return rc;
 }
 
diff --git a/libsql-sqlite3/ext/fts5/fts5_aux.c b/libsql-sqlite3/ext/fts5/fts5_aux.c
index b178f47334..fa58b9aac3 100644
--- a/libsql-sqlite3/ext/fts5/fts5_aux.c
+++ b/libsql-sqlite3/ext/fts5/fts5_aux.c
@@ -110,15 +110,19 @@ static int fts5CInstIterInit(
 */
 typedef struct HighlightContext HighlightContext;
 struct HighlightContext {
-  CInstIter iter;                 /* Coalesced Instance Iterator */
-  int iPos;                       /* Current token offset in zIn[] */
+  /* Constant parameters to fts5HighlightCb() */
   int iRangeStart;                /* First token to include */
   int iRangeEnd;                  /* If non-zero, last token to include */
   const char *zOpen;              /* Opening highlight */
   const char *zClose;             /* Closing highlight */
   const char *zIn;                /* Input text */
   int nIn;                        /* Size of input text in bytes */
-  int iOff;                       /* Current offset within zIn[] */
+
+  /* Variables modified by fts5HighlightCb() */
+  CInstIter iter;                 /* Coalesced Instance Iterator */
+  int iPos;                       /* Current token offset in zIn[] */
+  int iOff;                       /* Have copied up to this offset in zIn[] */
+  int bOpen;                      /* True if highlight is open */
   char *zOut;                     /* Output value */
 };
 
@@ -151,8 +155,8 @@ static int fts5HighlightCb(
   int tflags,                     /* Mask of FTS5_TOKEN_* flags */
   const char *pToken,             /* Buffer containing token */
   int nToken,                     /* Size of token in bytes */
-  int iStartOff,                  /* Start offset of token */
-  int iEndOff                     /* End offset of token */
+  int iStartOff,                  /* Start byte offset of token */
+  int iEndOff                     /* End byte offset of token */
 ){
   HighlightContext *p = (HighlightContext*)pContext;
   int rc = SQLITE_OK;
@@ -168,30 +172,47 @@ static int fts5HighlightCb(
     if( p->iRangeStart && iPos==p->iRangeStart ) p->iOff = iStartOff;
   }
 
-  if( iPos==p->iter.iStart ){
+  /* If the parenthesis is open, and this token is not part of the current
+  ** phrase, and the starting byte offset of this token is past the point
+  ** that has currently been copied into the output buffer, close the
+  ** parenthesis. */
+  if( p->bOpen 
+   && (iPos<=p->iter.iStart || p->iter.iStart<0)
+   && iStartOff>p->iOff 
+  ){
+    fts5HighlightAppend(&rc, p, p->zClose, -1);
+    p->bOpen = 0;
+  }
+
+  /* If this is the start of a new phrase, and the highlight is not open:
+  **
+  **   * copy text from the input up to the start of the phrase, and
+  **   * open the highlight.
+  */
+  if( iPos==p->iter.iStart && p->bOpen==0 ){
     fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iStartOff - p->iOff);
     fts5HighlightAppend(&rc, p, p->zOpen, -1);
     p->iOff = iStartOff;
+    p->bOpen = 1;
   }
 
   if( iPos==p->iter.iEnd ){
-    if( p->iRangeEnd>=0 && p->iter.iStart<p->iRangeStart ){
+    if( p->bOpen==0 ){
+      assert( p->iRangeEnd>=0 );
       fts5HighlightAppend(&rc, p, p->zOpen, -1);
+      p->bOpen = 1;
     }
     fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
-    fts5HighlightAppend(&rc, p, p->zClose, -1);
     p->iOff = iEndOff;
+
     if( rc==SQLITE_OK ){
       rc = fts5CInstIterNext(&p->iter);
     }
   }
 
-  if( p->iRangeEnd>=0 && iPos==p->iRangeEnd ){
+  if( iPos==p->iRangeEnd ){
     fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
     p->iOff = iEndOff;
-    if( iPos>=p->iter.iStart && iPos<p->iter.iEnd ){
-      fts5HighlightAppend(&rc, p, p->zClose, -1);
-    }
   }
 
   return rc;
@@ -232,6 +253,9 @@ static void fts5HighlightFunction(
     if( rc==SQLITE_OK ){
       rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
     }
+    if( ctx.bOpen ){
+      fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1);
+    }
     fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
 
     if( rc==SQLITE_OK ){
@@ -510,6 +534,9 @@ static void fts5SnippetFunction(
     if( rc==SQLITE_OK ){
       rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
     }
+    if( ctx.bOpen ){
+      fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1);
+    }
     if( ctx.iRangeEnd>=(nColSize-1) ){
       fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
     }else{
diff --git a/libsql-sqlite3/ext/fts5/fts5_index.c b/libsql-sqlite3/ext/fts5/fts5_index.c
index 267489a7eb..4e6afb2815 100644
--- a/libsql-sqlite3/ext/fts5/fts5_index.c
+++ b/libsql-sqlite3/ext/fts5/fts5_index.c
@@ -2904,7 +2904,6 @@ static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){
       assert_nc( i2!=0 );
       pRes->bTermEq = 1;
       if( p1->iRowid==p2->iRowid ){
-        p1->bDel = p2->bDel;
         return i2;
       }
       res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : +1;
@@ -3272,7 +3271,7 @@ static Fts5Iter *fts5MultiIterAlloc(
   int nSeg
 ){
   Fts5Iter *pNew;
-  int nSlot;                      /* Power of two >= nSeg */
+  i64 nSlot;                      /* Power of two >= nSeg */
 
   for(nSlot=2; nSlot<nSeg; nSlot=nSlot*2);
   pNew = fts5IdxMalloc(p, 
@@ -5048,7 +5047,6 @@ static void fts5DoSecureDelete(
   int iPgIdx = pSeg->pLeaf->szLeaf;
 
   u64 iDelta = 0;
-  u64 iNextDelta = 0;
   int iNextOff = 0;
   int iOff = 0;
   int nIdx = 0;
@@ -5056,8 +5054,6 @@ static void fts5DoSecureDelete(
   int bLastInDoclist = 0;
   int iIdx = 0;
   int iStart = 0;
-  int iKeyOff = 0;
-  int iPrevKeyOff = 0;
   int iDelKeyOff = 0;       /* Offset of deleted key, if any */
 
   nIdx = nPg-iPgIdx;
@@ -5082,10 +5078,21 @@ static void fts5DoSecureDelete(
   ** This block sets the following variables:
   **
   **   iStart:
+  **     The offset of the first byte of the rowid or delta-rowid
+  **     value for the doclist entry being removed.
+  **
   **   iDelta:
+  **     The value of the rowid or delta-rowid value for the doclist
+  **     entry being removed.
+  **
+  **   iNextOff:
+  **     The offset of the next entry following the position list
+  **     for the one being removed. If the position list for this
+  **     entry overflows onto the next leaf page, this value will be
+  **     greater than pLeaf->szLeaf.
   */
   {
-    int iSOP;
+    int iSOP;                     /* Start-Of-Position-list */
     if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){
       iStart = pSeg->iTermLeafOffset;
     }else{
@@ -5121,47 +5128,75 @@ static void fts5DoSecureDelete(
   }
 
   iOff = iStart;
-  if( iNextOff>=iPgIdx ){
-    int pgno = pSeg->iLeafPgno+1;
-    fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist);
-    iNextOff = iPgIdx;
-  }else{
-    /* Set bLastInDoclist to true if the entry being removed is the last
-    ** in its doclist.  */
-    for(iIdx=0, iKeyOff=0; iIdx<nIdx; /* no-op */){
-      u32 iVal = 0;
-      iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
-      iKeyOff += iVal;
-      if( iKeyOff==iNextOff ){
-        bLastInDoclist = 1;
+
+  /* Set variable bLastInDoclist to true if this entry happens to be
+  ** the last rowid in the doclist for its term.  */
+  if( pSeg->bDel==0 ){
+    if( iNextOff>=iPgIdx ){
+      int pgno = pSeg->iLeafPgno+1;
+      fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist);
+      iNextOff = iPgIdx;
+    }else{
+      /* Loop through the page-footer. If iNextOff (offset of the
+      ** entry following the one we are removing) is equal to the 
+      ** offset of a key on this page, then the entry is the last 
+      ** in its doclist.  */
+      int iKeyOff = 0;
+      for(iIdx=0; iIdx<nIdx; /* no-op */){
+        u32 iVal = 0;
+        iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
+        iKeyOff += iVal;
+        if( iKeyOff==iNextOff ){
+          bLastInDoclist = 1;
+        }
       }
     }
+
+    /* If this is (a) the first rowid on a page and (b) is not followed by
+    ** another position list on the same page, set the "first-rowid" field
+    ** of the header to 0.  */
+    if( fts5GetU16(&aPg[0])==iStart && (bLastInDoclist || iNextOff==iPgIdx) ){
+      fts5PutU16(&aPg[0], 0);
+    }
   }
 
-  if( fts5GetU16(&aPg[0])==iStart && (bLastInDoclist||iNextOff==iPgIdx) ){
-    fts5PutU16(&aPg[0], 0);
-  }
-
-  if( bLastInDoclist==0 ){
+  if( pSeg->bDel ){
+    iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta);
+    aPg[iOff++] = 0x01;
+  }else if( bLastInDoclist==0 ){
     if( iNextOff!=iPgIdx ){
+      u64 iNextDelta = 0;
       iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta);
       iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta);
     }
   }else if( 
-      iStart==pSeg->iTermLeafOffset && pSeg->iLeafPgno==pSeg->iTermLeafPgno 
+      pSeg->iLeafPgno==pSeg->iTermLeafPgno 
+   && iStart==pSeg->iTermLeafOffset 
   ){
     /* The entry being removed was the only position list in its
     ** doclist. Therefore the term needs to be removed as well. */
     int iKey = 0;
-    for(iIdx=0, iKeyOff=0; iIdx<nIdx; iKey++){
+    int iKeyOff = 0;
+
+    /* Set iKeyOff to the offset of the term that will be removed - the
+    ** last offset in the footer that is not greater than iStart. */
+    for(iIdx=0; iIdx<nIdx; iKey++){
       u32 iVal = 0;
       iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
       if( (iKeyOff+iVal)>(u32)iStart ) break;
       iKeyOff += iVal;
     }
+    assert_nc( iKey>=1 );
 
+    /* Set iDelKeyOff to the value of the footer entry to remove from 
+    ** the page. */
     iDelKeyOff = iOff = iKeyOff;
+
     if( iNextOff!=iPgIdx ){
+      /* This is the only position-list associated with the term, and there
+      ** is another term following it on this page. So the subsequent term
+      ** needs to be moved to replace the term associated with the entry
+      ** being removed. */
       int nPrefix = 0;
       int nSuffix = 0;
       int nPrefix2 = 0;
@@ -5198,80 +5233,88 @@ static void fts5DoSecureDelete(
       }
     }
   }else if( iStart==4 ){
-      int iPgno;
+    int iPgno;
 
-      assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
-      /* The entry being removed may be the only position list in
-      ** its doclist. */
-      for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
-        Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
-        int bEmpty = (pPg && pPg->nn==4);
-        fts5DataRelease(pPg);
-        if( bEmpty==0 ) break;
-      }
+    assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
+    /* The entry being removed may be the only position list in
+    ** its doclist. */
+    for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
+      Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
+      int bEmpty = (pPg && pPg->nn==4);
+      fts5DataRelease(pPg);
+      if( bEmpty==0 ) break;
+    }
 
-      if( iPgno==pSeg->iTermLeafPgno ){
-        i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
-        Fts5Data *pTerm = fts5DataRead(p, iId);
-        if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
-          u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
-          int nTermIdx = pTerm->nn - pTerm->szLeaf;
-          int iTermIdx = 0;
-          int iTermOff = 0;
+    if( iPgno==pSeg->iTermLeafPgno ){
+      i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
+      Fts5Data *pTerm = fts5DataRead(p, iId);
+      if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
+        u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
+        int nTermIdx = pTerm->nn - pTerm->szLeaf;
+        int iTermIdx = 0;
+        int iTermOff = 0;
 
-          while( 1 ){
-            u32 iVal = 0;
-            int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
-            iTermOff += iVal;
-            if( (iTermIdx+nByte)>=nTermIdx ) break;
-            iTermIdx += nByte;
-          }
-          nTermIdx = iTermIdx;
-
-          memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
-          fts5PutU16(&pTerm->p[2], iTermOff);
-
-          fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
-          if( nTermIdx==0 ){
-            fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
-          }
+        while( 1 ){
+          u32 iVal = 0;
+          int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
+          iTermOff += iVal;
+          if( (iTermIdx+nByte)>=nTermIdx ) break;
+          iTermIdx += nByte;
         }
-        fts5DataRelease(pTerm);
+        nTermIdx = iTermIdx;
+
+        memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
+        fts5PutU16(&pTerm->p[2], iTermOff);
+
+        fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
+        if( nTermIdx==0 ){
+          fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
+        }
+      }
+      fts5DataRelease(pTerm);
+    }
+  }
+
+  /* Assuming no error has occurred, this block does final edits to the
+  ** leaf page before writing it back to disk. Input variables are:
+  **
+  **   nPg: Total initial size of leaf page.
+  **   iPgIdx: Initial offset of page footer.
+  **
+  **   iOff: Offset to move data to
+  **   iNextOff: Offset to move data from
+  */
+  if( p->rc==SQLITE_OK ){
+    const int nMove = nPg - iNextOff;     /* Number of bytes to move */
+    int nShift = iNextOff - iOff;         /* Distance to move them */
+
+    int iPrevKeyOut = 0;
+    int iKeyIn = 0;
+
+    memmove(&aPg[iOff], &aPg[iNextOff], nMove);
+    iPgIdx -= nShift;
+    nPg = iPgIdx;
+    fts5PutU16(&aPg[2], iPgIdx);
+
+    for(iIdx=0; iIdx<nIdx; /* no-op */){
+      u32 iVal = 0;
+      iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
+      iKeyIn += iVal;
+      if( iKeyIn!=iDelKeyOff ){
+        int iKeyOut = (iKeyIn - (iKeyIn>iOff ? nShift : 0));
+        nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut);
+        iPrevKeyOut = iKeyOut;
       }
     }
 
-    if( p->rc==SQLITE_OK ){
-      const int nMove = nPg - iNextOff;
-      int nShift = 0;
-
-      memmove(&aPg[iOff], &aPg[iNextOff], nMove);
-      iPgIdx -= (iNextOff - iOff);
-      nPg = iPgIdx;
-      fts5PutU16(&aPg[2], iPgIdx);
-
-      nShift = iNextOff - iOff;
-      for(iIdx=0, iKeyOff=0, iPrevKeyOff=0; iIdx<nIdx; /* no-op */){
-        u32 iVal = 0;
-        iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
-        iKeyOff += iVal;
-        if( iKeyOff!=iDelKeyOff ){
-          if( iKeyOff>iOff ){
-            iKeyOff -= nShift;
-            nShift = 0;
-          }
-          nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOff - iPrevKeyOff);
-          iPrevKeyOff = iKeyOff;
-        }
-      }
-
-      if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
-        fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
-      }
-
-      assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
-      fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg,nPg);
+    if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
+      fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
     }
-    sqlite3_free(aIdx);
+
+    assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
+    fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg);
+  }
+  sqlite3_free(aIdx);
 }
 
 /*
@@ -5441,10 +5484,16 @@ static void fts5FlushOneHash(Fts5Index *p){
                 fts5WriteFlushLeaf(p, &writer);
               }
             }else{
-              int bDummy;
-              int nPos;
-              int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy);
-              nCopy += nPos;
+              int bDel = 0;
+              int nPos = 0;
+              int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDel);
+              if( bDel && bSecureDelete ){
+                fts5BufferAppendVarint(&p->rc, pBuf, nPos*2);
+                iOff += nCopy;
+                nCopy = nPos;
+              }else{
+                nCopy += nPos;
+              }
               if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){
                 /* The entire poslist will fit on the current leaf. So copy
                 ** it in one go. */
@@ -5482,7 +5531,6 @@ static void fts5FlushOneHash(Fts5Index *p){
         assert( pBuf->n<=pBuf->nSpace );
         if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash);
       }
-      sqlite3Fts5HashClear(pHash);
       fts5WriteFinish(p, &writer, &pgnoLast);
   
       assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 );
@@ -5515,7 +5563,6 @@ static void fts5FlushOneHash(Fts5Index *p){
   fts5IndexCrisismerge(p, &pStruct);
   fts5StructureWrite(p, pStruct);
   fts5StructureRelease(pStruct);
-  p->nContentlessDelete = 0;
 }
 
 /*
@@ -5526,8 +5573,12 @@ static void fts5IndexFlush(Fts5Index *p){
   if( p->nPendingData || p->nContentlessDelete ){
     assert( p->pHash );
     fts5FlushOneHash(p);
-    p->nPendingData = 0;
-    p->nPendingRow = 0;
+    if( p->rc==SQLITE_OK ){
+      sqlite3Fts5HashClear(p->pHash);
+      p->nPendingData = 0;
+      p->nPendingRow = 0;
+      p->nContentlessDelete = 0;
+    }
   }
 }
 
@@ -8269,7 +8320,8 @@ int sqlite3Fts5IndexInit(sqlite3 *db){
       0,                           /* xSavepoint    */
       0,                           /* xRelease      */
       0,                           /* xRollbackTo   */
-      0                            /* xShadowName   */
+      0,                           /* xShadowName   */
+      0                            /* xIntegrity    */
     };
     rc = sqlite3_create_module(db, "fts5_structure", &fts5structure_module, 0);
   }
diff --git a/libsql-sqlite3/ext/fts5/fts5_main.c b/libsql-sqlite3/ext/fts5/fts5_main.c
index c34a5a332b..6e86ca5951 100644
--- a/libsql-sqlite3/ext/fts5/fts5_main.c
+++ b/libsql-sqlite3/ext/fts5/fts5_main.c
@@ -117,6 +117,8 @@ struct Fts5FullTable {
   Fts5Storage *pStorage;          /* Document store */
   Fts5Global *pGlobal;            /* Global (connection wide) data */
   Fts5Cursor *pSortCsr;           /* Sort data from this cursor */
+  int iSavepoint;                 /* Successful xSavepoint()+1 */
+  int bInSavepoint;
 #ifdef SQLITE_DEBUG
   struct Fts5TransactionState ts;
 #endif
@@ -405,6 +407,13 @@ static int fts5InitVtab(
     pConfig->pzErrmsg = 0;
   }
 
+  if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
+    rc = sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, (int)1);
+  }
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+  }
+
   if( rc!=SQLITE_OK ){
     fts5FreeVtab(pTab);
     pTab = 0;
@@ -1329,6 +1338,9 @@ static int fts5FilterMethod(
     pCsr->iFirstRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64);
   }
 
+  rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
+  if( rc!=SQLITE_OK ) goto filter_out;
+
   if( pTab->pSortCsr ){
     /* If pSortCsr is non-NULL, then this call is being made as part of 
     ** processing for a "... MATCH <expr> ORDER BY rank" query (ePlan is
@@ -1351,6 +1363,7 @@ static int fts5FilterMethod(
     pCsr->pExpr = pTab->pSortCsr->pExpr;
     rc = fts5CursorFirst(pTab, pCsr, bDesc);
   }else if( pCsr->pExpr ){
+    assert( rc==SQLITE_OK );
     rc = fts5CursorParseRank(pConfig, pCsr, pRank);
     if( rc==SQLITE_OK ){
       if( bOrderByRank ){
@@ -1522,6 +1535,7 @@ static int fts5SpecialInsert(
   Fts5Config *pConfig = pTab->p.pConfig;
   int rc = SQLITE_OK;
   int bError = 0;
+  int bLoadConfig = 0;
 
   if( 0==sqlite3_stricmp("delete-all", zCmd) ){
     if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
@@ -1533,6 +1547,7 @@ static int fts5SpecialInsert(
     }else{
       rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage);
     }
+    bLoadConfig = 1;
   }else if( 0==sqlite3_stricmp("rebuild", zCmd) ){
     if( pConfig->eContent==FTS5_CONTENT_NONE ){
       fts5SetVtabError(pTab, 
@@ -1542,6 +1557,7 @@ static int fts5SpecialInsert(
     }else{
       rc = sqlite3Fts5StorageRebuild(pTab->pStorage);
     }
+    bLoadConfig = 1;
   }else if( 0==sqlite3_stricmp("optimize", zCmd) ){
     rc = sqlite3Fts5StorageOptimize(pTab->pStorage);
   }else if( 0==sqlite3_stricmp("merge", zCmd) ){
@@ -1554,6 +1570,8 @@ static int fts5SpecialInsert(
   }else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){
     pConfig->bPrefixIndex = sqlite3_value_int(pVal);
 #endif
+  }else if( 0==sqlite3_stricmp("flush", zCmd) ){
+    rc = sqlite3Fts5FlushToDisk(&pTab->p);
   }else{
     rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
     if( rc==SQLITE_OK ){
@@ -1567,6 +1585,12 @@ static int fts5SpecialInsert(
       }
     }
   }
+
+  if( rc==SQLITE_OK && bLoadConfig ){
+    pTab->p.pConfig->iCookie--;
+    rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
+  }
+
   return rc;
 }
 
@@ -1685,7 +1709,7 @@ static int fts5UpdateMethod(
     assert( nArg!=1 || eType0==SQLITE_INTEGER );
 
     /* Filter out attempts to run UPDATE or DELETE on contentless tables.
-    ** This is not suported. Except - DELETE is supported if the CREATE
+    ** This is not suported. Except - they are both supported if the CREATE
     ** VIRTUAL TABLE statement contained "contentless_delete=1". */
     if( eType0==SQLITE_INTEGER 
      && pConfig->eContent==FTS5_CONTENT_NONE 
@@ -1714,7 +1738,8 @@ static int fts5UpdateMethod(
       }
 
       else if( eType0!=SQLITE_INTEGER ){     
-        /* If this is a REPLACE, first remove the current entry (if any) */
+        /* An INSERT statement. If the conflict-mode is REPLACE, first remove
+        ** the current entry (if any). */
         if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){
           i64 iNew = sqlite3_value_int64(apVal[1]);  /* Rowid to delete */
           rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
@@ -2588,8 +2613,12 @@ static int fts5RenameMethod(
   sqlite3_vtab *pVtab,            /* Virtual table handle */
   const char *zName               /* New name of table */
 ){
+  int rc;
   Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
-  return sqlite3Fts5StorageRename(pTab->pStorage, zName);
+  pTab->bInSavepoint = 1;
+  rc = sqlite3Fts5StorageRename(pTab->pStorage, zName);
+  pTab->bInSavepoint = 0;
+  return rc;
 }
 
 int sqlite3Fts5FlushToDisk(Fts5Table *pTab){
@@ -2603,9 +2632,29 @@ int sqlite3Fts5FlushToDisk(Fts5Table *pTab){
 ** Flush the contents of the pending-terms table to disk.
 */
 static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
-  UNUSED_PARAM(iSavepoint);  /* Call below is a no-op for NDEBUG builds */
-  fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_SAVEPOINT, iSavepoint);
-  return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab);
+  Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+  int rc = SQLITE_OK;
+  char *zSql = 0;
+  fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint);
+
+  if( pTab->bInSavepoint==0 ){
+    zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')",
+        pTab->p.pConfig->zDb, pTab->p.pConfig->zName, pTab->p.pConfig->zName
+    );
+    if( zSql ){
+      pTab->bInSavepoint = 1;
+      rc = sqlite3_exec(pTab->p.pConfig->db, zSql, 0, 0, 0);
+      pTab->bInSavepoint = 0;
+      sqlite3_free(zSql);
+    }else{
+      rc = SQLITE_NOMEM;
+    }
+    if( rc==SQLITE_OK ){
+      pTab->iSavepoint = iSavepoint+1;
+    }
+  }
+
+  return rc;
 }
 
 /*
@@ -2614,9 +2663,16 @@ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
 ** This is a no-op.
 */
 static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
-  UNUSED_PARAM(iSavepoint);  /* Call below is a no-op for NDEBUG builds */
-  fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_RELEASE, iSavepoint);
-  return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab);
+  Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+  int rc = SQLITE_OK;
+  fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint);
+  if( (iSavepoint+1)<pTab->iSavepoint ){
+    rc = sqlite3Fts5FlushToDisk(&pTab->p);
+    if( rc==SQLITE_OK ){
+      pTab->iSavepoint = iSavepoint;
+    }
+  }
+  return rc;
 }
 
 /*
@@ -2626,11 +2682,14 @@ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
 */
 static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
   Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
-  UNUSED_PARAM(iSavepoint);  /* Call below is a no-op for NDEBUG builds */
+  int rc = SQLITE_OK;
   fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint);
   fts5TripCursors(pTab);
   pTab->p.pConfig->pgsz = 0;
-  return sqlite3Fts5StorageRollback(pTab->pStorage);
+  if( (iSavepoint+1)<=pTab->iSavepoint ){
+    rc = sqlite3Fts5StorageRollback(pTab->pStorage);
+  }
+  return rc;
 }
 
 /*
@@ -2850,9 +2909,46 @@ static int fts5ShadowName(const char *zName){
   return 0;
 }
 
+/*
+** Run an integrity check on the FTS5 data structures.  Return a string
+** if anything is found amiss.  Return a NULL pointer if everything is
+** OK.
+*/
+static int fts5Integrity(
+  sqlite3_vtab *pVtab,    /* the FTS5 virtual table to check */
+  const char *zSchema,    /* Name of schema in which this table lives */
+  const char *zTabname,   /* Name of the table itself */
+  int isQuick,            /* True if this is a quick-check */
+  char **pzErr            /* Write error message here */
+){
+  Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+  Fts5Config *pConfig = pTab->p.pConfig;
+  char *zSql;
+  char *zErr = 0;
+  int rc;
+  assert( pzErr!=0 && *pzErr==0 );
+  UNUSED_PARAM(isQuick);
+  zSql = sqlite3_mprintf(
+            "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
+            zSchema, zTabname, pConfig->zName);
+  if( zSql==0 ) return SQLITE_NOMEM;
+  rc = sqlite3_exec(pConfig->db, zSql, 0, 0, &zErr);
+  sqlite3_free(zSql);
+  if( (rc&0xff)==SQLITE_CORRUPT ){
+    *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s",
+                zSchema, zTabname);
+  }else if( rc!=SQLITE_OK ){
+    *pzErr = sqlite3_mprintf("unable to validate the inverted index for"
+                             " FTS5 table %s.%s: %s",
+                zSchema, zTabname, zErr);
+  }
+  sqlite3_free(zErr);
+  return SQLITE_OK;
+}
+
 static int fts5Init(sqlite3 *db){
   static const sqlite3_module fts5Mod = {
-    /* iVersion      */ 3,
+    /* iVersion      */ 4,
     /* xCreate       */ fts5CreateMethod,
     /* xConnect      */ fts5ConnectMethod,
     /* xBestIndex    */ fts5BestIndexMethod,
@@ -2875,7 +2971,8 @@ static int fts5Init(sqlite3 *db){
     /* xSavepoint    */ fts5SavepointMethod,
     /* xRelease      */ fts5ReleaseMethod,
     /* xRollbackTo   */ fts5RollbackToMethod,
-    /* xShadowName   */ fts5ShadowName
+    /* xShadowName   */ fts5ShadowName,
+    /* xIntegrity    */ fts5Integrity
   };
 
   int rc;
diff --git a/libsql-sqlite3/ext/fts5/fts5_storage.c b/libsql-sqlite3/ext/fts5/fts5_storage.c
index 0a0af9d4b5..9480da7c52 100644
--- a/libsql-sqlite3/ext/fts5/fts5_storage.c
+++ b/libsql-sqlite3/ext/fts5/fts5_storage.c
@@ -1184,7 +1184,9 @@ int sqlite3Fts5StorageSync(Fts5Storage *p){
   i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db);
   if( p->bTotalsValid ){
     rc = fts5StorageSaveTotals(p);
-    p->bTotalsValid = 0;
+    if( rc==SQLITE_OK ){
+      p->bTotalsValid = 0;
+    }
   }
   if( rc==SQLITE_OK ){
     rc = sqlite3Fts5IndexSync(p->pIndex);
diff --git a/libsql-sqlite3/ext/fts5/fts5_test_tok.c b/libsql-sqlite3/ext/fts5/fts5_test_tok.c
index a5d839da66..994d304dc6 100644
--- a/libsql-sqlite3/ext/fts5/fts5_test_tok.c
+++ b/libsql-sqlite3/ext/fts5/fts5_test_tok.c
@@ -472,7 +472,8 @@ int sqlite3Fts5TestRegisterTok(sqlite3 *db, fts5_api *pApi){
      0,                           /* xSavepoint    */
      0,                           /* xRelease      */
      0,                           /* xRollbackTo   */
-     0                            /* xShadowName   */
+     0,                           /* xShadowName   */
+     0                            /* xIntegrity    */
   };
   int rc;                         /* Return code */
 
diff --git a/libsql-sqlite3/ext/fts5/fts5_vocab.c b/libsql-sqlite3/ext/fts5/fts5_vocab.c
index 18774c4e4a..d738ada311 100644
--- a/libsql-sqlite3/ext/fts5/fts5_vocab.c
+++ b/libsql-sqlite3/ext/fts5/fts5_vocab.c
@@ -783,7 +783,8 @@ int sqlite3Fts5VocabInit(Fts5Global *pGlobal, sqlite3 *db){
     /* xSavepoint    */ 0,
     /* xRelease      */ 0,
     /* xRollbackTo   */ 0,
-    /* xShadowName   */ 0
+    /* xShadowName   */ 0,
+    /* xIntegrity    */ 0
   };
   void *p = (void*)pGlobal;
 
diff --git a/libsql-sqlite3/ext/fts5/test/fts5aa.test b/libsql-sqlite3/ext/fts5/test/fts5aa.test
index 59ce4f6a1f..e1551fc516 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5aa.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5aa.test
@@ -65,7 +65,9 @@ foreach w {a b c d e f} {
 
 do_execsql_test 2.4 {
   INSERT INTO t1(t1) VALUES('integrity-check');
-}
+  PRAGMA integrity_check;
+  PRAGMA integrity_check(t1);
+} {ok ok}
 
 
 #-------------------------------------------------------------------------
@@ -88,6 +90,7 @@ foreach {i x y} {
 } {
   do_execsql_test 3.$i.1 { INSERT INTO t1 VALUES($x, $y) }
   do_execsql_test 3.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+  do_execsql_test 3.$i.3 { PRAGMA integrity_check(t1) } ok
   if {[set_test_counter errors]} break
 }
 
@@ -135,7 +138,7 @@ foreach {i x y} {
    10 {ddd abcde dddd dd c} {dddd c c d abcde}
 } {
   do_execsql_test 5.$i.1 { INSERT INTO t1 VALUES($x, $y) }
-  do_execsql_test 5.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+  do_execsql_test 5.$i.2 { PRAGMA integrity_check(t1) } ok
   if {[set_test_counter errors]} break
 }
 
diff --git a/libsql-sqlite3/ext/fts5/test/fts5aux.test b/libsql-sqlite3/ext/fts5/test/fts5aux.test
index 561067c4bc..b5a13aea1a 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5aux.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5aux.test
@@ -307,5 +307,31 @@ do_catchsql_test 10.1.4 {
   SELECT group_concat(firstcol(t1), '.') FROM t1 GROUP BY rowid
 } {1 {unable to use function firstcol in the requested context}}
 
-finish_test
+#-------------------------------------------------------------------------
+# Test that xInstCount() works from within an xPhraseQuery() callback.
+#
+reset_db
 
+proc xCallback {cmd} {
+  incr ::hitcount [$cmd xInstCount]
+  return SQLITE_OK
+}
+proc fts5_hitcount {cmd} {
+  set ::hitcount 0
+  $cmd xQueryPhrase 0 xCallback
+  return $::hitcount
+}
+sqlite3_fts5_create_function db fts5_hitcount fts5_hitcount
+
+do_execsql_test 11.1 {
+  CREATE VIRTUAL TABLE x1 USING fts5(z);
+  INSERT INTO x1 VALUES('one two three');
+  INSERT INTO x1 VALUES('one two one three one');
+  INSERT INTO x1 VALUES('one two three');
+}
+
+do_execsql_test 11.2 {
+  SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1;
+} {5}
+
+finish_test
diff --git a/libsql-sqlite3/ext/fts5/test/fts5conflict.test b/libsql-sqlite3/ext/fts5/test/fts5conflict.test
index 644db53a1e..b5bf0a1160 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5conflict.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5conflict.test
@@ -65,4 +65,44 @@ do_execsql_test 2.1 {
   INSERT INTO fts_idx(fts_idx) VALUES('integrity-check');
 }
 
+#-------------------------------------------------------------------------
+# Tests for OR IGNORE conflict handling.
+#
+reset_db
+foreach_detail_mode $::testprefix {
+
+  do_execsql_test 3.0 {
+    CREATE VIRTUAL TABLE t1 USING fts5(xyz, detail=%DETAIL%);
+
+    BEGIN;
+    INSERT INTO t1(rowid, xyz) VALUES(13, 'thirteen documents');
+    INSERT INTO t1(rowid, xyz) VALUES(14, 'fourteen documents');
+    INSERT INTO t1(rowid, xyz) VALUES(15, 'fifteen documents');
+    COMMIT;
+  }
+
+  set db_cksum [cksum]
+    foreach {tn sql} {
+    1 {
+      INSERT OR IGNORE INTO t1(rowid, xyz) VALUES(14, 'new text');
+    }
+    2 {
+      UPDATE OR IGNORE t1 SET rowid=13 WHERE rowid=15;
+    }
+    3 {
+      INSERT OR IGNORE INTO t1(rowid, xyz) 
+      SELECT 13, 'some text'
+      UNION ALL
+      SELECT 14, 'some text'
+      UNION ALL
+      SELECT 15, 'some text'
+    }
+  } {
+    do_execsql_test 3.1.$tn.1 $sql
+    do_test 3.1.$tn.2 { cksum } $db_cksum
+  }
+
+}
+
+
 finish_test
diff --git a/libsql-sqlite3/ext/fts5/test/fts5content.test b/libsql-sqlite3/ext/fts5/test/fts5content.test
index 74a74e2ad0..ca2726a902 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5content.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5content.test
@@ -294,4 +294,3 @@ do_catchsql_test 7.2.5 {
 } {1 {recursively defined fts5 content table}}
 
 finish_test
-
diff --git a/libsql-sqlite3/ext/fts5/test/fts5contentless.test b/libsql-sqlite3/ext/fts5/test/fts5contentless.test
index f75ccb44c2..48cfd10ffa 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5contentless.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5contentless.test
@@ -268,4 +268,3 @@ do_execsql_test 8.2 {
 } {}
 
 finish_test
-
diff --git a/libsql-sqlite3/ext/fts5/test/fts5contentless2.test b/libsql-sqlite3/ext/fts5/test/fts5contentless2.test
index fbb857ab38..fdd7a60fce 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5contentless2.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5contentless2.test
@@ -205,4 +205,3 @@ foreach {tn step} {
 
 
 finish_test
-
diff --git a/libsql-sqlite3/ext/fts5/test/fts5contentless3.test b/libsql-sqlite3/ext/fts5/test/fts5contentless3.test
index a44311e45a..76119dc592 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5contentless3.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5contentless3.test
@@ -193,4 +193,3 @@ do_execsql_test 3.7 {
 
 
 finish_test
-
diff --git a/libsql-sqlite3/ext/fts5/test/fts5contentless4.test b/libsql-sqlite3/ext/fts5/test/fts5contentless4.test
index 1c2666dcf8..702b33a9de 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5contentless4.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5contentless4.test
@@ -245,4 +245,3 @@ do_execsql_test 4.3 {
 }
 
 finish_test
-
diff --git a/libsql-sqlite3/ext/fts5/test/fts5contentless5.test b/libsql-sqlite3/ext/fts5/test/fts5contentless5.test
index 1541b0c68d..a20134d1e7 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5contentless5.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5contentless5.test
@@ -56,4 +56,3 @@ foreach {tn up err} {
 }
 
 finish_test
-
diff --git a/libsql-sqlite3/ext/fts5/test/fts5corrupt.test b/libsql-sqlite3/ext/fts5/test/fts5corrupt.test
index 5f13513ec7..9aa84a0ef2 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5corrupt.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5corrupt.test
@@ -48,6 +48,10 @@ do_test 1.3 {
   }
   catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
 } {1 {database disk image is malformed}}
+do_execsql_test 1.3b {
+  PRAGMA integrity_check(t1);
+} {{malformed inverted index for FTS5 table main.t1}}
+
 
 do_test 1.4 {
   db_restore_and_reopen
diff --git a/libsql-sqlite3/ext/fts5/test/fts5corrupt2.test b/libsql-sqlite3/ext/fts5/test/fts5corrupt2.test
index a815320b76..06e2e74258 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5corrupt2.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5corrupt2.test
@@ -167,6 +167,9 @@ foreach {tn hdr} {
       do_test 3.$tn.$tn2.2 {
         catchsql { INSERT INTO x3(x3) VALUES('integrity-check') }
       } {1 {database disk image is malformed}}
+      do_execsql_test 3.$tn.$tn2.3 {
+        PRAGMA integrity_check(x3);
+      } {{malformed inverted index for FTS5 table main.x3}}
     }
 
     execsql ROLLBACK
diff --git a/libsql-sqlite3/ext/fts5/test/fts5faultG.test b/libsql-sqlite3/ext/fts5/test/fts5faultG.test
new file mode 100644
index 0000000000..bdcc153ad2
--- /dev/null
+++ b/libsql-sqlite3/ext/fts5/test/fts5faultG.test
@@ -0,0 +1,50 @@
+# 2010 June 15
+#
+# 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.
+#
+#***********************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+source $testdir/malloc_common.tcl
+set testprefix fts5faultG
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+  finish_test
+  return
+}
+
+set ::testprefix fts5faultG
+
+
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE t1 USING fts5(a);
+  INSERT INTO t1 VALUES('test renaming the table');
+  INSERT INTO t1 VALUES(' after it has been written');
+  INSERT INTO t1 VALUES(' actually other stuff instead');
+}
+faultsim_save_and_close
+do_faultsim_test 1 -faults oom* -prep { 
+  faultsim_restore_and_reopen
+  execsql {
+    BEGIN;
+      DELETE FROM t1 WHERE rowid=2;
+  }
+} -body {
+  execsql {
+    DELETE FROM t1;
+  }
+} -test {
+  catchsql { COMMIT }
+  faultsim_integrity_check
+  faultsim_test_result {0 {}}
+}
+
+
+finish_test
diff --git a/libsql-sqlite3/ext/fts5/test/fts5integrity.test b/libsql-sqlite3/ext/fts5/test/fts5integrity.test
index 4038830861..f9851dc15a 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5integrity.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5integrity.test
@@ -77,6 +77,9 @@ do_catchsql_test 4.2 {
     UPDATE aa_docsize SET sz = X'44' WHERE rowid = 3;
     INSERT INTO aa(aa) VALUES('integrity-check'); 
 } {1 {database disk image is malformed}}
+do_execsql_test 4.2.1 {
+  PRAGMA integrity_check(aa);
+} {{malformed inverted index for FTS5 table main.aa}}
 
 do_catchsql_test 4.3 { 
   ROLLBACK;
@@ -317,4 +320,39 @@ do_catchsql_test 10.5.3 {
   INSERT INTO vt0(vt0) VALUES('integrity-check');
 } {0 {}}
 
+reset_db
+proc slang {in} {return [string map {th d e eh} $in]}
+db function slang -deterministic -innocuous slang
+do_execsql_test 11.0 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT AS (slang(b)));
+  INSERT INTO t1(b) VALUES('the quick fox jumps over the lazy brown dog');
+  SELECT c FROM t1;
+} {{deh quick fox jumps ovehr deh lazy brown dog}}
+
+do_execsql_test 11.1 {
+  CREATE VIRTUAL TABLE t2 USING fts5(content="t1", c);
+  INSERT INTO t2(t2) VALUES('rebuild');
+  SELECT rowid FROM t2 WHERE t2 MATCH 'deh';
+} {1}
+
+do_execsql_test 11.2 {
+  PRAGMA integrity_check(t2);
+} {ok}
+db close
+sqlite3 db test.db
+
+# FIX ME?
+#
+# FTS5 integrity-check does not care if the content table is unreadable or
+# does not exist.  It only looks for internal inconsistencies in the
+# inverted index.
+#
+do_execsql_test 11.3 {
+  PRAGMA integrity_check(t2);
+} {ok}
+do_execsql_test 11.4 {
+  DROP TABLE t1;
+  PRAGMA integrity_check(t2);
+} {ok}
+
 finish_test
diff --git a/libsql-sqlite3/ext/fts5/test/fts5misc.test b/libsql-sqlite3/ext/fts5/test/fts5misc.test
index da3f652697..d67d79e29f 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5misc.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5misc.test
@@ -44,12 +44,12 @@ do_catchsql_test 1.2.2 {
 
 do_catchsql_test 1.3.1 { 
   SELECT highlight(t1, 4, '<b>', '</b>') FROM t1('*reads'); 
-} {1 {no such cursor: 1}}
+} {1 {no such cursor: 2}}
 
 do_catchsql_test 1.3.2 {
   SELECT a FROM t1
     WHERE rank = (SELECT highlight(t1, 4, '<b>', '</b>') FROM t1('*reads'));
-} {1 {no such cursor: 1}}
+} {1 {no such cursor: 2}}
 
 db close
 sqlite3 db test.db
@@ -424,10 +424,12 @@ do_execsql_test -db db2 15.3 {
   SAVEPOINT one;
 } {}
 do_execsql_test 15.4 END
-do_test 15.4 {
+do_test 15.5 {
   list [catch { db2 eval COMMIT } msg] $msg
 } {0 {}}
 
+db2 close
+
 #-------------------------------------------------------------------------
 reset_db
 forcedelete test.db2
@@ -469,6 +471,8 @@ do_execsql_test -db db2 16.6 {
   SELECT * FROM x1
 } {abc def}
 
+db2 close
+
 #-------------------------------------------------------------------------
 reset_db
 do_execsql_test 17.1 {
diff --git a/libsql-sqlite3/ext/fts5/test/fts5optimize2.test b/libsql-sqlite3/ext/fts5/test/fts5optimize2.test
index a0782ee790..b0b28874c3 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5optimize2.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5optimize2.test
@@ -9,7 +9,7 @@
 #
 #***********************************************************************
 #
-# TESTRUNNER: slow
+# TESTRUNNER: superslow
 #
 
 source [file join [file dirname [info script]] fts5_common.tcl]
@@ -42,23 +42,4 @@ do_execsql_test 1.2 {
   SELECT count(*) FROM t1('mno')
 } $nLoop
 
-do_execsql_test 2.0 {
-  CREATE VIRTUAL TABLE t2 USING fts5(x);
-  INSERT INTO t2(t2, rank) VALUES('pgsz', 32);
-}
-
-do_test 2.1 {
-  for {set ii 0} {$ii < $nLoop} {incr ii} {
-    execsql {
-      INSERT INTO t2 VALUES('abc def ghi');
-      INSERT INTO t2 VALUES('jkl mno pqr');
-      INSERT INTO t2(t2, rank) VALUES('merge', -1);
-    }
-  }
-} {}
-
-do_execsql_test 2.2 {
-  SELECT count(*) FROM t2('mno')
-} $nLoop
-
 finish_test
diff --git a/libsql-sqlite3/ext/fts5/test/fts5optimize3.test b/libsql-sqlite3/ext/fts5/test/fts5optimize3.test
new file mode 100644
index 0000000000..7b11b9402d
--- /dev/null
+++ b/libsql-sqlite3/ext/fts5/test/fts5optimize3.test
@@ -0,0 +1,45 @@
+# 2023 Aug 27
+#
+# 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.
+#
+#***********************************************************************
+#
+# TESTRUNNER: superslow
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5optimize2
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+  finish_test
+  return
+}
+
+set nLoop 2500
+
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE t2 USING fts5(x);
+  INSERT INTO t2(t2, rank) VALUES('pgsz', 32);
+}
+
+do_test 1.1 {
+  for {set ii 0} {$ii < $nLoop} {incr ii} {
+    execsql {
+      INSERT INTO t2 VALUES('abc def ghi');
+      INSERT INTO t2 VALUES('jkl mno pqr');
+      INSERT INTO t2(t2, rank) VALUES('merge', -1);
+    }
+  }
+} {}
+
+do_execsql_test 1.2 {
+  SELECT count(*) FROM t2('mno')
+} $nLoop
+
+finish_test
diff --git a/libsql-sqlite3/ext/fts5/test/fts5rank.test b/libsql-sqlite3/ext/fts5/test/fts5rank.test
index 22534e8e03..8cf223f44b 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5rank.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5rank.test
@@ -180,4 +180,28 @@ do_execsql_test 6.1 {
   {table table table} {the table names.} {rank on an fts5 table}
 }
 
+
+#-------------------------------------------------------------------------
+# forum post: https://sqlite.org/forum/forumpost/a2dd636330
+#
+reset_db
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE t USING fts5 (a, b);  
+  INSERT INTO t (a, b) VALUES ('data1', 'sentence1'), ('data2', 'sentence2'); 
+  INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
+}
+
+sqlite3 db2 test.db 
+do_execsql_test -db db2 1.1 {
+  SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
+} {data1 sentence1 1 data2 sentence2 1}
+
+do_execsql_test 1.2 {
+  INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
+}
+do_execsql_test -db db2 1.3 {
+  SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
+} {data1 sentence1 1 data2 sentence2 1}
+db2 close
+
 finish_test
diff --git a/libsql-sqlite3/ext/fts5/test/fts5savepoint.test b/libsql-sqlite3/ext/fts5/test/fts5savepoint.test
index e431f9f5fd..1126222750 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5savepoint.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5savepoint.test
@@ -71,7 +71,7 @@ ifcapable fts3 {
 
   do_catchsql_test 3.2 {
     DROP TABLE vt1;
-  } {1 {SQL logic error}}
+  } {0 {}}
 
   do_execsql_test 3.3 {
     SAVEPOINT x;
diff --git a/libsql-sqlite3/ext/fts5/test/fts5secure.test b/libsql-sqlite3/ext/fts5/test/fts5secure.test
index 50d84cef79..7314946162 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5secure.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5secure.test
@@ -273,6 +273,76 @@ do_execsql_test 5.3 {
 do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2
 do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2
 
+#-------------------------------------------------------------------------
+# Tests for the bug fixed by https://sqlite.org/src/info/4b60a1c3
+#
+reset_db
+do_execsql_test 6.0 {
+  CREATE VIRTUAL TABLE fts USING fts5(content);
+  INSERT INTO fts(fts, rank) VALUES ('secure-delete', 1);
+  INSERT INTO fts(rowid, content) VALUES
+    (3407, 'profile profile profile profile profile profile profile profile pull pulling pulling really');
+  DELETE FROM fts WHERE rowid IS 3407;
+  INSERT INTO fts(fts) VALUES ('integrity-check');
+}
+
+foreach {tn detail} {
+  1 full
+  2 column
+  3 none
+} {
+  do_execsql_test 6.1.$detail "
+    DROP TABLE IF EXISTS t1;
+    CREATE VIRTUAL TABLE t1 USING fts5(x, detail=$detail);
+  "
+
+  do_execsql_test 6.2.$detail {
+    INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+  }
+
+  for {set ii 1} {$ii < 100} {incr ii} {
+    do_execsql_test 6.3.$detail.$ii.1 {
+      BEGIN;
+        INSERT INTO t1(rowid, x) VALUES(10, 'word1');
+        WITH s(i) AS (
+          SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<CAST($ii AS integer)
+        )
+        INSERT INTO t1(x) SELECT 'word3' FROM s;
+      COMMIT;
+      INSERT INTO t1(t1) VALUES('optimize');
+    }
+
+    do_execsql_test 6.3.$detail.$ii.2 {
+      DELETE FROM t1 WHERE rowid=10;
+      INSERT INTO t1(t1) VALUES ('integrity-check');
+    }
+
+    do_execsql_test 6.3.$detail.$ii.3 {
+      DELETE FROM t1;
+    }
+
+    do_execsql_test 6.3.$detail.$ii.4 {
+      BEGIN;
+        INSERT INTO t1(rowid, x) VALUES(10, 'tokenA');
+        WITH s(i) AS (
+          SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<CAST($ii AS integer)
+        )
+        INSERT INTO t1(x) SELECT group_concat('tokenB ') FROM s;
+      COMMIT;
+      INSERT INTO t1(t1) VALUES('optimize');
+    }
+
+    do_execsql_test 6.3.$detail.$ii.5 {
+      DELETE FROM t1 WHERE rowid=10;
+      INSERT INTO t1(t1) VALUES ('integrity-check');
+    }
+
+    do_execsql_test 6.3.$detail.$ii.6 {
+      DELETE FROM t1;
+    }
+  }
+}
+
 
 finish_test
 
diff --git a/libsql-sqlite3/ext/fts5/test/fts5secure6.test b/libsql-sqlite3/ext/fts5/test/fts5secure6.test
index 5ab17c4f32..e2f4ceabc8 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5secure6.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5secure6.test
@@ -18,7 +18,7 @@ db progress 1 progress_handler
 set ::PHC 0
 proc progress_handler {args} {
   incr ::PHC
-  if {($::PHC % 100000)==0}  breakpoint
+  # if {($::PHC % 100000)==0}  breakpoint
   return 0
 }
 
@@ -70,5 +70,72 @@ do_execsql_test 2.2 {
   SELECT rowid FROM t1('def')
 } {-100000 -99999 9223372036854775800}
 
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 3.0 {
+  CREATE VIRTUAL TABLE t1 USING fts5(x);
+  INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd) 
+}
+
+do_execsql_test 3.1 {
+  BEGIN;
+    INSERT INTO t1(rowid, x) 
+      VALUES(51869, 'when whenever where weress what turn'), 
+            (51871, 'to were');
+  COMMIT;
+}
+
+do_execsql_test 3.2 {
+  DELETE FROM t1 WHERE rowid=51871;
+  INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 4.0 {
+  CREATE VIRTUAL TABLE t1 USING fts5(x);
+  INSERT INTO t1(rowid, x) VALUES(10, 'one two');
+}
+do_execsql_test 4.1 {
+  UPDATE t1 SET x = 'one three' WHERE rowid=10;
+  INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+do_execsql_test 4.2 {
+  DELETE FROM t1 WHERE rowid=10;
+}
+do_execsql_test 4.3 {
+  INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 5.0 {
+  CREATE VIRTUAL TABLE t1 USING fts5(content);
+
+  INSERT INTO t1(t1,rank) VALUES('secure-delete',1);
+  INSERT INTO t1 VALUES('active'),('boomer'),('atom'),('atomic'),
+         ('alpha channel backup abandon test aback boomer atom alpha active');
+  DELETE FROM t1 WHERE t1 MATCH 'abandon';
+} 
+
+do_execsql_test 5.1 {
+  INSERT INTO t1(t1) VALUES('rebuild');
+}
+
+do_execsql_test 5.2 {
+  DELETE FROM t1 WHERE rowid NOTNULL<5;
+}
+
+db close
+sqlite3 db test.db
+
+do_execsql_test 5.3 {
+  PRAGMA integrity_check;
+} {ok}
+
+
 finish_test
 
diff --git a/libsql-sqlite3/ext/fts5/test/fts5secure7.test b/libsql-sqlite3/ext/fts5/test/fts5secure7.test
new file mode 100644
index 0000000000..16a044f538
--- /dev/null
+++ b/libsql-sqlite3/ext/fts5/test/fts5secure7.test
@@ -0,0 +1,116 @@
+# 2023 Feb 17
+#
+# 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.
+#
+#*************************************************************************
+#
+# TESTRUNNER: slow
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+ifcapable !fts5 { finish_test ; return }
+set ::testprefix fts5secure7
+
+
+set NVOCAB 500
+set NDOC [expr 1000]
+
+set NREP 100
+set nDeletePerRep [expr 5]
+
+set VOCAB [list]
+
+proc select_one {list} {
+  set n [llength $list]
+  lindex $list [expr {abs(int(rand()*$n))}]
+}
+
+proc init_vocab {} {
+  set L [split "abcdefghijklmnopqrstuvwxyz" {}]
+  set nL [llength $L]
+  for {set i 0} {$i < $::NVOCAB} {incr i} {
+    set n [expr {6 + int(rand()*8)}]
+    set word ""
+    for {set j 0} {$j < $n} {incr j} {
+      append word [select_one $L]
+    }
+    lappend ::VOCAB $word
+  }
+}
+
+proc get_word {} {
+  select_one $::VOCAB
+}
+
+proc get_document {nWord} {
+  set ret [list]
+  for {set i 0} {$i < $nWord} {incr i} {
+    lappend ret [get_word]
+  }
+  return $ret
+}
+
+init_vocab
+
+db func document [list get_document 12]
+
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE t1 USING fts5(body);
+  INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+do_execsql_test 1.1 {
+  WITH s(i) AS (
+    SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
+  )
+  INSERT INTO t1 SELECT document() FROM s;
+}
+
+for {set iRep 0} {$iRep < $NREP} {incr iRep} {
+  set lRowid [db eval {SELECT rowid FROM t1}]
+  for {set iDel 0} {$iDel < $nDeletePerRep} {incr iDel} {
+    set idx [select_one $lRowid]
+    db eval {
+      DELETE FROM t1 WHERE rowid=$idx
+    }
+  }
+  db eval {
+    WITH s(i) AS (
+      SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$nDeletePerRep
+    )
+    INSERT INTO t1 SELECT document() FROM s;
+  }
+  do_execsql_test 1.2.$iRep {
+    INSERT INTO t1(t1) VALUES('integrity-check');
+  }
+}
+
+reset_db
+db func document [list get_document 12]
+do_execsql_test 2.0 {
+  CREATE VIRTUAL TABLE t1 USING fts5(body);
+  INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+  INSERT INTO t1(t1, rank) VALUES('pgsz', 128);
+}
+do_execsql_test 2.1 {
+  WITH s(i) AS (
+    SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
+  )
+  INSERT INTO t1 SELECT document() FROM s;
+}
+for {set ii 0} {$ii < $NDOC} {incr ii} {
+  set lRowid [db eval {SELECT rowid FROM t1}]
+  set idx [select_one $lRowid]
+  db eval { DELETE FROM t1 WHERE rowid=$idx }
+  do_execsql_test 2.2.$ii {
+    INSERT INTO t1(t1) VALUES('integrity-check');
+  }
+}
+
+finish_test
+
+
diff --git a/libsql-sqlite3/ext/fts5/test/fts5trigram.test b/libsql-sqlite3/ext/fts5/test/fts5trigram.test
index 951daf1440..351c059bf5 100644
--- a/libsql-sqlite3/ext/fts5/test/fts5trigram.test
+++ b/libsql-sqlite3/ext/fts5/test/fts5trigram.test
@@ -215,4 +215,42 @@ do_execsql_test 7.2 {
   SELECT rowid FROM f WHERE filename GLOB '*ΠΈΡ€*';
 } {20}
 
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 8.0 {
+  CREATE VIRTUAL TABLE t1 USING fts5(y, tokenize=trigram);
+  INSERT INTO t1 VALUES('abcdefghijklm');
+}
+
+foreach {tn match res} {
+  1 "abc ghi"         "(abc)def(ghi)jklm"
+  2 "def ghi"         "abc(defghi)jklm"
+  3 "efg ghi"         "abcd(efghi)jklm"
+  4 "efghi"           "abcd(efghi)jklm"
+  5 "abcd jklm"       "(abcd)efghi(jklm)"
+  6 "ijkl jklm"       "abcdefgh(ijklm)"
+  7 "ijk ijkl hijk"   "abcdefg(hijkl)m"
+
+} {
+  do_execsql_test 8.1.$tn {
+    SELECT highlight(t1, 0, '(', ')') FROM t1($match)
+  } $res
+}
+
+do_execsql_test 8.2 {
+  CREATE VIRTUAL TABLE ft2 USING fts5(a, tokenize="trigram");
+  INSERT INTO ft2 VALUES('abc x cde');
+  INSERT INTO ft2 VALUES('abc cde');
+  INSERT INTO ft2 VALUES('abcde');
+}
+
+do_execsql_test 8.3 {
+  SELECT highlight(ft2, 0, '[', ']') FROM ft2 WHERE ft2 MATCH 'abc AND cde';
+} {
+  {[abc] x [cde]}
+  {[abc] [cde]}
+  {[abcde]}
+}
+
 finish_test
diff --git a/libsql-sqlite3/ext/jni/GNUmakefile b/libsql-sqlite3/ext/jni/GNUmakefile
index 1be4c04439..19a5080471 100644
--- a/libsql-sqlite3/ext/jni/GNUmakefile
+++ b/libsql-sqlite3/ext/jni/GNUmakefile
@@ -6,9 +6,10 @@ JAVA_HOME ?= $(HOME)/jdk/current
 # e.g. /usr/lib/jvm/default-javajava-19-openjdk-amd64
 JDK_HOME ?= $(JAVA_HOME)
 # ^^^ JDK_HOME is not as widely used as JAVA_HOME
-bin.javac := $(JDK_HOME)/bin/javac
-bin.java  := $(JDK_HOME)/bin/java
-bin.jar   := $(JDK_HOME)/bin/jar
+bin.jar     := $(JDK_HOME)/bin/jar
+bin.java    := $(JDK_HOME)/bin/java
+bin.javac   := $(JDK_HOME)/bin/javac
+bin.javadoc := $(JDK_HOME)/bin/javadoc
 ifeq (,$(wildcard $(JDK_HOME)))
 $(error set JDK_HOME to the top-most dir of your JDK installation.)
 endif
@@ -17,18 +18,27 @@ $(MAKEFILE):
 
 package.jar := sqlite3-jni.jar
 
-dir.top := ../..
-dir.tool := ../../tool
-dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE)))
-
+dir.top     := ../..
+dir.tool    := ../../tool
+dir.jni     := $(patsubst %/,%,$(dir $(MAKEFILE)))
 dir.src     := $(dir.jni)/src
 dir.src.c   := $(dir.src)/c
 dir.bld     := $(dir.jni)/bld
 dir.bld.c   := $(dir.bld)
 dir.src.jni := $(dir.src)/org/sqlite/jni
-dir.src.jni.tester := $(dir.src.jni)/tester
+dir.src.capi := $(dir.src.jni)/capi
+dir.src.fts5 := $(dir.src.jni)/fts5
+dir.tests   := $(dir.src)/tests
+mkdir       ?= mkdir -p
 $(dir.bld.c):
-	mkdir -p $@
+	$(mkdir) $@
+
+javac.flags ?= -Xlint:unchecked -Xlint:deprecation
+java.flags ?=
+jnicheck ?= 1
+ifeq (1,$(jnicheck))
+  java.flags += -Xcheck:jni
+endif
 
 classpath := $(dir.src)
 CLEAN_FILES := $(package.jar)
@@ -36,17 +46,28 @@ DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~
 
 sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
 .NOTPARALLEL: $(sqlite3-jni.h)
-SQLite3Jni.java := src/org/sqlite/jni/SQLite3Jni.java
-SQLTester.java := src/org/sqlite/jni/tester/SQLTester.java
-SQLite3Jni.class := $(SQLite3Jni.java:.java=.class)
+CApi.java := $(dir.src.capi)/CApi.java
+SQLTester.java := $(dir.src.capi)/SQLTester.java
+CApi.class := $(CApi.java:.java=.class)
 SQLTester.class := $(SQLTester.java:.java=.class)
 
 ########################################################################
 # The future of FTS5 customization in this API is as yet unclear.
-# It would be a real doozy to bind to JNI.
+# The pieces are all in place, and are all thin proxies so not much
+# complexity, but some semantic changes were required in porting
+# which are largely untested.
+#
+# Reminder: this flag influences the contents of $(sqlite3-jni.h),
+# which is checked in. Please do not check in changes to that file in
+# which the fts5 APIs have been stripped unless that feature is
+# intended to be stripped for good.
 enable.fts5 ?= 1
-# If enable.tester is 0, the org/sqlite/jni/tester/* bits are elided.
-enable.tester ?= 1
+
+ifeq (,$(wildcard $(dir.tests)/*))
+  enable.tester := 0
+else
+  enable.tester := 1
+endif
 
 # bin.version-info = binary to output various sqlite3 version info
 # building the distribution zip file.
@@ -57,68 +78,95 @@ $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
 
 # Be explicit about which Java files to compile so that we can work on
 # in-progress files without requiring them to be in a compilable statae.
-JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\
-  BusyHandler.java \
-  Collation.java \
-  CollationNeeded.java \
-  CommitHook.java \
+JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\
+  NotNull.java \
+  Nullable.java \
+) $(patsubst %,$(dir.src.capi)/%,\
+  AbstractCollationCallback.java \
+  AggregateFunction.java \
+  AuthorizerCallback.java \
+  AutoExtensionCallback.java \
+  BusyHandlerCallback.java \
+  CollationCallback.java \
+  CollationNeededCallback.java \
+  CommitHookCallback.java \
+  ConfigLogCallback.java \
+  ConfigSqllogCallback.java \
   NativePointerHolder.java \
   OutputPointer.java \
-  ProgressHandler.java \
+  PrepareMultiCallback.java \
+  PreupdateHookCallback.java \
+  ProgressHandlerCallback.java \
   ResultCode.java \
-  RollbackHook.java \
+  RollbackHookCallback.java \
+  ScalarFunction.java \
   SQLFunction.java \
-  sqlite3_context.java \
+  CallbackProxy.java \
+  CApi.java \
+  TableColumnMetadata.java \
+  TraceV2Callback.java \
+  UpdateHookCallback.java \
+  ValueHolder.java \
+  WindowFunction.java \
+  XDestroyCallback.java \
   sqlite3.java \
-  SQLite3Jni.java \
+  sqlite3_context.java \
   sqlite3_stmt.java \
   sqlite3_value.java \
-  Tester1.java \
-  Tracer.java \
-  UpdateHook.java \
+) $(patsubst %,$(dir.src.jni)/wrapper1/%,\
+  AggregateFunction.java \
+  ScalarFunction.java \
+  SqlFunction.java \
+  Sqlite.java \
+  SqliteException.java \
   ValueHolder.java \
 )
+
+JAVA_FILES.unittest := $(patsubst %,$(dir.src.jni)/%,\
+  capi/Tester1.java \
+  wrapper1/Tester2.java \
+)
 ifeq (1,$(enable.fts5))
-  JAVA_FILES.main += $(patsubst %,$(dir.src.jni)/%,\
+  JAVA_FILES.unittest += $(patsubst %,$(dir.src.fts5)/%,\
+    TesterFts5.java \
+  )
+  JAVA_FILES.main += $(patsubst %,$(dir.src.fts5)/%,\
     fts5_api.java \
     fts5_extension_function.java \
     fts5_tokenizer.java \
     Fts5.java \
     Fts5Context.java \
     Fts5ExtensionApi.java \
-    Fts5Function.java \
     Fts5PhraseIter.java \
     Fts5Tokenizer.java \
-    TesterFts5.java \
+    XTokenizeCallback.java \
   )
 endif
-JAVA_FILES.tester := $(dir.src.jni.tester)/SQLTester.java
+JAVA_FILES.tester := $(SQLTester.java)
+JAVA_FILES.package.info := \
+  $(dir.src.jni)/package-info.java \
+  $(dir.src.jni)/annotation/package-info.java
 
 CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
+CLASS_FILES.unittest := $(JAVA_FILES.unittest:.java=.class)
 CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class)
 
-JAVA_FILES += $(JAVA_FILES.main)
+JAVA_FILES += $(JAVA_FILES.main) $(JAVA_FILES.unittest)
 ifeq (1,$(enable.tester))
   JAVA_FILES += $(JAVA_FILES.tester)
 endif
 
 CLASS_FILES :=
-define DOTCLASS_DEPS
-$(1).class: $(1).java $(MAKEFILE)
+define CLASSFILE_DEPS
 all: $(1).class
 CLASS_FILES += $(1).class
 endef
-$(foreach B,$(basename $(JAVA_FILES)),$(eval $(call DOTCLASS_DEPS,$(B))))
-$(CLASS_FILES.tester): $(CLASS_FILES.main)
-javac.flags ?= -Xlint:unchecked -Xlint:deprecation
-java.flags ?=
-jnicheck ?= 1
-ifeq (1,$(jnicheck))
-  java.flags += -Xcheck:jni
-endif
-$(SQLite3Jni.class): $(JAVA_FILES)
+$(foreach B,$(basename \
+  $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.tester)),\
+  $(eval $(call CLASSFILE_DEPS,$(B))))
+$(CLASS_FILES): $(JAVA_FILES) $(MAKEFILE)
 	$(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES)
-all: $(SQLite3Jni.class)
+
 #.PHONY: classfiles
 
 ########################################################################
@@ -152,27 +200,40 @@ $(sqlite3.h):
 	$(MAKE) -C $(dir.top) sqlite3.c
 $(sqlite3.c): $(sqlite3.h)
 
+opt.threadsafe ?= 1
+opt.fatal-oom ?= 1
+opt.debug ?= 1
+opt.metrics ?= 1
 SQLITE_OPT = \
-  -DSQLITE_ENABLE_RTREE \
+  -DSQLITE_THREADSAFE=$(opt.threadsafe) \
+  -DSQLITE_TEMP_STORE=2 \
+  -DSQLITE_USE_URI=1 \
+  -DSQLITE_OMIT_LOAD_EXTENSION \
+  -DSQLITE_OMIT_DEPRECATED \
+  -DSQLITE_OMIT_SHARED_CACHE \
+  -DSQLITE_C=$(sqlite3.c) \
+  -DSQLITE_JNI_FATAL_OOM=$(opt.fatal-oom) \
+  -DSQLITE_JNI_ENABLE_METRICS=$(opt.metrics)
+
+opt.extras ?= 1
+ifeq (1,$(opt.extras))
+SQLITE_OPT += -DSQLITE_ENABLE_RTREE \
   -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
   -DSQLITE_ENABLE_STMTVTAB \
   -DSQLITE_ENABLE_DBPAGE_VTAB \
   -DSQLITE_ENABLE_DBSTAT_VTAB \
   -DSQLITE_ENABLE_BYTECODE_VTAB \
   -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
-  -DSQLITE_OMIT_LOAD_EXTENSION \
-  -DSQLITE_OMIT_DEPRECATED \
-  -DSQLITE_OMIT_SHARED_CACHE \
-  -DSQLITE_THREADSAFE=0 \
-  -DSQLITE_TEMP_STORE=2 \
-  -DSQLITE_USE_URI=1 \
-  -DSQLITE_C=$(sqlite3.c)
-#  -DSQLITE_DEBUG
-# -DSQLITE_DEBUG is just to work around a -Wall warning
-# for a var which gets set in all builds but only read
-# via assert().
+  -DSQLITE_ENABLE_PREUPDATE_HOOK \
+  -DSQLITE_ENABLE_NORMALIZE \
+  -DSQLITE_ENABLE_SQLLOG
+endif
 
-SQLITE_OPT += -g -DDEBUG -UNDEBUG
+ifeq (1,$(opt.debug))
+  SQLITE_OPT += -DSQLITE_DEBUG -g -DDEBUG -UNDEBUG
+else
+  SQLITE_OPT += -Os
+endif
 
 ifeq (1,$(enable.fts5))
   SQLITE_OPT += -DSQLITE_ENABLE_FTS5
@@ -181,25 +242,30 @@ endif
 sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c
 sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o
 sqlite3-jni.h   := $(dir.src.c)/sqlite3-jni.h
-sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so
+package.dll := $(dir.bld.c)/libsqlite3-jni.so
 # All javac-generated .h files must be listed in $(sqlite3-jni.h.in):
 sqlite3-jni.h.in :=
+# $(java.with.jni) lists all Java files which contain JNI decls:
+java.with.jni :=
 define ADD_JNI_H
-sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni_$(1).h
-$$(dir.bld.c)/org_sqlite_jni_$(1).h: $$(dir.src.jni)/$(1).java
+sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h
+java.with.jni += $(1)/$(2).java
+$$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h: $(1)/$(2).java
 endef
-$(eval $(call ADD_JNI_H,SQLite3Jni))
+# Invoke ADD_JNI_H once for each Java file which includes JNI
+# declarations:
+$(eval $(call ADD_JNI_H,$(dir.src.capi),CApi,_capi))
+$(eval $(call ADD_JNI_H,$(dir.src.capi),SQLTester,_capi))
 ifeq (1,$(enable.fts5))
-  $(eval $(call ADD_JNI_H,Fts5ExtensionApi))
-  $(eval $(call ADD_JNI_H,fts5_api))
-  $(eval $(call ADD_JNI_H,fts5_tokenizer))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),Fts5ExtensionApi,_fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_api,_fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_tokenizer,_fts5))
 endif
-ifeq (1,$(enable.tester))
-  sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h
-  $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java
-endif
-#sqlite3-jni.dll.cfiles := $(dir.src.c)
-sqlite3-jni.dll.cflags = \
+$(sqlite3-jni.h.in): $(dir.bld.c)
+
+#package.dll.cfiles :=
+package.dll.cflags = \
+  -std=c99 \
   -fPIC \
   -I. \
   -I$(dir $(sqlite3.h)) \
@@ -207,54 +273,82 @@ sqlite3-jni.dll.cflags = \
   -I$(JDK_HOME)/include \
   $(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \
   -Wall
-# Using (-Wall -Wextra) triggers an untennable number of
-# gcc warnings from sqlite3.c for mundane things like
-# unused parameters.
-#
 # The gross $(patsubst...) above is to include the platform-specific
 # subdir which lives under $(JDK_HOME)/include and is a required
 # include path for client-level code.
+#
+# Using (-Wall -Wextra) triggers an untennable number of
+# gcc warnings from sqlite3.c for mundane things like
+# unused parameters.
 ########################################################################
 ifeq (1,$(enable.tester))
-  sqlite3-jni.dll.cflags += -DS3JNI_ENABLE_SQLTester
+  package.dll.cflags += -DSQLITE_JNI_ENABLE_SQLTester
 endif
-$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
-	cat $(sqlite3-jni.h.in) > $@
-$(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
-$(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE)
-	$(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \
-			$(sqlite3-jni.c) -shared -o $@
-all: $(sqlite3-jni.dll)
 
-.PHONY: test
-test.flags ?= -v
-test: $(SQLite3Jni.class) $(sqlite3-jni.dll)
-	$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
-		$(java.flags) -cp $(classpath) \
-		org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),)
+$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
+	@cat $(sqlite3-jni.h.in) > $@.tmp
+	@if cmp $@ $@.tmp >/dev/null; then \
+		rm -f $@.tmp; \
+		echo "$@ not modified"; \
+	else \
+		mv $@.tmp $@; \
+		echo "Updated $@"; \
+	fi
+	@if [ x1 != x$(enable.fts5) ]; then \
+		echo "*** REMINDER:"; \
+		echo "*** enable.fts5=0, so please do not check in changes to $@."; \
+	fi
+
+$(package.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
+$(package.dll): $(sqlite3-jni.c) $(MAKEFILE)
+	$(CC) $(package.dll.cflags) $(SQLITE_OPT) \
+			$(sqlite3-jni.c) -shared -o $@
+all: $(package.dll)
+
+.PHONY: test test-one
+Tester1.flags ?=
+Tester2.flags ?=
+test.flags.jvm = -ea -Djava.library.path=$(dir.bld.c) \
+                  $(java.flags) -cp $(classpath)
+test.deps := $(CLASS_FILES) $(package.dll)
+test-one: $(test.deps)
+	$(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 $(Tester1.flags)
+	$(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 $(Tester2.flags)
+test-sqllog: $(test.deps)
+	@echo "Testing with -sqllog..."
+	$(bin.java) $(test.flags.jvm) -sqllog
+test-mt: $(test.deps)
+	@echo "Testing in multi-threaded mode:";
+	$(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \
+    -t 7 -r 50 -shuffle $(Tester1.flags)
+	$(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 \
+    -t 7 -r 50 -shuffle $(Tester2.flags)
+
+test: test-one test-mt
+tests: test test-sqllog
 
 tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test))
 tester.flags ?= # --verbose
 .PHONY: tester tester-local tester-ext
 ifeq (1,$(enable.tester))
-tester-local: $(CLASS_FILES.tester) $(sqlite3-jni.dll)
+tester-local: $(CLASS_FILES.tester) $(package.dll)
 	$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
 		$(java.flags) -cp $(classpath) \
-		org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts)
+		org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.scripts)
 tester: tester-local
 else
 tester:
-	@echo "SQLTester support is disabled. Build with enable.tester=1 to enable it."
+	@echo "SQLTester support is disabled."
 endif
 
-tester.extdir.default := src/tests/ext
+tester.extdir.default := $(dir.tests)/ext
 tester.extdir ?= $(tester.extdir.default)
 tester.extern-scripts := $(wildcard $(tester.extdir)/*.test)
 ifneq (,$(tester.extern-scripts))
 tester-ext:
 	$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
 		$(java.flags) -cp $(classpath) \
-		org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.extern-scripts)
+		org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.extern-scripts)
 else
 tester-ext:
 	@echo "******************************************************"; \
@@ -267,25 +361,79 @@ endif
 
 tester-ext: tester-local
 tester: tester-ext
-tests: test tester
+tests: tester
+########################################################################
+# Build each SQLITE_THREADMODE variant and run all tests against them.
+multitest: clean
+define MULTIOPT
+multitest: multitest-$(1)
+multitest-$(1):
+	$$(MAKE) opt.debug=$$(opt.debug) $(patsubst %,opt.%,$(2)) \
+		tests clean enable.fts5=1
+endef
+
+$(eval $(call MULTIOPT,01,threadsafe=0 oom=1))
+$(eval $(call MULTIOPT,00,threadsafe=0 oom=0))
+$(eval $(call MULTIOPT,11,threadsafe=1 oom=1))
+$(eval $(call MULTIOPT,10,threadsafe=1 oom=0))
+$(eval $(call MULTIOPT,21,threadsafe=2 oom=1))
+$(eval $(call MULTIOPT,20,threadsafe=2 oom=0))
+
+
+########################################################################
+# jar bundle...
 package.jar.in := $(abspath $(dir.src)/jar.in)
 CLEAN_FILES += $(package.jar.in)
-$(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main)
-	cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@
-	@ls -la $@
-	@echo "To use this jar you will need the -Djava.library.path=DIR/WITH/libsqlite3-jni.so flag."
-	@echo "e.g. java -jar $@ -Djava.library.path=bld"
+JAVA_FILES.jar := $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.package.info)
+CLASS_FILES.jar := $(filter-out %/package-info.class,$(JAVA_FILES.jar:.java=.class))
+$(package.jar.in): $(package.dll) $(MAKEFILE)
+	ls -1 \
+		$(dir.src.jni)/*/*.java $(dir.src.jni)/*/*.class \
+	| sed -e 's,^$(dir.src)/,,' | sort > $@
 
-$(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in)
-	rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
-	cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.Tester1 @$(package.jar.in)
+$(package.jar): $(CLASS_FILES.jar) $(MAKEFILE) $(package.jar.in)
+	@rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
+	cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.capi.Tester1 @$(package.jar.in)
+	@ls -la $@
+	@echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag."
+	@echo "e.g. java -Djava.library.path=bld -jar $@"
 
 jar: $(package.jar)
+run-jar: $(package.jar) $(package.dll)
+	$(bin.java) -Djava.library.path=$(dir.bld) -jar $(package.jar) $(run-jar.flags)
 
+########################################################################
+# javadoc...
+dir.doc   := $(dir.jni)/javadoc
+doc.index := $(dir.doc)/index.html
+javadoc.exclude := -exclude org.sqlite.jni.fts5
+# ^^^^ 2023-09-13: elide the fts5 parts from the public docs for
+# the time being, as it's not clear where the Java bindings for
+# those bits are going.
+# javadoc.exclude += -exclude org.sqlite.jni.capi
+# ^^^^ exclude the capi API only for certain builds (TBD)
+$(doc.index): $(JAVA_FILES.main) $(MAKEFILE)
+	@if [ -d $(dir.doc) ]; then rm -fr $(dir.doc)/*; fi
+	$(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet \
+		-subpackages org.sqlite.jni $(javadoc.exclude)
+	@echo "javadoc output is in $@"
+
+.PHONY: doc javadoc docserve
+.FORCE: doc
+doc: $(doc.index)
+javadoc: $(doc.index)
+# Force rebild of docs
+redoc:
+	@rm -f $(doc.index)
+	@$(MAKE) doc
+docserve: $(doc.index)
+	cd $(dir.doc) && althttpd -max-age 1 -page index.html
+########################################################################
+# Clean up...
 CLEAN_FILES += $(dir.bld.c)/* \
   $(dir.src.jni)/*.class \
-  $(dir.src.jni.tester)/*.class \
-  $(sqlite3-jni.dll) \
+  $(dir.src.jni)/*/*.class \
+  $(package.dll) \
   hs_err_pid*.log
 
 .PHONY: clean distclean
@@ -293,7 +441,7 @@ clean:
 	-rm -f $(CLEAN_FILES)
 distclean: clean
 	-rm -f $(DISTCLEAN_FILES)
-	-rm -fr $(dir.bld.c)
+	-rm -fr $(dir.bld.c) $(dir.doc)
 
 ########################################################################
 # disttribution bundle rules...
@@ -317,6 +465,10 @@ dist: \
     $(bin.version-info) $(sqlite3.canonical.c) \
     $(package.jar) $(MAKEFILE)
 	@echo "Making end-user deliverables..."
+	@echo "****************************************************************************"; \
+	echo  "*** WARNING: be sure to build this with JDK8 (javac 1.8) for compatibility."; \
+	echo  "*** reasons!"; $$($(bin.javac) -version); \
+	echo  "****************************************************************************"
 	@rm -fr $(dist-dir.top)
 	@mkdir -p $(dist-dir.src)
 	@cp -p $(dist.top.extras) $(dist-dir.top)/.
diff --git a/libsql-sqlite3/ext/jni/README.md b/libsql-sqlite3/ext/jni/README.md
index cb51a21cd3..f2811fddb2 100644
--- a/libsql-sqlite3/ext/jni/README.md
+++ b/libsql-sqlite3/ext/jni/README.md
@@ -15,7 +15,10 @@ Technical support is available in the forum:
 
 > **FOREWARNING:** this subproject is very much in development and
   subject to any number of changes. Please do not rely on any
-  information about its API until this disclaimer is removed.
+  information about its API until this disclaimer is removed.  The JNI
+  bindings released with version 3.43 are a "tech preview" and 3.44
+  will be "final," at which point strong backward compatibility
+  guarantees will apply.
 
 Project goals/requirements:
 
@@ -40,13 +43,41 @@ Non-goals:
 - Creation of high-level OO wrapper APIs. Clients are free to create
   them off of the C-style API.
 
+- Support for mixed-mode operation, where client code accesses SQLite
+  both via the Java-side API and the C API via their own native
+  code. In such cases, proxy functionalities (primarily callback
+  handler wrappers of all sorts) may fail because the C-side use of
+  the SQLite APIs will bypass those proxies.
 
-Significant TODOs
-========================================================================
 
-- The initial beta release with version 3.43 has severe threading
-  limitations.  Namely, two threads cannot call into the JNI-bound API
-  at once. This limitation will be remove in a subsequent release.
+Hello World
+-----------------------------------------------------------------------
+
+```java
+import org.sqlite.jni.*;
+import static org.sqlite.jni.CApi.*;
+
+...
+
+final sqlite3 db = sqlite3_open(":memory:");
+try {
+  final int rc = sqlite3_errcode(db);
+  if( 0 != rc ){
+    if( null != db ){
+      System.out.print("Error opening db: "+sqlite3_errmsg(db));
+    }else{
+      System.out.print("Error opening db: rc="+rc);
+    }
+    ... handle error ...
+  }
+  // ... else use the db ...
+}finally{
+  // ALWAYS close databases using sqlite3_close() or sqlite3_close_v2()
+  // when done with them. All of their active statement handles must
+  // first have been passed to sqlite3_finalize().
+  sqlite3_close_v2(db);
+}
+```
 
 
 Building
@@ -60,55 +91,97 @@ The canonical builds assumes a Linux-like environment and requires:
 
 Put simply:
 
-```
+```console
 $ export JAVA_HOME=/path/to/jdk/root
 $ make
 $ make test
 $ make clean
 ```
 
+The jar distribution can be created with `make jar`, but note that it
+does not contain the binary DLL file. A different DLL is needed for
+each target platform.
+
+
 <a id='1to1ish'></a>
 One-to-One(-ish) Mapping to C
 ========================================================================
 
 This JNI binding aims to provide as close to a 1-to-1 experience with
-the C API as cross-language semantics allow. Exceptions are
+the C API as cross-language semantics allow. Interface changes are
 necessarily made where cross-language semantics do not allow a 1-to-1,
 and judiciously made where a 1-to-1 mapping would be unduly cumbersome
-to use in Java.
+to use in Java. In all cases, this binding makes every effort to
+provide semantics compatible with the C API documentation even if the
+interface to those semantics is slightly different.  Any cases which
+deviate from those semantics (either removing or adding semantics) are
+clearly documented.
 
-Golden Rule: _Never_ Throw from Callbacks
+Where it makes sense to do so for usability, Java-side overloads are
+provided which accept or return data in alternative forms or provide
+sensible default argument values. In all such cases they are thin
+proxies around the corresponding C APIs and do not introduce new
+semantics.
+
+In some very few cases, Java-specific capabilities have been added in
+new APIs, all of which have "_java" somewhere in their names.
+Examples include:
+
+- `sqlite3_result_java_object()`
+- `sqlite3_column_java_object()`
+- `sqlite3_column_java_casted()`
+- `sqlite3_value_java_object()`
+- `sqlite3_value_java_casted()`
+
+which, as one might surmise, collectively enable the passing of
+arbitrary Java objects from user-defined SQL functions through to the
+caller.
+
+
+Golden Rule: Garbage Collection Cannot Free SQLite Resources
 ------------------------------------------------------------------------
 
-JNI bindings which accept client-defined functions _must never throw
-exceptions_ unless _very explicitly documented_ as being
-throw-safe. Exceptions are generally reserved for higher-level
-bindings which are constructed to specifically deal with them and
-ensure that they do not leak C-level resources. Some of the JNI
-bindings are provided as Java functions which expect this rule to
-always hold.
+It is important that all databases and prepared statement handles get
+cleaned up by client code. A database cannot be closed if it has open
+statement handles. `sqlite3_close()` fails if the db cannot be closed
+whereas `sqlite3_close_v2()` recognizes that case and marks the db as
+a "zombie," pending finalization when the library detects that all
+pending statements have been closed. Be aware that Java garbage
+collection _cannot_ close a database or finalize a prepared statement.
+Those things require explicit API calls.
 
-UTF-8(-ish)
+
+Golden Rule #2: _Never_ Throw from Callbacks (Unless...)
 ------------------------------------------------------------------------
 
-SQLite internally uses UTF-8 encoding, whereas Java natively uses
-UTF-16.  Java JNI has routines for converting to and from UTF-8, _but_
-Java uses what its docs call "[modified UTF-8][modutf8]." Care must be
-taken when converting Java strings to UTF-8 to ensure that the proper
-conversion is performed. In short,
-`String.getBytes(StandardCharsets.UTF_8)` performs the proper
-conversion in Java, and there is no JNI C API for that conversion
-(JNI's `NewStringUTF()` returns MUTF-8).
+All routines in this API, barring explicitly documented exceptions,
+retain C-like semantics. For example, they are not permitted to throw
+or propagate exceptions and must return error information (if any) via
+result codes or `null`. The only cases where the C-style APIs may
+throw is through client-side misuse, e.g. passing in a null where it
+shouldn't be used. The APIs clearly mark function parameters which
+should not be null, but does not actively defend itself against such
+misuse. Some C-style APIs explicitly accept `null` as a no-op for
+usability's sake, and some of the JNI APIs deliberately return an
+error code, instead of segfaulting, when passed a `null`.
 
-Known consequences and limitations of this discrepancy include:
+Client-defined callbacks _must never throw exceptions_ unless _very
+explicitly documented_ as being throw-safe. Exceptions are generally
+reserved for higher-level bindings which are constructed to
+specifically deal with them and ensure that they do not leak C-level
+resources. In some cases, callback handlers are permitted to throw, in
+which cases they get translated to C-level result codes and/or
+messages. If a callback which is not permitted to throw throws, its
+exception may trigger debug output but will otherwise be suppressed.
 
-- Names of databases, tables, and collations must not contain
-  characters which differ in MUTF-8 and UTF-8, or certain APIs will
-  mis-translate them on their way between languages. APIs which
-  transfer other client-side data to Java take extra care to
-  convert the data at the cost of performance.
-
-[modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8
+The reason some callbacks are permitted to throw and others not is
+because all such callbacks act as proxies for C function callback
+interfaces and some of those interfaces have no error-reporting
+mechanism. Those which are capable of propagating errors back through
+the library convert exceptions from callbacks into corresponding
+C-level error information. Those which cannot propagate errors
+necessarily suppress any exceptions in order to maintain the C-style
+semantics of the APIs.
 
 
 Unwieldy Constructs are Re-mapped
@@ -126,7 +199,7 @@ A prime example of where interface changes for Java are necessary for
 usability is [registration of a custom
 collation](https://sqlite.org/c3ref/create_collation.html):
 
-```
+```c
 // C:
 int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep,
                              void *pUserData,
@@ -145,7 +218,7 @@ passed that object as their first argument. That data is passed around
 bind that part as-is to Java, the result would be awkward to use (^Yes,
 we tried this.):
 
-```
+```java
 // Java:
 int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
                              Object pUserData, xCompareType xCompare);
@@ -160,20 +233,20 @@ for callbacks and (B) having their internal state provided separately,
 which is ill-fitting in Java. For the sake of usability, C APIs which
 follow that pattern use a slightly different Java interface:
 
-```
+```java
 int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
-                             Collation collation);
+                             SomeCallbackType collation);
 ```
 
-Where the `Collation` class has an abstract `xCompare()` method and
+Where the `Collation` class has an abstract `call()` method and
 no-op `xDestroy()` method which can be overridden if needed, leading to
 a much more Java-esque usage:
 
-```
-int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(){
+```java
+int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new SomeCallbackType(){
 
   // Required comparison function:
-  @Override public int xCompare(byte[] lhs, byte[] rhs){ ... }
+  @Override public int call(byte[] lhs, byte[] rhs){ ... }
 
   // Optional finalizer function:
   @Override public void xDestroy(){ ... }
@@ -188,8 +261,8 @@ int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(
 
 Noting that:
 
-- It is still possible to bind in call-scope-local state via closures,
-  if desired.
+- It is possible to bind in call-scope-local state via closures, if
+  desired, as opposed to packing it into the Collation object.
 
 - No capabilities of the C API are lost or unduly obscured via the
   above API reshaping, so power users need not make any compromises.
@@ -200,6 +273,7 @@ Noting that:
   overriding the `xDestroy()` method effectively gives it v2
   semantics.
 
+
 ### User-defined SQL Functions (a.k.a. UDFs)
 
 The [`sqlite3_create_function()`](https://sqlite.org/c3ref/create_function.html)
@@ -207,12 +281,13 @@ family of APIs make heavy use of function pointers to provide
 client-defined callbacks, necessitating interface changes in the JNI
 binding. The Java API has only one core function-registration function:
 
-```
+```java
 int sqlite3_create_function(sqlite3 db, String funcName, int nArgs,
                             int encoding, SQLFunction func);
 ```
 
-> Design question: does the encoding argument serve any purpose in JS?
+> Design question: does the encoding argument serve any purpose in
+  Java? That's as-yet undetermined. If not, it will be removed.
 
 `SQLFunction` is not used directly, but is instead instantiated via
 one of its three subclasses:
@@ -230,5 +305,9 @@ Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for
 Reminder: see the disclaimer at the top of this document regarding the
 in-flux nature of this API.
 
-[jsrc]: /file/
-[www]: https://sqlite.org
+### And so on...
+
+Various APIs which accept callbacks, e.g. `sqlite3_trace_v2()` and
+`sqlite3_update_hook()`, use interfaces similar to those shown above.
+Despite the changes in signature, the JNI layer makes every effort to
+provide the same semantics as the C API documentation suggests.
diff --git a/libsql-sqlite3/ext/jni/jar-dist.make b/libsql-sqlite3/ext/jni/jar-dist.make
index 23a26e4a87..7596c99f3f 100644
--- a/libsql-sqlite3/ext/jni/jar-dist.make
+++ b/libsql-sqlite3/ext/jni/jar-dist.make
@@ -6,7 +6,9 @@
 # proper top-level JDK directory and, depending on the platform, add a
 # platform-specific -I directory. It should build as-is with any
 # 2020s-era version of gcc or clang. It requires JDK version 8 or
-# higher.
+# higher and that JAVA_HOME points to the top-most installation
+# directory of that JDK. On Ubuntu-style systems the JDK is typically
+# installed under /usr/lib/jvm/java-VERSION-PLATFORM.
 
 default: all
 
@@ -31,10 +33,11 @@ SQLITE_OPT = \
   -DSQLITE_OMIT_LOAD_EXTENSION \
   -DSQLITE_OMIT_DEPRECATED \
   -DSQLITE_OMIT_SHARED_CACHE \
-  -DSQLITE_THREADSAFE=0 \
+  -DSQLITE_THREADSAFE=1 \
   -DSQLITE_TEMP_STORE=2 \
   -DSQLITE_USE_URI=1 \
-  -DSQLITE_ENABLE_FTS5
+  -DSQLITE_ENABLE_FTS5 \
+  -DSQLITE_DEBUG
 
 sqlite3-jni.dll = libsqlite3-jni.so
 $(sqlite3-jni.dll):
@@ -46,8 +49,10 @@ $(sqlite3-jni.dll):
 		src/sqlite3-jni.c -shared -o $@
 	@echo "Now try running it with: make test"
 
+test.flags = -Djava.library.path=. sqlite3-jni-*.jar
 test: $(sqlite3-jni.dll)
-	java -jar -Djava.library.path=. sqlite3-jni-*.jar
+	java -jar $(test.flags)
+	java -jar $(test.flags) -t 7 -r 10 -shuffle
 
 clean:
 	-rm -f $(sqlite3-jni.dll)
diff --git a/libsql-sqlite3/ext/jni/src/c/sqlite3-jni.c b/libsql-sqlite3/ext/jni/src/c/sqlite3-jni.c
index b28ea71144..245ce4f9e9 100644
--- a/libsql-sqlite3/ext/jni/src/c/sqlite3-jni.c
+++ b/libsql-sqlite3/ext/jni/src/c/sqlite3-jni.c
@@ -10,18 +10,18 @@
 **
 *************************************************************************
 ** This file implements the JNI bindings declared in
-** org.sqlite.jni.SQLiteJni (from which sqlite3-jni.h is generated).
+** org.sqlite.jni.capi.CApi (from which sqlite3-jni.h is generated).
 */
 
-/**
-   If you found this comment by searching the code for
-   CallStaticObjectMethod then you're the victim of an OpenJDK bug:
-
-   https://bugs.openjdk.org/browse/JDK-8130659
-
-   It's known to happen with OpenJDK v8 but not with v19.
-
-   This code does not use JNI's CallStaticObjectMethod().
+/*
+** If you found this comment by searching the code for
+** CallStaticObjectMethod then you're the victim of an OpenJDK bug:
+**
+** https://bugs.openjdk.org/browse/JDK-8130659
+**
+** It's known to happen with OpenJDK v8 but not with v19.
+**
+** This code does not use JNI's CallStaticObjectMethod().
 */
 
 /*
@@ -43,6 +43,14 @@
 
 /**********************************************************************/
 /* SQLITE_ENABLE_... */
+/*
+** Unconditionally enable API_ARMOR in the JNI build. It ensures that
+** public APIs behave predictable in the face of passing illegal NULLs
+** or ranges which might otherwise invoke undefined behavior.
+*/
+#undef SQLITE_ENABLE_API_ARMOR
+#define SQLITE_ENABLE_API_ARMOR 1
+
 #ifndef SQLITE_ENABLE_BYTECODE_VTAB
 #  define SQLITE_ENABLE_BYTECODE_VTAB 1
 #endif
@@ -61,9 +69,6 @@
 #ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
 #  define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
 #endif
-#ifndef SQLITE_ENABLE_PREUPDATE_HOOK
-#  define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/
-#endif
 #ifndef SQLITE_ENABLE_RTREE
 #  define SQLITE_ENABLE_RTREE 1
 #endif
@@ -77,6 +82,14 @@
 //#  define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
 //#endif
 
+/**********************************************************************/
+/* SQLITE_J... */
+#ifdef SQLITE_JNI_FATAL_OOM
+#if !SQLITE_JNI_FATAL_OOM
+#undef SQLITE_JNI_FATAL_OOM
+#endif
+#endif
+
 /**********************************************************************/
 /* SQLITE_M... */
 #ifndef SQLITE_MAX_ALLOCATION_SIZE
@@ -105,7 +118,7 @@
 # define SQLITE_TEMP_STORE 2
 #endif
 #ifndef SQLITE_THREADSAFE
-# define SQLITE_THREADSAFE 0
+# define SQLITE_THREADSAFE 1
 #endif
 
 /**********************************************************************/
@@ -119,9 +132,11 @@
 ** Which sqlite3.c we're using needs to be configurable to enable
 ** building against a custom copy, e.g. the SEE variant. We have to
 ** include sqlite3.c, as opposed to sqlite3.h, in order to get access
-** to SQLITE_MAX_... and friends. This increases the rebuild time
-** considerably but we need this in order to keep the exported values
-** of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C build.
+** to some interal details like SQLITE_MAX_... and friends. This
+** increases the rebuild time considerably but we need this in order
+** to access some internal functionality and keep the to-Java-exported
+** values of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C
+** build.
 */
 #ifndef SQLITE_C
 # define SQLITE_C sqlite3.c
@@ -133,9 +148,14 @@
 #undef INC__STRINGIFY
 #undef SQLITE_C
 
+/*
+** End of the sqlite3 lib setup. What follows is JNI-specific.
+*/
+
 #include "sqlite3-jni.h"
-#include <stdio.h> /* only for testing/debugging */
 #include <assert.h>
+#include <stdio.h> /* only for testing/debugging */
+#include <stdint.h> /* intptr_t for 32-bit builds */
 
 /* Only for debugging */
 #define MARKER(pfexp)                                               \
@@ -143,494 +163,763 @@
     printf pfexp;                                                   \
   } while(0)
 
-/* Creates a verbose JNI function name. */
-#define JFuncName(Suffix) \
-  Java_org_sqlite_jni_SQLite3Jni_sqlite3_ ## Suffix
-
-/* Prologue for JNI functions. */
-#define JDECL(ReturnType,Suffix)                \
-  JNIEXPORT ReturnType JNICALL                  \
-  JFuncName(Suffix)
-/**
-   Shortcuts for the first 2 parameters to all JNI bindings.
-
-   The type of the jSelf arg differs, but no docs seem to mention
-   this: for static methods it's of type jclass and for non-static
-   it's jobject. jobject actually works for all funcs, in the sense
-   that it compiles and runs so long as we don't use jSelf (which is
-   only rarely needed in this code), but to be pedantically correct we
-   need the proper type in the signature.
-
-   Not even the official docs mention this discrepancy:
-
-   https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers
+/*
+** Creates a verbose JNI function name. Suffix must be
+** the JNI-mangled form of the function's name, minus the
+** prefix seen in this macro.
 */
-#define JENV_OSELF JNIEnv * const env, jobject jSelf
-#define JENV_CSELF JNIEnv * const env, jclass jKlazz
-/* Helpers to account for -Xcheck:jni warnings about not having
-   checked for exceptions. */
-#define IFTHREW if((*env)->ExceptionCheck(env))
-#define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env))
-#define EXCEPTION_CLEAR (*env)->ExceptionClear(env)
-#define EXCEPTION_REPORT (*env)->ExceptionDescribe(env)
-#define EXCEPTION_WARN_CALLBACK_THREW(STR)             \
+#define JniFuncName(Suffix) \
+  Java_org_sqlite_jni_capi_CApi_sqlite3_ ## Suffix
+
+/* Prologue for JNI function declarations and definitions. */
+#define JniDecl(ReturnType,Suffix) \
+  JNIEXPORT ReturnType JNICALL JniFuncName(Suffix)
+
+/*
+** S3JniApi's intent is that CFunc be the C API func(s) the
+** being-declared JNI function is wrapping, making it easier to find
+** that function's JNI-side entry point. The other args are for JniDecl.
+** See the many examples in this file.
+*/
+#define S3JniApi(CFunc,ReturnType,Suffix) JniDecl(ReturnType,Suffix)
+
+/*
+** S3JniCast_L2P and P2L cast jlong (64-bit) to/from pointers. This is
+** required for casting warning-free on 32-bit builds, where we
+** otherwise get complaints that we're casting between different-sized
+** int types.
+**
+** This use of intptr_t is the _only_ reason we require <stdint.h>
+** which, in turn, requires building with -std=c99 (or later).
+*/
+#define S3JniCast_L2P(JLongAsPtr) (void*)((intptr_t)(JLongAsPtr))
+#define S3JniCast_P2L(PTR) (jlong)((intptr_t)(PTR))
+
+/*
+** Shortcuts for the first 2 parameters to all JNI bindings.
+**
+** The type of the jSelf arg differs, but no docs seem to mention
+** this: for static methods it's of type jclass and for non-static
+** it's jobject. jobject actually works for all funcs, in the sense
+** that it compiles and runs so long as we don't use jSelf (which is
+** only rarely needed in this code), but to be pedantically correct we
+** need the proper type in the signature.
+**
+** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers
+*/
+#define JniArgsEnvObj JNIEnv * const env, jobject jSelf
+#define JniArgsEnvClass JNIEnv * const env, jclass jKlazz
+/*
+** Helpers to account for -Xcheck:jni warnings about not having
+** checked for exceptions.
+*/
+#define S3JniIfThrew if( (*env)->ExceptionCheck(env) )
+#define S3JniExceptionClear (*env)->ExceptionClear(env)
+#define S3JniExceptionReport (*env)->ExceptionDescribe(env)
+#define S3JniExceptionIgnore S3JniIfThrew S3JniExceptionClear
+#define S3JniExceptionWarnIgnore \
+  S3JniIfThrew {S3JniExceptionReport; S3JniExceptionClear;}(void)0
+#define S3JniExceptionWarnCallbackThrew(STR)             \
   MARKER(("WARNING: " STR " MUST NOT THROW.\n"));  \
   (*env)->ExceptionDescribe(env)
-#define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT
-#define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR
 
 /** To be used for cases where we're _really_ not expecting an
     exception, e.g. looking up well-defined Java class members. */
-#define EXCEPTION_IS_FATAL(MSG) IFTHREW {\
-    EXCEPTION_REPORT; EXCEPTION_CLEAR; \
+#define S3JniExceptionIsFatal(MSG) S3JniIfThrew {\
+    S3JniExceptionReport; S3JniExceptionClear; \
     (*env)->FatalError(env, MSG); \
   }
 
-/** Helpers for extracting pointers from jobjects, noting that the
-    corresponding Java interfaces have already done the type-checking.
- */
-#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3)
-#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_stmt)
-#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_value)
-#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_context)
-/* Helpers for Java value reference management. */
-static inline jobject new_global_ref(JNIEnv * const env, jobject const v){
-  return v ? (*env)->NewGlobalRef(env, v) : NULL;
-}
-static inline void delete_global_ref(JNIEnv * const env, jobject const v){
-  if(v) (*env)->DeleteGlobalRef(env, v);
-}
-static inline void delete_local_ref(JNIEnv * const env, jobject const v){
-  if(v) (*env)->DeleteLocalRef(env, v);
-}
-#define REF_G(VAR) new_global_ref(env, (VAR))
-#define REF_L(VAR) (*env)->NewLocalRef(env, VAR)
-#define UNREF_G(VAR) delete_global_ref(env,(VAR))
-#define UNREF_L(VAR) delete_local_ref(env,(VAR))
+/*
+** Declares local var env = s3jni_env(). All JNI calls involve a
+** JNIEnv somewhere, always named env, and many of our macros assume
+** env is in scope. Where it's not, but should be, use this to make it
+** so.
+*/
+#define S3JniDeclLocal_env JNIEnv * const env = s3jni_env()
 
-/**
-   Constant string class names used as keys for S3JniGlobal_nph_cache(),
-S3Jni
- and
-   friends.
+/* Fail fatally with an OOM message. */
+static inline void s3jni_oom(JNIEnv * const env){
+  (*env)->FatalError(env, "SQLite3 JNI is out of memory.") /* does not return */;
+}
+
+/*
+** sqlite3_malloc() proxy which fails fatally on OOM.  This should
+** only be used for routines which manage global state and have no
+** recovery strategy for OOM. For sqlite3 API which can reasonably
+** return SQLITE_NOMEM, s3jni_malloc() should be used instead.
+*/
+static void * s3jni_malloc_or_die(JNIEnv * const env, size_t n){
+  void * const rv = sqlite3_malloc(n);
+  if( n && !rv ) s3jni_oom(env);
+  return rv;
+}
+
+/*
+** Works like sqlite3_malloc() unless built with SQLITE_JNI_FATAL_OOM,
+** in which case it calls s3jni_oom() on OOM.
+*/
+#ifdef SQLITE_JNI_FATAL_OOM
+#define s3jni_malloc(SIZE) s3jni_malloc_or_die(env, SIZE)
+#else
+#define s3jni_malloc(SIZE) sqlite3_malloc(((void)env,(SIZE)))
+/* the ((void)env) trickery here is to avoid ^^^^^^ an otherwise
+   unused arg in at least one place. */
+#endif
+
+/*
+** Works like sqlite3_realloc() unless built with SQLITE_JNI_FATAL_OOM,
+** in which case it calls s3jni_oom() on OOM.
+*/
+#ifdef SQLITE_JNI_FATAL_OOM
+static void * s3jni_realloc_or_die(JNIEnv * const env, void * p, size_t n){
+  void * const rv = sqlite3_realloc(p, (int)n);
+  if( n && !rv ) s3jni_oom(env);
+  return rv;
+}
+#define s3jni_realloc(MEM,SIZE) s3jni_realloc_or_die(env, (MEM), (SIZE))
+#else
+#define s3jni_realloc(MEM,SIZE) sqlite3_realloc((MEM), ((void)env, (SIZE)))
+#endif
+
+/* Fail fatally if !EXPR. */
+#define s3jni_oom_fatal(EXPR) if( !(EXPR) ) s3jni_oom(env)
+/* Maybe fail fatally if !EXPR. */
+#ifdef SQLITE_JNI_FATAL_OOM
+#define s3jni_oom_check s3jni_oom_fatal
+#else
+#define s3jni_oom_check(EXPR)
+#endif
+//#define S3JniDb_oom(pDb,EXPR) ((EXPR) ? sqlite3OomFault(pDb) : 0)
+
+#define s3jni_db_oom(pDb) (void)((pDb) ? ((pDb)->mallocFailed=1) : 0)
+
+/* Helpers for Java value reference management. */
+static jobject s3jni_ref_global(JNIEnv * const env, jobject const v){
+  jobject const rv = v ? (*env)->NewGlobalRef(env, v) : NULL;
+  s3jni_oom_fatal( v ? !!rv : 1 );
+  return rv;
+}
+static jobject s3jni_ref_local(JNIEnv * const env, jobject const v){
+  jobject const rv = v ? (*env)->NewLocalRef(env, v) : NULL;
+  s3jni_oom_fatal( v ? !!rv : 1 );
+  return rv;
+}
+static inline void s3jni_unref_global(JNIEnv * const env, jobject const v){
+  if( v ) (*env)->DeleteGlobalRef(env, v);
+}
+static inline void s3jni_unref_local(JNIEnv * const env, jobject const v){
+  if( v ) (*env)->DeleteLocalRef(env, v);
+}
+#define S3JniRefGlobal(VAR) s3jni_ref_global(env, (VAR))
+#define S3JniRefLocal(VAR) s3jni_ref_local(env, (VAR))
+#define S3JniUnrefGlobal(VAR) s3jni_unref_global(env, (VAR))
+#define S3JniUnrefLocal(VAR) s3jni_unref_local(env, (VAR))
+
+/*
+** Lookup key type for use with s3jni_nphop() and a cache of a
+** frequently-needed Java-side class reference and one or two Java
+** class member IDs.
+*/
+typedef struct S3JniNphOp S3JniNphOp;
+struct S3JniNphOp {
+  const int index             /* index into S3JniGlobal.nph[] */;
+  const char * const zName    /* Full Java name of the class */;
+  const char * const zMember  /* Name of member property */;
+  const char * const zTypeSig /* JNI type signature of zMember */;
+  /*
+  ** klazz is a global ref to the class represented by pRef.
+  **
+  ** According to:
+  **
+  **   https://developer.ibm.com/articles/j-jni/
+  **
+  ** > ... the IDs returned for a given class don't change for the
+  **   lifetime of the JVM process. But the call to get the field or
+  **   method can require significant work in the JVM, because fields
+  **   and methods might have been inherited from superclasses, making
+  **   the JVM walk up the class hierarchy to find them. Because the
+  **   IDs are the same for a given class, you should look them up
+  **   once and then reuse them. Similarly, looking up class objects
+  **   can be expensive, so they should be cached as well.
+  */
+  jclass klazz;
+  volatile jfieldID fidValue  /* NativePointerHolder.nativePointer or
+                              ** OutputPointer.T.value */;
+  volatile jmethodID midCtor  /* klazz's no-arg constructor. Used by
+                              ** NativePointerHolder_new(). */;
+};
+
+/*
+** Cache keys for each concrete NativePointerHolder subclasses and
+** OutputPointer.T types. The members are to be used with s3jni_nphop()
+** and friends, and each one's member->index corresponds to its index
+** in the S3JniGlobal.nph[] array.
 */
 static const struct {
-  const char * const sqlite3;
-  const char * const sqlite3_stmt;
-  const char * const sqlite3_context;
-  const char * const sqlite3_value;
-  const char * const OutputPointer_Int32;
-  const char * const OutputPointer_Int64;
-  const char * const OutputPointer_String;
-  const char * const OutputPointer_ByteArray;
-  const char * const OutputPointer_sqlite3;
-  const char * const OutputPointer_sqlite3_stmt;
+  const S3JniNphOp sqlite3;
+  const S3JniNphOp sqlite3_backup;
+  const S3JniNphOp sqlite3_blob;
+  const S3JniNphOp sqlite3_context;
+  const S3JniNphOp sqlite3_stmt;
+  const S3JniNphOp sqlite3_value;
+  const S3JniNphOp OutputPointer_Bool;
+  const S3JniNphOp OutputPointer_Int32;
+  const S3JniNphOp OutputPointer_Int64;
+  const S3JniNphOp OutputPointer_sqlite3;
+  const S3JniNphOp OutputPointer_sqlite3_blob;
+  const S3JniNphOp OutputPointer_sqlite3_stmt;
+  const S3JniNphOp OutputPointer_sqlite3_value;
+  const S3JniNphOp OutputPointer_String;
 #ifdef SQLITE_ENABLE_FTS5
-  const char * const Fts5Context;
-  const char * const Fts5ExtensionApi;
-  const char * const fts5_api;
-  const char * const fts5_tokenizer;
-  const char * const Fts5Tokenizer;
+  const S3JniNphOp OutputPointer_ByteArray;
+  const S3JniNphOp Fts5Context;
+  const S3JniNphOp Fts5ExtensionApi;
+  const S3JniNphOp fts5_api;
+  const S3JniNphOp fts5_tokenizer;
+  const S3JniNphOp Fts5Tokenizer;
 #endif
-} S3JniClassNames = {
-  "org/sqlite/jni/sqlite3",
-  "org/sqlite/jni/sqlite3_stmt",
-  "org/sqlite/jni/sqlite3_context",
-  "org/sqlite/jni/sqlite3_value",
-  "org/sqlite/jni/OutputPointer$Int32",
-  "org/sqlite/jni/OutputPointer$Int64",
-  "org/sqlite/jni/OutputPointer$String",
-  "org/sqlite/jni/OutputPointer$ByteArray",
-  "org/sqlite/jni/OutputPointer$sqlite3",
-  "org/sqlite/jni/OutputPointer$sqlite3_stmt",
+} S3JniNphOps = {
+#define MkRef(INDEX, KLAZZ, MEMBER, SIG) \
+  { INDEX, "org/sqlite/jni/" KLAZZ, MEMBER, SIG }
+/* NativePointerHolder ref */
+#define RefN(INDEX, KLAZZ) MkRef(INDEX, KLAZZ, "nativePointer", "J")
+/* OutputPointer.T ref */
+#define RefO(INDEX, KLAZZ, SIG) MkRef(INDEX, KLAZZ, "value", SIG)
+  RefN(0,  "capi/sqlite3"),
+  RefN(1,  "capi/sqlite3_backup"),
+  RefN(2,  "capi/sqlite3_blob"),
+  RefN(3,  "capi/sqlite3_context"),
+  RefN(4,  "capi/sqlite3_stmt"),
+  RefN(5,  "capi/sqlite3_value"),
+  RefO(6,  "capi/OutputPointer$Bool",  "Z"),
+  RefO(7,  "capi/OutputPointer$Int32", "I"),
+  RefO(8,  "capi/OutputPointer$Int64", "J"),
+  RefO(9,  "capi/OutputPointer$sqlite3",
+           "Lorg/sqlite/jni/capi/sqlite3;"),
+  RefO(10, "capi/OutputPointer$sqlite3_blob",
+           "Lorg/sqlite/jni/capi/sqlite3_blob;"),
+  RefO(11, "capi/OutputPointer$sqlite3_stmt",
+           "Lorg/sqlite/jni/capi/sqlite3_stmt;"),
+  RefO(12, "capi/OutputPointer$sqlite3_value",
+           "Lorg/sqlite/jni/capi/sqlite3_value;"),
+  RefO(13, "capi/OutputPointer$String", "Ljava/lang/String;"),
 #ifdef SQLITE_ENABLE_FTS5
-  "org/sqlite/jni/Fts5Context",
-  "org/sqlite/jni/Fts5ExtensionApi",
-  "org/sqlite/jni/fts5_api",
-  "org/sqlite/jni/fts5_tokenizer",
-  "org/sqlite/jni/Fts5Tokenizer"
+  RefO(14, "capi/OutputPointer$ByteArray", "[B"),
+  RefN(15, "fts5/Fts5Context"),
+  RefN(16, "fts5/Fts5ExtensionApi"),
+  RefN(17, "fts5/fts5_api"),
+  RefN(18, "fts5/fts5_tokenizer"),
+  RefN(19, "fts5/Fts5Tokenizer")
 #endif
+#undef MkRef
+#undef RefN
+#undef RefO
 };
 
-/** Create a trivial JNI wrapper for (int CName(void)). */
-#define WRAP_INT_VOID(JniNameSuffix,CName) \
-  JDECL(jint,JniNameSuffix)(JENV_CSELF){ \
-    return (jint)CName(); \
-  }
-
-/** Create a trivial JNI wrapper for (int CName(int)). */
-#define WRAP_INT_INT(JniNameSuffix,CName)               \
-  JDECL(jint,JniNameSuffix)(JENV_CSELF, jint arg){   \
-    return (jint)CName((int)arg);                                    \
-  }
-
-/** Create a trivial JNI wrapper for (const mutf8_string *
-    CName(void)). This is only valid for functions which are known to
-    return ASCII or text which is equivalent in UTF-8 and MUTF-8. */
-#define WRAP_MUTF8_VOID(JniNameSuffix,CName)                             \
-  JDECL(jstring,JniNameSuffix)(JENV_CSELF){                  \
-    return (*env)->NewStringUTF( env, CName() );                        \
-  }
-/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */
-#define WRAP_INT_STMT(JniNameSuffix,CName) \
-  JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpStmt){   \
-    jint const rc = (jint)CName(PtrGet_sqlite3_stmt(jpStmt)); \
-    EXCEPTION_IGNORE /* squelch -Xcheck:jni */;        \
-    return rc; \
-  }
-/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */
-#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \
-  JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint n){ \
-    return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n);                          \
-  }
-/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */
-#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \
-  JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \
-    return (*env)->NewStringUTF(env, CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx));  \
-  }
-/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */
-#define WRAP_INT_DB(JniNameSuffix,CName) \
-  JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pDb){   \
-    return (jint)CName(PtrGet_sqlite3(pDb)); \
-  }
-/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */
-#define WRAP_INT64_DB(JniNameSuffix,CName) \
-  JDECL(jlong,JniNameSuffix)(JENV_CSELF, jobject pDb){   \
-    return (jlong)CName(PtrGet_sqlite3(pDb)); \
-  }
-/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */
-#define WRAP_INT_SVALUE(JniNameSuffix,CName) \
-  JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpSValue){   \
-    return (jint)CName(PtrGet_sqlite3_value(jpSValue)); \
-  }
-
-/* Helpers for jstring and jbyteArray. */
-#define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL)
-#define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR)
-#define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL)
-#define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT)
-
-/* Marker for code which needs(?) to be made thread-safe.  REASON is a
-   terse reminder about why that function requires a mutex.
-*/
-#define FIXME_THREADING(REASON)
+#define S3JniNph(T) &S3JniNphOps.T
 
 enum {
-  /**
-     Size of the NativePointerHolder cache.  Need enough space for
-     (only) the library's NativePointerHolder types, a fixed count
-     known at build-time. If we add more than this a fatal error will
-     be triggered with a reminder to increase this.  This value needs
-     to be exactly the number of entries in the S3JniClassNames
-     object. The S3JniClassNames entries are the keys for this particular
-     cache.
+  /*
+  ** Size of the NativePointerHolder cache.  Need enough space for
+  ** (only) the library's NativePointerHolder and OutputPointer types,
+  ** a fixed count known at build-time.  This value needs to be
+  ** exactly the number of S3JniNphOp entries in the S3JniNphOps
+  ** object.
   */
-  NphCache_SIZE = sizeof(S3JniClassNames) / sizeof(char const *)
+  S3Jni_NphCache_size = sizeof(S3JniNphOps) / sizeof(S3JniNphOp)
 };
 
-/**
-   Cache entry for NativePointerHolder lookups.
+/*
+** State for binding C callbacks to Java methods.
 */
-typedef struct S3JniNphCache S3JniNphCache;
-struct S3JniNphCache {
-  const char * zClassName /* "full/class/Name". Must be a static
-                             string pointer from the S3JniClassNames
-                             struct. */;
-  jclass klazz        /* global ref to the concrete
-                         NativePointerHolder subclass represented by
-                         zClassName */;
-  jmethodID midCtor   /* klazz's no-arg constructor. Used by
-                         new_NativePointerHolder_object(). */;
-  jfieldID fidValue   /* NativePointerHolder.nativePointer and
-                         OutputPointer.X.value */;
-  jfieldID fidSetAgg  /* sqlite3_context::aggregateContext. Used only
-                         by the sqlite3_context binding. */;
+typedef struct S3JniHook S3JniHook;
+struct S3JniHook{
+  jobject jObj            /* global ref to Java instance */;
+  jmethodID midCallback   /* callback method. Signature depends on
+                          ** jObj's type */;
+  /* We lookup the jObj.xDestroy() method as-needed for contexts which
+  ** support custom finalizers. Fundamentally we can support them for
+  ** any Java type, but we only want to expose support for them where
+  ** the C API does. */
+  jobject jExtra          /* Global ref to a per-hook-type value */;
+  int doXDestroy          /* If true then S3JniHook_unref() will call
+                             jObj->xDestroy() if it's available. */;
+  S3JniHook * pNext      /* Next entry in S3Global.hooks.aFree */;
 };
+/* For clean bitwise-copy init of local instances. */
+static const S3JniHook S3JniHook_empty = {0,0,0,0,0};
 
-/**
-   Cache for per-JNIEnv data.
-
-   Potential TODO: move the jclass entries to global space because,
-   per https://developer.android.com/training/articles/perf-jni:
-
-   > once you have a valid jclass global reference you can use it from
-     any attached thread.
-
-   Whereas we cache new refs for each thread.
+/*
+** Per-(sqlite3*) state for various JNI bindings.  This state is
+** allocated as needed, cleaned up in sqlite3_close(_v2)(), and
+** recycled when possible.
+**
+** Trivia: vars and parameters of this type are often named "ps"
+** because this class used to have a name for which that abbreviation
+** made sense.
 */
-typedef struct S3JniEnvCache S3JniEnvCache;
-struct S3JniEnvCache {
-  JNIEnv *env            /* env in which this cache entry was created */;
-  //! The various refs to global classes might be cacheable a single
-  // time globally. Information online seems inconsistent on that
-  // point.
+typedef struct S3JniDb S3JniDb;
+struct S3JniDb {
+  sqlite3 *pDb  /* The associated db handle */;
+  jobject jDb   /* A global ref of the output object which gets
+                   returned from sqlite3_open(_v2)(). We need this in
+                   order to have an object to pass to routines like
+                   sqlite3_collation_needed()'s callback, or else we
+                   have to dynamically create one for that purpose,
+                   which would be fine except that it would be a
+                   different instance (and maybe even a different
+                   class) than the one the user may expect to
+                   receive. */;
+  char * zMainDbName  /* Holds the string allocated on behalf of
+                         SQLITE_DBCONFIG_MAINDBNAME. */;
+  struct {
+    S3JniHook busyHandler;
+    S3JniHook collationNeeded;
+    S3JniHook commit;
+    S3JniHook progress;
+    S3JniHook rollback;
+    S3JniHook trace;
+    S3JniHook update;
+    S3JniHook auth;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    S3JniHook preUpdate;
+#endif
+  } hooks;
+#ifdef SQLITE_ENABLE_FTS5
+  /* FTS5-specific state */
+  struct {
+    jobject jApi  /* global ref to s3jni_fts5_api_from_db() */;
+  } fts;
+#endif
+  S3JniDb * pNext /* Next entry in SJG.perDb.aFree */;
+};
+
+static const char * const S3JniDb_clientdata_key = "S3JniDb";
+#define S3JniDb_from_clientdata(pDb)                                \
+  (pDb ? sqlite3_get_clientdata(pDb, S3JniDb_clientdata_key) : 0)
+
+/*
+** Cache for per-JNIEnv (i.e. per-thread) data.
+**
+** Trivia: vars and parameters of this type are often named "jc"
+** because this class used to have a name for which that abbreviation
+** made sense.
+*/
+typedef struct S3JniEnv S3JniEnv;
+struct S3JniEnv {
+  JNIEnv *env /* JNIEnv in which this cache entry was created */;
+  /*
+  ** pdbOpening is used to coordinate the Java/DB connection of a
+  ** being-open()'d db in the face of auto-extensions.
+  ** Auto-extensions run before we can bind the C db to its Java
+  ** representation, but auto-extensions require that binding to pass
+  ** on to their Java-side callbacks. We handle this as follows:
+  **
+  ** - In the JNI side of sqlite3_open(), allocate the Java side of
+  **   that connection and set pdbOpening to point to that
+  **   object.
+  **
+  ** - Call sqlite3_open(), which triggers the auto-extension
+  **   handler.  That handler uses pdbOpening to connect the native
+  **   db handle which it receives with pdbOpening.
+  **
+  ** - When sqlite3_open() returns, check whether pdbOpening->pDb is
+  **   NULL. If it isn't, auto-extension handling set it up.  If it
+  **   is, complete the Java/C binding unless sqlite3_open() returns
+  **   a NULL db, in which case free pdbOpening.
+  */
+  S3JniDb * pdbOpening;
+  S3JniEnv * pNext /* Next entry in SJG.envCache.aHead or
+                      SJG.envCache.aFree */;
+};
+
+/*
+** State for proxying sqlite3_auto_extension() in Java. This was
+** initially a separate class from S3JniHook and now the older name is
+** retained for readability in the APIs which use this, as well as for
+** its better code-searchability.
+*/
+typedef S3JniHook S3JniAutoExtension;
+
+/*
+** Type IDs for SQL function categories.
+*/
+enum UDFType {
+  UDF_UNKNOWN_TYPE = 0/*for error propagation*/,
+  UDF_SCALAR,
+  UDF_AGGREGATE,
+  UDF_WINDOW
+};
+
+/*
+** State for binding Java-side UDFs.
+*/
+typedef struct S3JniUdf S3JniUdf;
+struct S3JniUdf {
+  jobject jObj           /* SQLFunction instance */;
+  char * zFuncName       /* Only for error reporting and debug logging */;
+  enum UDFType type      /* UDF type */;
+  /** Method IDs for the various UDF methods. */
+  jmethodID jmidxFunc    /* xFunc method (scalar) */;
+  jmethodID jmidxStep    /* xStep method (aggregate/window) */;
+  jmethodID jmidxFinal   /* xFinal method (aggregate/window) */;
+  jmethodID jmidxValue   /* xValue method (window) */;
+  jmethodID jmidxInverse /* xInverse method (window) */;
+  S3JniUdf * pNext       /* Next entry in SJG.udf.aFree. */;
+};
+
+#if defined(SQLITE_JNI_ENABLE_METRICS) && 0==SQLITE_JNI_ENABLE_METRICS
+#  undef SQLITE_JNI_ENABLE_METRICS
+#endif
+
+/*
+** If true, modifying S3JniGlobal.metrics is protected by a mutex,
+** else it isn't.
+*/
+#ifdef SQLITE_DEBUG
+#  define S3JNI_METRICS_MUTEX SQLITE_THREADSAFE
+#else
+#  define S3JNI_METRICS_MUTEX 0
+#endif
+#ifndef SQLITE_JNI_ENABLE_METRICS
+#  undef S3JNI_METRICS_MUTEX
+#  define S3JNI_METRICS_MUTEX 0
+#endif
+
+/*
+** Global state, e.g. caches and metrics.
+*/
+typedef struct S3JniGlobalType S3JniGlobalType;
+struct S3JniGlobalType {
+  /*
+  ** According to: https://developer.ibm.com/articles/j-jni/
+  **
+  ** > A thread can get a JNIEnv by calling GetEnv() using the JNI
+  **   invocation interface through a JavaVM object. The JavaVM object
+  **   itself can be obtained by calling the JNI GetJavaVM() method
+  **   using a JNIEnv object and can be cached and shared across
+  **   threads. Caching a copy of the JavaVM object enables any thread
+  **   with access to the cached object to get access to its own
+  **   JNIEnv when necessary.
+  */
+  JavaVM * jvm;
+  /*
+  ** Global mutex. It must not be used for anything which might call
+  ** back into the JNI layer.
+  */
+  sqlite3_mutex * mutex;
+  /*
+  ** Cache of references to Java classes and method IDs for
+  ** NativePointerHolder subclasses and OutputPointer.T types.
+  */
+  struct {
+    S3JniNphOp list[S3Jni_NphCache_size];
+    sqlite3_mutex * mutex;    /* mutex for this->list */
+    volatile void const * locker;  /* sanity-checking-only context object
+                                      for this->mutex */
+  } nph;
+  /*
+  ** Cache of per-thread state.
+  */
+  struct {
+    S3JniEnv * aHead      /* Linked list of in-use instances */;
+    S3JniEnv * aFree      /* Linked list of free instances */;
+    sqlite3_mutex * mutex /* mutex for aHead and aFree. */;
+    volatile void const * locker  /* env mutex is held on this
+                                     object's behalf.  Used only for
+                                     sanity checking. */;
+  } envCache;
+  /*
+  ** Per-db state. This can move into the core library once we can tie
+  ** client-defined state to db handles there.
+  */
+  struct {
+    S3JniDb * aFree  /* Linked list of free instances */;
+    sqlite3_mutex * mutex /* mutex for aHead and aFree */;
+    volatile void const * locker
+    /* perDb mutex is held on this object's behalf. Used only for
+       sanity checking. Note that the mutex is at the class level, not
+       instance level. */;
+  } perDb;
+  struct {
+    S3JniUdf * aFree    /* Head of the free-item list. Guarded by global
+                           mutex. */;
+  } udf;
+  /*
+  ** Refs to global classes and methods. Obtained during static init
+  ** and never released.
+  */
   struct {
-    jclass cObj              /* global ref to java.lang.Object */;
     jclass cLong             /* global ref to java.lang.Long */;
     jclass cString           /* global ref to java.lang.String */;
     jobject oCharsetUtf8     /* global ref to StandardCharset.UTF_8 */;
     jmethodID ctorLong1      /* the Long(long) constructor */;
     jmethodID ctorStringBA   /* the String(byte[],Charset) constructor */;
     jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
-  } g /* refs to global Java state */;
-#ifdef SQLITE_ENABLE_FTS5
-  jobject jFtsExt     /* Global ref to Java singleton for the
-                         Fts5ExtensionApi instance. */;
-  struct {
-    jclass klazz;
-    jfieldID fidA;
-    jfieldID fidB;
-  } jPhraseIter;
-#endif
-  S3JniEnvCache * pPrev /* Previous entry in the linked list */;
-  S3JniEnvCache * pNext /* Next entry in the linked list */;
-  /** TODO?: S3JniNphCache *pNphHit;
-
-      and always set it to the most recent cache search result.
-
-      The intent would be to help fast-track cache lookups and would
-      speed up, e.g., the sqlite3_value-to-Java-array loop in a
-      multi-threaded app.
+  } g;
+  /*
+  ** The list of Java-side auto-extensions
+  ** (org.sqlite.jni.capi.AutoExtensionCallback objects).
   */
-  S3JniNphCache nph[NphCache_SIZE];
-};
-
-static void S3JniNphCache_clear(JNIEnv * const env, S3JniNphCache * const p){
-  UNREF_G(p->klazz);
-  memset(p, 0, sizeof(S3JniNphCache));
-}
-
-#define S3JNI_ENABLE_AUTOEXT 1
-#if S3JNI_ENABLE_AUTOEXT
-/*
-  Whether auto extensions are feasible here is currently unknown due
-  to...
-
-  1) JNIEnv/threading issues.  A db instance is mapped to a specific
-  JNIEnv object but auto extensions may be added from any thread.  In
-  such contexts, which JNIEnv do we use for the JNI APIs?
-
-  2) a chicken/egg problem involving the Java/C mapping of the db:
-  when auto extensions are run, the db has not yet been connected to
-  Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave
-  properly because they have a different jobject and the API
-  guarantees the user that _that_ object is the one the API will bind
-  the native to.
-
-  If we change the open(_v2()) interfaces to use OutputPointer.sqlite3
-  instead of the client passing in an instance, we could work around
-  (2).
-*/
-typedef struct S3JniAutoExtension S3JniAutoExtension;
-typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*);
-struct S3JniAutoExtension {
-  jobject jObj;
-  jmethodID midFunc;
-  S3JniAutoExtension_xEntryPoint xEntryPoint;
-  S3JniAutoExtension *pNext  /* next linked-list entry */;
-  S3JniAutoExtension *pPrev  /* previous linked-list entry */;
-};
-#endif
-
-/** State for various hook callbacks. */
-typedef struct S3JniHook S3JniHook;
-struct S3JniHook{
-  jobject jObj            /* global ref to Java instance */;
-  jmethodID midCallback   /* callback method. Signature depends on
-                             jObj's type */;
-  jclass klazz            /* global ref to jObj's class. Only needed
-                             by hooks which have an xDestroy() method,
-                             as lookup of that method is deferred
-                             until the object requires cleanup. */;
-};
-
-/**
-   Per-(sqlite3*) state for various JNI bindings.  This state is
-   allocated as needed, cleaned up in sqlite3_close(_v2)(), and
-   recycled when possible. It is freed during sqlite3_shutdown().
-*/
-typedef struct S3JniDb S3JniDb;
-struct S3JniDb {
-  JNIEnv *env   /* The associated JNIEnv handle */;
-  sqlite3 *pDb  /* The associated db handle */;
-  jobject jDb   /* A global ref of the object which was passed to
-                   sqlite3_open(_v2)(). We need this in order to have
-                   an object to pass to sqlite3_collation_needed()'s
-                   callback, or else we have to dynamically create one
-                   for that purpose, which would be fine except that
-                   it would be a different instance (and maybe even a
-                   different class) than the one the user may expect
-                   to receive. */;
-  char * zMainDbName  /* Holds any string allocated on behave of
-                         SQLITE_DBCONFIG_MAINDBNAME. */;
-  S3JniHook busyHandler;
-  S3JniHook collation;
-  S3JniHook collationNeeded;
-  S3JniHook commitHook;
-  S3JniHook progress;
-  S3JniHook rollbackHook;
-  S3JniHook trace;
-  S3JniHook updateHook;
-  S3JniHook authHook;
+  struct {
+    S3JniAutoExtension *aExt /* The auto-extension list. It is
+                                maintained such that all active
+                                entries are in the first contiguous
+                                nExt array elements. */;
+    int nAlloc               /* number of entries allocated for aExt,
+                                as distinct from the number of active
+                                entries. */;
+    int nExt                 /* number of active entries in aExt, all in the
+                                first nExt'th array elements. */;
+    sqlite3_mutex * mutex    /* mutex for manipulation/traversal of aExt */;
+    volatile const void * locker /* object on whose behalf the mutex
+                                    is held.  Only for sanity checking
+                                    in debug builds. */;
+  } autoExt;
 #ifdef SQLITE_ENABLE_FTS5
-  jobject jFtsApi  /* global ref to s3jni_fts5_api_from_db() */;
+  struct {
+    volatile jobject jExt /* Global ref to Java singleton for the
+                             Fts5ExtensionApi instance. */;
+    struct {
+      jfieldID fidA       /* Fts5Phrase::a member */;
+      jfieldID fidB       /* Fts5Phrase::b member */;
+    } jPhraseIter;
+  } fts5;
 #endif
-  S3JniDb * pNext /* Next entry in the available/free list */;
-  S3JniDb * pPrev /* Previous entry in the available/free list */;
-};
-
-/**
-   Global state, e.g. caches and metrics.
-*/
-static struct {
-  /**
-     According to: https://developer.ibm.com/articles/j-jni/
-
-     > A thread can get a JNIEnv by calling GetEnv() using the JNI
-       invocation interface through a JavaVM object. The JavaVM object
-       itself can be obtained by calling the JNI GetJavaVM() method
-       using a JNIEnv object and can be cached and shared across
-       threads. Caching a copy of the JavaVM object enables any thread
-       with access to the cached object to get access to its own
-       JNIEnv when necessary.
-  */
-  JavaVM * jvm;
   struct {
-    S3JniEnvCache * aHead /* Linked list of in-use instances */;
-    S3JniEnvCache * aFree /* Linked list of free instances */;
-  } envCache;
+#ifdef SQLITE_ENABLE_SQLLOG
+    S3JniHook sqllog      /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */;
+#endif
+    S3JniHook configlog   /* sqlite3_config(SQLITE_CONFIG_LOG) callback */;
+    S3JniHook * aFree     /* free-item list, for recycling. */;
+    sqlite3_mutex * mutex /* mutex for aFree */;
+    volatile const void * locker /* object on whose behalf the mutex
+                                    is held.  Only for sanity checking
+                                    in debug builds. */;
+  } hook;
+#ifdef SQLITE_JNI_ENABLE_METRICS
+  /* Internal metrics. */
   struct {
-    S3JniDb * aUsed  /* Linked list of in-use instances */;
-    S3JniDb * aFree  /* Linked list of free instances */;
-  } perDb;
-  struct {
-    unsigned nphCacheHits;
-    unsigned nphCacheMisses;
-    unsigned envCacheHits;
-    unsigned envCacheMisses;
-    unsigned nDestroy        /* xDestroy() calls across all types */;
+    volatile unsigned nEnvHit;
+    volatile unsigned nEnvMiss;
+    volatile unsigned nEnvAlloc;
+    volatile unsigned nMutexEnv       /* number of times envCache.mutex was entered for
+                                         a S3JniEnv operation. */;
+    volatile unsigned nMutexNph       /* number of times SJG.mutex was entered */;
+    volatile unsigned nMutexHook      /* number of times SJG.mutex hooks.was entered */;
+    volatile unsigned nMutexPerDb     /* number of times perDb.mutex was entered */;
+    volatile unsigned nMutexAutoExt   /* number of times autoExt.mutex was entered */;
+    volatile unsigned nMutexGlobal    /* number of times global mutex was entered. */;
+    volatile unsigned nMutexUdf       /* number of times global mutex was entered
+                                         for UDFs. */;
+    volatile unsigned nDestroy        /* xDestroy() calls across all types */;
+    volatile unsigned nPdbAlloc       /* Number of S3JniDb alloced. */;
+    volatile unsigned nPdbRecycled    /* Number of S3JniDb reused. */;
+    volatile unsigned nUdfAlloc       /* Number of S3JniUdf alloced. */;
+    volatile unsigned nUdfRecycled    /* Number of S3JniUdf reused. */;
+    volatile unsigned nHookAlloc      /* Number of S3JniHook alloced. */;
+    volatile unsigned nHookRecycled   /* Number of S3JniHook reused. */;
     struct {
       /* Number of calls for each type of UDF callback. */
-      unsigned nFunc;
-      unsigned nStep;
-      unsigned nFinal;
-      unsigned nValue;
-      unsigned nInverse;
+      volatile unsigned nFunc;
+      volatile unsigned nStep;
+      volatile unsigned nFinal;
+      volatile unsigned nValue;
+      volatile unsigned nInverse;
     } udf;
-  } metrics;
-#if S3JNI_ENABLE_AUTOEXT
-  struct {
-    S3JniAutoExtension *pHead  /* Head of the auto-extension list */;
-    S3JniDb * psOpening  /* handle to the being-opened db. We
-                                  need this so that auto extensions
-                                  can have a consistent view of the
-                                  cross-language db connection and
-                                  behave property if they call further
-                                  db APIs. */;
-    int isRunning              /* True while auto extensions are
-                                  running.  This is used to prohibit
-                                  manipulation of the auto-extension
-                                  list while extensions are
-                                  running. */;
-  } autoExt;
+    unsigned nMetrics                 /* Total number of mutex-locked
+                                         metrics increments. */;
+#if S3JNI_METRICS_MUTEX
+    sqlite3_mutex * mutex;
 #endif
-} S3JniGlobal;
+  } metrics;
+#endif /* SQLITE_JNI_ENABLE_METRICS */
+};
+static S3JniGlobalType S3JniGlobal = {};
+#define SJG S3JniGlobal
 
-#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env)
-static void s3jni_oom(JNIEnv * const env){
-  (*env)->FatalError(env, "Out of memory.") /* does not return */;
+/* Increments *p, possibly protected by a mutex. */
+#ifndef SQLITE_JNI_ENABLE_METRICS
+#define s3jni_incr(PTR)
+#elif S3JNI_METRICS_MUTEX
+static void s3jni_incr( volatile unsigned int * const p ){
+  sqlite3_mutex_enter(SJG.metrics.mutex);
+  ++SJG.metrics.nMetrics;
+  ++(*p);
+  sqlite3_mutex_leave(SJG.metrics.mutex);
+}
+#else
+#define s3jni_incr(PTR) ++(*(PTR))
+#endif
+
+/* Helpers for working with specific mutexes. */
+#if SQLITE_THREADSAFE
+#define s3jni_mutex_enter2(M, Metric) \
+  sqlite3_mutex_enter( M );           \
+  s3jni_incr( &SJG.metrics.Metric )
+#define s3jni_mutex_leave2(M) \
+  sqlite3_mutex_leave( M )
+
+#define s3jni_mutex_enter(M, L, Metric)                    \
+  assert( (void*)env != (void*)L && "Invalid use of " #L); \
+  s3jni_mutex_enter2( M, Metric );                         \
+  L = env
+#define s3jni_mutex_leave(M, L)                            \
+  assert( (void*)env == (void*)L && "Invalid use of " #L); \
+  L = 0;                                                   \
+  s3jni_mutex_leave2( M )
+
+#define S3JniEnv_mutex_assertLocked \
+  assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define S3JniEnv_mutex_assertLocker \
+  assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define S3JniEnv_mutex_assertNotLocker \
+  assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+
+#define S3JniEnv_mutex_enter \
+  s3jni_mutex_enter( SJG.envCache.mutex, SJG.envCache.locker, nMutexEnv )
+#define S3JniEnv_mutex_leave \
+  s3jni_mutex_leave( SJG.envCache.mutex, SJG.envCache.locker )
+
+#define S3JniAutoExt_mutex_enter \
+  s3jni_mutex_enter( SJG.autoExt.mutex, SJG.autoExt.locker, nMutexAutoExt )
+#define S3JniAutoExt_mutex_leave \
+  s3jni_mutex_leave( SJG.autoExt.mutex, SJG.autoExt.locker )
+#define S3JniAutoExt_mutex_assertLocker                     \
+  assert( env == SJG.autoExt.locker && "Misuse of S3JniGlobal.autoExt.mutex" )
+
+#define S3JniGlobal_mutex_enter \
+  s3jni_mutex_enter2( SJG.mutex, nMutexGlobal )
+#define S3JniGlobal_mutex_leave \
+  s3jni_mutex_leave2( SJG.mutex )
+
+#define S3JniHook_mutex_enter \
+  s3jni_mutex_enter( SJG.hook.mutex, SJG.hook.locker, nMutexHook )
+#define S3JniHook_mutex_leave \
+  s3jni_mutex_leave( SJG.hook.mutex, SJG.hook.locker )
+
+#define S3JniNph_mutex_enter \
+  s3jni_mutex_enter( SJG.nph.mutex, SJG.nph.locker, nMutexNph )
+#define S3JniNph_mutex_leave \
+  s3jni_mutex_leave( SJG.nph.mutex, SJG.nph.locker )
+
+#define S3JniDb_mutex_assertLocker \
+  assert( (env) == SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" )
+#define S3JniDb_mutex_enter \
+  s3jni_mutex_enter( SJG.perDb.mutex, SJG.perDb.locker, nMutexPerDb )
+#define S3JniDb_mutex_leave \
+  s3jni_mutex_leave( SJG.perDb.mutex, SJG.perDb.locker )
+
+#else /* SQLITE_THREADSAFE==0 */
+#define S3JniAutoExt_mutex_assertLocker
+#define S3JniAutoExt_mutex_enter
+#define S3JniAutoExt_mutex_leave
+#define S3JniDb_mutex_assertLocker
+#define S3JniDb_mutex_enter
+#define S3JniDb_mutex_leave
+#define S3JniEnv_mutex_assertLocked
+#define S3JniEnv_mutex_assertLocker
+#define S3JniEnv_mutex_assertNotLocker
+#define S3JniEnv_mutex_enter
+#define S3JniEnv_mutex_leave
+#define S3JniGlobal_mutex_enter
+#define S3JniGlobal_mutex_leave
+#define S3JniHook_mutex_enter
+#define S3JniHook_mutex_leave
+#define S3JniNph_mutex_enter
+#define S3JniNph_mutex_leave
+#endif
+
+/* Helpers for jstring and jbyteArray. */
+static const char * s3jni__jstring_to_mutf8(JNIEnv * const env, jstring v ){
+  const char *z = v ? (*env)->GetStringUTFChars(env, v, NULL) : 0;
+  s3jni_oom_check( v ? !!z : !z );
+  return z;
 }
 
-/**
-   sqlite3_malloc() proxy which fails fatally on OOM.  This should
-   only be used for routines which manage global state and have no
-   recovery strategy for OOM. For sqlite3 API which can reasonably
-   return SQLITE_NOMEM, sqlite3_malloc() should be used instead.
-*/
-static void * s3jni_malloc(JNIEnv * const env, size_t n){
-  void * const rv = sqlite3_malloc(n);
-  if(n && !rv) s3jni_oom(env);
+#define s3jni_jstring_to_mutf8(ARG) s3jni__jstring_to_mutf8(env, (ARG))
+#define s3jni_mutf8_release(ARG,VAR) if( VAR ) (*env)->ReleaseStringUTFChars(env, ARG, VAR)
+
+/*
+** If jBA is not NULL then its GetByteArrayElements() value is
+** returned. If jBA is not NULL and nBA is not NULL then *nBA is set
+** to the GetArrayLength() of jBA. If GetByteArrayElements() requires
+** an allocation and that allocation fails then this function either
+** fails fatally or returns 0, depending on build-time options.
+ */
+static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsize * nBA ){
+  jbyte * const rv = jBA ? (*env)->GetByteArrayElements(env, jBA, NULL) : 0;
+  s3jni_oom_check( jBA ? !!rv : 1 );
+  if( jBA && nBA ) *nBA = (*env)->GetArrayLength(env, jBA);
   return rv;
 }
 
-/**
-   Fetches the S3JniGlobal.envCache row for the given env, allocing
-   a row if needed. When a row is allocated, its state is initialized
-   insofar as possible. Calls (*env)->FatalError() if allocation of
-   an entry fails. That's hypothetically possible but "shouldn't happen."
+#define s3jni_jbyteArray_bytes2(jByteArray,ptrToSz) \
+  s3jni__jbyteArray_bytes2(env, (jByteArray), (ptrToSz))
+#define s3jni_jbyteArray_bytes(jByteArray) s3jni__jbyteArray_bytes2(env, (jByteArray), 0)
+#define s3jni_jbyteArray_release(jByteArray,jBytes) \
+  if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_ABORT)
+#define s3jni_jbyteArray_commit(jByteArray,jBytes) \
+  if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT)
+
+/*
+** Returns the current JNIEnv object. Fails fatally if it cannot find
+** the object.
 */
-FIXME_THREADING(S3JniEnvCache)
-static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){
-  struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead;
+static JNIEnv * s3jni_env(void){
+  JNIEnv * env = 0;
+  if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env,
+                                 JNI_VERSION_1_8) ){
+    fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n");
+    abort();
+  }
+  return env;
+}
+
+/*
+** Fetches the S3JniGlobal.envCache row for the given env, allocing a
+** row if needed. When a row is allocated, its state is initialized
+** insofar as possible. Calls (*env)->FatalError() if allocation of an
+** entry fails. That's hypothetically possible but "shouldn't happen."
+*/
+static S3JniEnv * S3JniEnv__get(JNIEnv * const env){
+  struct S3JniEnv * row;
+  S3JniEnv_mutex_enter;
+  row = SJG.envCache.aHead;
   for( ; row; row = row->pNext ){
     if( row->env == env ){
-      ++S3JniGlobal.metrics.envCacheHits;
+      s3jni_incr( &SJG.metrics.nEnvHit );
+      S3JniEnv_mutex_leave;
       return row;
     }
   }
-  ++S3JniGlobal.metrics.envCacheMisses;
-  row = S3JniGlobal.envCache.aFree;
+  s3jni_incr( &SJG.metrics.nEnvMiss );
+  row = SJG.envCache.aFree;
   if( row ){
-    assert(!row->pPrev);
-    S3JniGlobal.envCache.aFree = row->pNext;
-    if( row->pNext ) row->pNext->pPrev = 0;
+    SJG.envCache.aFree = row->pNext;
   }else{
-    row = sqlite3_malloc(sizeof(S3JniEnvCache));
-    if( !row ){
-      (*env)->FatalError(env, "Maintenance required: S3JniEnvCache is full.")
-        /* Does not return, but cc doesn't know that */;
-      return NULL;
-    }
+    row = s3jni_malloc_or_die(env, sizeof(*row));
+    s3jni_incr( &SJG.metrics.nEnvAlloc );
   }
   memset(row, 0, sizeof(*row));
-  row->pNext = S3JniGlobal.envCache.aHead;
-  if(row->pNext) row->pNext->pPrev = row;
-  S3JniGlobal.envCache.aHead = row;
+  row->pNext = SJG.envCache.aHead;
+  SJG.envCache.aHead = row;
   row->env = env;
 
-  /* Grab references to various global classes and objects... */
-  row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object"));
-  EXCEPTION_IS_FATAL("Error getting reference to Object class.");
-
-  row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long"));
-  EXCEPTION_IS_FATAL("Error getting reference to Long class.");
-  row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong,
-                                         "<init>", "(J)V");
-  EXCEPTION_IS_FATAL("Error getting reference to Long constructor.");
-
-  row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String"));
-  EXCEPTION_IS_FATAL("Error getting reference to String class.");
-  row->g.ctorStringBA =
-    (*env)->GetMethodID(env, row->g.cString,
-                        "<init>", "([BLjava/nio/charset/Charset;)V");
-  EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor.");
-  row->g.stringGetBytes =
-    (*env)->GetMethodID(env, row->g.cString,
-                        "getBytes", "(Ljava/nio/charset/Charset;)[B");
-  EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset).");
-
-  { /* StandardCharsets.UTF_8 */
-    jfieldID fUtf8;
-    jclass const klazzSC =
-      (*env)->FindClass(env,"java/nio/charset/StandardCharsets");
-    EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class.");
-    fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8",
-                                     "Ljava/nio/charset/Charset;");
-    EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field.");
-    row->g.oCharsetUtf8 =
-      REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8));
-    EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8.");
-  }
+  S3JniEnv_mutex_leave;
   return row;
 }
 
+#define S3JniEnv_get() S3JniEnv__get(env)
+
 /*
 ** This function is NOT part of the sqlite3 public API. It is strictly
 ** for use by the sqlite project's own Java/JNI bindings.
 **
 ** For purposes of certain hand-crafted JNI function bindings, we
 ** need a way of reporting errors which is consistent with the rest of
-** the C API, as opposed to throwing JS exceptions. To that end, this
+** the C API, as opposed to throwing Java exceptions. To that end, this
 ** internal-use-only function is a thin proxy around
 ** sqlite3ErrorWithMessage(). The intent is that it only be used from
 ** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not
@@ -638,813 +927,741 @@ static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){
 **
 ** Returns err_code.
 */
-static int s3jni_db_error(sqlite3* const db, int err_code, const char * const zMsg){
+static int s3jni_db_error(sqlite3* const db, int err_code,
+                          const char * const zMsg){
   if( db!=0 ){
     if( 0==zMsg ){
       sqlite3Error(db, err_code);
     }else{
       const int nMsg = sqlite3Strlen30(zMsg);
+      sqlite3_mutex_enter(sqlite3_db_mutex(db));
       sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
+      sqlite3_mutex_leave(sqlite3_db_mutex(db));
     }
   }
   return err_code;
 }
 
-/**
-   Creates a new jByteArray of length nP, copies p's contents into it, and
-   returns that byte array.
- */
-static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * const p, int nP){
+/*
+** Creates a new jByteArray of length nP, copies p's contents into it,
+** and returns that byte array (NULL on OOM unless fail-fast alloc
+** errors are enabled). p may be NULL, in which case the array is
+** created but no bytes are filled.
+*/
+static jbyteArray s3jni__new_jbyteArray(JNIEnv * const env,
+                                       const void * const p, int nP){
   jbyteArray jba = (*env)->NewByteArray(env, (jint)nP);
-  if(jba){
+
+  s3jni_oom_check( jba );
+  if( jba && p ){
     (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p);
   }
   return jba;
 }
 
-/**
-   Uses the java.lang.String(byte[],Charset) constructor to create a
-   new String from UTF-8 string z. n is the number of bytes to
-   copy. If n<0 then sqlite3Strlen30() is used to calculate it.
+#define s3jni_new_jbyteArray(P,n) s3jni__new_jbyteArray(env, P, n)
 
-   Returns NULL if z is NULL or on OOM, else returns a new jstring
-   owned by the caller.
 
-   Sidebar: this is a painfully inefficient way to convert from
-   standard UTF-8 to a Java string, but JNI offers only algorithms for
-   working with MUTF-8, not UTF-8.
+/*
+** Uses the java.lang.String(byte[],Charset) constructor to create a
+** new String from UTF-8 string z. n is the number of bytes to
+** copy. If n<0 then sqlite3Strlen30() is used to calculate it.
+**
+** Returns NULL if z is NULL or on OOM, else returns a new jstring
+** owned by the caller.
+**
+** Sidebar: this is a painfully inefficient way to convert from
+** standard UTF-8 to a Java string, but JNI offers only algorithms for
+** working with MUTF-8, not UTF-8.
 */
-static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc,
-                                     const char * const z, int n){
+static jstring s3jni__utf8_to_jstring(JNIEnv * const env,
+                                      const char * const z, int n){
   jstring rv = NULL;
-  JNIEnv * const env = jc->env;
   if( 0==n || (n<0 && z && !z[0]) ){
     /* Fast-track the empty-string case via the MUTF-8 API. We could
        hypothetically do this for any strings where n<4 and z is
        NUL-terminated and none of z[0..3] are NUL bytes. */
     rv = (*env)->NewStringUTF(env, "");
+    s3jni_oom_check( rv );
   }else if( z ){
     jbyteArray jba;
     if( n<0 ) n = sqlite3Strlen30(z);
-    jba = s3jni_new_jbyteArray(env, (unsigned const char *)z, (jsize)n);
+    jba = s3jni_new_jbyteArray((unsigned const char *)z, n);
     if( jba ){
-      rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA,
-                             jba, jc->g.oCharsetUtf8);
-      UNREF_L(jba);
+      rv = (*env)->NewObject(env, SJG.g.cString, SJG.g.ctorStringBA,
+                             jba, SJG.g.oCharsetUtf8);
+      S3JniIfThrew{
+        S3JniExceptionReport;
+        S3JniExceptionClear;
+      }
+      S3JniUnrefLocal(jba);
     }
+    s3jni_oom_check( rv );
   }
   return rv;
 }
+#define s3jni_utf8_to_jstring(CStr,n) s3jni__utf8_to_jstring(env, CStr, n)
 
-/**
-   Converts the given java.lang.String object into a NUL-terminated
-   UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8).
-   Returns NULL if jstr is NULL or on allocation error. If jstr is not
-   NULL and nLen is not NULL then nLen is set to the length of the
-   returned string, not including the terminating NUL. If jstr is not
-   NULL and it returns NULL, this indicates an allocation error. In
-   that case, if nLen is not NULL then it is either set to 0 (if
-   fetching of jstr's bytes fails to allocate) or set to what would
-   have been the length of the string had C-string allocation
-   succeeded.
-
-   The returned memory is allocated from sqlite3_malloc() and
-   ownership is transferred to the caller.
+/*
+** Converts the given java.lang.String object into a NUL-terminated
+** UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8).
+** Returns NULL if jstr is NULL or on allocation error. If jstr is not
+** NULL and nLen is not NULL then nLen is set to the length of the
+** returned string, not including the terminating NUL. If jstr is not
+** NULL and it returns NULL, this indicates an allocation error. In
+** that case, if nLen is not NULL then it is either set to 0 (if
+** fetching of jstr's bytes fails to allocate) or set to what would
+** have been the length of the string had C-string allocation
+** succeeded.
+**
+** The returned memory is allocated from sqlite3_malloc() and
+** ownership is transferred to the caller.
 */
-static char * s3jni_jstring_to_utf8(S3JniEnvCache * const jc,
+static char * s3jni__jstring_to_utf8(JNIEnv * const env,
                                     jstring jstr, int *nLen){
-  JNIEnv * const env = jc->env;
   jbyteArray jba;
-  jsize nBa;
+  jsize nBA;
   char *rv;
 
-  if(!jstr) return 0;
-  jba = (*env)->CallObjectMethod(env, jstr, jc->g.stringGetBytes,
-                                 jc->g.oCharsetUtf8);
+  if( !jstr ) return 0;
+  jba = (*env)->CallObjectMethod(env, jstr, SJG.g.stringGetBytes,
+                                 SJG.g.oCharsetUtf8);
+
   if( (*env)->ExceptionCheck(env) || !jba
       /* order of these checks is significant for -Xlint:jni */ ) {
-    EXCEPTION_REPORT;
+    S3JniExceptionReport;
+    s3jni_oom_check( jba );
     if( nLen ) *nLen = 0;
     return 0;
   }
-  nBa = (*env)->GetArrayLength(env, jba);
-  if( nLen ) *nLen = (int)nBa;
-  rv = sqlite3_malloc( nBa + 1 );
+  nBA = (*env)->GetArrayLength(env, jba);
+  if( nLen ) *nLen = (int)nBA;
+  rv = s3jni_malloc( nBA + 1 );
   if( rv ){
-    (*env)->GetByteArrayRegion(env, jba, 0, nBa, (jbyte*)rv);
-    rv[nBa] = 0;
+    (*env)->GetByteArrayRegion(env, jba, 0, nBA, (jbyte*)rv);
+    rv[nBA] = 0;
   }
-  UNREF_L(jba);
+  S3JniUnrefLocal(jba);
+  return rv;
+}
+#define s3jni_jstring_to_utf8(JStr,n) s3jni__jstring_to_utf8(env, JStr, n)
+
+/*
+** Expects to be passed a pointer from sqlite3_column_text16() or
+** sqlite3_value_text16() and a byte-length value from
+** sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a
+** Java String of exactly half that character length, returning NULL
+** if !p or (*env)->NewString() fails.
+*/
+static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){
+  jstring const rv = p
+    ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2))
+    : NULL;
+  s3jni_oom_check( p ? !!rv : 1 );
   return rv;
 }
 
-/**
-   Expects to be passed a pointer from sqlite3_column_text16() or
-   sqlite3_value_text16() and a byte-length value from
-   sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a
-   Java String of exactly half that character length, returning NULL
-   if !p or (*env)->NewString() fails.
+/*
+** Requires jx to be a Throwable. Calls its toString() method and
+** returns its value converted to a UTF-8 string. The caller owns the
+** returned string and must eventually sqlite3_free() it.  Returns 0
+** if there is a problem fetching the info or on OOM.
+**
+** Design note: we use toString() instead of getMessage() because the
+** former includes the exception type's name:
+**
+**  Exception e = new RuntimeException("Hi");
+**  System.out.println(e.toString()); // java.lang.RuntimeException: Hi
+**  System.out.println(e.getMessage()); // Hi
 */
-static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){
-  return p
-    ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2))
-    : NULL;
-}
-
-/**
-   Requires jx to be a Throwable. Calls its toString() method and
-   returns its value converted to a UTF-8 string. The caller owns the
-   returned string and must eventually sqlite3_free() it.  Returns 0
-   if there is a problem fetching the info or on OOM.
-
-   Design note: we use toString() instead of getMessage() because the
-   former includes the exception type's name:
-
-    Exception e = new RuntimeException("Hi");
-    System.out.println(e.toString()); // java.lang.RuntimeException: Hi
-    System.out.println(e.getMessage()); // Hi
-  }
-*/
-FIXME_THREADING(S3JniEnvCache)
-static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){
-  S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
+static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx){
   jmethodID mid;
   jstring msg;
   char * zMsg;
   jclass const klazz = (*env)->GetObjectClass(env, jx);
   mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;");
-  IFTHREW{
-    EXCEPTION_REPORT;
-    EXCEPTION_CLEAR;
+  S3JniUnrefLocal(klazz);
+  S3JniIfThrew{
+    S3JniExceptionReport;
+    S3JniExceptionClear;
     return 0;
   }
   msg = (*env)->CallObjectMethod(env, jx, mid);
-  IFTHREW{
-    EXCEPTION_REPORT;
-    EXCEPTION_CLEAR;
+  S3JniIfThrew{
+    S3JniExceptionReport;
+    S3JniExceptionClear;
     return 0;
   }
-  zMsg = s3jni_jstring_to_utf8(jc, msg, 0);
-  UNREF_L(msg);
+  zMsg = s3jni_jstring_to_utf8( msg, 0);
+  S3JniUnrefLocal(msg);
   return zMsg;
 }
 
-/**
-   Extracts the current JNI exception, sets ps->pDb's error message to
-   its message string, and clears the exception. If errCode is non-0,
-   it is used as-is, else SQLITE_ERROR is assumed. If there's a
-   problem extracting the exception's message, it's treated as
-   non-fatal and zDfltMsg is used in its place.
-
-   This must only be called if a JNI exception is pending.
-
-   Returns errCode unless it is 0, in which case SQLITE_ERROR is
-   returned.
+/*
+** Extracts env's current exception, sets ps->pDb's error message to
+** its message string, and clears the exception. If errCode is non-0,
+** it is used as-is, else SQLITE_ERROR is assumed. If there's a
+** problem extracting the exception's message, it's treated as
+** non-fatal and zDfltMsg is used in its place.
+**
+** Locks the global S3JniDb mutex.
+**
+** This must only be called if a JNI exception is pending.
+**
+** Returns errCode unless it is 0, in which case SQLITE_ERROR is
+** returned.
 */
-static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps,
+static int s3jni__db_exception(JNIEnv * const env, sqlite3 * const pDb,
                               int errCode, const char *zDfltMsg){
   jthrowable const ex = (*env)->ExceptionOccurred(env);
 
   if( 0==errCode ) errCode = SQLITE_ERROR;
   if( ex ){
     char * zMsg;
-    EXCEPTION_CLEAR;
+    S3JniExceptionClear;
     zMsg = s3jni_exception_error_msg(env, ex);
-    s3jni_db_error(ps->pDb, errCode, zMsg ? zMsg : zDfltMsg);
+    s3jni_db_error(pDb, errCode, zMsg ? zMsg : zDfltMsg);
     sqlite3_free(zMsg);
-    UNREF_L(ex);
+    S3JniUnrefLocal(ex);
+  }else if( zDfltMsg ){
+    s3jni_db_error(pDb, errCode, zDfltMsg);
   }
-   return errCode;
+  return errCode;
 }
+#define s3jni_db_exception(pDb,ERRCODE,DFLTMSG) \
+  s3jni__db_exception(env, (pDb), (ERRCODE), (DFLTMSG) )
 
-/**
-   Extracts the (void xDestroy()) method from the given jclass and
-   applies it to jobj. If jObj is NULL, this is a no-op. If klazz is
-   NULL then it's derived from jobj. The lack of an xDestroy() method
-   is silently ignored and any exceptions thrown by the method trigger
-   a warning to stdout or stderr and then the exception is suppressed.
+/*
+** Extracts the (void xDestroy()) method from jObj and applies it to
+** jObj. If jObj is NULL, this is a no-op. The lack of an xDestroy()
+** method is silently ignored. Any exceptions thrown by xDestroy()
+** trigger a warning to stdout or stderr and then the exception is
+** suppressed.
 */
-static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){
-  if(jObj){
-    jmethodID method;
-    if(!klazz){
-      klazz = (*env)->GetObjectClass(env, jObj);
-      assert(klazz);
-    }
-    method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V");
-    //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method));
-    if(method){
-      ++S3JniGlobal.metrics.nDestroy;
+static void s3jni__call_xDestroy(JNIEnv * const env, jobject jObj){
+  if( jObj ){
+    jclass const klazz = (*env)->GetObjectClass(env, jObj);
+    jmethodID method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V");
+
+    S3JniUnrefLocal(klazz);
+    if( method ){
+      s3jni_incr( &SJG.metrics.nDestroy );
       (*env)->CallVoidMethod(env, jObj, method);
-      IFTHREW{
-        EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback");
-        EXCEPTION_CLEAR;
+      S3JniIfThrew{
+        S3JniExceptionWarnCallbackThrew("xDestroy() callback");
+        S3JniExceptionClear;
       }
     }else{
-      EXCEPTION_CLEAR;
+      /* Non-fatal. */
+      S3JniExceptionClear;
     }
   }
 }
+#define s3jni_call_xDestroy(JOBJ) s3jni__call_xDestroy(env, (JOBJ))
 
-/**
-   Removes any Java references from s and clears its state. If
-   doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's
-   s is passed to s3jni_call_xDestroy() before any references are
-   cleared. It is legal to call this when the object has no Java
-   references.
+/*
+** Internal helper for many hook callback impls. Locks the S3JniDb
+** mutex, makes a copy of src into dest, with a some differences: (1)
+** if src->jObj or src->jExtra are not NULL then dest will be a new
+** LOCAL ref to it instead of a copy of the prior GLOBAL ref. (2)
+** dest->doXDestroy is always false.
+**
+** If dest->jObj is not NULL when this returns then the caller is
+** obligated to eventually free the new ref by passing *dest to
+** S3JniHook_localundup(). The dest pointer must NOT be passed to
+** S3JniHook_unref(), as that routine assumes that dest->jObj/jExtra
+** are GLOBAL refs (it's illegal to try to unref the wrong ref type).
+**
+** Background: when running a hook we need a call-local copy lest
+** another thread modify the hook while we're running it. That copy
+** has to have its own Java reference, but it need only be call-local.
 */
-static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDestroy){
-  if(doXDestroy && s->klazz && s->jObj){
-    s3jni_call_xDestroy(env, s->jObj, s->klazz);
-  }
-  UNREF_G(s->jObj);
-  UNREF_G(s->klazz);
-  memset(s, 0, sizeof(*s));
+static void S3JniHook__localdup( JNIEnv * const env, S3JniHook const * const src,
+                                 S3JniHook * const dest ){
+  S3JniHook_mutex_enter;
+  *dest = *src;
+  if(src->jObj) dest->jObj = S3JniRefLocal(src->jObj);
+  if(src->jExtra) dest->jExtra = S3JniRefLocal(src->jExtra);
+  dest->doXDestroy = 0;
+  S3JniHook_mutex_leave;
 }
+#define S3JniHook_localdup(src,dest) S3JniHook__localdup(env,src,dest)
 
-/**
-   Clears s's state and moves it to the free-list.
+static void S3JniHook__localundup( JNIEnv * const env, S3JniHook * const h  ){
+  S3JniUnrefLocal(h->jObj);
+  S3JniUnrefLocal(h->jExtra);
+  *h = S3JniHook_empty;
+}
+#define S3JniHook_localundup(HOOK) S3JniHook__localundup(env, &(HOOK))
+
+/*
+** Removes any Java references from s and clears its state. If
+** doXDestroy is true and s->jObj is not NULL, s->jObj
+** is passed to s3jni_call_xDestroy() before any references are
+** cleared. It is legal to call this when the object has no Java
+** references. s must not be NULL.
 */
-FIXME_THREADING(perDb)
-static void S3JniDb_set_aside(S3JniDb * const s){
-  if(s){
-    JNIEnv * const env = s->env;
-    assert(s->pDb && "Else this object is already in the free-list.");
-    //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb));
-    assert(s->pPrev != s);
-    assert(s->pNext != s);
-    assert(s->pPrev ? (s->pPrev!=s->pNext) : 1);
-    if(s->pNext) s->pNext->pPrev = s->pPrev;
-    if(s->pPrev) s->pPrev->pNext = s->pNext;
-    else if(S3JniGlobal.perDb.aUsed == s){
-      assert(!s->pPrev);
-      S3JniGlobal.perDb.aUsed = s->pNext;
+static void S3JniHook__unref(JNIEnv * const env, S3JniHook * const s){
+  if( s->jObj ){
+    if( s->doXDestroy ){
+      s3jni_call_xDestroy(s->jObj);
     }
-    sqlite3_free( s->zMainDbName );
-#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY)
-    UNHOOK(trace, 0);
-    UNHOOK(progress, 0);
-    UNHOOK(commitHook, 0);
-    UNHOOK(rollbackHook, 0);
-    UNHOOK(updateHook, 0);
-    UNHOOK(authHook, 0);
-    UNHOOK(collation, 1);
-    UNHOOK(collationNeeded, 1);
-    UNHOOK(busyHandler, 1);
+    S3JniUnrefGlobal(s->jObj);
+    S3JniUnrefGlobal(s->jExtra);
+  }else{
+    assert( !s->jExtra );
+  }
+  *s = S3JniHook_empty;
+}
+#define S3JniHook_unref(hook) S3JniHook__unref(env, (hook))
+
+/*
+** Allocates one blank S3JniHook object from the recycling bin, if
+** available, else from the heap. Returns NULL or dies on OOM,
+** depending on build options.  Locks on SJG.hooks.mutex.
+*/
+static S3JniHook *S3JniHook__alloc(JNIEnv  * const env){
+  S3JniHook * p = 0;
+  S3JniHook_mutex_enter;
+  if( SJG.hook.aFree ){
+    p = SJG.hook.aFree;
+    SJG.hook.aFree = p->pNext;
+    p->pNext = 0;
+    s3jni_incr(&SJG.metrics.nHookRecycled);
+  }
+  S3JniHook_mutex_leave;
+  if( 0==p ){
+    p = s3jni_malloc(sizeof(S3JniHook));
+    if( p ){
+      s3jni_incr(&SJG.metrics.nHookAlloc);
+    }
+  }
+  if( p ){
+    *p = S3JniHook_empty;
+  }
+  return p;
+}
+#define S3JniHook_alloc() S3JniHook__alloc(env)
+
+/*
+** The rightful fate of all results from S3JniHook_alloc(). Locks on
+** SJG.hook.mutex.
+*/
+static void S3JniHook__free(JNIEnv  * const env, S3JniHook * const p){
+  if(p){
+    assert( !p->pNext );
+    S3JniHook_unref(p);
+    S3JniHook_mutex_enter;
+    p->pNext = SJG.hook.aFree;
+    SJG.hook.aFree = p;
+    S3JniHook_mutex_leave;
+  }
+}
+#define S3JniHook_free(hook) S3JniHook__free(env, hook)
+
+#if 0
+/* S3JniHook__free() without the lock: caller must hold the global mutex */
+static void S3JniHook__free_unlocked(JNIEnv  * const env, S3JniHook * const p){
+  if(p){
+    assert( !p->pNext );
+    assert( p->pNext != SJG.hook.aFree );
+    S3JniHook_unref(p);
+    p->pNext = SJG.hook.aFree;
+    SJG.hook.aFree = p;
+  }
+}
+#define S3JniHook_free_unlocked(hook) S3JniHook__free_unlocked(env, hook)
+#endif
+
+/*
+** Clears all of s's state. Requires that that the caller has locked
+** S3JniGlobal.perDb.mutex. Make sure to do anything needed with
+** s->pNext and s->pPrev before calling this, as this clears them.
+*/
+static void S3JniDb_clear(JNIEnv * const env, S3JniDb * const s){
+  S3JniDb_mutex_assertLocker;
+  sqlite3_free( s->zMainDbName );
+#define UNHOOK(MEMBER) \
+  S3JniHook_unref(&s->hooks.MEMBER)
+  UNHOOK(auth);
+  UNHOOK(busyHandler);
+  UNHOOK(collationNeeded);
+  UNHOOK(commit);
+  UNHOOK(progress);
+  UNHOOK(rollback);
+  UNHOOK(trace);
+  UNHOOK(update);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+  UNHOOK(preUpdate);
+#endif
 #undef UNHOOK
-    UNREF_G(s->jDb);
-#ifdef SQLITE_ENABLE_FTS5
-    UNREF_G(s->jFtsApi);
-#endif
-    memset(s, 0, sizeof(S3JniDb));
-    s->pNext = S3JniGlobal.perDb.aFree;
-    if(s->pNext) s->pNext->pPrev = s;
-    S3JniGlobal.perDb.aFree = s;
-    //MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext));
-    //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev));
-  }
+  S3JniUnrefGlobal(s->jDb);
+  memset(s, 0, sizeof(S3JniDb));
 }
 
-/**
-   Requires that p has been snipped from any linked list it is
-   in. Clears all Java refs p holds and zeroes out p.
+/*
+** Clears s's state and moves it to the free-list. Requires that
+** S3JniGlobal.perDb.mutex is locked.
 */
-static void S3JniEnvCache_clear(S3JniEnvCache * const p){
-  JNIEnv * const env = p->env;
-  if(env){
-    int i;
-    UNREF_G(p->g.cObj);
-    UNREF_G(p->g.cLong);
-    UNREF_G(p->g.cString);
-    UNREF_G(p->g.oCharsetUtf8);
-#ifdef SQLITE_ENABLE_FTS5
-    UNREF_G(p->jFtsExt);
-    UNREF_G(p->jPhraseIter.klazz);
-#endif
-    for( i = 0; i < NphCache_SIZE; ++i ){
-      S3JniNphCache_clear(env, &p->nph[i]);
-    }
-    memset(p, 0, sizeof(S3JniEnvCache));
+static void S3JniDb__set_aside_unlocked(JNIEnv * const env, S3JniDb * const s){
+  assert( s );
+  S3JniDb_mutex_assertLocker;
+  if( s ){
+    S3JniDb_clear(env, s);
+    s->pNext = SJG.perDb.aFree;
+    SJG.perDb.aFree = s;
   }
 }
+#define S3JniDb_set_aside_unlocked(JniDb) S3JniDb__set_aside_unlocked(env, JniDb)
 
-/**
-   Cleans up all state in S3JniGlobal.perDb for th given JNIEnv.
-   Results are undefined if a Java-side db uses the API
-   from the given JNIEnv after this call.
-*/
-FIXME_THREADING(perDb)
-static void S3JniDb_free_for_env(JNIEnv *env){
-  S3JniDb * ps = S3JniGlobal.perDb.aUsed;
-  S3JniDb * pNext = 0;
-  for( ; ps; ps = pNext ){
-    pNext = ps->pNext;
-    if(ps->env == env){
-      S3JniDb * const pPrev = ps->pPrev;
-      S3JniDb_set_aside(ps);
-      assert(pPrev ? pPrev->pNext!=ps : 1);
-      pNext = pPrev;
-    }
-  }
+static void S3JniDb__set_aside(JNIEnv * const env, S3JniDb * const s){
+  S3JniDb_mutex_enter;
+  S3JniDb_set_aside_unlocked(s);
+  S3JniDb_mutex_leave;
 }
+#define S3JniDb_set_aside(JNIDB) S3JniDb__set_aside(env, JNIDB)
 
-/**
-   Uncache any state for the given JNIEnv, clearing all Java
-   references the cache owns. Returns true if env was cached and false
-   if it was not found in the cache.
-
-   Also passes env to S3JniDb_free_for_env() to free up
-   what would otherwise be stale references.
+/*
+** Uncache any state for the given JNIEnv, clearing all Java
+** references the cache owns. Returns true if env was cached and false
+** if it was not found in the cache. Ownership of the S3JniEnv object
+** associated with the given argument is transferred to this function,
+** which makes it free for re-use.
+**
+** Requires that the env mutex be locked.
 */
-static int S3JniGlobal_env_uncache(JNIEnv * const env){
-  struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead;
-  for( ; row; row = row->pNext ){
+static int S3JniEnv_uncache(JNIEnv * const env){
+  struct S3JniEnv * row;
+  struct S3JniEnv * pPrev = 0;
+
+  S3JniEnv_mutex_assertLocked;
+  row = SJG.envCache.aHead;
+  for( ; row; pPrev = row, row = row->pNext ){
     if( row->env == env ){
       break;
     }
   }
-  if( !row ) return 0;
-  if( row->pNext ) row->pNext->pPrev = row->pPrev;
-  if( row->pPrev ) row->pPrev->pNext = row->pNext;
-  if( S3JniGlobal.envCache.aHead == row ){
-    assert( !row->pPrev );
-    S3JniGlobal.envCache.aHead = row->pNext;
+  if( !row ){
+    return 0;
   }
-  S3JniEnvCache_clear(row);
-  assert( !row->pNext );
-  assert( !row->pPrev );
-  row->pNext = S3JniGlobal.envCache.aFree;
-  if( row->pNext ) row->pNext->pPrev = row;
-  S3JniGlobal.envCache.aFree = row;
-  S3JniDb_free_for_env(env);
+  if( pPrev) pPrev->pNext = row->pNext;
+  else{
+    assert( SJG.envCache.aHead == row );
+    SJG.envCache.aHead = row->pNext;
+  }
+  memset(row, 0, sizeof(S3JniEnv));
+  row->pNext = SJG.envCache.aFree;
+  SJG.envCache.aFree = row;
   return 1;
 }
 
-static void S3JniGlobal_S3JniEnvCache_clear(void){
-  while( S3JniGlobal.envCache.aHead ){
-    S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env );
-  }
-}
-
-/**
-   Searches the NativePointerHolder cache for the given combination.
-   If it finds one, it returns it as-is. If it doesn't AND the cache
-   has a free slot, it populates that slot with (env, zClassName,
-   klazz) and returns it. If the cache is full with no match it
-   returns NULL.
-
-   It is up to the caller to populate the other members of the returned
-   object if needed.
-
-   zClassName must be a static string so we can use its address as a
-   cache key.
-
-   This simple cache catches >99% of searches in the current
-   (2023-07-31) tests.
+/*
+** Fetches the given nph-ref from cache the cache and returns the
+** object with its klazz member set. This is an O(1) operation except
+** on the first call for a given pRef, during which pRef->klazz and
+** pRef->pRef are initialized thread-safely. In the latter case it's
+** still effectively O(1), but with a much longer 1.
+**
+** It is up to the caller to populate the other members of the
+** returned object if needed, taking care to lock the modification
+** with S3JniNph_mutex_enter/leave.
+**
+** This simple cache catches >99% of searches in the current
+** (2023-07-31) tests.
 */
-FIXME_THREADING(S3JniEnvCache)
-static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zClassName){
-  /**
-     According to:
+static S3JniNphOp * s3jni__nphop(JNIEnv * const env, S3JniNphOp const* pRef){
+  S3JniNphOp * const pNC = &SJG.nph.list[pRef->index];
 
-     https://developer.ibm.com/articles/j-jni/
-
-     > ... the IDs returned for a given class don't change for the
-     lifetime of the JVM process. But the call to get the field or
-     method can require significant work in the JVM, because
-     fields and methods might have been inherited from
-     superclasses, making the JVM walk up the class hierarchy to
-     find them. Because the IDs are the same for a given class,
-     you should look them up once and then reuse them. Similarly,
-     looking up class objects can be expensive, so they should be
-     cached as well.
-  */
-  struct S3JniEnvCache * const envRow = S3JniGlobal_env_cache(env);
-  S3JniNphCache * freeSlot = 0;
-  S3JniNphCache * pCache = 0;
-  int i;
-  assert(envRow);
-  for( i = 0; i < NphCache_SIZE; ++i ){
-    pCache = &envRow->nph[i];
-    if(zClassName == pCache->zClassName){
-      ++S3JniGlobal.metrics.nphCacheHits;
-#define DUMP_NPH_CACHES 0
-#if DUMP_NPH_CACHES
-      MARKER(("Cache hit #%u %s klazz@%p nativePointer field@%p, ctor@%p\n",
-              S3JniGlobal.metrics.nphCacheHits, zClassName, pCache->klazz, pCache->fidValue,
-              pCache->midCtor));
-#endif
-      assert(pCache->klazz);
-      return pCache;
-    }else if(!freeSlot && !pCache->zClassName){
-      freeSlot = pCache;
+  assert( (void*)pRef>=(void*)&S3JniNphOps && (void*)pRef<(void*)(&S3JniNphOps + 1)
+          && "pRef is out of range" );
+  assert( pRef->index>=0
+          && (pRef->index < (sizeof(S3JniNphOps) / sizeof(S3JniNphOp)))
+          && "pRef->index is out of range" );
+  if( !pNC->klazz ){
+    S3JniNph_mutex_enter;
+    if( !pNC->klazz ){
+      jclass const klazz = (*env)->FindClass(env, pRef->zName);
+      //printf("FindClass %s\n", pRef->zName);
+      S3JniExceptionIsFatal("FindClass() unexpectedly threw");
+      pNC->klazz = S3JniRefGlobal(klazz);
     }
+    S3JniNph_mutex_leave;
   }
-  if(freeSlot){
-    freeSlot->zClassName = zClassName;
-    freeSlot->klazz = (*env)->FindClass(env, zClassName);
-    EXCEPTION_IS_FATAL("FindClass() unexpectedly threw");
-    freeSlot->klazz = REF_G(freeSlot->klazz);
-    ++S3JniGlobal.metrics.nphCacheMisses;
-#if DUMP_NPH_CACHES
-    static unsigned int cacheMisses = 0;
-    MARKER(("Cache miss #%u %s klazz@%p nativePointer field@%p, ctor@%p\n",
-            S3JniGlobal.metrics.nphCacheMisses, zClassName, freeSlot->klazz,
-            freeSlot->fidValue, freeSlot->midCtor));
-#endif
-#undef DUMP_NPH_CACHES
-  }else{
-    (*env)->FatalError(env, "MAINTENANCE REQUIRED: NphCache_SIZE is too low.");
-  }
-  return freeSlot;
+  assert( pNC->klazz );
+  return pNC;
 }
 
-/**
-   Returns the ID of the "nativePointer" field from the given
-   NativePointerHolder<T> class.
- */
-static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){
-  jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J");
-  EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field.");
-  return rv;
-}
+#define s3jni_nphop(PRef) s3jni__nphop(env, PRef)
 
-/**
-   Sets a native ptr value in NativePointerHolder object ppOut.
-   zClassName must be a static string so we can use its address
-   as a cache key.
+/*
+** Common code for accessor functions for NativePointerHolder and
+** OutputPointer types. pRef must be a pointer from S3JniNphOps. jOut
+** must be an instance of that class (Java's type safety takes care of
+** that requirement). If necessary, this fetches the jfieldID for
+** jOut's pRef->zMember, which must be of the type represented by the
+** JNI type signature pRef->zTypeSig, and stores it in
+** S3JniGlobal.nph.list[pRef->index].  Fails fatally if the pRef->zMember
+** property is not found, as that presents a serious internal misuse.
+**
+** Property lookups are cached on a per-pRef basis.
 */
-static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p,
-                                    const char *zClassName){
-  jfieldID setter = 0;
-  S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName);
-  if(pCache && pCache->klazz && pCache->fidValue){
-    assert(zClassName == pCache->zClassName);
-    setter = pCache->fidValue;
-    assert(setter);
-  }else{
-    jclass const klazz =
-      pCache ? pCache->klazz : (*env)->GetObjectClass(env, ppOut);
-    setter = NativePointerHolder_getField(env, klazz);
-    if(pCache){
-      assert(pCache->klazz);
-      assert(!pCache->fidValue);
-      assert(zClassName == pCache->zClassName);
-      pCache->fidValue = setter;
+static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphOp const* pRef){
+  S3JniNphOp * const pNC = s3jni_nphop(pRef);
+
+  if( !pNC->fidValue ){
+    S3JniNph_mutex_enter;
+    if( !pNC->fidValue ){
+      pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz,
+                                         pRef->zMember, pRef->zTypeSig);
+      S3JniExceptionIsFatal("Code maintenance required: missing "
+                            "required S3JniNphOp::fidValue.");
     }
+    S3JniNph_mutex_leave;
   }
-  (*env)->SetLongField(env, ppOut, setter, (jlong)p);
-  EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer.");
+  assert( pNC->fidValue );
+  return pNC->fidValue;
 }
 
-/**
-   Fetches a native ptr value from NativePointerHolder object ppOut.
-   zClassName must be a static string so we can use its address as a
-   cache key.
+/*
+** Sets a native ptr value in NativePointerHolder object jNph,
+** which must be of the native type described by pRef. jNph
+** may not be NULL.
 */
-static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zClassName){
-  if( pObj ){
-    jfieldID getter = 0;
-    void * rv = 0;
-    S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName);
-    if(pCache && pCache->fidValue){
-      getter = pCache->fidValue;
-    }else{
-      jclass const klazz =
-        pCache ? pCache->klazz : (*env)->GetObjectClass(env, pObj);
-      getter = NativePointerHolder_getField(env, klazz);
-      if(pCache){
-        assert(pCache->klazz);
-        assert(zClassName == pCache->zClassName);
-        pCache->fidValue = getter;
-      }
-    }
-    rv = (void*)(*env)->GetLongField(env, pObj, getter);
-    IFTHREW_REPORT;
-    return rv;
-  }else{
-    return 0;
-  }
+static void NativePointerHolder__set(JNIEnv * const env, S3JniNphOp const* pRef,
+                                     jobject jNph, const void * p){
+  assert( jNph );
+  (*env)->SetLongField(env, jNph, s3jni_nphop_field(env, pRef),
+                       S3JniCast_P2L(p));
+  S3JniExceptionIsFatal("Could not set NativePointerHolder.nativePointer.");
 }
 
-/**
-   Extracts the new S3JniDb instance from the free-list, or
-   allocates one if needed, associats it with pDb, and returns.
-   Returns NULL on OOM. pDb MUST be associated with jDb via
-   NativePointerHolder_set().
+#define NativePointerHolder_set(PREF,JNPH,P) \
+  NativePointerHolder__set(env, PREF, JNPH, P)
+
+/*
+** Fetches a native ptr value from NativePointerHolder object jNph,
+** which must be of the native type described by pRef.  This is a
+** no-op if jNph is NULL.
 */
-static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb,
-                               jobject jDb){
-  S3JniDb * rv;
-  if(S3JniGlobal.perDb.aFree){
-    rv = S3JniGlobal.perDb.aFree;
-    //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb));
-    //MARKER(("%p->pPrev@%p, pNext@%p\n", rv, rv->pPrev, rv->pNext));
-    S3JniGlobal.perDb.aFree = rv->pNext;
-    assert(rv->pNext != rv);
-    assert(rv->pPrev != rv);
-    assert(rv->pPrev ? (rv->pPrev!=rv->pNext) : 1);
-    if(rv->pNext){
-      assert(rv->pNext->pPrev == rv);
-      assert(rv->pPrev ? (rv->pNext == rv->pPrev->pNext) : 1);
-      rv->pNext->pPrev = 0;
-      rv->pNext = 0;
-    }
-  }else{
-    rv = s3jni_malloc(env, sizeof(S3JniDb));
-    //MARKER(("state@%p for db allocating for db@%p from heap\n", rv, pDb));
-    if(rv){
-      memset(rv, 0, sizeof(S3JniDb));
-    }
-  }
-  if(rv){
-    rv->pNext = S3JniGlobal.perDb.aUsed;
-    S3JniGlobal.perDb.aUsed = rv;
-    if(rv->pNext){
-      assert(!rv->pNext->pPrev);
-      rv->pNext->pPrev = rv;
-    }
-    rv->jDb = REF_G(jDb);
-    rv->pDb = pDb;
-    rv->env = env;
+static void * NativePointerHolder__get(JNIEnv * env, jobject jNph,
+                                       S3JniNphOp const* pRef){
+  void * rv = 0;
+  if( jNph ){
+    rv = S3JniCast_L2P(
+      (*env)->GetLongField(env, jNph, s3jni_nphop_field(env, pRef))
+    );
+    S3JniExceptionIsFatal("Cannot fetch NativePointerHolder.nativePointer.");
   }
   return rv;
 }
 
-#if 0
-static void S3JniDb_dump(S3JniDb *s){
-  MARKER(("S3JniDb->env @ %p\n", s->env));
-  MARKER(("S3JniDb->pDb @ %p\n", s->pDb));
-  MARKER(("S3JniDb->trace.jObj @ %p\n", s->trace.jObj));
-  MARKER(("S3JniDb->progress.jObj @ %p\n", s->progress.jObj));
-  MARKER(("S3JniDb->commitHook.jObj @ %p\n", s->commitHook.jObj));
-  MARKER(("S3JniDb->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj));
-  MARKER(("S3JniDb->busyHandler.jObj @ %p\n", s->busyHandler.jObj));
-  MARKER(("S3JniDb->env @ %p\n", s->env));
-}
-#endif
+#define NativePointerHolder_get(JOBJ,NPHREF) \
+  NativePointerHolder__get(env, (JOBJ), (NPHREF))
 
-/**
-   Returns the S3JniDb object for the given db. If allocIfNeeded is
-   true then a new instance will be allocated if no mapping currently
-   exists, else NULL is returned if no mapping is found.
-
-   The 3rd and 4th args should normally only be non-0 for
-   sqlite3_open(_v2)(). For most other cases, they must be 0, in which
-   case the db handle will be fished out of the jDb object and NULL is
-   returned if jDb does not have any associated S3JniDb.
-
-   If called with a NULL jDb and non-NULL pDb then allocIfNeeded MUST
-   be false and it will look for a matching db object. That usage is
-   required for functions, like sqlite3_context_db_handle(), which
-   return a (sqlite3*) but do not take one as an argument.
+/*
+** Helpers for extracting pointers from jobjects, noting that we rely
+** on the corresponding Java interfaces having already done the
+** type-checking. OBJ must be a jobject referring to a
+** NativePointerHolder<T>, where T matches PtrGet_T. Don't use these
+** in contexts where that's not the case. Note that these aren't
+** type-safe in the strictest sense:
+**
+**   sqlite3 * s = PtrGet_sqlite3_stmt(...)
+**
+** will work, despite the incorrect macro name, so long as the
+** argument is a Java sqlite3 object, as this operation only has void
+** pointers to work with.
 */
-FIXME_THREADING(S3JniEnvCache)
-FIXME_THREADING(perDb)
-static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb,
-                                sqlite3 *pDb, int allocIfNeeded){
-  S3JniDb * s = S3JniGlobal.perDb.aUsed;
-  if(!jDb){
-    if(pDb){
-      assert(!allocIfNeeded);
-    }else{
-      return 0;
+#define PtrGet_T(T,OBJ) (T*)NativePointerHolder_get(OBJ, S3JniNph(T))
+#define PtrGet_sqlite3(OBJ) PtrGet_T(sqlite3, OBJ)
+#define PtrGet_sqlite3_backup(OBJ) PtrGet_T(sqlite3_backup, OBJ)
+#define PtrGet_sqlite3_blob(OBJ) PtrGet_T(sqlite3_blob, OBJ)
+#define PtrGet_sqlite3_context(OBJ) PtrGet_T(sqlite3_context, OBJ)
+#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ)
+#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ)
+/*
+** S3JniLongPtr_T(X,Y) expects X to be an unqualified sqlite3 struct
+** type name and Y to be a native pointer to such an object in the
+** form of a jlong value. The jlong is simply cast to (X*). This
+** approach is, as of 2023-09-27, supplanting the former approach. We
+** now do the native pointer extraction in the Java side, rather than
+** the C side, because it's reportedly significantly faster. The
+** intptr_t part here is necessary for compatibility with (at least)
+** ARM32.
+*/
+#define S3JniLongPtr_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr))
+#define S3JniLongPtr_sqlite3(JLongAsPtr) S3JniLongPtr_T(sqlite3,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_backup(JLongAsPtr) S3JniLongPtr_T(sqlite3_backup,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_blob(JLongAsPtr) S3JniLongPtr_T(sqlite3_blob,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_stmt(JLongAsPtr) S3JniLongPtr_T(sqlite3_stmt,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_value(JLongAsPtr) S3JniLongPtr_T(sqlite3_value,JLongAsPtr)
+/*
+** Extracts the new S3JniDb instance from the free-list, or allocates
+** one if needed, associates it with pDb, and returns.  Returns NULL
+** on OOM. The returned object MUST, on success of the calling
+** operation, subsequently be associated with jDb via
+** NativePointerHolder_set() or freed using S3JniDb_set_aside().
+*/
+static S3JniDb * S3JniDb_alloc(JNIEnv * const env, jobject jDb){
+  S3JniDb * rv = 0;
+  S3JniDb_mutex_enter;
+  if( SJG.perDb.aFree ){
+    rv = SJG.perDb.aFree;
+    SJG.perDb.aFree = rv->pNext;
+    rv->pNext = 0;
+    s3jni_incr( &SJG.metrics.nPdbRecycled );
+  }
+  S3JniDb_mutex_leave;
+  if( 0==rv ){
+    rv = s3jni_malloc(sizeof(S3JniDb));
+    if( rv ){
+      s3jni_incr( &SJG.metrics.nPdbAlloc );
     }
   }
-  assert(allocIfNeeded ? !!pDb : 1);
-  if(!allocIfNeeded && !pDb){
-    pDb = PtrGet_sqlite3(jDb);
+  if( rv ){
+    memset(rv, 0, sizeof(S3JniDb));
+    rv->jDb = S3JniRefGlobal(jDb);
   }
-  for( ; pDb && s; s = s->pNext){
-    if(s->pDb == pDb) return s;
-  }
-  if(allocIfNeeded){
-    s = S3JniDb_alloc(env, pDb, jDb);
-  }
-  return s;
+  return rv;
 }
 
-#if 0
-/**
-   An alternative form which searches for the S3JniDb instance for
-   pDb with no JNIEnv-specific info. This can be (but _should_ it be?)
-   called from the context of a separate JNIEnv than the one mapped
-   to in the returned object. Returns 0 if no match is found.
+/*
+** Returns the S3JniDb object for the given org.sqlite.jni.capi.sqlite3
+** object, or NULL if jDb is NULL, no pointer can be extracted
+** from it, or no matching entry can be found.
 */
-FIXME_THREADING(perDb)
-static S3JniDb * S3JniDb_for_db2(sqlite3 *pDb){
-  S3JniDb * s = S3JniGlobal.perDb.aUsed;
-  for( ; pDb && s; s = s->pNext){
-    if(s->pDb == pDb) return s;
+static S3JniDb * S3JniDb__from_java(JNIEnv * const env, jobject jDb){
+  sqlite3 * const pDb = jDb ? PtrGet_sqlite3(jDb) : 0;
+  return pDb ? S3JniDb_from_clientdata(pDb) : 0;
+}
+#define S3JniDb_from_java(jObject) S3JniDb__from_java(env,(jObject))
+
+/*
+** S3JniDb finalizer for use with sqlite3_set_clientdata().
+*/
+static void S3JniDb_xDestroy(void *p){
+  S3JniDeclLocal_env;
+  S3JniDb * const ps = p;
+  assert( !ps->pNext && "Else ps is already in the free-list.");
+  S3JniDb_set_aside(ps);
+}
+
+/*
+** Evaluates to the S3JniDb object for the given sqlite3 object, or
+** NULL if pDb is NULL or was not initialized via the JNI interfaces.
+*/
+#define S3JniDb_from_c(sqlite3Ptr) \
+  ((sqlite3Ptr) ? S3JniDb_from_clientdata(sqlite3Ptr) : 0)
+#define S3JniDb_from_jlong(sqlite3PtrAsLong) \
+  S3JniDb_from_c(S3JniLongPtr_T(sqlite3,sqlite3PtrAsLong))
+
+/*
+** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out
+** AX.
+*/
+#define S3JniAutoExtension_clear(AX) S3JniHook_unref(AX);
+
+/*
+** Initializes a pre-allocated S3JniAutoExtension object.  Returns
+** non-0 if there is an error collecting the required state from
+** jAutoExt (which must be an AutoExtensionCallback object). On error,
+** it passes ax to S3JniAutoExtension_clear().
+*/
+static int S3JniAutoExtension_init(JNIEnv *const env,
+                                   S3JniAutoExtension * const ax,
+                                   jobject const jAutoExt){
+  jclass const klazz = (*env)->GetObjectClass(env, jAutoExt);
+
+  S3JniAutoExt_mutex_assertLocker;
+  *ax = S3JniHook_empty;
+  ax->midCallback = (*env)->GetMethodID(env, klazz, "call",
+                                        "(Lorg/sqlite/jni/capi/sqlite3;)I");
+  S3JniUnrefLocal(klazz);
+  S3JniExceptionWarnIgnore;
+  if( !ax->midCallback ){
+    S3JniAutoExtension_clear(ax);
+    return SQLITE_ERROR;
   }
+  ax->jObj = S3JniRefGlobal(jAutoExt);
   return 0;
 }
-#endif
 
-#if S3JNI_ENABLE_AUTOEXT
-/**
-   Unlink ax from S3JniGlobal.autoExt and free it.
+/*
+** Sets the value property of the OutputPointer.Bool jOut object to
+** v.
 */
-static void S3JniAutoExtension_free(JNIEnv * const env,
-                                    S3JniAutoExtension * const ax){
-  if( ax ){
-    if( ax->pNext ) ax->pNext->pPrev = ax->pPrev;
-    if( ax == S3JniGlobal.autoExt.pHead ){
-      assert( !ax->pNext );
-      S3JniGlobal.autoExt.pHead = ax->pNext;
-    }else if( ax->pPrev ){
-      ax->pPrev->pNext = ax->pNext;
-    }
-    ax->pNext = ax->pPrev = 0;
-    UNREF_G(ax->jObj);
-    sqlite3_free(ax);
-  }
+static void OutputPointer_set_Bool(JNIEnv * const env, jobject const jOut,
+                                    int v){
+  (*env)->SetBooleanField(env, jOut, s3jni_nphop_field(
+                            env, S3JniNph(OutputPointer_Bool)
+                          ), v ? JNI_TRUE : JNI_FALSE );
+  S3JniExceptionIsFatal("Cannot set OutputPointer.Bool.value");
 }
 
-/**
-   Allocates a new auto extension and plugs it in to S3JniGlobal.autoExt.
-   Returns 0 on OOM or if there is an error collecting the required
-   state from jAutoExt (which must be an AutoExtension object).
+/*
+** Sets the value property of the OutputPointer.Int32 jOut object to
+** v.
 */
-static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env,
-                                                     jobject const jAutoExt){
-  S3JniAutoExtension * const ax = sqlite3_malloc(sizeof(*ax));
-  if( ax ){
-    jclass klazz;
-    memset(ax, 0, sizeof(*ax));
-    klazz = (*env)->GetObjectClass(env, jAutoExt);
-    if(!klazz){
-      S3JniAutoExtension_free(env, ax);
-      return 0;
-    }
-    ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint",
-                                     "(Lorg/sqlite/jni/sqlite3;)I");
-    if(!ax->midFunc){
-      MARKER(("Error getting xEntryPoint(sqlite3) from object."));
-      S3JniAutoExtension_free(env, ax);
-      return 0;
-    }
-    ax->jObj = REF_G(jAutoExt);
-    ax->pNext = S3JniGlobal.autoExt.pHead;
-    if( ax->pNext ) ax->pNext->pPrev = ax;
-    S3JniGlobal.autoExt.pHead = ax;
-  }
-  return ax;
+static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut,
+                                    int v){
+  (*env)->SetIntField(env, jOut, s3jni_nphop_field(
+                        env, S3JniNph(OutputPointer_Int32)
+                      ), (jint)v);
+  S3JniExceptionIsFatal("Cannot set OutputPointer.Int32.value");
 }
-#endif /* S3JNI_ENABLE_AUTOEXT */
 
-/**
-   Requires that jCx be a Java-side sqlite3_context wrapper for pCx.
-   This function calls sqlite3_aggregate_context() to allocate a tiny
-   sliver of memory, the address of which is set in
-   jCx->aggregateContext.  The memory is only used as a key for
-   mapping client-side results of aggregate result sets across
-   calls to the UDF's callbacks.
-
-   isFinal must be 1 for xFinal() calls and 0 for all others, the
-   difference being that the xFinal() invocation will not allocate
-   new memory if it was not already, resulting in a value of 0
-   for jCx->aggregateContext.
-
-   Returns 0 on success. Returns SQLITE_NOMEM on allocation error,
-   noting that it will not allocate when isFinal is true. It returns
-   SQLITE_ERROR if there's a serious internal error in dealing with
-   the JNI state.
+/*
+** Sets the value property of the OutputPointer.Int64 jOut object to
+** v.
 */
-static int udf_setAggregateContext(JNIEnv * env, jobject jCx,
-                                   sqlite3_context * pCx,
-                                   int isFinal){
-  jfieldID member;
-  void * pAgg;
-  int rc = 0;
-  S3JniNphCache * const pCache =
-    S3JniGlobal_nph_cache(env, S3JniClassNames.sqlite3_context);
-  if(pCache && pCache->klazz && pCache->fidSetAgg){
-    member = pCache->fidSetAgg;
-    assert(member);
-  }else{
-    jclass const klazz =
-      pCache ? pCache->klazz : (*env)->GetObjectClass(env, jCx);
-    member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J");
-    if( !member ){
-      IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; }
-      return s3jni_db_error(sqlite3_context_db_handle(pCx),
-                            SQLITE_ERROR,
-                            "Internal error: cannot find "
-                            "sqlite3_context::aggregateContext field.");
-    }
-    if(pCache){
-      assert(pCache->klazz);
-      assert(!pCache->fidSetAgg);
-      pCache->fidSetAgg = member;
-    }
-  }
-  pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4);
-  if( pAgg || isFinal ){
-    (*env)->SetLongField(env, jCx, member, (jlong)pAgg);
-  }else{
-    assert(!pAgg);
-    rc = SQLITE_NOMEM;
-  }
-  return rc;
+static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut,
+                                    jlong v){
+  (*env)->SetLongField(env, jOut, s3jni_nphop_field(
+                         env, S3JniNph(OutputPointer_Int64)
+                       ), v);
+  S3JniExceptionIsFatal("Cannot set OutputPointer.Int64.value");
 }
 
-/**
-   Common init for OutputPointer_set_Int32() and friends. zClassName must be a
-   pointer from S3JniClassNames. jOut must be an instance of that
-   class. Fetches the jfieldID for jOut's [value] property, which must
-   be of the type represented by the JNI type signature zTypeSig, and
-   stores it in pFieldId. Fails fatally if the property is not found,
-   as that presents a serious internal misuse.
-
-   Property lookups are cached on a per-zClassName basis. Do not use
-   this routine with the same zClassName but different zTypeSig: it
-   will misbehave.
+/*
+** Internal helper for OutputPointer_set_TYPE() where TYPE is an
+** Object type.
 */
-static void setupOutputPointer(JNIEnv * const env, const char *zClassName,
-                               const char * const zTypeSig,
-                               jobject const jOut, jfieldID * const pFieldId){
-  jfieldID setter = 0;
-  S3JniNphCache * const pCache =
-    S3JniGlobal_nph_cache(env, zClassName);
-  if(pCache && pCache->klazz && pCache->fidValue){
-    setter = pCache->fidValue;
-  }else{
-    const jclass klazz = (*env)->GetObjectClass(env, jOut);
-    /*MARKER(("%s => %s\n", zClassName, zTypeSig));*/
-    setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig);
-    EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value");
-    if(pCache){
-      assert(!pCache->fidValue);
-      pCache->fidValue = setter;
-    }
-  }
-  *pFieldId = setter;
-}
-
-/* Sets the value property of the OutputPointer.Int32 jOut object
-   to v. */
-static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){
-  jfieldID setter = 0;
-  setupOutputPointer(env, S3JniClassNames.OutputPointer_Int32, "I", jOut, &setter);
-  (*env)->SetIntField(env, jOut, setter, (jint)v);
-  EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value");
-}
-
-/* Sets the value property of the OutputPointer.Int64 jOut object
-   to v. */
-static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){
-  jfieldID setter = 0;
-  setupOutputPointer(env, S3JniClassNames.OutputPointer_Int64, "J", jOut, &setter);
-  (*env)->SetLongField(env, jOut, setter, v);
-  EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value");
-}
-
-static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut,
-                              jobject jDb){
-  jfieldID setter = 0;
-  setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3,
-                     "Lorg/sqlite/jni/sqlite3;", jOut, &setter);
-  (*env)->SetObjectField(env, jOut, setter, jDb);
-  EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value");
-}
-
-static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut,
-                                   jobject jStmt){
-  jfieldID setter = 0;
-  setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3_stmt,
-                     "Lorg/sqlite/jni/sqlite3_stmt;", jOut, &setter);
-  (*env)->SetObjectField(env, jOut, setter, jStmt);
-  EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value");
+static void OutputPointer_set_obj(JNIEnv * const env,
+                                  S3JniNphOp const * const pRef,
+                                  jobject const jOut,
+                                  jobject v){
+  (*env)->SetObjectField(env, jOut, s3jni_nphop_field(env, pRef), v);
+  S3JniExceptionIsFatal("Cannot set OutputPointer.T.value");
 }
 
 #ifdef SQLITE_ENABLE_FTS5
 #if 0
-/* Sets the value property of the OutputPointer.ByteArray jOut object
-   to v. */
+/*
+** Sets the value property of the OutputPointer.ByteArray jOut object
+** to v.
+*/
 static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut,
-                               jbyteArray const v){
-  jfieldID setter = 0;
-  setupOutputPointer(env, S3JniClassNames.OutputPointer_ByteArray, "[B",
-                     jOut, &setter);
-  (*env)->SetObjectField(env, jOut, setter, v);
-  EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value");
+                                        jbyteArray const v){
+  OutputPointer_set_obj(env, S3JniNph(OutputPointer_ByteArray), jOut, v);
 }
 #endif
-/* Sets the value property of the OutputPointer.String jOut object
-   to v. */
-static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut,
-                            jstring const v){
-  jfieldID setter = 0;
-  setupOutputPointer(env, S3JniClassNames.OutputPointer_String,
-                     "Ljava/lang/String;", jOut, &setter);
-  (*env)->SetObjectField(env, jOut, setter, v);
-  EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value");
-}
 #endif /* SQLITE_ENABLE_FTS5 */
 
+/*
+** Sets the value property of the OutputPointer.String jOut object to
+** v.
+*/
+static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut,
+                                     jstring const v){
+  OutputPointer_set_obj(env, S3JniNph(OutputPointer_String), jOut, v);
+}
+
+/*
+** Returns true if eTextRep is a valid sqlite3 encoding constant, else
+** returns false.
+*/
 static int encodingTypeIsValid(int eTextRep){
-  switch(eTextRep){
+  switch( eTextRep ){
     case SQLITE_UTF8: case SQLITE_UTF16:
     case SQLITE_UTF16LE: case SQLITE_UTF16BE:
       return 1;
@@ -1453,217 +1670,129 @@ static int encodingTypeIsValid(int eTextRep){
   }
 }
 
-static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs,
-                                   int nRhs, const void *rhs){
-  S3JniDb * const ps = pArg;
-  JNIEnv * env = ps->env;
-  jint rc = 0;
-  jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs);
-  jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL;
-  //MARKER(("native xCompare nLhs=%d nRhs=%d\n", nLhs, nRhs));
-  if(!jbaRhs){
-    s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
-    return 0;
-    //(*env)->FatalError(env, "Out of memory. Cannot allocate arrays for collation.");
-  }
-  (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs);
-  (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs);
-  rc = (*env)->CallIntMethod(env, ps->collation.jObj, ps->collation.midCallback,
-                             jbaLhs, jbaRhs);
-  EXCEPTION_IGNORE;
-  UNREF_L(jbaLhs);
-  UNREF_L(jbaRhs);
-  return (int)rc;
-}
-
-/* Collation finalizer for use by the sqlite3 internals. */
-static void CollationState_xDestroy(void *pArg){
-  S3JniDb * const ps = pArg;
-  S3JniHook_unref( ps->env, &ps->collation, 1 );
-}
-
-/* State for sqlite3_result_java_object() and
-   sqlite3_value_java_object(). */
-typedef struct {
-  /* The JNI docs say:
-
-     https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html
-
-     > The VM is guaranteed to pass the same interface pointer to a
-       native method when it makes multiple calls to the native method
-       from the same Java thread.
-
-     Per the accompanying diagram, the "interface pointer" is the
-     pointer-to-pointer which is passed to all JNI calls
-     (`JNIEnv *env`), implying that we need to be caching that. The
-     verbiage "interface pointer" implies, however, that we should be
-     storing the dereferenced `(*env)` pointer.
-
-     This posts claims it's unsafe to cache JNIEnv at all, even when
-     it's always used in the same thread:
-
-     https://stackoverflow.com/questions/12420463
-
-     And this one seems to contradict that:
-
-     https://stackoverflow.com/questions/13964608
-
-     For later reference:
-
-     https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1242
-
-     https://developer.android.com/training/articles/perf-jni
-
-     The later has the following say about caching:
-
-     > If performance is important, it's useful to look the
-       [class/method ID] values up once and cache the results in your
-       native code. Because there is a limit of one JavaVM per
-       process, it's reasonable to store this data in a static local
-       structure. ... The class references, field IDs, and method IDs
-       are guaranteed valid until the class is unloaded. Classes are
-       only unloaded if all classes associated with a ClassLoader can
-       be garbage collected, which is rare but will not be impossible
-       in Android. Note however that the jclass is a class reference
-       and must be protected with a call to NewGlobalRef (see the next
-       section).
-  */
-  JNIEnv * env;
-  jobject jObj;
-} ResultJavaVal;
-
 /* For use with sqlite3_result/value_pointer() */
-#define RESULT_JAVA_VAL_STRING "ResultJavaVal"
+static const char * const ResultJavaValuePtrStr = "org.sqlite.jni.capi.ResultJavaVal";
 
-static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){
-  ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal));
-  if(rv){
-    rv->env = env;
-    rv->jObj = jObj ? REF_G(jObj) : 0;
-  }
-  return rv;
-}
-
-static void ResultJavaVal_finalizer(void *v){
-  if(v){
-    ResultJavaVal * const rv = (ResultJavaVal*)v;
-    if(rv->jObj) (*(rv->env))->DeleteGlobalRef(rv->env, rv->jObj);
-    sqlite3_free(rv);
-  }
-}
-
-
-
-/**
-   Returns a new Java instance of the class named by zClassName, which
-   MUST be interface-compatible with NativePointerHolder and MUST have
-   a no-arg constructor. The NativePointerHolder_set() method is
-   passed the new Java object and pNative. Hypothetically returns NULL
-   if Java fails to allocate, but the JNI docs are not entirely clear
-   on that detail.
-
-   Always use a static string pointer from S3JniClassNames for the 2nd
-   argument so that we can use its address as a cache key.
+/*
+** If v is not NULL, it must be a jobject global reference. Its
+** reference is relinquished.
 */
-static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zClassName,
-                                              const void * pNative){
-  jobject rv = 0;
-  jclass klazz = 0;
-  jmethodID ctor = 0;
-  S3JniNphCache * const pCache =
-    S3JniGlobal_nph_cache(env, zClassName);
-  if(pCache && pCache->midCtor){
-    assert( pCache->klazz );
-    klazz = pCache->klazz;
-    ctor = pCache->midCtor;
-  }else{
-    klazz = pCache
-      ? pCache->klazz
-      : (*env)->FindClass(env, zClassName);
-    ctor = klazz ? (*env)->GetMethodID(env, klazz, "<init>", "()V") : 0;
-    EXCEPTION_IS_FATAL("Cannot find constructor for class.");
-    if(pCache){
-      assert(zClassName == pCache->zClassName);
-      assert(pCache->klazz);
-      assert(!pCache->midCtor);
-      pCache->midCtor = ctor;
-    }
+static void S3Jni_jobject_finalizer(void *v){
+  if( v ){
+    S3JniDeclLocal_env;
+    S3JniUnrefGlobal((jobject)v);
   }
-  assert(klazz);
-  assert(ctor);
-  rv = (*env)->NewObject(env, klazz, ctor);
-  EXCEPTION_IS_FATAL("No-arg constructor threw.");
-  if(rv) NativePointerHolder_set(env, rv, pNative, zClassName);
+}
+
+/*
+** Returns a new Java instance of the class referred to by pRef, which
+** MUST be interface-compatible with NativePointerHolder and MUST have
+** a no-arg constructor. The NativePointerHolder_set() method is
+** passed the new Java object (which must not be NULL) and pNative
+** (which may be NULL). Hypothetically returns NULL if Java fails to
+** allocate, but the JNI docs are not entirely clear on that detail.
+**
+** Always use a static pointer from the S3JniNphOps struct for the
+** 2nd argument.
+*/
+static jobject NativePointerHolder_new(JNIEnv * const env,
+                                       S3JniNphOp const * pRef,
+                                       const void * pNative){
+  jobject rv = 0;
+  S3JniNphOp * const pNC = s3jni_nphop(pRef);
+  if( !pNC->midCtor ){
+    S3JniNph_mutex_enter;
+    if( !pNC->midCtor ){
+      pNC->midCtor = (*env)->GetMethodID(env, pNC->klazz, "<init>", "()V");
+      S3JniExceptionIsFatal("Cannot find constructor for class.");
+    }
+    S3JniNph_mutex_leave;
+  }
+  rv = (*env)->NewObject(env, pNC->klazz, pNC->midCtor);
+  S3JniExceptionIsFatal("No-arg constructor threw.");
+  s3jni_oom_check(rv);
+  if( rv ) NativePointerHolder_set(pRef, rv, pNative);
   return rv;
 }
 
-static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){
-  return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3, sv);
+static inline jobject new_java_sqlite3(JNIEnv * const env, sqlite3 *sv){
+  return NativePointerHolder_new(env, S3JniNph(sqlite3), sv);
 }
-static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){
-  return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_context, sv);
+static inline jobject new_java_sqlite3_backup(JNIEnv * const env, sqlite3_backup *sv){
+  return NativePointerHolder_new(env, S3JniNph(sqlite3_backup), sv);
 }
-static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){
-  return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_stmt, sv);
+static inline jobject new_java_sqlite3_blob(JNIEnv * const env, sqlite3_blob *sv){
+  return NativePointerHolder_new(env, S3JniNph(sqlite3_blob), sv);
 }
-static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){
-  return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_value, sv);
+static inline jobject new_java_sqlite3_context(JNIEnv * const env, sqlite3_context *sv){
+  return NativePointerHolder_new(env, S3JniNph(sqlite3_context), sv);
+}
+static inline jobject new_java_sqlite3_stmt(JNIEnv * const env, sqlite3_stmt *sv){
+  return NativePointerHolder_new(env, S3JniNph(sqlite3_stmt), sv);
+}
+static inline jobject new_java_sqlite3_value(JNIEnv * const env, sqlite3_value *sv){
+  return NativePointerHolder_new(env, S3JniNph(sqlite3_value), sv);
 }
 
-enum UDFType {
-  UDF_SCALAR = 1,
-  UDF_AGGREGATE,
-  UDF_WINDOW,
-  UDF_UNKNOWN_TYPE/*for error propagation*/
-};
-
+/* Helper typedefs for UDF callback types. */
 typedef void (*udf_xFunc_f)(sqlite3_context*,int,sqlite3_value**);
 typedef void (*udf_xStep_f)(sqlite3_context*,int,sqlite3_value**);
 typedef void (*udf_xFinal_f)(sqlite3_context*);
 /*typedef void (*udf_xValue_f)(sqlite3_context*);*/
 /*typedef void (*udf_xInverse_f)(sqlite3_context*,int,sqlite3_value**);*/
 
-/**
-   State for binding Java-side UDFs.
+/*
+** Allocate a new S3JniUdf (User-defined Function) and associate it
+** with the SQLFunction-type jObj. Returns NULL on OOM. If the
+** returned object's type==UDF_UNKNOWN_TYPE then the type of UDF was
+** not unambiguously detected based on which callback members it has,
+** which falls into the category of user error.
+**
+** The caller must arrange for the returned object to eventually be
+** passed to S3JniUdf_free().
 */
-typedef struct S3JniUdf S3JniUdf;
-struct S3JniUdf {
-  JNIEnv * env;         /* env registered from */;
-  jobject jObj          /* SQLFunction instance */;
-  jclass klazz          /* jObj's class */;
-  char * zFuncName      /* Only for error reporting and debug logging */;
-  enum UDFType type;
-  /** Method IDs for the various UDF methods. */
-  jmethodID jmidxFunc;
-  jmethodID jmidxStep;
-  jmethodID jmidxFinal;
-  jmethodID jmidxValue;
-  jmethodID jmidxInverse;
-};
-
 static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
-  S3JniUdf * const s = sqlite3_malloc(sizeof(S3JniUdf));
-  if(s){
+  S3JniUdf * s = 0;
+
+  S3JniGlobal_mutex_enter;
+  s3jni_incr(&SJG.metrics.nMutexUdf);
+  if( SJG.udf.aFree ){
+    s = SJG.udf.aFree;
+    SJG.udf.aFree = s->pNext;
+    s->pNext = 0;
+    s3jni_incr(&SJG.metrics.nUdfRecycled);
+  }
+  S3JniGlobal_mutex_leave;
+  if( !s ){
+    s = s3jni_malloc( sizeof(*s));
+    s3jni_incr(&SJG.metrics.nUdfAlloc);
+  }
+  if( s ){
     const char * zFSI = /* signature for xFunc, xStep, xInverse */
-      "(Lorg/sqlite/jni/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V";
+      "(Lorg/sqlite/jni/capi/sqlite3_context;[Lorg/sqlite/jni/capi/sqlite3_value;)V";
     const char * zFV = /* signature for xFinal, xValue */
-      "(Lorg/sqlite/jni/sqlite3_context;)V";
-    memset(s, 0, sizeof(S3JniUdf));
-    s->env = env;
-    s->jObj = REF_G(jObj);
-    s->klazz = REF_G((*env)->GetObjectClass(env, jObj));
-#define FGET(FuncName,FuncType,Field) \
-    s->Field = (*env)->GetMethodID(env, s->klazz, FuncName, FuncType); \
-    if(!s->Field) (*env)->ExceptionClear(env)
+      "(Lorg/sqlite/jni/capi/sqlite3_context;)V";
+    jclass const klazz = (*env)->GetObjectClass(env, jObj);
+
+    memset(s, 0, sizeof(*s));
+    s->jObj = S3JniRefGlobal(jObj);
+
+#define FGET(FuncName,FuncSig,Field)                               \
+    s->Field = (*env)->GetMethodID(env, klazz, FuncName, FuncSig); \
+    if( !s->Field ) (*env)->ExceptionClear(env)
+
     FGET("xFunc",    zFSI, jmidxFunc);
     FGET("xStep",    zFSI, jmidxStep);
     FGET("xFinal",   zFV,  jmidxFinal);
     FGET("xValue",   zFV,  jmidxValue);
     FGET("xInverse", zFSI, jmidxInverse);
 #undef FGET
-    if(s->jmidxFunc) s->type = UDF_SCALAR;
-    else if(s->jmidxStep && s->jmidxFinal){
-      s->type = s->jmidxValue ? UDF_WINDOW : UDF_AGGREGATE;
+
+    S3JniUnrefLocal(klazz);
+    if( s->jmidxFunc ) s->type = UDF_SCALAR;
+    else if( s->jmidxStep && s->jmidxFinal ){
+      s->type = (s->jmidxValue && s->jmidxInverse)
+        ? UDF_WINDOW : UDF_AGGREGATE;
     }else{
       s->type = UDF_UNKNOWN_TYPE;
     }
@@ -1671,184 +1800,318 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
   return s;
 }
 
-static void S3JniUdf_free(S3JniUdf * s){
-  JNIEnv * const env = s->env;
-  if(env){
-    //MARKER(("UDF cleanup: %s\n", s->zFuncName));
-    s3jni_call_xDestroy(env, s->jObj, s->klazz);
-    UNREF_G(s->jObj);
-    UNREF_G(s->klazz);
+/*
+** Frees up all resources owned by s, clears its state, then either
+** caches it for reuse (if cacheIt is true) or frees it. The former
+** requires locking the global mutex, so it must not be held when this
+** is called.
+*/
+static void S3JniUdf_free(JNIEnv * const env, S3JniUdf * const s,
+                          int cacheIt){
+  assert( !s->pNext );
+  if( s->jObj ){
+    s3jni_call_xDestroy(s->jObj);
+    S3JniUnrefGlobal(s->jObj);
+    sqlite3_free(s->zFuncName);
+    assert( !s->pNext );
+    memset(s, 0, sizeof(*s));
+  }
+  if( cacheIt ){
+    S3JniGlobal_mutex_enter;
+    s->pNext = S3JniGlobal.udf.aFree;
+    S3JniGlobal.udf.aFree = s;
+    S3JniGlobal_mutex_leave;
+  }else{
+    sqlite3_free( s );
   }
-  sqlite3_free(s->zFuncName);
-  sqlite3_free(s);
 }
 
+/* Finalizer for sqlite3_create_function() and friends. */
 static void S3JniUdf_finalizer(void * s){
-  //MARKER(("UDF finalizer @ %p\n", s));
-  if(s) S3JniUdf_free((S3JniUdf*)s);
+  S3JniUdf_free(s3jni_env(), (S3JniUdf*)s, 1);
 }
 
-/**
-   Helper for processing args to UDF handlers
-   with signature (sqlite3_context*,int,sqlite3_value**).
+/*
+** Helper for processing args to UDF handlers with signature
+** (sqlite3_context*,int,sqlite3_value**).
 */
 typedef struct {
-  jobject jcx;
-  jobjectArray jargv;
+  jobject jcx         /* sqlite3_context */;
+  jobjectArray jargv  /* sqlite3_value[] */;
 } udf_jargs;
 
-/**
-   Converts the given (cx, argc, argv) into arguments for the given
-   UDF, placing the result in the final argument. Returns 0 on
-   success, SQLITE_NOMEM on allocation error.
-
-   TODO: see what we can do to optimize the
-   new_sqlite3_value_wrapper() call. e.g. find the ctor a single time
-   and call it here, rather than looking it up repeatedly.
+/*
+** Converts the given (cx, argc, argv) into arguments for the given
+** UDF, writing the result (Java wrappers for cx and argv) in the
+** final 2 arguments. Returns 0 on success, SQLITE_NOMEM on allocation
+** error. On error *jCx and *jArgv will be set to 0. The output
+** objects are of type org.sqlite.jni.capi.sqlite3_context and
+** array-of-org.sqlite.jni.capi.sqlite3_value, respectively.
 */
 static int udf_args(JNIEnv *env,
                     sqlite3_context * const cx,
                     int argc, sqlite3_value**argv,
                     jobject * jCx, jobjectArray *jArgv){
   jobjectArray ja = 0;
-  jobject jcx = new_sqlite3_context_wrapper(env, cx);
+  jobject jcx = new_java_sqlite3_context(env, cx);
   jint i;
   *jCx = 0;
   *jArgv = 0;
-  if(!jcx) goto error_oom;
-  ja = (*env)->NewObjectArray(env, argc,
-                              S3JniGlobal_env_cache(env)->g.cObj,
-                              NULL);
-  if(!ja) goto error_oom;
+  if( !jcx ) goto error_oom;
+  ja = (*env)->NewObjectArray(
+    env, argc, s3jni_nphop(S3JniNph(sqlite3_value))->klazz,
+    NULL);
+  s3jni_oom_check( ja );
+  if( !ja ) goto error_oom;
   for(i = 0; i < argc; ++i){
-    jobject jsv = new_sqlite3_value_wrapper(env, argv[i]);
-    if(!jsv) goto error_oom;
+    jobject jsv = new_java_sqlite3_value(env, argv[i]);
+    if( !jsv ) goto error_oom;
     (*env)->SetObjectArrayElement(env, ja, i, jsv);
-    UNREF_L(jsv)/*array has a ref*/;
+    S3JniUnrefLocal(jsv)/*ja has a ref*/;
   }
   *jCx = jcx;
   *jArgv = ja;
   return 0;
 error_oom:
-  sqlite3_result_error_nomem(cx);
-  UNREF_L(jcx);
-  UNREF_L(ja);
+  S3JniUnrefLocal(jcx);
+  S3JniUnrefLocal(ja);
   return SQLITE_NOMEM;
 }
 
-static int udf_report_exception(sqlite3_context * cx,
-                                const char *zFuncName,
-                                const char *zFuncType){
-  int rc;
-  char * z =
-    sqlite3_mprintf("Client-defined function %s.%s() threw. It should "
-                    "not do that.",
-                    zFuncName ? zFuncName : "<unnamed>", zFuncType);
-  if(z){
-    sqlite3_result_error(cx, z, -1);
-    sqlite3_free(z);
-    rc = SQLITE_ERROR;
+/*
+** Requires that jCx and jArgv are sqlite3_context
+** resp. array-of-sqlite3_value values initialized by udf_args(). This
+** function zeroes out the nativePointer member of jCx and each entry
+** in jArgv. This is a safety-net precaution to avoid undefined
+** behavior if a Java-side UDF holds a reference to one of its
+** arguments. This MUST be called from any function which successfully
+** calls udf_args(), after calling the corresponding UDF and checking
+** its exception status. It MUST NOT be called in any other case.
+*/
+static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){
+  int i = 0;
+  assert(jCx);
+  NativePointerHolder_set(S3JniNph(sqlite3_context), jCx, 0);
+  for( ; i < argc; ++i ){
+    jobject jsv = (*env)->GetObjectArrayElement(env, jArgv, i);
+    assert(jsv);
+    NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0);
+  }
+}
+
+
+/*
+** Must be called immediately after a Java-side UDF callback throws.
+** If translateToErr is true then it sets the exception's message in
+** the result error using sqlite3_result_error(). If translateToErr is
+** false then it emits a warning that the function threw but should
+** not do so. In either case, it clears the exception state.
+**
+** Returns SQLITE_NOMEM if an allocation fails, else SQLITE_ERROR. In
+** the former case it calls sqlite3_result_error_nomem().
+*/
+static int udf_report_exception(JNIEnv * const env, int translateToErr,
+                                sqlite3_context * cx,
+                                const char *zFuncName, const char *zFuncType ){
+  jthrowable const ex = (*env)->ExceptionOccurred(env);
+  int rc = SQLITE_ERROR;
+
+  assert(ex && "This must only be called when a Java exception is pending.");
+  if( translateToErr ){
+    char * zMsg;
+    char * z;
+
+    S3JniExceptionClear;
+    zMsg = s3jni_exception_error_msg(env, ex);
+    z = sqlite3_mprintf("Client-defined SQL function %s.%s() threw: %s",
+                        zFuncName ? zFuncName : "<unnamed>", zFuncType,
+                        zMsg ? zMsg : "Unknown exception" );
+    sqlite3_free(zMsg);
+    if( z ){
+      sqlite3_result_error(cx, z, -1);
+      sqlite3_free(z);
+    }else{
+      sqlite3_result_error_nomem(cx);
+      rc = SQLITE_NOMEM;
+    }
   }else{
-    sqlite3_result_error_nomem(cx);
+    S3JniExceptionWarnCallbackThrew("client-defined SQL function");
+    S3JniExceptionClear;
+  }
+  S3JniUnrefLocal(ex);
+  return rc;
+}
+
+/*
+** Sets up the state for calling a Java-side xFunc/xStep/xInverse()
+** UDF, calls it, and returns 0 on success.
+*/
+static int udf_xFSI(sqlite3_context* const pCx, int argc,
+                    sqlite3_value** const argv, S3JniUdf * const s,
+                    jmethodID xMethodID, const char * const zFuncType){
+  S3JniDeclLocal_env;
+  udf_jargs args = {0,0};
+  int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv);
+
+  if( 0 == rc ){
+    (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
+    S3JniIfThrew{
+      rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx,
+                                s->zFuncName, zFuncType);
+    }
+    udf_unargs(env, args.jcx, argc, args.jargv);
+  }
+  S3JniUnrefLocal(args.jcx);
+  S3JniUnrefLocal(args.jargv);
+  return rc;
+}
+
+/*
+** Sets up the state for calling a Java-side xFinal/xValue() UDF,
+** calls it, and returns 0 on success.
+*/
+static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
+                   jmethodID xMethodID,
+                   const char *zFuncType){
+  S3JniDeclLocal_env;
+  jobject jcx = new_java_sqlite3_context(env, cx);
+  int rc = 0;
+  int const isFinal = 'F'==zFuncType[1]/*xFinal*/;
+
+  if( jcx ){
+    (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
+    S3JniIfThrew{
+      rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
+                                zFuncType);
+    }
+    S3JniUnrefLocal(jcx);
+  }else{
+    if( isFinal ) sqlite3_result_error_nomem(cx);
     rc = SQLITE_NOMEM;
   }
   return rc;
 }
 
-/**
-   Sets up the state for calling a Java-side xFunc/xStep/xInverse()
-   UDF, calls it, and returns 0 on success.
-*/
-static int udf_xFSI(sqlite3_context* pCx, int argc,
-                    sqlite3_value** argv,
-                    S3JniUdf * s,
-                    jmethodID xMethodID,
-                    const char * zFuncType){
-  JNIEnv * const env = s->env;
-  udf_jargs args = {0,0};
-  int rc = udf_args(s->env, pCx, argc, argv, &args.jcx, &args.jargv);
-  //MARKER(("%s.%s() pCx = %p\n", s->zFuncName, zFuncType, pCx));
-  if(rc) return rc;
-  //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType));
-  if( UDF_SCALAR != s->type ){
-    rc = udf_setAggregateContext(env, args.jcx, pCx, 0);
-  }
-  if( 0 == rc ){
-    (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
-    IFTHREW{
-      rc = udf_report_exception(pCx, s->zFuncName, zFuncType);
-    }
-  }
-  UNREF_L(args.jcx);
-  UNREF_L(args.jargv);
-  return rc;
-}
-
-/**
-   Sets up the state for calling a Java-side xFinal/xValue() UDF,
-   calls it, and returns 0 on success.
-*/
-static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
-                   jmethodID xMethodID,
-                   const char *zFuncType){
-  JNIEnv * const env = s->env;
-  jobject jcx = new_sqlite3_context_wrapper(s->env, cx);
-  int rc = 0;
-  //MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx));
-  if(!jcx){
-    sqlite3_result_error_nomem(cx);
-    return SQLITE_NOMEM;
-  }
-  //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType));
-  if( UDF_SCALAR != s->type ){
-    rc = udf_setAggregateContext(env, jcx, cx, 1);
-  }
-  if( 0 == rc ){
-    (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
-    IFTHREW{
-      rc = udf_report_exception(cx,s->zFuncName, zFuncType);
-    }
-  }
-  UNREF_L(jcx);
-  return rc;
-}
-
+/* Proxy for C-to-Java xFunc. */
 static void udf_xFunc(sqlite3_context* cx, int argc,
                       sqlite3_value** argv){
   S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
-  ++S3JniGlobal.metrics.udf.nFunc;
+  s3jni_incr( &SJG.metrics.udf.nFunc );
   udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc");
 }
+/* Proxy for C-to-Java xStep. */
 static void udf_xStep(sqlite3_context* cx, int argc,
                       sqlite3_value** argv){
   S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
-  ++S3JniGlobal.metrics.udf.nStep;
+  s3jni_incr( &SJG.metrics.udf.nStep );
   udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep");
 }
+/* Proxy for C-to-Java xFinal. */
 static void udf_xFinal(sqlite3_context* cx){
   S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
-  ++S3JniGlobal.metrics.udf.nFinal;
+  s3jni_incr( &SJG.metrics.udf.nFinal );
   udf_xFV(cx, s, s->jmidxFinal, "xFinal");
 }
+/* Proxy for C-to-Java xValue. */
 static void udf_xValue(sqlite3_context* cx){
   S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
-  ++S3JniGlobal.metrics.udf.nValue;
+  s3jni_incr( &SJG.metrics.udf.nValue );
   udf_xFV(cx, s, s->jmidxValue, "xValue");
 }
+/* Proxy for C-to-Java xInverse. */
 static void udf_xInverse(sqlite3_context* cx, int argc,
                          sqlite3_value** argv){
   S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
-  ++S3JniGlobal.metrics.udf.nInverse;
+  s3jni_incr( &SJG.metrics.udf.nInverse );
   udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse");
 }
 
 
 ////////////////////////////////////////////////////////////////////////
 // What follows is the JNI/C bindings. They are in alphabetical order
-// except for this macro-generated subset which are kept together here
-// at the front...
+// except for this macro-generated subset which are kept together
+// (alphabetized) here at the front...
 ////////////////////////////////////////////////////////////////////////
-WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count)
+
+/** Create a trivial JNI wrapper for (int CName(void)). */
+#define WRAP_INT_VOID(JniNameSuffix,CName)      \
+  JniDecl(jint,JniNameSuffix)(JniArgsEnvClass){ \
+    return (jint)CName();                       \
+  }
+/** Create a trivial JNI wrapper for (int CName(int)). */
+#define WRAP_INT_INT(JniNameSuffix,CName)                 \
+  JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jint arg){ \
+    return (jint)CName((int)arg);                         \
+  }
+/*
+** Create a trivial JNI wrapper for (const mutf8_string *
+** CName(void)). This is only valid for functions which are known to
+** return ASCII or text which is equivalent in UTF-8 and MUTF-8.
+*/
+#define WRAP_MUTF8_VOID(JniNameSuffix,CName)                   \
+  JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass){             \
+    jstring const rv = (*env)->NewStringUTF( env, CName() );   \
+    s3jni_oom_check(rv);                                       \
+    return rv;                                                 \
+  }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */
+#define WRAP_INT_STMT(JniNameSuffix,CName)                    \
+  JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt){ \
+    return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt));    \
+  }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */
+#define WRAP_INT_STMT_INT(JniNameSuffix,CName)                         \
+  JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint n){ \
+    return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)n);            \
+  }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3_stmt*)). */
+#define WRAP_BOOL_STMT(JniNameSuffix,CName)                           \
+  JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jobject jStmt){     \
+    return CName(PtrGet_sqlite3_stmt(jStmt)) ? JNI_TRUE : JNI_FALSE; \
+  }
+/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */
+#define WRAP_STR_STMT_INT(JniNameSuffix,CName)                             \
+  JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint ndx){ \
+    return s3jni_utf8_to_jstring(                                       \
+      CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx),               \
+      -1);                                                              \
+  }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3*)). */
+#define WRAP_BOOL_DB(JniNameSuffix,CName)                           \
+  JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){     \
+    return CName(S3JniLongPtr_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \
+  }
+/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */
+#define WRAP_INT_DB(JniNameSuffix,CName)                    \
+  JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+  return (jint)CName(S3JniLongPtr_sqlite3(jpDb)); \
+  }
+/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */
+#define WRAP_INT64_DB(JniNameSuffix,CName)                   \
+  JniDecl(jlong,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+  return (jlong)CName(S3JniLongPtr_sqlite3(jpDb));  \
+  }
+/** Create a trivial JNI wrapper for (jstring CName(sqlite3*,int)). */
+#define WRAP_STR_DB_INT(JniNameSuffix,CName)                             \
+  JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpDb, jint ndx){ \
+    return s3jni_utf8_to_jstring(                                       \
+      CName(S3JniLongPtr_sqlite3(jpDb), (int)ndx),               \
+      -1);                                                              \
+  }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */
+#define WRAP_INT_SVALUE(JniNameSuffix,CName,DfltOnNull)         \
+  JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
+    sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \
+    return (jint)(sv ? CName(sv): DfltOnNull);                      \
+  }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3_value*)). */
+#define WRAP_BOOL_SVALUE(JniNameSuffix,CName,DfltOnNull)            \
+  JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
+    sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \
+    return (jint)(sv ? CName(sv) : DfltOnNull)                       \
+      ? JNI_TRUE : JNI_FALSE;                                       \
+  }
+
 WRAP_INT_DB(1changes,                  sqlite3_changes)
 WRAP_INT64_DB(1changes64,              sqlite3_changes64)
 WRAP_INT_STMT(1clear_1bindings,        sqlite3_clear_bindings)
@@ -1862,280 +2125,637 @@ WRAP_STR_STMT_INT(1column_1origin_1name,    sqlite3_column_origin_name)
 WRAP_STR_STMT_INT(1column_1table_1name,     sqlite3_column_table_name)
 WRAP_INT_STMT_INT(1column_1type,       sqlite3_column_type)
 WRAP_INT_STMT(1data_1count,            sqlite3_data_count)
+WRAP_STR_DB_INT(1db_1name,             sqlite3_db_name)
 WRAP_INT_DB(1error_1offset,            sqlite3_error_offset)
 WRAP_INT_DB(1extended_1errcode,        sqlite3_extended_errcode)
+WRAP_BOOL_DB(1get_1autocommit,         sqlite3_get_autocommit)
 WRAP_MUTF8_VOID(1libversion,           sqlite3_libversion)
 WRAP_INT_VOID(1libversion_1number,     sqlite3_libversion_number)
+WRAP_INT_VOID(1keyword_1count,         sqlite3_keyword_count)
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+WRAP_INT_DB(1preupdate_1blobwrite,     sqlite3_preupdate_blobwrite)
+WRAP_INT_DB(1preupdate_1count,         sqlite3_preupdate_count)
+WRAP_INT_DB(1preupdate_1depth,         sqlite3_preupdate_depth)
+#endif
+WRAP_INT_INT(1release_1memory,         sqlite3_release_memory)
 WRAP_INT_INT(1sleep,                   sqlite3_sleep)
 WRAP_MUTF8_VOID(1sourceid,             sqlite3_sourceid)
+WRAP_BOOL_STMT(1stmt_1busy,            sqlite3_stmt_busy)
+WRAP_INT_STMT_INT(1stmt_1explain,      sqlite3_stmt_explain)
+WRAP_INT_STMT(1stmt_1isexplain,        sqlite3_stmt_isexplain)
+WRAP_BOOL_STMT(1stmt_1readonly,        sqlite3_stmt_readonly)
+WRAP_INT_DB(1system_1errno,            sqlite3_system_errno)
 WRAP_INT_VOID(1threadsafe,             sqlite3_threadsafe)
 WRAP_INT_DB(1total_1changes,           sqlite3_total_changes)
 WRAP_INT64_DB(1total_1changes64,       sqlite3_total_changes64)
-WRAP_INT_SVALUE(1value_1bytes,         sqlite3_value_bytes)
-WRAP_INT_SVALUE(1value_1bytes16,       sqlite3_value_bytes16)
-WRAP_INT_SVALUE(1value_1encoding,      sqlite3_value_encoding)
-WRAP_INT_SVALUE(1value_1frombind,      sqlite3_value_frombind)
-WRAP_INT_SVALUE(1value_1nochange,      sqlite3_value_nochange)
-WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type)
-WRAP_INT_SVALUE(1value_1subtype,       sqlite3_value_subtype)
-WRAP_INT_SVALUE(1value_1type,          sqlite3_value_type)
+WRAP_INT_SVALUE(1value_1encoding,      sqlite3_value_encoding,SQLITE_UTF8)
+WRAP_BOOL_SVALUE(1value_1frombind,     sqlite3_value_frombind,0)
+WRAP_INT_SVALUE(1value_1nochange,      sqlite3_value_nochange,0)
+WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type,SQLITE_NULL)
+WRAP_INT_SVALUE(1value_1subtype,       sqlite3_value_subtype,0)
+WRAP_INT_SVALUE(1value_1type,          sqlite3_value_type,SQLITE_NULL)
+
+#undef WRAP_BOOL_DB
+#undef WRAP_BOOL_STMT
+#undef WRAP_BOOL_SVALUE
+#undef WRAP_INT64_DB
+#undef WRAP_INT_DB
+#undef WRAP_INT_INT
+#undef WRAP_INT_STMT
+#undef WRAP_INT_STMT_INT
+#undef WRAP_INT_SVALUE
+#undef WRAP_INT_VOID
+#undef WRAP_MUTF8_VOID
+#undef WRAP_STR_STMT_INT
+#undef WRAP_STR_DB_INT
+
+S3JniApi(sqlite3_aggregate_context(),jlong,1aggregate_1context)(
+  JniArgsEnvClass, jobject jCx, jboolean initialize
+){
+  sqlite3_context * const pCx = PtrGet_sqlite3_context(jCx);
+  void * const p = pCx
+    ? sqlite3_aggregate_context(pCx, (int)(initialize
+                                           ? (int)sizeof(void*)
+                                           : 0))
+    : 0;
+  return S3JniCast_P2L(p);
+}
 
-#if S3JNI_ENABLE_AUTOEXT
 /* Central auto-extension handler. */
-FIXME_THREADING(autoExt)
-static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr,
-                                const struct sqlite3_api_routines *ignored){
-  S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead;
-  int rc;
+static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
+                                          const struct sqlite3_api_routines *ignored){
+  int rc = 0;
+  unsigned i, go = 1;
   JNIEnv * env = 0;
-  S3JniDb * const ps = S3JniGlobal.autoExt.psOpening;
-  //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb));
-  S3JniGlobal.autoExt.psOpening = 0;
-  if( !pAX ){
-    assert( 0==S3JniGlobal.autoExt.isRunning );
-    return 0;
-  }
-  else if( S3JniGlobal.autoExt.isRunning ){
-    /* Necessary to avoid certain endless loop/stack overflow cases. */
-    *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while "
-                             "auto-extensions are running.");
-    return SQLITE_MISUSE;
-  }
-  else if(!ps){
-    MARKER(("Internal error: cannot find S3JniDb for auto-extension\n"));
-    return SQLITE_ERROR;
-  }else if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env, JNI_VERSION_1_8) ){
-    assert(!"Cannot get JNIEnv");
-    *pzErr = sqlite3_mprintf("Could not get current JNIEnv.");
+  S3JniDb * ps;
+  S3JniEnv * jc;
+
+  if( 0==SJG.autoExt.nExt ) return 0;
+  env = s3jni_env();
+  jc = S3JniEnv_get();
+  S3JniDb_mutex_enter;
+  ps = jc->pdbOpening ? jc->pdbOpening : S3JniDb_from_c(pDb);
+  if( !ps ){
+    *pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in "
+                             "auto-extension runner.");
+    S3JniDb_mutex_leave;
     return SQLITE_ERROR;
   }
-  assert( !ps->pDb /* it's still being opened */ );
-  ps->pDb = pDb;
   assert( ps->jDb );
-  NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3);
-  ++S3JniGlobal.autoExt.isRunning;
-  for( ; pAX; pAX = pAX->pNext ){
-    rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb);
-    IFTHREW {
-      jthrowable const ex = (*env)->ExceptionOccurred(env);
-      char * zMsg;
-      EXCEPTION_CLEAR;
-      zMsg = s3jni_exception_error_msg(env, ex);
-      UNREF_L(ex);
-      *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg);
-      sqlite3_free(zMsg);
-      rc = rc ? rc : SQLITE_ERROR;
-      break;
-    }else if( rc ){
-      break;
+  if( !ps->pDb ){
+    assert( jc->pdbOpening == ps );
+    rc = sqlite3_set_clientdata(pDb, S3JniDb_clientdata_key,
+                                ps, 0/* we'll re-set this after open()
+                                        completes. */);
+    if( rc ){
+      S3JniDb_mutex_leave;
+      return rc;
+    }
+  }
+  else{
+    assert( ps == jc->pdbOpening );
+    jc->pdbOpening = 0;
+  }
+  S3JniDb_mutex_leave;
+  NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, pDb)
+    /* As of here, the Java/C connection is complete except for the
+       (temporary) lack of finalizer for the ps object. */;
+  ps->pDb = pDb;
+  for( i = 0; go && 0==rc; ++i ){
+    S3JniAutoExtension ax = S3JniHook_empty
+      /* We need a copy of the auto-extension object, with our own
+      ** local reference to it, to avoid a race condition with another
+      ** thread manipulating the list during the call and invaliding
+      ** what ax references. */;
+    S3JniAutoExt_mutex_enter;
+    if( i >= SJG.autoExt.nExt ){
+      go = 0;
+    }else{
+      S3JniHook_localdup(&SJG.autoExt.aExt[i], &ax);
+    }
+    S3JniAutoExt_mutex_leave;
+    if( ax.jObj ){
+      rc = (*env)->CallIntMethod(env, ax.jObj, ax.midCallback, ps->jDb);
+      S3JniHook_localundup(ax);
+      S3JniIfThrew {
+        jthrowable const ex = (*env)->ExceptionOccurred(env);
+        char * zMsg;
+        S3JniExceptionClear;
+        zMsg = s3jni_exception_error_msg(env, ex);
+        S3JniUnrefLocal(ex);
+        *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg);
+        sqlite3_free(zMsg);
+        rc = SQLITE_ERROR;
+      }
     }
   }
-  --S3JniGlobal.autoExt.isRunning;
   return rc;
 }
 
-FIXME_THREADING(autoExt)
-JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){
-  static int once = 0;
-  S3JniAutoExtension * ax;
+S3JniApi(sqlite3_auto_extension(),jint,1auto_1extension)(
+  JniArgsEnvClass, jobject jAutoExt
+){
+  int i;
+  S3JniAutoExtension * ax = 0;
+  int rc = 0;
 
   if( !jAutoExt ) return SQLITE_MISUSE;
-  else if( 0==once && ++once ){
-    sqlite3_auto_extension( (void(*)(void))s3jni_auto_extension );
-  }
-  ax = S3JniGlobal.autoExt.pHead;
-  for( ; ax; ax = ax->pNext ){
-    if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
-      return 0 /* C API treats this as a no-op. */;
+  S3JniAutoExt_mutex_enter;
+  for( i = 0; i < SJG.autoExt.nExt; ++i ){
+    /* Look for a match. */
+    ax = &SJG.autoExt.aExt[i];
+    if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+      /* same object, so this is a no-op. */
+      S3JniAutoExt_mutex_leave;
+      return 0;
     }
   }
-  return S3JniAutoExtension_alloc(env, jAutoExt) ? 0 : SQLITE_NOMEM;
+  if( i == SJG.autoExt.nExt ){
+    assert( SJG.autoExt.nExt <= SJG.autoExt.nAlloc );
+    if( SJG.autoExt.nExt == SJG.autoExt.nAlloc ){
+      /* Allocate another slot. */
+      unsigned n = 1 + SJG.autoExt.nAlloc;
+      S3JniAutoExtension * const aNew =
+        s3jni_realloc( SJG.autoExt.aExt, n * sizeof(*ax) );
+      if( !aNew ){
+        rc = SQLITE_NOMEM;
+      }else{
+        SJG.autoExt.aExt = aNew;
+        ++SJG.autoExt.nAlloc;
+      }
+    }
+    if( 0==rc ){
+      ax = &SJG.autoExt.aExt[SJG.autoExt.nExt];
+      rc = S3JniAutoExtension_init(env, ax, jAutoExt);
+      assert( rc ? (0==ax->jObj && 0==ax->midCallback)
+              : (0!=ax->jObj && 0!=ax->midCallback) );
+    }
+  }
+  if( 0==rc ){
+    static int once = 0;
+    if( 0==once && ++once ){
+      rc = sqlite3_auto_extension(
+        (void(*)(void))s3jni_run_java_auto_extensions
+        /* Reminder: the JNI binding of sqlite3_reset_auto_extension()
+        ** does not call the core-lib impl. It only clears Java-side
+        ** auto-extensions. */
+      );
+      if( rc ){
+        assert( ax );
+        S3JniAutoExtension_clear(ax);
+      }
+    }
+    if( 0==rc ){
+      ++SJG.autoExt.nExt;
+    }
+  }
+  S3JniAutoExt_mutex_leave;
+  return rc;
 }
-#endif /* S3JNI_ENABLE_AUTOEXT */
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt,
-                        jint ndx, jbyteArray baData, jint nMax){
+S3JniApi(sqlite3_backup_finish(),jint,1backup_1finish)(
+  JniArgsEnvClass, jlong jpBack
+){
+  int rc = 0;
+  if( jpBack!=0 ){
+    rc = sqlite3_backup_finish( S3JniLongPtr_sqlite3_backup(jpBack) );
+  }
+  return rc;
+}
+
+S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)(
+  JniArgsEnvClass, jlong jpDbDest, jstring jTDest,
+  jlong jpDbSrc, jstring jTSrc
+){
+  sqlite3 * const pDest = S3JniLongPtr_sqlite3(jpDbDest);
+  sqlite3 * const pSrc = S3JniLongPtr_sqlite3(jpDbSrc);
+  char * const zDest = s3jni_jstring_to_utf8(jTDest, 0);
+  char * const zSrc = s3jni_jstring_to_utf8(jTSrc, 0);
+  jobject rv = 0;
+
+  if( pDest && pSrc && zDest && zSrc ){
+    sqlite3_backup * const pB =
+      sqlite3_backup_init(pDest, zDest, pSrc, zSrc);
+    if( pB ){
+      rv = new_java_sqlite3_backup(env, pB);
+      if( !rv ){
+        sqlite3_backup_finish( pB );
+      }
+    }
+  }
+  sqlite3_free(zDest);
+  sqlite3_free(zSrc);
+  return rv;
+}
+
+S3JniApi(sqlite3_backup_pagecount(),jint,1backup_1pagecount)(
+  JniArgsEnvClass, jlong jpBack
+){
+  return sqlite3_backup_pagecount(S3JniLongPtr_sqlite3_backup(jpBack));
+}
+
+S3JniApi(sqlite3_backup_remaining(),jint,1backup_1remaining)(
+  JniArgsEnvClass, jlong jpBack
+){
+  return sqlite3_backup_remaining(S3JniLongPtr_sqlite3_backup(jpBack));
+}
+
+S3JniApi(sqlite3_backup_step(),jint,1backup_1step)(
+  JniArgsEnvClass, jlong jpBack, jint nPage
+){
+  return sqlite3_backup_step(S3JniLongPtr_sqlite3_backup(jpBack), (int)nPage);
+}
+
+S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+  jsize nBA = 0;
+  jbyte * const pBuf = baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0;
   int rc;
-  if(!baData){
-    rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx);
+  if( pBuf ){
+    if( nMax>nBA ){
+      nMax = nBA;
+    }
+    rc = sqlite3_bind_blob(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+                           pBuf, (int)nMax, SQLITE_TRANSIENT);
+    s3jni_jbyteArray_release(baData, pBuf);
   }else{
-    jbyte * const pBuf = JBA_TOC(baData);
-    rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax,
-                           SQLITE_TRANSIENT);
-    JBA_RELEASE(baData,pBuf);
+    rc = baData
+      ? SQLITE_NOMEM
+      : sqlite3_bind_null( S3JniLongPtr_sqlite3_stmt(jpStmt), ndx );
   }
   return (jint)rc;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt,
-                         jint ndx, jdouble val){
-  return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val);
+S3JniApi(sqlite3_bind_double(),jint,1bind_1double)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val
+){
+  return (jint)sqlite3_bind_double(S3JniLongPtr_sqlite3_stmt(jpStmt),
+                                   (int)ndx, (double)val);
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt,
-                      jint ndx, jint val){
-  return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val);
+S3JniApi(sqlite3_bind_int(),jint,1bind_1int)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jint val
+){
+  return (jint)sqlite3_bind_int(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (int)val);
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt,
-                        jint ndx, jlong val){
-  return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
+S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jlong val
+){
+  return (jint)sqlite3_bind_int64(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt,
-                       jint ndx){
-  return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
-}
+/*
+** Bind a new global ref to Object `val` using sqlite3_bind_pointer().
+*/
+S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jobject val
+){
+  sqlite3_stmt * const pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+  int rc = SQLITE_MISUSE;
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){
-  int rc = 0;
-  jbyte * const pBuf = JBA_TOC(jName);
-  if(pBuf){
-    rc = sqlite3_bind_parameter_index(PtrGet_sqlite3_stmt(jpStmt),
-                                      (const char *)pBuf);
-    JBA_RELEASE(jName, pBuf);
+  if(pStmt){
+    jobject const rv = S3JniRefGlobal(val);
+    if( rv ){
+      rc = sqlite3_bind_pointer(pStmt, ndx, rv, ResultJavaValuePtrStr,
+                                S3Jni_jobject_finalizer);
+    }else if(val){
+      rc = SQLITE_NOMEM;
+    }else{
+      rc = sqlite3_bind_null(pStmt, ndx);
+    }
   }
   return rc;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt,
-                       jint ndx, jbyteArray baData, jint nMax){
-  if(baData){
-    jbyte * const pBuf = JBA_TOC(baData);
-    int rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf,
-                               (int)nMax, SQLITE_TRANSIENT);
-    JBA_RELEASE(baData, pBuf);
-    return (jint)rc;
-  }else{
-    return sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+S3JniApi(sqlite3_bind_null(),jint,1bind_1null)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+  return (jint)sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_bind_parameter_count(),jint,1bind_1parameter_1count)(
+  JniArgsEnvClass, jlong jpStmt
+){
+  return (jint)sqlite3_bind_parameter_count(S3JniLongPtr_sqlite3_stmt(jpStmt));
+}
+
+S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)(
+  JniArgsEnvClass, jlong jpStmt, jbyteArray jName
+){
+  int rc = 0;
+  jbyte * const pBuf = s3jni_jbyteArray_bytes(jName);
+  if( pBuf ){
+    rc = sqlite3_bind_parameter_index(S3JniLongPtr_sqlite3_stmt(jpStmt),
+                                      (const char *)pBuf);
+    s3jni_jbyteArray_release(jName, pBuf);
   }
+  return rc;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt,
-                           jint ndx, jint n){
-  return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n);
+S3JniApi(sqlite3_bind_parameter_name(),jstring,1bind_1parameter_1name)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+  const char *z =
+    sqlite3_bind_parameter_name(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx);
+  return z ? s3jni_utf8_to_jstring(z, -1) : 0;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt,
-                           jint ndx, jlong n){
-  return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n);
+/*
+** Impl of sqlite3_bind_text/text16().
+*/
+static int s3jni__bind_text(int is16, JNIEnv *env, jlong jpStmt, jint ndx,
+                            jbyteArray baData, jint nMax){
+  jsize nBA = 0;
+  jbyte * const pBuf =
+    baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0;
+  int rc;
+  if( pBuf ){
+    if( nMax>nBA ){
+      nMax = nBA;
+    }
+    /* Note that we rely on the Java layer having assured that baData
+       is NUL-terminated if nMax is negative. In order to avoid UB for
+       such cases, we do not expose the byte-limit arguments in the
+       public API. */
+    rc = is16
+      ? sqlite3_bind_text16(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+                            pBuf, (int)nMax, SQLITE_TRANSIENT)
+      : sqlite3_bind_text(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+                          (const char *)pBuf,
+                          (int)nMax, SQLITE_TRANSIENT);
+  }else{
+    rc = baData
+      ? sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx)
+      : SQLITE_NOMEM;
+  }
+  s3jni_jbyteArray_release(baData, pBuf);
+  return (jint)rc;
+
 }
 
+S3JniApi(sqlite3_bind_text(),jint,1bind_1text)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+  return s3jni__bind_text(0, env, jpStmt, ndx, baData, nMax);
+}
+
+S3JniApi(sqlite3_bind_text16(),jint,1bind_1text16)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+  return s3jni__bind_text(1, env, jpStmt, ndx, baData, nMax);
+}
+
+S3JniApi(sqlite3_bind_value(),jint,1bind_1value)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jlong jpValue
+){
+  int rc = 0;
+  sqlite3_stmt * pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+  if( pStmt ){
+    sqlite3_value *v = S3JniLongPtr_sqlite3_value(jpValue);
+    if( v ){
+      rc = sqlite3_bind_value(pStmt, (int)ndx, v);
+    }else{
+      rc = sqlite3_bind_null(pStmt, (int)ndx);
+    }
+  }else{
+    rc = SQLITE_MISUSE;
+  }
+  return (jint)rc;
+}
+
+S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jint n
+){
+  return (jint)sqlite3_bind_zeroblob(S3JniLongPtr_sqlite3_stmt(jpStmt),
+                                     (int)ndx, (int)n);
+}
+
+S3JniApi(sqlite3_bind_zeroblob64(),jint,1bind_1zeroblob64)(
+  JniArgsEnvClass, jlong jpStmt, jint ndx, jlong n
+){
+  return (jint)sqlite3_bind_zeroblob64(S3JniLongPtr_sqlite3_stmt(jpStmt),
+                                       (int)ndx, (sqlite3_uint64)n);
+}
+
+S3JniApi(sqlite3_blob_bytes(),jint,1blob_1bytes)(
+  JniArgsEnvClass, jlong jpBlob
+){
+  return sqlite3_blob_bytes(S3JniLongPtr_sqlite3_blob(jpBlob));
+}
+
+S3JniApi(sqlite3_blob_close(),jint,1blob_1close)(
+  JniArgsEnvClass, jlong jpBlob
+){
+  sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob);
+  return b ? (jint)sqlite3_blob_close(b) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_blob_open(),jint,1blob_1open)(
+  JniArgsEnvClass, jlong jpDb, jstring jDbName, jstring jTbl, jstring jCol,
+  jlong jRowId, jint flags, jobject jOut
+){
+  sqlite3 * const db = S3JniLongPtr_sqlite3(jpDb);
+  sqlite3_blob * pBlob = 0;
+  char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
+  int rc;
+
+  if( !db || !jDbName || !jTbl || !jCol ) return SQLITE_MISUSE;
+  zDbName = s3jni_jstring_to_utf8(jDbName,0);
+  zTableName = zDbName ? s3jni_jstring_to_utf8(jTbl,0) : 0;
+  zColumnName = zTableName ? s3jni_jstring_to_utf8(jCol,0) : 0;
+  rc = zColumnName
+    ? sqlite3_blob_open(db, zDbName, zTableName, zColumnName,
+                        (sqlite3_int64)jRowId, (int)flags, &pBlob)
+    : SQLITE_NOMEM;
+  if( 0==rc ){
+    jobject rv = new_java_sqlite3_blob(env, pBlob);
+    if( !rv ){
+      sqlite3_blob_close(pBlob);
+      rc = SQLITE_NOMEM;
+    }
+    OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_blob), jOut, rv);
+  }
+  sqlite3_free(zDbName);
+  sqlite3_free(zTableName);
+  sqlite3_free(zColumnName);
+  return rc;
+}
+
+S3JniApi(sqlite3_blob_read(),jint,1blob_1read)(
+  JniArgsEnvClass, jlong jpBlob, jbyteArray jTgt, jint iOffset
+){
+  jbyte * const pBa = s3jni_jbyteArray_bytes(jTgt);
+  int rc = jTgt ? (pBa ? SQLITE_MISUSE : SQLITE_NOMEM) : SQLITE_MISUSE;
+  if( pBa ){
+    jsize const nTgt = (*env)->GetArrayLength(env, jTgt);
+    rc = sqlite3_blob_read(S3JniLongPtr_sqlite3_blob(jpBlob), pBa,
+                           (int)nTgt, (int)iOffset);
+    if( 0==rc ){
+      s3jni_jbyteArray_commit(jTgt, pBa);
+    }else{
+      s3jni_jbyteArray_release(jTgt, pBa);
+    }
+  }
+  return rc;
+}
+
+S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)(
+  JniArgsEnvClass, jlong jpBlob, jlong iNewRowId
+){
+  return (jint)sqlite3_blob_reopen(S3JniLongPtr_sqlite3_blob(jpBlob),
+                                   (sqlite3_int64)iNewRowId);
+}
+
+S3JniApi(sqlite3_blob_write(),jint,1blob_1write)(
+  JniArgsEnvClass, jlong jpBlob, jbyteArray jBa, jint iOffset
+){
+  sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob);
+  jbyte * const pBuf = b ? s3jni_jbyteArray_bytes(jBa) : 0;
+  const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jBa) : 0;
+  int rc = SQLITE_MISUSE;
+  if(b && pBuf){
+    rc = sqlite3_blob_write( b, pBuf, (int)nBA, (int)iOffset );
+  }
+  s3jni_jbyteArray_release(jBa, pBuf);
+  return (jint)rc;
+}
+
+/* Central C-to-Java busy handler proxy. */
 static int s3jni_busy_handler(void* pState, int n){
   S3JniDb * const ps = (S3JniDb *)pState;
   int rc = 0;
-  if( ps->busyHandler.jObj ){
-    JNIEnv * const env = ps->env;
-    rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj,
-                               ps->busyHandler.midCallback, (jint)n);
-    IFTHREW{
-      EXCEPTION_WARN_CALLBACK_THREW("busy-handler callback");
-      EXCEPTION_CLEAR;
-      rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "busy-handle callback threw.");
+  S3JniDeclLocal_env;
+  S3JniHook hook;
+
+  S3JniHook_localdup(&ps->hooks.busyHandler, &hook);
+  if( hook.jObj ){
+    rc = (*env)->CallIntMethod(env, hook.jObj,
+                               hook.midCallback, (jint)n);
+    S3JniIfThrew{
+      S3JniExceptionWarnCallbackThrew("sqlite3_busy_handler() callback");
+      rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+                              "sqlite3_busy_handler() callback threw.");
     }
+    S3JniHook_localundup(hook);
   }
   return rc;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+S3JniApi(sqlite3_busy_handler(),jint,1busy_1handler)(
+  JniArgsEnvClass, jlong jpDb, jobject jBusy
+){
+  S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+  S3JniHook * const pHook = ps ? &ps->hooks.busyHandler : 0;
+  S3JniHook hook = S3JniHook_empty;
   int rc = 0;
-  if(!ps) return (jint)SQLITE_NOMEM;
-  if(jBusy){
-    S3JniHook * const pHook = &ps->busyHandler;
-    if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){
+
+  if( !ps ) return (jint)SQLITE_MISUSE;
+  S3JniDb_mutex_enter;
+  if( jBusy ){
+    if( pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy) ){
       /* Same object - this is a no-op. */
-      return 0;
+    }else{
+      jclass const klazz = (*env)->GetObjectClass(env, jBusy);
+      hook.jObj = S3JniRefGlobal(jBusy);
+      hook.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I)I");
+      S3JniUnrefLocal(klazz);
+      S3JniIfThrew {
+        rc = SQLITE_ERROR;
+      }
     }
-    S3JniHook_unref(env, pHook, 1);
-    pHook->jObj = REF_G(jBusy);
-    pHook->klazz = REF_G((*env)->GetObjectClass(env, jBusy));
-    pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, "xCallback", "(I)I");
-    IFTHREW {
-      S3JniHook_unref(env, pHook, 0);
-      rc = SQLITE_ERROR;
-    }
-    if(rc){
-      return rc;
-    }
-  }else{
-    S3JniHook_unref(env, &ps->busyHandler, 1);
   }
-  return jBusy
-    ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps)
-    : sqlite3_busy_handler(ps->pDb, 0, 0);
+  if( 0==rc ){
+    if( jBusy ){
+      if( hook.jObj ){ /* Replace handler */
+        rc = sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps);
+        if( 0==rc ){
+          S3JniHook_unref(pHook);
+          *pHook = hook /* transfer Java ref ownership */;
+          hook = S3JniHook_empty;
+        }
+      }/* else no-op */
+    }else{ /* Clear handler */
+      rc = sqlite3_busy_handler(ps->pDb, 0, 0);
+      if( 0==rc ){
+        S3JniHook_unref(pHook);
+      }
+    }
+  }
+  S3JniHook_unref(&hook);
+  S3JniDb_mutex_leave;
+  return rc;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-FIXME_THREADING(perDb)
-JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+S3JniApi(sqlite3_busy_timeout(),jint,1busy_1timeout)(
+  JniArgsEnvClass, jlong jpDb, jint ms
+){
+  S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+  int rc = SQLITE_MISUSE;
   if( ps ){
-    S3JniHook_unref(env, &ps->busyHandler, 1);
-    return sqlite3_busy_timeout(ps->pDb, (int)ms);
+    S3JniDb_mutex_enter;
+    S3JniHook_unref(&ps->hooks.busyHandler);
+    rc = sqlite3_busy_timeout(ps->pDb, (int)ms);
+    S3JniDb_mutex_leave;
   }
-  return SQLITE_MISUSE;
+  return rc;
 }
 
-#if S3JNI_ENABLE_AUTOEXT
-FIXME_THREADING(autoExt)
-JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){
-  S3JniAutoExtension * ax;;
-  if( S3JniGlobal.autoExt.isRunning ) return JNI_FALSE;
-  for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){
-    if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
-      S3JniAutoExtension_free(env, ax);
-      return JNI_TRUE;
+S3JniApi(sqlite3_cancel_auto_extension(),jboolean,1cancel_1auto_1extension)(
+  JniArgsEnvClass, jobject jAutoExt
+){
+  S3JniAutoExtension * ax;
+  jboolean rc = JNI_FALSE;
+  int i;
+
+  if( !jAutoExt ){
+    return rc;
+  }
+  S3JniAutoExt_mutex_enter;
+  /* This algo corresponds to the one in the core. */
+  for( i = SJG.autoExt.nExt-1; i >= 0; --i ){
+    ax = &SJG.autoExt.aExt[i];
+    if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+      S3JniAutoExtension_clear(ax);
+      /* Move final entry into this slot. */
+      --SJG.autoExt.nExt;
+      *ax = SJG.autoExt.aExt[SJG.autoExt.nExt];
+      SJG.autoExt.aExt[SJG.autoExt.nExt] = S3JniHook_empty;
+      assert( !SJG.autoExt.aExt[SJG.autoExt.nExt].jObj );
+      rc = JNI_TRUE;
+      break;
     }
   }
-  return JNI_FALSE;
+  S3JniAutoExt_mutex_leave;
+  return rc;
 }
-#endif /* S3JNI_ENABLE_AUTOEXT */
 
-
-/**
-   Wrapper for sqlite3_close(_v2)().
-*/
-static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){
+/* Wrapper for sqlite3_close(_v2)(). */
+static jint s3jni_close_db(JNIEnv * const env, jlong jpDb, int version){
   int rc = 0;
-  S3JniDb * ps = 0;
+  S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+
   assert(version == 1 || version == 2);
-  ps = S3JniDb_for_db(env, jDb, 0, 0);
-  if(ps){
-    //MARKER(("close()ing db@%p\n", ps->pDb));
-    rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb);
-    S3JniDb_set_aside(ps)
-      /* MUST come after close() because of ps->trace. */;
-    NativePointerHolder_set(env, jDb, 0, S3JniClassNames.sqlite3);
+  if( ps ){
+    rc = 1==version
+      ? (jint)sqlite3_close(ps->pDb)
+      : (jint)sqlite3_close_v2(ps->pDb);
   }
   return (jint)rc;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-FIXME_THREADING(perDb)
-JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){
-  return s3jni_close_db(env, pDb, 2);
-}
-
-FIXME_THREADING(S3JniEnvCache)
-FIXME_THREADING(perDb)
-JDECL(jint,1close)(JENV_CSELF, jobject pDb){
+S3JniApi(sqlite3_close(),jint,1close)(JniArgsEnvClass, jlong pDb){
   return s3jni_close_db(env, pDb, 1);
 }
 
-/**
-   Assumes z is an array of unsigned short and returns the index in
-   that array of the first element with the value 0.
+S3JniApi(sqlite3_close_v2(),jint,1close_1v2)(JniArgsEnvClass, jlong pDb){
+  return s3jni_close_db(env, pDb, 2);
+}
+
+/*
+** Assumes z is an array of unsigned short and returns the index in
+** that array of the first element with the value 0.
 */
 static unsigned int s3jni_utf16_strlen(void const * z){
   unsigned int i = 0;
@@ -2144,280 +2764,559 @@ static unsigned int s3jni_utf16_strlen(void const * z){
   return i;
 }
 
-/**
-   sqlite3_collation_needed16() hook impl.
- */
+/* Descriptive alias for use with sqlite3_collation_needed(). */
+typedef S3JniHook S3JniCollationNeeded;
+
+/* Central C-to-Java sqlite3_collation_needed16() hook impl. */
 static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb,
                                           int eTextRep, const void * z16Name){
-  S3JniDb * const ps = pState;
-  JNIEnv * const env = ps->env;
-  unsigned int const nName = s3jni_utf16_strlen(z16Name);
-  jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName);
-  IFTHREW{
-    s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
-    EXCEPTION_CLEAR;
-  }else{
-    (*env)->CallVoidMethod(env, ps->collationNeeded.jObj,
-                           ps->collationNeeded.midCallback,
-                           ps->jDb, (jint)eTextRep, jName);
-    IFTHREW{
-      s3jni_db_exception(env, ps, 0, "collation-needed callback threw");
+  S3JniCollationNeeded * const pHook = pState;
+  S3JniDeclLocal_env;
+  S3JniHook hook;
+
+  S3JniHook_localdup(pHook, &hook);
+  if( hook.jObj ){
+    unsigned int const nName = s3jni_utf16_strlen(z16Name);
+    jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName);
+
+    s3jni_oom_check( jName );
+    assert( hook.jExtra );
+    S3JniIfThrew{
+      S3JniExceptionClear;
+    }else if( hook.jExtra ){
+      (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+                             hook.jExtra, (jint)eTextRep, jName);
+      S3JniIfThrew{
+        S3JniExceptionWarnCallbackThrew("sqlite3_collation_needed() callback");
+      }
     }
+    S3JniUnrefLocal(jName);
+    S3JniHook_localundup(hook);
   }
-  UNREF_L(jName);
 }
 
-FIXME_THREADING(S3JniEnvCache)
-FIXME_THREADING(perDb)
-JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
-  jclass klazz;
-  jobject pOld = 0;
-  jmethodID xCallback;
-  S3JniHook * const pHook = &ps->collationNeeded;
-  int rc;
+S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)(
+  JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+  S3JniDb * ps;
+  S3JniCollationNeeded * pHook;
+  int rc = 0;
 
-  if( !ps ) return SQLITE_MISUSE;
-  pOld = pHook->jObj;
-  if(pOld && jHook &&
-     (*env)->IsSameObject(env, pOld, jHook)){
-    return 0;
+  S3JniDb_mutex_enter;
+  ps = S3JniDb_from_jlong(jpDb);
+  if( !ps ){
+    S3JniDb_mutex_leave;
+    return SQLITE_MISUSE;
   }
-  if( !jHook ){
-    UNREF_G(pOld);
-    memset(pHook, 0, sizeof(S3JniHook));
-    sqlite3_collation_needed(ps->pDb, 0, 0);
-    return 0;
-  }
-  klazz = (*env)->GetObjectClass(env, jHook);
-  xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded",
-                                  "(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I");
-  IFTHREW {
-    rc = s3jni_db_exception(env, ps, SQLITE_MISUSE,
-                            "Cannot not find matching callback on "
-                            "collation-needed hook object.");
+  pHook = &ps->hooks.collationNeeded;
+  if( pHook->jObj && jHook &&
+     (*env)->IsSameObject(env, pHook->jObj, jHook) ){
+    /* no-op */
+  }else if( !jHook ){
+    rc = sqlite3_collation_needed(ps->pDb, 0, 0);
+    if( 0==rc ){
+      S3JniHook_unref(pHook);
+    }
   }else{
-    pHook->midCallback = xCallback;
-    pHook->jObj = REF_G(jHook);
-    UNREF_G(pOld);
-    rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16);
+    jclass const klazz = (*env)->GetObjectClass(env, jHook);
+    jmethodID const xCallback = (*env)->GetMethodID(
+      env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I"
+    );
+    S3JniUnrefLocal(klazz);
+    S3JniIfThrew {
+      rc = s3jni_db_exception(ps->pDb, SQLITE_MISUSE,
+                              "Cannot not find matching call() in "
+                              "CollationNeededCallback object.");
+    }else{
+      rc = sqlite3_collation_needed16(ps->pDb, pHook,
+                                      s3jni_collation_needed_impl16);
+      if( 0==rc ){
+        S3JniHook_unref(pHook);
+        pHook->midCallback = xCallback;
+        pHook->jObj = S3JniRefGlobal(jHook);
+        pHook->jExtra = S3JniRefGlobal(ps->jDb);
+      }
+    }
   }
+  S3JniDb_mutex_leave;
   return rc;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt,
-                                jint ndx){
+S3JniApi(sqlite3_column_blob(),jbyteArray,1column_1blob)(
+  JniArgsEnvClass, jobject jpStmt, jint ndx
+){
   sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
   void const * const p = sqlite3_column_blob(pStmt, (int)ndx);
   int const n = p ? sqlite3_column_bytes(pStmt, (int)ndx) : 0;
-  if( 0==p ) return NULL;
-  else{
-    jbyteArray const jba = (*env)->NewByteArray(env, n);
-    (*env)->SetByteArrayRegion(env, jba, 0, n, (const jbyte *)p);
-    return jba;
-  }
+
+  return p ? s3jni_new_jbyteArray(p, n) : 0;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt,
-                               jint ndx){
+S3JniApi(sqlite3_column_double(),jdouble,1column_1double)(
+  JniArgsEnvClass, jobject jpStmt, jint ndx
+){
   return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt,
-                            jint ndx){
+S3JniApi(sqlite3_column_int(),jint,1column_1int)(
+  JniArgsEnvClass, jobject jpStmt, jint ndx
+){
   return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt,
-                            jint ndx){
+S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)(
+  JniArgsEnvClass, jobject jpStmt, jint ndx
+){
   return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt,
-                                      jint ndx){
+S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)(
+  JniArgsEnvClass, jobject jpStmt, jint ndx
+){
   sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
-  const int n = sqlite3_column_bytes(stmt, (int)ndx);
-  const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx);
-  return s3jni_new_jbyteArray(env, p, n);
+  const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0;
+  const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0;
+  return p ? s3jni_new_jbyteArray(p, n) : NULL;
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt,
-                               jint ndx){
+#if 0
+// this impl might prove useful.
+S3JniApi(sqlite3_column_text(),jstring,1column_1text)(
+  JniArgsEnvClass, jobject jpStmt, jint ndx
+){
   sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
-  const int n = sqlite3_column_bytes16(stmt, (int)ndx);
-  const void * const p = sqlite3_column_text16(stmt, (int)ndx);
+  const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0;
+  const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0;
+  return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0;
+}
+#endif
+
+S3JniApi(sqlite3_column_text16(),jstring,1column_1text16)(
+  JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+  sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+  const void * const p = stmt ? sqlite3_column_text16(stmt, (int)ndx) : 0;
+  const int n = p ? sqlite3_column_bytes16(stmt, (int)ndx) : 0;
   return s3jni_text16_to_jstring(env, p, n);
 }
 
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt,
-                              jint ndx){
-  sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
-  return new_sqlite3_value_wrapper(env, sv);
+S3JniApi(sqlite3_column_value(),jobject,1column_1value)(
+  JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+  sqlite3_value * const sv =
+    sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx)
+    /* reminder: returns an SQL NULL if jpStmt==NULL */;
+  return new_java_sqlite3_value(env, sv);
 }
 
-
+/*
+** Impl for commit hooks (if isCommit is true) or rollback hooks.
+*/
 static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
-  JNIEnv * const env = ps->env;
-  int rc = isCommit
-    ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj,
-                                 ps->commitHook.midCallback)
-    : (int)((*env)->CallVoidMethod(env, ps->rollbackHook.jObj,
-                                   ps->rollbackHook.midCallback), 0);
-  IFTHREW{
-    EXCEPTION_CLEAR;
-    rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw.");
+  S3JniDeclLocal_env;
+  int rc = 0;
+  S3JniHook hook;
+
+  S3JniHook_localdup(isCommit
+                     ? &ps->hooks.commit : &ps->hooks.rollback,
+                     &hook);
+  if( hook.jObj ){
+    rc = isCommit
+      ? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback)
+      : (int)((*env)->CallVoidMethod(env, hook.jObj, hook.midCallback), 0);
+    S3JniIfThrew{
+      rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, "hook callback threw");
+    }
+    S3JniHook_localundup(hook);
   }
   return rc;
 }
 
+/* C-to-Java commit hook wrapper. */
 static int s3jni_commit_hook_impl(void *pP){
   return s3jni_commit_rollback_hook_impl(1, pP);
 }
 
+/* C-to-Java rollback hook wrapper. */
 static void s3jni_rollback_hook_impl(void *pP){
   (void)s3jni_commit_rollback_hook_impl(0, pP);
 }
 
-FIXME_THREADING(perDb)
-static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb,
-                                          jobject jHook){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
-  jclass klazz;
-  jobject pOld = 0;
-  jmethodID xCallback;
-  S3JniHook * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook;
-  if(!ps){
-    s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
+/*
+** Proxy for sqlite3_commit_hook() (if isCommit is true) or
+** sqlite3_rollback_hook().
+*/
+static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,
+                                          jlong jpDb, jobject jHook){
+  S3JniDb * ps;
+  jobject pOld = 0;  /* previous hoook */
+  S3JniHook * pHook; /* ps->hooks.commit|rollback */
+
+  S3JniDb_mutex_enter;
+  ps = S3JniDb_from_jlong(jpDb);
+  if( !ps ){
+    s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0);
+    S3JniDb_mutex_leave;
     return 0;
   }
+  pHook = isCommit ? &ps->hooks.commit : &ps->hooks.rollback;
   pOld = pHook->jObj;
-  if(pOld && jHook &&
-     (*env)->IsSameObject(env, pOld, jHook)){
-    return pOld;
-  }
-  if( !jHook ){
-    if(pOld){
-      jobject tmp = REF_L(pOld);
-      UNREF_G(pOld);
+  if( pOld && jHook &&
+      (*env)->IsSameObject(env, pOld, jHook) ){
+    /* No-op. */
+  }else if( !jHook ){
+    if( pOld ){
+      jobject tmp = S3JniRefLocal(pOld);
+      S3JniUnrefGlobal(pOld);
       pOld = tmp;
     }
-    memset(pHook, 0, sizeof(S3JniHook));
+    *pHook = S3JniHook_empty;
     if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0);
     else sqlite3_rollback_hook(ps->pDb, 0, 0);
-    return pOld;
-  }
-  klazz = (*env)->GetObjectClass(env, jHook);
-  xCallback = (*env)->GetMethodID(env, klazz,
-                                  isCommit ? "xCommitHook" : "xRollbackHook",
-                                  isCommit ? "()I" : "()V");
-  IFTHREW {
-    EXCEPTION_REPORT;
-    EXCEPTION_CLEAR;
-    s3jni_db_error(ps->pDb, SQLITE_ERROR,
-                   "Cannot not find matching callback on "
-                   "hook object.");
   }else{
-    pHook->midCallback = xCallback;
-    pHook->jObj = REF_G(jHook);
-    if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps);
-    else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps);
-    if(pOld){
-      jobject tmp = REF_L(pOld);
-      UNREF_G(pOld);
-      pOld = tmp;
+    jclass const klazz = (*env)->GetObjectClass(env, jHook);
+    jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call",
+                                                    isCommit ? "()I" : "()V");
+    S3JniUnrefLocal(klazz);
+    S3JniIfThrew {
+      S3JniExceptionReport;
+      S3JniExceptionClear;
+      s3jni_db_error(ps->pDb, SQLITE_ERROR,
+                     "Cannot not find matching call() method in"
+                     "hook object.");
+    }else{
+      pHook->midCallback = xCallback;
+      pHook->jObj = S3JniRefGlobal(jHook);
+      if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps);
+      else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps);
+      if( pOld ){
+        jobject tmp = S3JniRefLocal(pOld);
+        S3JniUnrefGlobal(pOld);
+        pOld = tmp;
+      }
     }
   }
+  S3JniDb_mutex_leave;
   return pOld;
 }
 
-JDECL(jobject,1commit_1hook)(JENV_CSELF,jobject jDb, jobject jHook){
-  return s3jni_commit_rollback_hook(1, env, jDb, jHook);
+S3JniApi(sqlite3_commit_hook(),jobject,1commit_1hook)(
+  JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+  return s3jni_commit_rollback_hook(1, env, jpDb, jHook);
 }
 
-
-JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){
-  return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) );
+S3JniApi(sqlite3_compileoption_get(),jstring,1compileoption_1get)(
+  JniArgsEnvClass, jint n
+){
+  const char * z = sqlite3_compileoption_get(n);
+  jstring const rv = z ? (*env)->NewStringUTF( env, z ) : 0;
+    /* We know these to be ASCII, so MUTF-8 is fine. */;
+  s3jni_oom_check(z ? !!rv : 1);
+  return rv;
 }
 
-JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){
-  const char *zUtf8 = JSTR_TOC(name);
+S3JniApi(sqlite3_compileoption_used(),jboolean,1compileoption_1used)(
+  JniArgsEnvClass, jstring name
+){
+  const char *zUtf8 = s3jni_jstring_to_mutf8(name)
+    /* We know these to be ASCII, so MUTF-8 is fine (and
+       hypothetically faster to convert). */;
   const jboolean rc =
     0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE;
-  JSTR_RELEASE(name, zUtf8);
+  s3jni_mutf8_release(name, zUtf8);
   return rc;
 }
 
-FIXME_THREADING(perDb)
-JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){
-  sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx));
-  S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb, 0) : 0;
+S3JniApi(sqlite3_complete(),int,1complete)(
+  JniArgsEnvClass, jbyteArray jSql
+){
+  jbyte * const pBuf = s3jni_jbyteArray_bytes(jSql);
+  const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jSql) : 0;
+  int rc;
+
+  assert( (nBA>0 ? 0==pBuf[nBA-1] : (pBuf ? 0==*pBuf : 1))
+          && "Byte array is not NUL-terminated." );
+  rc = (pBuf && 0==pBuf[(nBA ? nBA-1 : 0)])
+    ? sqlite3_complete( (const char *)pBuf )
+    : (jSql ? SQLITE_NOMEM : SQLITE_MISUSE);
+  s3jni_jbyteArray_release(jSql, pBuf);
+  return rc;
+}
+
+S3JniApi(sqlite3_config() /*for a small subset of options.*/,
+         jint,1config__I)(JniArgsEnvClass, jint n){
+  switch( n ){
+    case SQLITE_CONFIG_SINGLETHREAD:
+    case SQLITE_CONFIG_MULTITHREAD:
+    case SQLITE_CONFIG_SERIALIZED:
+      return sqlite3_config( n );
+    default:
+      return SQLITE_MISUSE;
+  }
+}
+/* C-to-Java SQLITE_CONFIG_LOG wrapper. */
+static void s3jni_config_log(void *ignored, int errCode, const char *z){
+  S3JniDeclLocal_env;
+  S3JniHook hook = S3JniHook_empty;
+
+  S3JniHook_localdup(&SJG.hook.configlog, &hook);
+  if( hook.jObj ){
+    jstring const jArg1 = z ? s3jni_utf8_to_jstring(z, -1) : 0;
+    if( z ? !!jArg1 : 1 ){
+      (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, errCode, jArg1);
+    }
+    S3JniIfThrew{
+      S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_LOG callback");
+      S3JniExceptionClear;
+    }
+    S3JniHook_localundup(hook);
+    S3JniUnrefLocal(jArg1);
+  }
+}
+
+S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */,
+         jint, 1config__Lorg_sqlite_jni_ConfigLogCallback_2
+)(JniArgsEnvClass, jobject jLog){
+  S3JniHook * const pHook = &SJG.hook.configlog;
+  int rc = 0;
+
+  S3JniGlobal_mutex_enter;
+  if( !jLog ){
+    rc = sqlite3_config( SQLITE_CONFIG_LOG, NULL, NULL );
+    if( 0==rc ){
+      S3JniHook_unref(pHook);
+    }
+  }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){
+    /* No-op */
+  }else {
+    jclass const klazz = (*env)->GetObjectClass(env, jLog);
+    jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call",
+                                                      "(ILjava/lang/String;)V");
+    S3JniUnrefLocal(klazz);
+    if( midCallback ){
+      rc = sqlite3_config( SQLITE_CONFIG_LOG, s3jni_config_log, NULL );
+      if( 0==rc ){
+        S3JniHook_unref(pHook);
+        pHook->midCallback = midCallback;
+        pHook->jObj = S3JniRefGlobal(jLog);
+      }
+    }else{
+      S3JniExceptionWarnIgnore;
+      rc = SQLITE_ERROR;
+    }
+  }
+  S3JniGlobal_mutex_leave;
+  return rc;
+}
+
+#ifdef SQLITE_ENABLE_SQLLOG
+/* C-to-Java SQLITE_CONFIG_SQLLOG wrapper. */
+static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int op){
+  jobject jArg0 = 0;
+  jstring jArg1 = 0;
+  S3JniDeclLocal_env;
+  S3JniDb * const ps = S3JniDb_from_c(pDb);
+  S3JniHook hook = S3JniHook_empty;
+
+  if( ps ){
+    S3JniHook_localdup(&SJG.hook.sqllog, &hook);
+  }
+  if( !hook.jObj ) return;
+  jArg0 = S3JniRefLocal(ps->jDb);
+  switch( op ){
+    case 0: /* db opened */
+    case 1: /* SQL executed */
+      jArg1 = s3jni_utf8_to_jstring( z, -1);
+      break;
+    case 2: /* db closed */
+      break;
+    default:
+      (*env)->FatalError(env, "Unhandled 4th arg to SQLITE_CONFIG_SQLLOG.");
+      break;
+  }
+  (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, jArg0, jArg1, op);
+  S3JniIfThrew{
+    S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_SQLLOG callback");
+    S3JniExceptionClear;
+  }
+  S3JniHook_localundup(hook);
+  S3JniUnrefLocal(jArg0);
+  S3JniUnrefLocal(jArg1);
+}
+//! Requirement of SQLITE_CONFIG_SQLLOG.
+void sqlite3_init_sqllog(void){
+  sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 );
+}
+#endif
+
+S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */,
+         jint, 1config__Lorg_sqlite_jni_ConfigSqllogCallback_2)(
+           JniArgsEnvClass, jobject jLog){
+#ifndef SQLITE_ENABLE_SQLLOG
+  return SQLITE_MISUSE;
+#else
+  S3JniHook * const pHook = &SJG.hook.sqllog;
+  int rc = 0;
+
+  S3JniGlobal_mutex_enter;
+  if( !jLog ){
+    rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, NULL );
+    if( 0==rc ){
+      S3JniHook_unref(pHook);
+    }
+  }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){
+    /* No-op */
+  }else {
+    jclass const klazz = (*env)->GetObjectClass(env, jLog);
+    jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call",
+                                                      "(Lorg/sqlite/jni/capi/sqlite3;"
+                                                      "Ljava/lang/String;"
+                                                      "I)V");
+    S3JniUnrefLocal(klazz);
+    if( midCallback ){
+      rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, NULL );
+      if( 0==rc ){
+        S3JniHook_unref(pHook);
+        pHook->midCallback = midCallback;
+        pHook->jObj = S3JniRefGlobal(jLog);
+      }
+    }else{
+      S3JniExceptionWarnIgnore;
+      rc = SQLITE_ERROR;
+    }
+  }
+  S3JniGlobal_mutex_leave;
+  return rc;
+#endif
+}
+
+S3JniApi(sqlite3_context_db_handle(),jobject,1context_1db_1handle)(
+  JniArgsEnvClass, jobject jpCx
+){
+  sqlite3_context * const pCx = PtrGet_sqlite3_context(jpCx);
+  sqlite3 * const pDb = pCx ? sqlite3_context_db_handle(pCx) : 0;
+  S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0;
   return ps ? ps->jDb : 0;
 }
 
-JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb,
-                               jstring name, jint eTextRep,
-                               jobject oCollation){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
-  jclass klazz;
+/*
+** State for CollationCallbacks. This used to be its own separate
+** type, but has since been consolidated with S3JniHook. It retains
+** its own typedef for code legibility and searchability reasons.
+*/
+typedef S3JniHook S3JniCollationCallback;
+
+/*
+** Proxy for Java-side CollationCallback.xCompare() callbacks.
+*/
+static int CollationCallback_xCompare(void *pArg, int nLhs, const void *lhs,
+                                      int nRhs, const void *rhs){
+  S3JniCollationCallback * const pCC = pArg;
+  S3JniDeclLocal_env;
+  jint rc = 0;
+  if( pCC->jObj ){
+    jbyteArray jbaLhs = s3jni_new_jbyteArray(lhs, (jint)nLhs);
+    jbyteArray jbaRhs = jbaLhs
+      ? s3jni_new_jbyteArray(rhs, (jint)nRhs) : 0;
+    if( !jbaRhs ){
+      S3JniUnrefLocal(jbaLhs);
+      /* We have no recovery strategy here. */
+      s3jni_oom_check( jbaRhs );
+      return 0;
+    }
+    rc = (*env)->CallIntMethod(env, pCC->jObj, pCC->midCallback,
+                               jbaLhs, jbaRhs);
+    S3JniExceptionIgnore;
+    S3JniUnrefLocal(jbaLhs);
+    S3JniUnrefLocal(jbaRhs);
+  }
+  return (int)rc;
+}
+
+/* CollationCallback finalizer for use by the sqlite3 internals. */
+static void CollationCallback_xDestroy(void *pArg){
+  S3JniCollationCallback * const pCC = pArg;
+  S3JniDeclLocal_env;
+  S3JniHook_free(pCC);
+}
+
+S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(),
+         jint,1create_1collation
+)(JniArgsEnvClass, jobject jDb, jstring name, jint eTextRep,
+  jobject oCollation){
   int rc;
-  const char *zName;
-  S3JniHook * pHook;
-  if(!ps) return (jint)SQLITE_NOMEM;
-  pHook = &ps->collation;
-  klazz = (*env)->GetObjectClass(env, oCollation);
-  pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare",
-                                           "([B[B)I");
-  IFTHREW{
-    EXCEPTION_REPORT;
-    return s3jni_db_error(ps->pDb, SQLITE_ERROR,
-                          "Could not get xCompare() method for object.");
+  S3JniDb * ps;
+
+  if( !jDb || !name || !encodingTypeIsValid(eTextRep) ){
+    return (jint)SQLITE_MISUSE;
   }
-  zName = JSTR_TOC(name);
-  rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep,
-                                   ps, CollationState_xCompare,
-                                   CollationState_xDestroy);
-  JSTR_RELEASE(name, zName);
-  if( 0==rc ){
-    pHook->jObj = REF_G(oCollation);
-    pHook->klazz = REF_G(klazz);
+  S3JniDb_mutex_enter;
+  ps = S3JniDb_from_java(jDb);
+  jclass const klazz = (*env)->GetObjectClass(env, oCollation);
+  jmethodID const midCallback =
+    (*env)->GetMethodID(env, klazz, "call", "([B[B)I");
+  S3JniUnrefLocal(klazz);
+  S3JniIfThrew{
+    rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+                        "Could not get call() method from "
+                        "CollationCallback object.");
   }else{
-    S3JniHook_unref(env, pHook, 1);
+    char * const zName = s3jni_jstring_to_utf8(name, 0);
+    S3JniCollationCallback * const pCC =
+      zName ? S3JniHook_alloc() : 0;
+    if( pCC ){
+      rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep,
+                                       pCC, CollationCallback_xCompare,
+                                       CollationCallback_xDestroy);
+      if( 0==rc ){
+        pCC->midCallback = midCallback;
+        pCC->jObj = S3JniRefGlobal(oCollation);
+        pCC->doXDestroy = 1;
+      }else{
+        CollationCallback_xDestroy(pCC);
+      }
+    }else{
+      rc = SQLITE_NOMEM;
+    }
+    sqlite3_free(zName);
   }
+  S3JniDb_mutex_leave;
   return (jint)rc;
 }
 
-static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName,
-                            jint nArg, jint eTextRep, jobject jFunctor){
+S3JniApi(sqlite3_create_function() sqlite3_create_function_v2()
+         sqlite3_create_window_function(),
+         jint,1create_1function
+)(JniArgsEnvClass, jobject jDb, jstring jFuncName, jint nArg,
+  jint eTextRep, jobject jFunctor){
   S3JniUdf * s = 0;
   int rc;
   sqlite3 * const pDb = PtrGet_sqlite3(jDb);
-  const char * zFuncName = 0;
+  char * zFuncName = 0;
 
-  if( !encodingTypeIsValid(eTextRep) ){
+  if( !pDb || !jFuncName ){
+    return SQLITE_MISUSE;
+  }else if( !encodingTypeIsValid(eTextRep) ){
     return s3jni_db_error(pDb, SQLITE_FORMAT,
-                                "Invalid function encoding option.");
+                          "Invalid function encoding option.");
   }
   s = S3JniUdf_alloc(env, jFunctor);
   if( !s ) return SQLITE_NOMEM;
-  else if( UDF_UNKNOWN_TYPE==s->type ){
+
+  if( UDF_UNKNOWN_TYPE==s->type ){
     rc = s3jni_db_error(pDb, SQLITE_MISUSE,
                         "Cannot unambiguously determine function type.");
-    S3JniUdf_free(s);
+    S3JniUdf_free(env, s, 1);
     goto error_cleanup;
   }
-  zFuncName = JSTR_TOC(jFuncName);
-  if(!zFuncName){
+  zFuncName = s3jni_jstring_to_utf8(jFuncName,0);
+  if( !zFuncName ){
     rc = SQLITE_NOMEM;
-    S3JniUdf_free(s);
+    S3JniUdf_free(env, s, 1);
     goto error_cleanup;
   }
+  s->zFuncName = zFuncName /* pass on ownership */;
   if( UDF_WINDOW == s->type ){
     rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s,
                                         udf_xStep, udf_xFinal, udf_xValue,
@@ -2436,34 +3335,26 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName,
     rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s,
                                     xFunc, xStep, xFinal, S3JniUdf_finalizer);
   }
-  if( 0==rc ){
-    s->zFuncName = sqlite3_mprintf("%s", zFuncName)
-      /* OOM here is non-fatal. Ignore it. Handling it would require
-         re-calling the appropriate create_function() func with 0
-         for all xAbc args so that s would be finalized. */;
-  }
 error_cleanup:
-  JSTR_RELEASE(jFuncName, zFuncName);
-  /* on create_function() error, s will be destroyed via create_function() */
+  /* Reminder: on sqlite3_create_function() error, s will be
+  ** destroyed via create_function(). */
   return (jint)rc;
 }
 
-JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName,
-                              jint nArg, jint eTextRep, jobject jFunctor){
-  return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor);
-}
 
-/* sqlite3_db_config() for (int,const char *) */
-JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)(
-  JENV_CSELF, jobject jDb, jint op, jstring jStr
-){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+S3JniApi(sqlite3_db_config() /*for MAINDBNAME*/,
+         jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2
+)(JniArgsEnvClass, jobject jDb, jint op, jstring jStr){
+  S3JniDb * const ps = S3JniDb_from_java(jDb);
   int rc;
   char *zStr;
 
   switch( (ps && jStr) ? op : 0 ){
     case SQLITE_DBCONFIG_MAINDBNAME:
-      zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0);
+      S3JniDb_mutex_enter
+        /* Protect against a race in modifying/freeing
+           ps->zMainDbName. */;
+      zStr = s3jni_jstring_to_utf8( jStr, 0);
       if( zStr ){
         rc = sqlite3_db_config(ps->pDb, (int)op, zStr);
         if( rc ){
@@ -2475,21 +3366,25 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)(
       }else{
         rc = SQLITE_NOMEM;
       }
+      S3JniDb_mutex_leave;
       break;
+    case 0:
     default:
       rc = SQLITE_MISUSE;
   }
   return rc;
 }
 
-FIXME_THREADING(perDb)
-/* sqlite3_db_config() for (int,int*) */
-/* ACHTUNG: openjdk v19 creates a different mangled name for this
-   function than openjdk v8 does. */
-JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)(
-  JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut
+S3JniApi(
+  sqlite3_db_config(),
+  /* WARNING: openjdk v19 creates a different mangled name for this
+  ** function than openjdk v8 does. We account for that by exporting
+  ** both versions of the name. */
+  jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2
+)(
+  JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut
 ){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+  S3JniDb * const ps = S3JniDb_from_java(jDb);
   int rc;
   switch( ps ? op : 0 ){
     case SQLITE_DBCONFIG_ENABLE_FKEY:
@@ -2517,29 +3412,31 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer
       }
       break;
     }
+    case 0:
     default:
       rc = SQLITE_MISUSE;
   }
   return (jint)rc;
 }
 
-/**
-   This is a workaround for openjdk v19 (and possibly others) encoding
-   this function's name differently than JDK v8 does. If we do not
-   install both names for this function then Java will not be able to
-   find the function in both environments.
+/*
+** This is a workaround for openjdk v19 (and possibly others) encoding
+** this function's name differently than JDK v8 does. If we do not
+** install both names for this function then Java will not be able to
+** find the function in both environments.
 */
-JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)(
-  JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut
+JniDecl(jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_00024Int32_2)(
+  JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut
 ){
-  return JFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)(
+  return JniFuncName(1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2)(
     env, jKlazz, jDb, op, onOff, jOut
   );
 }
 
-JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
-  S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
+S3JniApi(sqlite3_db_filename(),jstring,1db_1filename)(
+  JniArgsEnvClass, jobject jDb, jstring jDbName
+){
+  S3JniDb * const ps = S3JniDb_from_java(jDb);
   char *zDbName;
   jstring jRv = 0;
   int nStr = 0;
@@ -2547,20 +3444,48 @@ JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){
   if( !ps || !jDbName ){
     return 0;
   }
-  zDbName = s3jni_jstring_to_utf8(jc, jDbName, &nStr);
+  zDbName = s3jni_jstring_to_utf8( jDbName, &nStr);
   if( zDbName ){
     char const * zRv = sqlite3_db_filename(ps->pDb, zDbName);
     sqlite3_free(zDbName);
     if( zRv ){
-      jRv = s3jni_utf8_to_jstring(jc, zRv, -1);
+      jRv = s3jni_utf8_to_jstring( zRv, -1);
     }
   }
   return jRv;
 }
 
+S3JniApi(sqlite3_db_handle(),jobject,1db_1handle)(
+  JniArgsEnvClass, jobject jpStmt
+){
+  sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+  sqlite3 * const pDb = pStmt ? sqlite3_db_handle(pStmt) : 0;
+  S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0;
+  return ps ? ps->jDb : 0;
+}
 
-JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent,
-                        jobject jOutHigh, jboolean reset ){
+S3JniApi(sqlite3_db_readonly(),jint,1db_1readonly)(
+  JniArgsEnvClass, jobject jDb, jstring jDbName
+){
+  int rc = 0;
+  S3JniDb * const ps = S3JniDb_from_java(jDb);
+  char *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0 ) : 0;
+  rc = sqlite3_db_readonly(ps ? ps->pDb : 0, zDbName);
+  sqlite3_free(zDbName);
+  return (jint)rc;
+}
+
+S3JniApi(sqlite3_db_release_memory(),int,1db_1release_1memory)(
+  JniArgsEnvClass, jobject jDb
+){
+  sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+  return pDb ? sqlite3_db_release_memory(pDb) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_db_status(),jint,1db_1status)(
+  JniArgsEnvClass, jobject jDb, jint op, jobject jOutCurrent,
+                        jobject jOutHigh, jboolean reset
+){
   int iCur = 0, iHigh = 0;
   sqlite3 * const pDb = PtrGet_sqlite3(jDb);
   int rc = sqlite3_db_status( pDb, op, &iCur, &iHigh, reset );
@@ -2571,326 +3496,710 @@ JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent,
   return (jint)rc;
 }
 
-
-JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){
+S3JniApi(sqlite3_errcode(),jint,1errcode)(
+  JniArgsEnvClass, jobject jpDb
+){
   sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
   return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE;
 }
 
-JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){
+S3JniApi(sqlite3_errmsg(),jstring,1errmsg)(
+  JniArgsEnvClass, jobject jpDb
+){
   sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
-  S3JniEnvCache * const jc = pDb ? S3JniGlobal_env_cache(env) : 0;
-  return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0;
+  return pDb ? s3jni_utf8_to_jstring( sqlite3_errmsg(pDb), -1) : 0
+    /* We don't use errmsg16() directly only because it would cause an
+       additional level of internal encoding in sqlite3. The end
+       effect should be identical to using errmsg16(), however. */;
 }
 
-JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){
-  return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode))
-    /* We know these values to be plain ASCII, so pose no
-       MUTF-8 incompatibility */;
+S3JniApi(sqlite3_errstr(),jstring,1errstr)(
+  JniArgsEnvClass, jint rcCode
+){
+  jstring const rv = (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode))
+    /* We know these values to be plain ASCII, so pose no MUTF-8
+    ** incompatibility */;
+  s3jni_oom_check( rv );
+  return rv;
 }
 
-JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){
-  sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+#ifndef SQLITE_ENABLE_NORMALIZE
+/* Dummy stub for sqlite3_normalized_sql(). Never called. */
+static const char * sqlite3_normalized_sql(sqlite3_stmt *s){
+  S3JniDeclLocal_env;
+  (*env)->FatalError(env, "dummy sqlite3_normalized_sql() was "
+                     "impossibly called.") /* does not return */;
+  return 0;
+}
+#endif
+
+/*
+** Impl for sqlite3_expanded_sql() (if isExpanded is true) and
+** sqlite3_normalized_sql().
+*/
+static jstring s3jni_xn_sql(int isExpanded, JNIEnv *env, jobject jpStmt){
   jstring rv = 0;
+  sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+
   if( pStmt ){
-    S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
-    char * zSql = sqlite3_expanded_sql(pStmt);
-    OOM_CHECK(zSql);
+    char * zSql = isExpanded
+      ? sqlite3_expanded_sql(pStmt)
+      : (char*)sqlite3_normalized_sql(pStmt);
+    s3jni_oom_fatal(zSql);
     if( zSql ){
-      rv = s3jni_utf8_to_jstring(jc, zSql, -1);
-      sqlite3_free(zSql);
+      rv = s3jni_utf8_to_jstring(zSql, -1);
+      if( isExpanded ) sqlite3_free(zSql);
     }
   }
   return rv;
 }
 
-JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb,
-                                         jboolean onoff){
-  int const rc = sqlite3_extended_result_codes(PtrGet_sqlite3(jpDb), onoff ? 1 : 0);
+S3JniApi(sqlite3_expanded_sql(),jstring,1expanded_1sql)(
+  JniArgsEnvClass, jobject jpStmt
+){
+  return s3jni_xn_sql(1, env, jpStmt);
+}
+
+S3JniApi(sqlite3_normalized_sql(),jstring,1normalized_1sql)(
+  JniArgsEnvClass, jobject jpStmt
+){
+#ifdef SQLITE_ENABLE_NORMALIZE
+  return s3jni_xn_sql(0, env, jpStmt);
+#else
+  return 0;
+#endif
+}
+
+S3JniApi(sqlite3_extended_result_codes(),jboolean,1extended_1result_1codes)(
+  JniArgsEnvClass, jobject jpDb, jboolean onoff
+){
+  sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+  int const rc = pDb ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0) : 0;
   return rc ? JNI_TRUE : JNI_FALSE;
 }
 
-JDECL(jint,1initialize)(JENV_CSELF){
+S3JniApi(sqlite3_finalize(),jint,1finalize)(
+  JniArgsEnvClass, jlong jpStmt
+){
+  return jpStmt
+    ? sqlite3_finalize(S3JniLongPtr_sqlite3_stmt(jpStmt))
+    : 0;
+}
+
+S3JniApi(sqlite3_get_auxdata(),jobject,1get_1auxdata)(
+  JniArgsEnvClass, jobject jCx, jint n
+){
+  return sqlite3_get_auxdata(PtrGet_sqlite3_context(jCx), (int)n);
+}
+
+S3JniApi(sqlite3_initialize(),jint,1initialize)(
+  JniArgsEnvClass
+){
   return sqlite3_initialize();
 }
 
-JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){
+S3JniApi(sqlite3_interrupt(),void,1interrupt)(
+  JniArgsEnvClass, jobject jpDb
+){
+  sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+  if( pDb ){
+    sqlite3_interrupt(pDb);
+  }
+}
+
+S3JniApi(sqlite3_is_interrupted(),jboolean,1is_1interrupted)(
+  JniArgsEnvClass, jobject jpDb
+){
   int rc = 0;
-  sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
-  if( pStmt ){
-    rc = sqlite3_finalize(pStmt);
-    NativePointerHolder_set(env, jpStmt, 0, S3JniClassNames.sqlite3_stmt);
+  sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+  if( pDb ){
+    rc = sqlite3_is_interrupted(pDb);
+  }
+  return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+/*
+** Uncaches the current JNIEnv from the S3JniGlobal state, clearing
+** any resources owned by that cache entry and making that slot
+** available for re-use.
+*/
+JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){
+  int rc;
+  S3JniEnv_mutex_enter;
+  rc = S3JniEnv_uncache(env);
+  S3JniEnv_mutex_leave;
+  return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_keyword_check(),jboolean,1keyword_1check)(
+  JniArgsEnvClass, jstring jWord
+){
+  int nWord = 0;
+  char * zWord = s3jni_jstring_to_utf8(jWord, &nWord);
+  int rc = 0;
+
+  s3jni_oom_check(jWord ? !!zWord : 1);
+  if( zWord && nWord ){
+    rc = sqlite3_keyword_check(zWord, nWord);
+  }
+  sqlite3_free(zWord);
+  return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_keyword_name(),jstring,1keyword_1name)(
+  JniArgsEnvClass, jint ndx
+){
+  const char * zWord = 0;
+  int n = 0;
+  jstring rv = 0;
+
+  if( 0==sqlite3_keyword_name(ndx, &zWord, &n) ){
+    rv = s3jni_utf8_to_jstring(zWord, n);
+  }
+  return rv;
+}
+
+
+S3JniApi(sqlite3_last_insert_rowid(),jlong,1last_1insert_1rowid)(
+  JniArgsEnvClass, jobject jpDb
+){
+  return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb));
+}
+
+S3JniApi(sqlite3_limit(),jint,1limit)(
+  JniArgsEnvClass, jobject jpDb, jint id, jint newVal
+){
+  jint rc = 0;
+  sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+  if( pDb ){
+    rc = sqlite3_limit( pDb, (int)id, (int)newVal );
   }
   return rc;
 }
 
-
-JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){
-  return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb));
-}
-
-//! Pre-open() code common to sqlite3_open(_v2)().
-static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc,
+/* Pre-open() code common to sqlite3_open[_v2](). */
+static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc,
                           jstring jDbName, char **zDbName,
-                          S3JniDb ** ps, jobject *jDb){
-  *jc = S3JniGlobal_env_cache(env);
-  if(!*jc) return SQLITE_NOMEM;
-  *zDbName = jDbName ? s3jni_jstring_to_utf8(*jc, jDbName, 0) : 0;
-  if(jDbName && !*zDbName) return SQLITE_NOMEM;
-  *jDb = new_sqlite3_wrapper(env, 0);
-  if( !*jDb ){
+                          S3JniDb ** ps){
+  int rc = 0;
+  jobject jDb = 0;
+
+  *jc = S3JniEnv_get();
+  if( !*jc ){
+    rc = SQLITE_NOMEM;
+    goto end;
+  }
+  *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0) : 0;
+  if( jDbName && !*zDbName ){
+    rc = SQLITE_NOMEM;
+    goto end;
+  }
+  jDb = new_java_sqlite3(env, 0);
+  if( !jDb ){
     sqlite3_free(*zDbName);
     *zDbName = 0;
-    return SQLITE_NOMEM;
+    rc = SQLITE_NOMEM;
+    goto end;
   }
-  *ps = S3JniDb_alloc(env, 0, *jDb);
-#if S3JNI_ENABLE_AUTOEXT
-  if(*ps){
-    assert(!S3JniGlobal.autoExt.psOpening);
-    S3JniGlobal.autoExt.psOpening = *ps;
+  *ps = S3JniDb_alloc(env, jDb);
+  if( *ps ){
+    (*jc)->pdbOpening = *ps;
+  }else{
+    S3JniUnrefLocal(jDb);
+    rc = SQLITE_NOMEM;
   }
-#endif
-  //MARKER(("pre-open ps@%p\n", *ps));
-  return *ps ? 0 : SQLITE_NOMEM;
+end:
+  return rc;
 }
 
-/**
-   Post-open() code common to both the sqlite3_open() and
-   sqlite3_open_v2() bindings. ps->jDb must be the
-   org.sqlite.jni.sqlite3 object which will hold the db's native
-   pointer. theRc must be the result code of the open() op. If
-   *ppDb is NULL then ps is set aside and its state cleared,
-   else ps is associated with *ppDb. If *ppDb is not NULL then
-   ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance).
-
-   Returns theRc.
+/*
+** Post-open() code common to both the sqlite3_open() and
+** sqlite3_open_v2() bindings. ps->jDb must be the
+** org.sqlite.jni.capi.sqlite3 object which will hold the db's native
+** pointer. theRc must be the result code of the open() op. If
+** *ppDb is NULL then ps is set aside and its state cleared,
+** else ps is associated with *ppDb. If *ppDb is not NULL then
+** ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance).
+**
+** Must be called if s3jni_open_pre() succeeds and must not be called
+** if it doesn't.
+**
+** Returns theRc.
 */
-static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps,
-                           sqlite3 **ppDb, jobject jOut, int theRc){
-  //MARKER(("post-open() ps@%p db@%p\n", ps, *ppDb));
-#if S3JNI_ENABLE_AUTOEXT
-  assert( S3JniGlobal.autoExt.pHead ? ps!=S3JniGlobal.autoExt.psOpening : 1 );
-  S3JniGlobal.autoExt.psOpening = 0;
-#endif
-  if(*ppDb){
+static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc,
+                           S3JniDb * ps, sqlite3 **ppDb,
+                           jobject jOut, int theRc){
+  int rc = 0;
+  jc->pdbOpening = 0;
+  if( *ppDb ){
     assert(ps->jDb);
-#if S3JNI_ENABLE_AUTOEXT
-    //MARKER(("*autoExt.pHead=%p, ppDb=%p, ps->pDb=%p\n", S3JniGlobal.autoExt.pHead, *ppDb, ps->pDb));
-    // invalid when an autoext triggers another open():
-    // assert( S3JniGlobal.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb );
-#endif
-    ps->pDb = *ppDb;
-    NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3);
+    if( 0==ps->pDb ){
+      ps->pDb = *ppDb;
+      NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, *ppDb);
+    }else{
+      assert( ps->pDb==*ppDb
+              && "Set up via s3jni_run_java_auto_extensions()" );
+    }
+    rc = sqlite3_set_clientdata(ps->pDb, S3JniDb_clientdata_key,
+                                ps, S3JniDb_xDestroy)
+      /* As of here, the Java/C connection is complete */;
   }else{
     S3JniDb_set_aside(ps);
     ps = 0;
   }
-  OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0);
-  return theRc;
+  OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3),
+                        jOut, ps ? ps->jDb : 0);
+  return theRc ? theRc : rc;
 }
 
-JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){
+S3JniApi(sqlite3_open(),jint,1open)(
+  JniArgsEnvClass, jstring strName, jobject jOut
+){
   sqlite3 * pOut = 0;
   char *zName = 0;
-  jobject jDb = 0;
   S3JniDb * ps = 0;
-  S3JniEnvCache * jc = 0;
-  S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening;
-  int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb);
+  S3JniEnv * jc = 0;
+  int rc;
+
+  if( 0==jOut ) return SQLITE_MISUSE;
+  rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
   if( 0==rc ){
-    rc = sqlite3_open(zName, &pOut);
-    //MARKER(("env=%p, *env=%p\n", env, *env));
-    //MARKER(("open() ps@%p db@%p\n", ps, pOut));
-    rc = s3jni_open_post(env, ps, &pOut, jOut, rc);
+    rc = s3jni_open_post(env, jc, ps, &pOut, jOut,
+                         sqlite3_open(zName, &pOut));
     assert(rc==0 ? pOut!=0 : 1);
     sqlite3_free(zName);
   }
-  S3JniGlobal.autoExt.psOpening = prevOpening;
   return (jint)rc;
 }
 
-JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName,
-                      jobject jOut, jint flags, jstring strVfs){
+S3JniApi(sqlite3_open_v2(),jint,1open_1v2)(
+  JniArgsEnvClass, jstring strName,
+  jobject jOut, jint flags, jstring strVfs
+){
   sqlite3 * pOut = 0;
   char *zName = 0;
-  jobject jDb = 0;
   S3JniDb * ps = 0;
-  S3JniEnvCache * jc = 0;
+  S3JniEnv * jc = 0;
   char *zVfs = 0;
-  S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening;
-  int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb);
-  if( 0==rc && strVfs ){
-    zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0);
-    if( !zVfs ){
-      rc = SQLITE_NOMEM;
-    }
-  }
+  int rc;
+
+  if( 0==jOut ) return SQLITE_MISUSE;
+  rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
   if( 0==rc ){
-    rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs);
+    if( strVfs ){
+      zVfs = s3jni_jstring_to_utf8( strVfs, 0);
+      if( !zVfs ){
+        rc = SQLITE_NOMEM;
+      }
+    }
+    if( 0==rc ){
+      rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs);
+    }
+    rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc);
   }
-  //MARKER(("open_v2() ps@%p db@%p\n", ps, pOut));
-  /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n",
-    zName, zVfs, pOut, (int)flags, nrc));*/
-  rc = s3jni_open_post(env, ps, &pOut, jOut, rc);
   assert(rc==0 ? pOut!=0 : 1);
   sqlite3_free(zName);
   sqlite3_free(zVfs);
-  S3JniGlobal.autoExt.psOpening = prevOpening;
   return (jint)rc;
 }
 
 /* Proxy for the sqlite3_prepare[_v2/3]() family. */
-static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass self,
-                                     jobject jDb, jbyteArray baSql,
-                                     jint nMax, jint prepFlags,
-                                     jobject jOutStmt, jobject outTail){
+jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self,
+                               jlong jpDb, jbyteArray baSql,
+                               jint nMax, jint prepFlags,
+                               jobject jOutStmt, jobject outTail){
   sqlite3_stmt * pStmt = 0;
   jobject jStmt = 0;
   const char * zTail = 0;
-  jbyte * const pBuf = JBA_TOC(baSql);
+  sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb);
+  jbyte * const pBuf = pDb ? s3jni_jbyteArray_bytes(baSql)  : 0;
   int rc = SQLITE_ERROR;
+
   assert(prepVersion==1 || prepVersion==2 || prepVersion==3);
-  if( !pBuf ){
-     rc = baSql ? SQLITE_MISUSE : SQLITE_NOMEM;
-     goto end;
+  if( !pDb || !jOutStmt ){
+    rc = SQLITE_MISUSE;
+    goto end;
+  }else if( !pBuf ){
+    rc = baSql ? SQLITE_NOMEM : SQLITE_MISUSE;
+    goto end;
   }
-  jStmt = new_sqlite3_stmt_wrapper(env, 0);
+  jStmt = new_java_sqlite3_stmt(env, 0);
   if( !jStmt ){
     rc = SQLITE_NOMEM;
     goto end;
   }
   switch( prepVersion ){
-    case 1: rc = sqlite3_prepare(PtrGet_sqlite3(jDb), (const char *)pBuf,
+    case 1: rc = sqlite3_prepare(pDb, (const char *)pBuf,
                                  (int)nMax, &pStmt, &zTail);
       break;
-    case 2: rc = sqlite3_prepare_v2(PtrGet_sqlite3(jDb), (const char *)pBuf,
+    case 2: rc = sqlite3_prepare_v2(pDb, (const char *)pBuf,
                                     (int)nMax, &pStmt, &zTail);
       break;
-    case 3: rc = sqlite3_prepare_v3(PtrGet_sqlite3(jDb), (const char *)pBuf,
+    case 3: rc = sqlite3_prepare_v3(pDb, (const char *)pBuf,
                                     (int)nMax, (unsigned int)prepFlags,
                                     &pStmt, &zTail);
       break;
     default:
-      assert(0 && "Invalid prepare() version");
+      assert(!"Invalid prepare() version");
   }
 end:
-  JBA_RELEASE(baSql,pBuf);
+  s3jni_jbyteArray_release(baSql,pBuf);
   if( 0==rc ){
     if( 0!=outTail ){
-      /* Noting that pBuf is deallocated now but its address is all we need. */
+      /* Noting that pBuf is deallocated now but its address is all we need for
+      ** what follows... */
       assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1);
       assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1);
-      OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0));
+      OutputPointer_set_Int32(
+        env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)
+      );
     }
     if( pStmt ){
-      NativePointerHolder_set(env, jStmt, pStmt, S3JniClassNames.sqlite3_stmt);
+      NativePointerHolder_set(S3JniNph(sqlite3_stmt), jStmt, pStmt);
     }else{
-      /* Happens for comments and whitespace */
-      UNREF_L(jStmt);
+      /* Happens for comments and whitespace. */
+      S3JniUnrefLocal(jStmt);
       jStmt = 0;
     }
   }else{
-    UNREF_L(jStmt);
+    S3JniUnrefLocal(jStmt);
     jStmt = 0;
   }
-#if 0
-  if( 0!=rc ){
-    MARKER(("prepare rc = %d\n", rc));
+  if( jOutStmt ){
+    OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_stmt),
+                          jOutStmt, jStmt);
   }
-#endif
-  OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt);
   return (jint)rc;
 }
-JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql,
-                     jint nMax, jobject jOutStmt, jobject outTail){
-  return sqlite3_jni_prepare_v123(1, env, self, jDb, baSql, nMax, 0,
+S3JniApi(sqlite3_prepare(),jint,1prepare)(
+  JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+                     jint nMax, jobject jOutStmt, jobject outTail
+){
+  return sqlite3_jni_prepare_v123(1, env, self, jpDb, baSql, nMax, 0,
                                   jOutStmt, outTail);
 }
-JDECL(jint,1prepare_1v2)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql,
-                         jint nMax, jobject jOutStmt, jobject outTail){
-  return sqlite3_jni_prepare_v123(2, env, self, jDb, baSql, nMax, 0,
+S3JniApi(sqlite3_prepare_v2(),jint,1prepare_1v2)(
+  JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+                         jint nMax, jobject jOutStmt, jobject outTail
+){
+  return sqlite3_jni_prepare_v123(2, env, self, jpDb, baSql, nMax, 0,
                                   jOutStmt, outTail);
 }
-JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql,
-                         jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){
-  return sqlite3_jni_prepare_v123(3, env, self, jDb, baSql, nMax,
+S3JniApi(sqlite3_prepare_v3(),jint,1prepare_1v3)(
+  JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+                         jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail
+){
+  return sqlite3_jni_prepare_v123(3, env, self, jpDb, baSql, nMax,
                                   prepFlags, jOutStmt, outTail);
 }
 
+/*
+** Impl for C-to-Java of the callbacks for both sqlite3_update_hook()
+** and sqlite3_preupdate_hook().  The differences are that for
+** update_hook():
+**
+** - pDb is NULL
+** - iKey1 is the row ID
+** - iKey2 is unused
+*/
+static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId,
+                                      const char *zDb, const char *zTable,
+                                      sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+  S3JniDb * const ps = pState;
+  S3JniDeclLocal_env;
+  jstring jDbName;
+  jstring jTable;
+  const int isPre = 0!=pDb;
+  S3JniHook hook;
 
+  S3JniHook_localdup(isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+                 &ps->hooks.preUpdate
+#else
+                 &S3JniHook_empty
+#endif
+                 : &ps->hooks.update, &hook);
+  if( !hook.jObj ){
+    return;
+  }
+  jDbName  = s3jni_utf8_to_jstring( zDb, -1);
+  jTable = jDbName ? s3jni_utf8_to_jstring( zTable, -1) : 0;
+  S3JniIfThrew {
+    S3JniExceptionClear;
+    s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
+  }else{
+    assert( hook.jObj );
+    assert( hook.midCallback );
+    assert( ps->jDb );
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    if( isPre ) (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+                                       ps->jDb, (jint)opId, jDbName, jTable,
+                                       (jlong)iKey1, (jlong)iKey2);
+    else
+#endif
+    (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+                           (jint)opId, jDbName, jTable, (jlong)iKey1);
+    S3JniIfThrew{
+      S3JniExceptionWarnCallbackThrew("sqlite3_(pre)update_hook() callback");
+      s3jni_db_exception(ps->pDb, 0,
+                         "sqlite3_(pre)update_hook() callback threw");
+    }
+  }
+  S3JniUnrefLocal(jDbName);
+  S3JniUnrefLocal(jTable);
+  S3JniHook_localundup(hook);
+}
+
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+static void s3jni_preupdate_hook_impl(void * pState, sqlite3 *pDb, int opId,
+                                      const char *zDb, const char *zTable,
+                                      sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+  return s3jni_updatepre_hook_impl(pState, pDb, opId, zDb, zTable,
+                                   iKey1, iKey2);
+}
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+
+static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
+                                   const char *zTable, sqlite3_int64 nRowid){
+  return s3jni_updatepre_hook_impl(pState, NULL, opId, zDb, zTable, nRowid, 0);
+}
+
+#if !defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+/* We need no-op impls for preupdate_{count,depth,blobwrite}() */
+S3JniApi(sqlite3_preupdate_blobwrite(),int,1preupdate_1blobwrite)(
+  JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+S3JniApi(sqlite3_preupdate_count(),int,1preupdate_1count)(
+  JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+S3JniApi(sqlite3_preupdate_depth(),int,1preupdate_1depth)(
+  JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+#endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */
+
+/*
+** JNI wrapper for both sqlite3_update_hook() and
+** sqlite3_preupdate_hook() (if isPre is true).
+*/
+static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jlong jpDb, jobject jHook){
+  S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+  jclass klazz;
+  jobject pOld = 0;
+  jmethodID xCallback;
+  S3JniHook * pHook;
+
+  if( !ps ) return 0;
+  S3JniDb_mutex_enter;
+  pHook = isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    &ps->hooks.preUpdate
+#else
+    0
+#endif
+    : &ps->hooks.update;
+  if( !pHook ){
+    goto end;
+  }
+  pOld = pHook->jObj;
+  if( pOld && jHook && (*env)->IsSameObject(env, pOld, jHook) ){
+    goto end;
+  }
+  if( !jHook ){
+    if( pOld ){
+      jobject tmp = S3JniRefLocal(pOld);
+      S3JniUnrefGlobal(pOld);
+      pOld = tmp;
+    }
+    *pHook = S3JniHook_empty;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    if( isPre ) sqlite3_preupdate_hook(ps->pDb, 0, 0);
+    else
+#endif
+    sqlite3_update_hook(ps->pDb, 0, 0);
+    goto end;
+  }
+  klazz = (*env)->GetObjectClass(env, jHook);
+  xCallback = isPre
+    ? (*env)->GetMethodID(env, klazz, "call",
+                          "(Lorg/sqlite/jni/capi/sqlite3;"
+                          "I"
+                          "Ljava/lang/String;"
+                          "Ljava/lang/String;"
+                          "JJ)V")
+    : (*env)->GetMethodID(env, klazz, "call",
+                          "(ILjava/lang/String;Ljava/lang/String;J)V");
+  S3JniUnrefLocal(klazz);
+  S3JniIfThrew {
+    S3JniExceptionClear;
+    s3jni_db_error(ps->pDb, SQLITE_ERROR,
+                   "Cannot not find matching callback on "
+                   "(pre)update hook object.");
+  }else{
+    pHook->midCallback = xCallback;
+    pHook->jObj = S3JniRefGlobal(jHook);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    if( isPre ) sqlite3_preupdate_hook(ps->pDb, s3jni_preupdate_hook_impl, ps);
+    else
+#endif
+    sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps);
+    if( pOld ){
+      jobject tmp = S3JniRefLocal(pOld);
+      S3JniUnrefGlobal(pOld);
+      pOld = tmp;
+    }
+  }
+end:
+  S3JniDb_mutex_leave;
+  return pOld;
+}
+
+
+S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)(
+  JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+  return s3jni_updatepre_hook(env, 1, jpDb, jHook);
+#else
+  return NULL;
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+}
+
+/* Impl for sqlite3_preupdate_{new,old}(). */
+static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jlong jpDb,
+                                  jint iCol, jobject jOut){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+  sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb);
+  int rc = SQLITE_MISUSE;
+  if( pDb ){
+    sqlite3_value * pOut = 0;
+    int (*fOrig)(sqlite3*,int,sqlite3_value**) =
+      isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old;
+    rc = fOrig(pDb, (int)iCol, &pOut);
+    if( 0==rc ){
+      jobject pWrap = new_java_sqlite3_value(env, pOut);
+      if( !pWrap ){
+        rc = SQLITE_NOMEM;
+      }
+      OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_value),
+                            jOut, pWrap);
+      S3JniUnrefLocal(pWrap);
+    }
+  }
+  return rc;
+#else
+  return SQLITE_MISUSE;
+#endif
+}
+
+S3JniApi(sqlite3_preupdate_new(),jint,1preupdate_1new)(
+  JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut
+){
+  return s3jni_preupdate_newold(env, 1, jpDb, iCol, jOut);
+}
+
+S3JniApi(sqlite3_preupdate_old(),jint,1preupdate_1old)(
+  JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut
+){
+  return s3jni_preupdate_newold(env, 0, jpDb, iCol, jOut);
+}
+
+
+/* Central C-to-Java sqlite3_progress_handler() proxy. */
 static int s3jni_progress_handler_impl(void *pP){
   S3JniDb * const ps = (S3JniDb *)pP;
-  JNIEnv * const env = ps->env;
-  int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj,
-                                      ps->progress.midCallback);
-  IFTHREW{
-    rc = s3jni_db_exception(env, ps, rc,
-                            "sqlite3_progress_handler() callback threw");
-  }
-  return rc;
-}
-
-JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){
-  S3JniDb * ps = S3JniDb_for_db(env, jDb, 0, 0);
-  jclass klazz;
-  jmethodID xCallback;
-  if( n<1 || !jProgress ){
-    if(ps){
-      UNREF_G(ps->progress.jObj);
-      memset(&ps->progress, 0, sizeof(ps->progress));
-    }
-    sqlite3_progress_handler(ps->pDb, 0, 0, 0);
-    return;
-  }
-  if(!ps){
-    s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
-    return;
-  }
-  klazz = (*env)->GetObjectClass(env, jProgress);
-  xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I");
-  IFTHREW {
-    EXCEPTION_CLEAR;
-    s3jni_db_error(ps->pDb, SQLITE_ERROR,
-                   "Cannot not find matching xCallback() on "
-                   "ProgressHandler object.");
-  }else{
-    UNREF_G(ps->progress.jObj);
-    ps->progress.midCallback = xCallback;
-    ps->progress.jObj = REF_G(jProgress);
-    sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps);
-  }
-}
-
-
-JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){
   int rc = 0;
-  sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
-  if( pStmt ){
-    rc = sqlite3_reset(pStmt);
+  S3JniDeclLocal_env;
+  S3JniHook hook;
+
+  S3JniHook_localdup(&ps->hooks.progress, &hook);
+  if( hook.jObj ){
+    rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback);
+    S3JniIfThrew{
+      rc = s3jni_db_exception(ps->pDb, rc,
+                              "sqlite3_progress_handler() callback threw");
+    }
+    S3JniHook_localundup(hook);
   }
   return rc;
 }
 
-#if S3JNI_ENABLE_AUTOEXT
-JDECL(void,1reset_1auto_1extension)(JENV_CSELF){
-  if(!S3JniGlobal.autoExt.isRunning){
-    while( S3JniGlobal.autoExt.pHead ){
-      S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead);
+S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)(
+  JniArgsEnvClass,jobject jDb, jint n, jobject jProgress
+){
+  S3JniDb * const ps = S3JniDb_from_java(jDb);
+  S3JniHook * const pHook = ps ? &ps->hooks.progress : 0;
+
+  if( !ps ) return;
+  S3JniDb_mutex_enter;
+  if( n<1 || !jProgress ){
+    S3JniHook_unref(pHook);
+    sqlite3_progress_handler(ps->pDb, 0, 0, 0);
+  }else{
+    jclass const klazz = (*env)->GetObjectClass(env, jProgress);
+    jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call", "()I");
+    S3JniUnrefLocal(klazz);
+    S3JniIfThrew {
+      S3JniExceptionClear;
+      s3jni_db_error(ps->pDb, SQLITE_ERROR,
+                     "Cannot not find matching xCallback() on "
+                     "ProgressHandler object.");
+    }else{
+      S3JniUnrefGlobal(pHook->jObj);
+      pHook->midCallback = xCallback;
+      pHook->jObj = S3JniRefGlobal(jProgress);
+      sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps);
     }
   }
+  S3JniDb_mutex_leave;
 }
-#endif /* S3JNI_ENABLE_AUTOEXT */
 
-/* sqlite3_result_text/blob() and friends. */
-static void result_blob_text(int asBlob, int as64,
-                             int eTextRep/*only for (asBlob=0)*/,
+S3JniApi(sqlite3_randomness(),void,1randomness)(
+  JniArgsEnvClass, jbyteArray jTgt
+){
+  jbyte * const jba = s3jni_jbyteArray_bytes(jTgt);
+  if( jba ){
+    jsize const nTgt = (*env)->GetArrayLength(env, jTgt);
+    sqlite3_randomness( (int)nTgt, jba );
+    s3jni_jbyteArray_commit(jTgt, jba);
+  }
+}
+
+
+S3JniApi(sqlite3_reset(),jint,1reset)(
+  JniArgsEnvClass, jobject jpStmt
+){
+  sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+  return pStmt ? sqlite3_reset(pStmt) : SQLITE_MISUSE;
+}
+
+/* Clears all entries from S3JniGlobal.autoExt. */
+static void s3jni_reset_auto_extension(JNIEnv *env){
+  int i;
+  S3JniAutoExt_mutex_enter;
+  for( i = 0; i < SJG.autoExt.nExt; ++i ){
+    S3JniAutoExtension_clear( &SJG.autoExt.aExt[i] );
+  }
+  SJG.autoExt.nExt = 0;
+  S3JniAutoExt_mutex_leave;
+}
+
+S3JniApi(sqlite3_reset_auto_extension(),void,1reset_1auto_1extension)(
+  JniArgsEnvClass
+){
+  s3jni_reset_auto_extension(env);
+}
+
+/* Impl for sqlite3_result_text/blob() and friends. */
+static void result_blob_text(int as64     /* true for text64/blob64() mode */,
+                             int eTextRep /* 0 for blobs, else SQLITE_UTF... */,
                              JNIEnv * const env, sqlite3_context *pCx,
                              jbyteArray jBa, jlong nMax){
-  if(jBa){
-    jbyte * const pBuf = JBA_TOC(jBa);
-    jsize nBa = (*env)->GetArrayLength(env, jBa);
-    if( nMax>=0 && nBa>(jsize)nMax ){
-      nBa = (jsize)nMax;
+  int const asBlob = 0==eTextRep;
+  if( !pCx ){
+    /* We should arguably emit a warning here. But where to log it? */
+    return;
+  }else if( jBa ){
+    jbyte * const pBuf = s3jni_jbyteArray_bytes(jBa);
+    jsize nBA = (*env)->GetArrayLength(env, jBa);
+    if( nMax>=0 && nBA>(jsize)nMax ){
+      nBA = (jsize)nMax;
       /**
          From the sqlite docs:
 
@@ -2902,24 +4211,23 @@ static void result_blob_text(int asBlob, int as64,
          Note that the text64() interfaces take an unsigned value for
          the length, which Java does not support. This binding takes
          the approach of passing on negative values to the C API,
-         which will, in turn fail with SQLITE_TOOBIG at some later
+         which will in turn fail with SQLITE_TOOBIG at some later
          point (recall that the sqlite3_result_xyz() family do not
          have result values).
       */
     }
-    if(as64){ /* 64-bit... */
+    if( as64 ){ /* 64-bit... */
       static const jsize nLimit64 =
-        SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary!*/
-        /* jsize is int32, not int64! */;
-      if(nBa > nLimit64){
+        SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary*/;
+      if( nBA > nLimit64 ){
         sqlite3_result_error_toobig(pCx);
-      }else if(asBlob){
-        sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBa,
+      }else if( asBlob ){
+        sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBA,
                               SQLITE_TRANSIENT);
       }else{ /* text64... */
-        if(encodingTypeIsValid(eTextRep)){
+        if( encodingTypeIsValid(eTextRep) ){
           sqlite3_result_text64(pCx, (const char *)pBuf,
-                                (sqlite3_uint64)nBa,
+                                (sqlite3_uint64)nBA,
                                 SQLITE_TRANSIENT, eTextRep);
         }else{
           sqlite3_result_error_code(pCx, SQLITE_FORMAT);
@@ -2927,64 +4235,71 @@ static void result_blob_text(int asBlob, int as64,
       }
     }else{ /* 32-bit... */
       static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE;
-      if(nBa > nLimit){
+      if( nBA > nLimit ){
         sqlite3_result_error_toobig(pCx);
-      }else if(asBlob){
-        sqlite3_result_blob(pCx, pBuf, (int)nBa,
+      }else if( asBlob ){
+        sqlite3_result_blob(pCx, pBuf, (int)nBA,
                             SQLITE_TRANSIENT);
       }else{
-        switch(eTextRep){
+        switch( eTextRep ){
           case SQLITE_UTF8:
-            sqlite3_result_text(pCx, (const char *)pBuf, (int)nBa,
+            sqlite3_result_text(pCx, (const char *)pBuf, (int)nBA,
                                 SQLITE_TRANSIENT);
             break;
           case SQLITE_UTF16:
-            sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBa,
+            sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBA,
                                   SQLITE_TRANSIENT);
             break;
           case SQLITE_UTF16LE:
-            sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBa,
+            sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBA,
                                     SQLITE_TRANSIENT);
             break;
           case SQLITE_UTF16BE:
-            sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBa,
+            sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBA,
                                     SQLITE_TRANSIENT);
             break;
         }
       }
-      JBA_RELEASE(jBa, pBuf);
+      s3jni_jbyteArray_release(jBa, pBuf);
     }
   }else{
     sqlite3_result_null(pCx);
   }
 }
 
-JDECL(void,1result_1blob)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){
-  return result_blob_text(1, 0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+S3JniApi(sqlite3_result_blob(),void,1result_1blob)(
+  JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax
+){
+  return result_blob_text(0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
 }
 
-JDECL(void,1result_1blob64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax){
-  return result_blob_text(1, 1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+S3JniApi(sqlite3_result_blob64(),void,1result_1blob64)(
+  JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax
+){
+  return result_blob_text(1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
 }
 
-JDECL(void,1result_1double)(JENV_CSELF, jobject jpCx, jdouble v){
+S3JniApi(sqlite3_result_double(),void,1result_1double)(
+  JniArgsEnvClass, jobject jpCx, jdouble v
+){
   sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v);
 }
 
-JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg,
-                           int eTextRep){
+S3JniApi(sqlite3_result_error(),void,1result_1error)(
+  JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, int eTextRep
+){
   const char * zUnspecified = "Unspecified error.";
   jsize const baLen = (*env)->GetArrayLength(env, baMsg);
-  jbyte * const pjBuf = baMsg ? JBA_TOC(baMsg) : NULL;
-  switch(pjBuf ? eTextRep : SQLITE_UTF8){
+  jbyte * const pjBuf = baMsg ? s3jni_jbyteArray_bytes(baMsg) : NULL;
+  switch( pjBuf ? eTextRep : SQLITE_UTF8 ){
     case SQLITE_UTF8: {
       const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified;
-      sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen);
+      int const n = pjBuf ? (int)baLen : (int)sqlite3Strlen30(zMsg);
+      sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, n);
       break;
     }
     case SQLITE_UTF16: {
-      const void *zMsg = pjBuf
-        ? (const void *)pjBuf : (const void *)zUnspecified;
+      const void *zMsg = pjBuf;
       sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen);
       break;
     }
@@ -2994,35 +4309,49 @@ JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg,
                            "to sqlite3_result_error().", -1);
       break;
   }
-  JBA_RELEASE(baMsg,pjBuf);
+  s3jni_jbyteArray_release(baMsg,pjBuf);
 }
 
-JDECL(void,1result_1error_1code)(JENV_CSELF, jobject jpCx, jint v){
-  sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), v ? (int)v : SQLITE_ERROR);
+S3JniApi(sqlite3_result_error_code(),void,1result_1error_1code)(
+  JniArgsEnvClass, jobject jpCx, jint v
+){
+  sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), (int)v);
 }
 
-JDECL(void,1result_1error_1nomem)(JENV_CSELF, jobject jpCx){
+S3JniApi(sqlite3_result_error_nomem(),void,1result_1error_1nomem)(
+  JniArgsEnvClass, jobject jpCx
+){
   sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
 }
 
-JDECL(void,1result_1error_1toobig)(JENV_CSELF, jobject jpCx){
+S3JniApi(sqlite3_result_error_toobig(),void,1result_1error_1toobig)(
+  JniArgsEnvClass, jobject jpCx
+){
   sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx));
 }
 
-JDECL(void,1result_1int)(JENV_CSELF, jobject jpCx, jint v){
+S3JniApi(sqlite3_result_int(),void,1result_1int)(
+  JniArgsEnvClass, jobject jpCx, jint v
+){
   sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v);
 }
 
-JDECL(void,1result_1int64)(JENV_CSELF, jobject jpCx, jlong v){
+S3JniApi(sqlite3_result_int64(),void,1result_1int64)(
+  JniArgsEnvClass, jobject jpCx, jlong v
+){
   sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v);
 }
 
-JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){
-  if(v){
-    ResultJavaVal * const rjv = ResultJavaVal_alloc(env, v);
-    if(rjv){
-      sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv, RESULT_JAVA_VAL_STRING,
-                             ResultJavaVal_finalizer);
+S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)(
+  JniArgsEnvClass, jobject jpCx, jobject v
+){
+  sqlite3_context * pCx = PtrGet_sqlite3_context(jpCx);
+  if( !pCx ) return;
+  else if( v ){
+    jobject const rjv = S3JniRefGlobal(v);
+    if( rjv ){
+      sqlite3_result_pointer(pCx, rjv,
+                             ResultJavaValuePtrStr, S3Jni_jobject_finalizer);
     }else{
       sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
     }
@@ -3031,108 +4360,206 @@ JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){
   }
 }
 
-JDECL(void,1result_1null)(JENV_CSELF, jobject jpCx){
+S3JniApi(sqlite3_result_null(),void,1result_1null)(
+  JniArgsEnvClass, jobject jpCx
+){
   sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
 }
 
-JDECL(void,1result_1text)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){
-  return result_blob_text(0, 0, SQLITE_UTF8, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+S3JniApi(sqlite3_result_text(),void,1result_1text)(
+  JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax
+){
+  return result_blob_text(0, SQLITE_UTF8, env,
+                          PtrGet_sqlite3_context(jpCx), jBa, nMax);
 }
 
-JDECL(void,1result_1text64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax,
-                            jint eTextRep){
-  return result_blob_text(0, 1, eTextRep, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+S3JniApi(sqlite3_result_text64(),void,1result_1text64)(
+  JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax,
+                            jint eTextRep
+){
+  return result_blob_text(1, eTextRep, env,
+                          PtrGet_sqlite3_context(jpCx), jBa, nMax);
 }
 
-JDECL(void,1result_1value)(JENV_CSELF, jobject jpCx, jobject jpSVal){
-  sqlite3_result_value(PtrGet_sqlite3_context(jpCx), PtrGet_sqlite3_value(jpSVal));
+S3JniApi(sqlite3_result_value(),void,1result_1value)(
+  JniArgsEnvClass, jobject jpCx, jobject jpSVal
+){
+  sqlite3_result_value(PtrGet_sqlite3_context(jpCx),
+                       PtrGet_sqlite3_value(jpSVal));
 }
 
-JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){
+S3JniApi(sqlite3_result_zeroblob(),void,1result_1zeroblob)(
+  JniArgsEnvClass, jobject jpCx, jint v
+){
   sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v);
 }
 
-JDECL(jint,1result_1zeroblob64)(JENV_CSELF, jobject jpCx, jlong v){
-  return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v);
+S3JniApi(sqlite3_result_zeroblob64(),jint,1result_1zeroblob64)(
+  JniArgsEnvClass, jobject jpCx, jlong v
+){
+  return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx),
+                                         (sqlite3_int64)v);
 }
 
-JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){
-  return s3jni_commit_rollback_hook(0, env, jDb, jHook);
+S3JniApi(sqlite3_rollback_hook(),jobject,1rollback_1hook)(
+  JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+  return s3jni_commit_rollback_hook(0, env, jpDb, jHook);
 }
 
-/* sqlite3_set_authorizer() callback proxy. */
-static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
-                       const char*z2,const char*z3){
+/* Callback for sqlite3_set_authorizer(). */
+int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
+                const char*z2,const char*z3){
   S3JniDb * const ps = pState;
-  JNIEnv * const env = ps->env;
-  jstring const s0 = z0 ? (*env)->NewStringUTF(env, z0) : 0;
-  jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0;
-  jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0;
-  jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0;
-  S3JniHook const * const pHook = &ps->authHook;
-  int rc;
+  S3JniDeclLocal_env;
+  S3JniHook hook;
+  int rc = 0;
 
-  assert( pHook->jObj );
-  rc = (*env)->CallIntMethod(env, pHook->jObj, pHook->midCallback, (jint)op,
-                             s0, s1, s3, s3);
-  IFTHREW{
-    EXCEPTION_WARN_CALLBACK_THREW("sqlite3_set_authorizer() callback");
-    EXCEPTION_CLEAR;
+  S3JniHook_localdup(&ps->hooks.auth, &hook );
+  if( hook.jObj ){
+    jstring const s0 = z0 ? s3jni_utf8_to_jstring( z0, -1) : 0;
+    jstring const s1 = z1 ? s3jni_utf8_to_jstring( z1, -1) : 0;
+    jstring const s2 = z2 ? s3jni_utf8_to_jstring( z2, -1) : 0;
+    jstring const s3 = z3 ? s3jni_utf8_to_jstring( z3, -1) : 0;
+
+    rc = (*env)->CallIntMethod(env, hook.jObj, hook.midCallback, (jint)op,
+                               s0, s1, s3, s3);
+    S3JniIfThrew{
+      rc = s3jni_db_exception(ps->pDb, rc, "sqlite3_set_authorizer() callback");
+    }
+    S3JniUnrefLocal(s0);
+    S3JniUnrefLocal(s1);
+    S3JniUnrefLocal(s2);
+    S3JniUnrefLocal(s3);
+    S3JniHook_localundup(hook);
   }
-  UNREF_L(s0);
-  UNREF_L(s1);
-  UNREF_L(s2);
-  UNREF_L(s3);
   return rc;
 }
 
-JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
-  S3JniHook * const pHook = ps ? &ps->authHook : 0;
+S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)(
+  JniArgsEnvClass,jobject jDb, jobject jHook
+){
+  S3JniDb * const ps = S3JniDb_from_java(jDb);
+  S3JniHook * const pHook = ps ? &ps->hooks.auth : 0;
+  int rc = 0;
 
   if( !ps ) return SQLITE_MISUSE;
-  else if( !jHook ){
-    S3JniHook_unref(env, pHook, 0);
-    return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 );
+  S3JniDb_mutex_enter;
+  if( !jHook ){
+    S3JniHook_unref(pHook);
+    rc = sqlite3_set_authorizer( ps->pDb, 0, 0 );
   }else{
-    int rc = 0;
+    jclass klazz;
     if( pHook->jObj ){
       if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){
       /* Same object - this is a no-op. */
+        S3JniDb_mutex_leave;
         return 0;
       }
-      S3JniHook_unref(env, pHook, 0);
+      S3JniHook_unref(pHook);
     }
-    pHook->jObj = REF_G(jHook);
-    pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook));
-    pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz,
-                                             "xAuth",
+    pHook->jObj = S3JniRefGlobal(jHook);
+    klazz = (*env)->GetObjectClass(env, jHook);
+    pHook->midCallback = (*env)->GetMethodID(env, klazz,
+                                             "call",
                                              "(I"
                                              "Ljava/lang/String;"
                                              "Ljava/lang/String;"
                                              "Ljava/lang/String;"
                                              "Ljava/lang/String;"
                                              ")I");
-    IFTHREW {
-      S3JniHook_unref(env, pHook, 0);
-      return s3jni_db_error(ps->pDb, SQLITE_ERROR,
-                            "Error setting up Java parts of authorizer hook.");
+    S3JniUnrefLocal(klazz);
+    S3JniIfThrew {
+      rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+                          "Error setting up Java parts of authorizer hook.");
+    }else{
+      rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps);
     }
-    rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps);
-    if( rc ) S3JniHook_unref(env, pHook, 0);
-    return rc;
+    if( rc ) S3JniHook_unref(pHook);
   }
+  S3JniDb_mutex_leave;
+  return rc;
 }
 
+S3JniApi(sqlite3_set_auxdata(),void,1set_1auxdata)(
+  JniArgsEnvClass, jobject jCx, jint n, jobject jAux
+){
+  sqlite3_set_auxdata(PtrGet_sqlite3_context(jCx), (int)n,
+                      S3JniRefGlobal(jAux), S3Jni_jobject_finalizer);
+}
 
-JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){
-  sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb),
+S3JniApi(sqlite3_set_last_insert_rowid(),void,1set_1last_1insert_1rowid)(
+  JniArgsEnvClass, jobject jpDb, jlong rowId
+){
+  sqlite3_set_last_insert_rowid(PtrGet_sqlite3(jpDb),
                                 (sqlite3_int64)rowId);
 }
 
-FIXME_THREADING(nphCache)
-JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh,
-                    jboolean reset ){
+S3JniApi(sqlite3_shutdown(),jint,1shutdown)(
+  JniArgsEnvClass
+){
+  s3jni_reset_auto_extension(env);
+#ifdef SQLITE_ENABLE_SQLLOG
+  S3JniHook_unref(&SJG.hook.sqllog);
+#endif
+  S3JniHook_unref(&SJG.hook.configlog);
+  /* Free up S3JniDb recycling bin. */
+  S3JniDb_mutex_enter; {
+    while( S3JniGlobal.perDb.aFree ){
+      S3JniDb * const d = S3JniGlobal.perDb.aFree;
+      S3JniGlobal.perDb.aFree = d->pNext;
+      S3JniDb_clear(env, d);
+      sqlite3_free(d);
+    }
+  } S3JniDb_mutex_leave;
+  S3JniGlobal_mutex_enter; {
+    /* Free up S3JniUdf recycling bin. */
+    while( S3JniGlobal.udf.aFree ){
+      S3JniUdf * const u = S3JniGlobal.udf.aFree;
+      S3JniGlobal.udf.aFree = u->pNext;
+      u->pNext = 0;
+      S3JniUdf_free(env, u, 0);
+    }
+  } S3JniGlobal_mutex_leave;
+  S3JniHook_mutex_enter; {
+    /* Free up S3JniHook recycling bin. */
+    while( S3JniGlobal.hook.aFree ){
+      S3JniHook * const u = S3JniGlobal.hook.aFree;
+      S3JniGlobal.hook.aFree = u->pNext;
+      u->pNext = 0;
+      assert( !u->doXDestroy );
+      assert( !u->jObj );
+      assert( !u->jExtra );
+      sqlite3_free( u );
+    }
+  } S3JniHook_mutex_leave;
+  /* Free up env cache. */
+  S3JniEnv_mutex_enter; {
+    while( SJG.envCache.aHead ){
+      S3JniEnv_uncache( SJG.envCache.aHead->env );
+    }
+  } S3JniEnv_mutex_leave;
+#if 0
+  /*
+  ** Is automatically closing any still-open dbs a good idea? We will
+  ** get rid of the perDb list once sqlite3 gets a per-db client
+  ** state, at which point we won't have a central list of databases
+  ** to close.
+  */
+  S3JniDb_mutex_enter;
+  while( SJG.perDb.pHead ){
+    s3jni_close_db(env, SJG.perDb.pHead->jDb, 2);
+  }
+  S3JniDb_mutex_leave;
+#endif
+  /* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */
+  return sqlite3_shutdown();
+}
+
+S3JniApi(sqlite3_status(),jint,1status)(
+  JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh,
+                    jboolean reset
+){
   int iCur = 0, iHigh = 0;
   int rc = sqlite3_status( op, &iCur, &iHigh, reset );
   if( 0==rc ){
@@ -3142,9 +4569,10 @@ JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh,
   return (jint)rc;
 }
 
-FIXME_THREADING(nphCache)
-JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh,
-                      jboolean reset ){
+S3JniApi(sqlite3_status64(),jint,1status64)(
+  JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh,
+                      jboolean reset
+){
   sqlite3_int64 iCur = 0, iHigh = 0;
   int rc = sqlite3_status64( op, &iCur, &iHigh, reset );
   if( 0==rc ){
@@ -3154,12 +4582,19 @@ JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh
   return (jint)rc;
 }
 
+S3JniApi(sqlite3_stmt_status(),jint,1stmt_1status)(
+  JniArgsEnvClass, jobject jStmt, jint op, jboolean reset
+){
+  return sqlite3_stmt_status(PtrGet_sqlite3_stmt(jStmt),
+                             (int)op, reset ? 1 : 0);
+}
+
+
 static int s3jni_strlike_glob(int isLike, JNIEnv *const env,
                               jbyteArray baG, jbyteArray baT, jint escLike){
   int rc = 0;
-  jbyte * const pG = JBA_TOC(baG);
-  jbyte * const pT = pG ? JBA_TOC(baT) : 0;
-  OOM_CHECK(pT);
+  jbyte * const pG = s3jni_jbyteArray_bytes(baG);
+  jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0;
 
   /* Note that we're relying on the byte arrays having been
      NUL-terminated on the Java side. */
@@ -3167,296 +4602,328 @@ static int s3jni_strlike_glob(int isLike, JNIEnv *const env,
     ? sqlite3_strlike((const char *)pG, (const char *)pT,
                       (unsigned int)escLike)
     : sqlite3_strglob((const char *)pG, (const char *)pT);
-  JBA_RELEASE(baG, pG);
-  JBA_RELEASE(baT, pT);
+  s3jni_jbyteArray_release(baG, pG);
+  s3jni_jbyteArray_release(baT, pT);
   return rc;
 }
 
-JDECL(jint,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){
+S3JniApi(sqlite3_strglob(),jint,1strglob)(
+  JniArgsEnvClass, jbyteArray baG, jbyteArray baT
+){
   return s3jni_strlike_glob(0, env, baG, baT, 0);
 }
 
-JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){
+S3JniApi(sqlite3_strlike(),jint,1strlike)(
+  JniArgsEnvClass, jbyteArray baG, jbyteArray baT, jint escChar
+){
   return s3jni_strlike_glob(1, env, baG, baT, escChar);
 }
 
-JDECL(jint,1shutdown)(JENV_CSELF){
-  S3JniGlobal_S3JniEnvCache_clear();
-  /* Do not clear S3JniGlobal.jvm: it's legal to call
-     sqlite3_initialize() again to restart the lib. */
-  return sqlite3_shutdown();
-}
-
-JDECL(jstring,1sql)(JENV_CSELF, jobject jpStmt){
+S3JniApi(sqlite3_sql(),jstring,1sql)(
+  JniArgsEnvClass, jobject jpStmt
+){
   sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
   jstring rv = 0;
   if( pStmt ){
     const char * zSql = 0;
-    S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
     zSql = sqlite3_sql(pStmt);
-    rv = s3jni_utf8_to_jstring(jc, zSql, -1);
-    OOM_CHECK(rv);
+    rv = s3jni_utf8_to_jstring( zSql, -1);
   }
   return rv;
 }
 
-JDECL(jint,1step)(JENV_CSELF,jobject jStmt){
-  int rc = SQLITE_MISUSE;
+S3JniApi(sqlite3_step(),jint,1step)(
+  JniArgsEnvClass,jobject jStmt
+){
   sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt);
-  if(pStmt){
-    rc = sqlite3_step(pStmt);
+  return pStmt ? (jint)sqlite3_step(pStmt) : (jint)SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_table_column_metadata(),int,1table_1column_1metadata)(
+  JniArgsEnvClass, jobject jDb, jstring jDbName, jstring jTableName,
+  jstring jColumnName, jobject jDataType, jobject jCollSeq, jobject jNotNull,
+  jobject jPrimaryKey, jobject jAutoinc
+){
+  sqlite3 * const db = PtrGet_sqlite3(jDb);
+  char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
+  const char * pzCollSeq = 0;
+  const char * pzDataType = 0;
+  int pNotNull = 0, pPrimaryKey = 0, pAutoinc = 0;
+  int rc;
+
+  if( !db || !jDbName || !jTableName ) return SQLITE_MISUSE;
+  zDbName = s3jni_jstring_to_utf8(jDbName,0);
+  zTableName = zDbName ? s3jni_jstring_to_utf8(jTableName,0) : 0;
+  zColumnName = (zTableName && jColumnName)
+    ? s3jni_jstring_to_utf8(jColumnName,0) : 0;
+  rc = zTableName
+    ? sqlite3_table_column_metadata(db, zDbName, zTableName,
+                                    zColumnName, &pzDataType, &pzCollSeq,
+                                    &pNotNull, &pPrimaryKey, &pAutoinc)
+    : SQLITE_NOMEM;
+  if( 0==rc ){
+    jstring jseq = jCollSeq
+      ? (pzCollSeq ? s3jni_utf8_to_jstring(pzCollSeq, -1) : 0)
+      : 0;
+    jstring jdtype = jDataType
+      ? (pzDataType ? s3jni_utf8_to_jstring(pzDataType, -1) : 0)
+      : 0;
+    if( (jCollSeq && pzCollSeq && !jseq)
+        || (jDataType && pzDataType && !jdtype) ){
+      rc = SQLITE_NOMEM;
+    }else{
+      if( jNotNull ) OutputPointer_set_Bool(env, jNotNull, pNotNull);
+      if( jPrimaryKey ) OutputPointer_set_Bool(env, jPrimaryKey, pPrimaryKey);
+      if( jAutoinc ) OutputPointer_set_Bool(env, jAutoinc, pAutoinc);
+      if( jCollSeq ) OutputPointer_set_String(env, jCollSeq, jseq);
+      if( jDataType ) OutputPointer_set_String(env, jDataType, jdtype);
+    }
+    S3JniUnrefLocal(jseq);
+    S3JniUnrefLocal(jdtype);
   }
+  sqlite3_free(zDbName);
+  sqlite3_free(zTableName);
+  sqlite3_free(zColumnName);
   return rc;
 }
 
 static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
   S3JniDb * const ps = (S3JniDb *)pC;
-  JNIEnv * const env = ps->env;
+  S3JniDeclLocal_env;
   jobject jX = NULL  /* the tracer's X arg */;
   jobject jP = NULL  /* the tracer's P arg */;
   jobject jPUnref = NULL /* potentially a local ref to jP */;
-  S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
-  int rc;
-  int createStmt = 0;
-  switch(traceflag){
+  int rc = 0;
+  S3JniHook hook;
+
+  S3JniHook_localdup(&ps->hooks.trace, &hook );
+  if( !hook.jObj ){
+    return 0;
+  }
+  switch( traceflag ){
     case SQLITE_TRACE_STMT:
-      jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1);
-      if(!jX) return SQLITE_NOMEM;
-      /*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/
-      createStmt = 1;
+      jX = s3jni_utf8_to_jstring( (const char *)pX, -1);
+      if( !jX ) rc = SQLITE_NOMEM;
       break;
     case SQLITE_TRACE_PROFILE:
-      jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1,
+      jX = (*env)->NewObject(env, SJG.g.cLong, SJG.g.ctorLong1,
                              (jlong)*((sqlite3_int64*)pX));
       // hmm. ^^^ (*pX) really is zero.
       // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX)));
-      if(!jX) return SQLITE_NOMEM;
-      createStmt = 1;
+      s3jni_oom_check( jX );
+      if( !jX ) rc = SQLITE_NOMEM;
       break;
     case SQLITE_TRACE_ROW:
-      createStmt = 1;
       break;
     case SQLITE_TRACE_CLOSE:
-      jP = ps->jDb;
+      jP = jPUnref = S3JniRefLocal(ps->jDb);
       break;
     default:
-      assert(!"cannot happen - unkown trace flag");
-      return SQLITE_ERROR;
+      assert(!"cannot happen - unknown trace flag");
+      rc =  SQLITE_ERROR;
   }
-  if( createStmt ){
-    jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP);
-    if(!jP){
-      UNREF_L(jX);
-      return SQLITE_NOMEM;
+  if( 0==rc ){
+    if( !jP ){
+      /* Create a new temporary sqlite3_stmt wrapper */
+      jP = jPUnref = new_java_sqlite3_stmt(env, pP);
+      if( !jP ){
+        rc = SQLITE_NOMEM;
+      }
+    }
+    if( 0==rc ){
+      assert(jP);
+      rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback,
+                                      (jint)traceflag, jP, jX);
+      S3JniIfThrew{
+        rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+                                "sqlite3_trace_v2() callback threw.");
+      }
     }
   }
-  assert(jP);
-  rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj,
-                                  ps->trace.midCallback,
-                                  (jint)traceflag, jP, jX);
-  IFTHREW{
-    EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback");
-    EXCEPTION_CLEAR;
-    rc = SQLITE_ERROR;
-  }
-  UNREF_L(jPUnref);
-  UNREF_L(jX);
+  S3JniUnrefLocal(jPUnref);
+  S3JniUnrefLocal(jX);
+  S3JniHook_localundup(hook);
   return rc;
 }
 
-JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
-  jclass klazz;
+S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)(
+  JniArgsEnvClass,jobject jDb, jint traceMask, jobject jTracer
+){
+  S3JniDb * const ps = S3JniDb_from_java(jDb);
+  int rc;
+
+  if( !ps ) return SQLITE_MISUSE;
   if( !traceMask || !jTracer ){
-    if(ps){
-      UNREF_G(ps->trace.jObj);
-      memset(&ps->trace, 0, sizeof(ps->trace));
-    }
-    return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0);
-  }
-  if(!ps) return SQLITE_NOMEM;
-  klazz = (*env)->GetObjectClass(env, jTracer);
-  ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback",
-                                              "(ILjava/lang/Object;Ljava/lang/Object;)I");
-  IFTHREW {
-    EXCEPTION_CLEAR;
-    return s3jni_db_error(ps->pDb, SQLITE_ERROR,
-                          "Cannot not find matching xCallback() on Tracer object.");
-  }
-  ps->trace.jObj = REF_G(jTracer);
-  return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps);
-}
-
-static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
-                                   const char *zTable, sqlite3_int64 nRowid){
-  S3JniDb * const ps = pState;
-  JNIEnv * const env = ps->env;
-  /* ACHTUNG: this will break if zDb or zTable contain chars which are
-     different in MUTF-8 than UTF-8. That seems like a low risk,
-     but it's possible. */
-  jstring jDbName;
-  jstring jTable;
-  jDbName  = (*env)->NewStringUTF(env, zDb);
-  jTable = jDbName ? (*env)->NewStringUTF(env, zTable) : 0;
-  IFTHREW {
-    s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
+    S3JniDb_mutex_enter;
+    rc = (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0);
+    S3JniHook_unref(&ps->hooks.trace);
+    S3JniDb_mutex_leave;
   }else{
-    (*env)->CallVoidMethod(env, ps->updateHook.jObj,
-                           ps->updateHook.midCallback,
-                           (jint)opId, jDbName, jTable, (jlong)nRowid);
-    IFTHREW{
-      EXCEPTION_WARN_CALLBACK_THREW("update hook");
-      EXCEPTION_CLEAR;
-      s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw.");
+    jclass const klazz = (*env)->GetObjectClass(env, jTracer);
+    S3JniHook hook = S3JniHook_empty;
+    hook.midCallback = (*env)->GetMethodID(
+      env, klazz, "call", "(ILjava/lang/Object;Ljava/lang/Object;)I"
+    );
+    S3JniUnrefLocal(klazz);
+    S3JniIfThrew {
+      S3JniExceptionClear;
+      rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+                          "Cannot not find matching call() on "
+                          "TracerCallback object.");
+    }else{
+      hook.jObj = S3JniRefGlobal(jTracer);
+      S3JniDb_mutex_enter;
+      rc = sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps);
+      if( 0==rc ){
+        S3JniHook_unref(&ps->hooks.trace);
+        ps->hooks.trace = hook /* transfer ownership of reference */;
+      }else{
+        S3JniHook_unref(&hook);
+      }
+      S3JniDb_mutex_leave;
     }
   }
-  UNREF_L(jDbName);
-  UNREF_L(jTable);
+  return rc;
+}
+
+S3JniApi(sqlite3_txn_state(),jint,1txn_1state)(
+  JniArgsEnvClass,jobject jDb, jstring jSchema
+){
+  sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+  int rc = SQLITE_MISUSE;
+  if( pDb ){
+    char * zSchema = jSchema
+      ? s3jni_jstring_to_utf8(jSchema, 0)
+      : 0;
+    if( !jSchema || (zSchema && jSchema) ){
+      rc = sqlite3_txn_state(pDb, zSchema);
+      sqlite3_free(zSchema);
+    }else{
+      rc = SQLITE_NOMEM;
+    }
+  }
+  return rc;
+}
+
+S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)(
+  JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+  return s3jni_updatepre_hook(env, 0, jpDb, jHook);
 }
 
 
-JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
-  jclass klazz;
-  jobject pOld = 0;
-  jmethodID xCallback;
-  S3JniHook * const pHook = &ps->updateHook;
-  if(!ps){
-    s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0);
-    return 0;
-  }
-  pOld = pHook->jObj;
-  if(pOld && jHook &&
-     (*env)->IsSameObject(env, pOld, jHook)){
-    return pOld;
-  }
-  if( !jHook ){
-    if(pOld){
-      jobject tmp = REF_L(pOld);
-      UNREF_G(pOld);
-      pOld = tmp;
-    }
-    memset(pHook, 0, sizeof(S3JniHook));
-    sqlite3_update_hook(ps->pDb, 0, 0);
-    return pOld;
-  }
-  klazz = (*env)->GetObjectClass(env, jHook);
-  xCallback = (*env)->GetMethodID(env, klazz, "xUpdateHook",
-                                  "(ILjava/lang/String;Ljava/lang/String;J)V");
-  IFTHREW {
-    EXCEPTION_CLEAR;
-    s3jni_db_error(ps->pDb, SQLITE_ERROR,
-                   "Cannot not find matching callback on "
-                   "update hook object.");
-  }else{
-    pHook->midCallback = xCallback;
-    pHook->jObj = REF_G(jHook);
-    sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps);
-    if(pOld){
-      jobject tmp = REF_L(pOld);
-      UNREF_G(pOld);
-      pOld = tmp;
-    }
-  }
-  return pOld;
-}
+S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  const jbyte * pBytes = sv ? sqlite3_value_blob(sv) : 0;
+  int const nLen = pBytes ? sqlite3_value_bytes(sv) : 0;
 
-
-JDECL(jbyteArray,1value_1blob)(JENV_CSELF, jobject jpSVal){
-  sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
-  int const nLen = sqlite3_value_bytes(sv);
-  const jbyte * pBytes = sqlite3_value_blob(sv);
-  jbyteArray const jba = pBytes
-    ? (*env)->NewByteArray(env, (jsize)nLen)
+  s3jni_oom_check( nLen ? !!pBytes : 1 );
+  return pBytes
+    ? s3jni_new_jbyteArray(pBytes, nLen)
     : NULL;
-  if(jba){
-    (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes);
+}
+
+S3JniApi(sqlite3_value_bytes(),int,1value_1bytes)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  return sv ? sqlite3_value_bytes(sv) : 0;
+}
+
+S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  return sv ? sqlite3_value_bytes16(sv) : 0;
+}
+
+
+S3JniApi(sqlite3_value_double(),jdouble,1value_1double)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  return (jdouble) (sv ? sqlite3_value_double(sv) : 0.0);
+}
+
+
+S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  sqlite3_value * const sd = sv ? sqlite3_value_dup(sv) : 0;
+  jobject rv = sd ? new_java_sqlite3_value(env, sd) : 0;
+  if( sd && !rv ) {
+    /* OOM */
+    sqlite3_value_free(sd);
   }
-  return jba;
+  return rv;
 }
 
-
-JDECL(jdouble,1value_1double)(JENV_CSELF, jobject jpSVal){
-  return (jdouble) sqlite3_value_double(PtrGet_sqlite3_value(jpSVal));
-}
-
-
-JDECL(jobject,1value_1dup)(JENV_CSELF, jobject jpSVal){
-  sqlite3_value * const sv = sqlite3_value_dup(PtrGet_sqlite3_value(jpSVal));
-  return sv ? new_sqlite3_value_wrapper(env, sv) : 0;
-}
-
-JDECL(void,1value_1free)(JENV_CSELF, jobject jpSVal){
-  sqlite3_value_free(PtrGet_sqlite3_value(jpSVal));
-}
-
-JDECL(jint,1value_1int)(JENV_CSELF, jobject jpSVal){
-  return (jint) sqlite3_value_int(PtrGet_sqlite3_value(jpSVal));
-}
-
-JDECL(jlong,1value_1int64)(JENV_CSELF, jobject jpSVal){
-  return (jlong) sqlite3_value_int64(PtrGet_sqlite3_value(jpSVal));
-}
-
-JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){
-  ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), RESULT_JAVA_VAL_STRING);
-  return rv ? rv->jObj : NULL;
-}
-
-JDECL(jstring,1value_1text)(JENV_CSELF, jobject jpSVal){
-  sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
-  int const n = sqlite3_value_bytes16(sv);
-  const void * const p = sqlite3_value_text16(sv);
-  return s3jni_text16_to_jstring(env, p, n);
-}
-
-JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){
-  sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
-  int const n = sqlite3_value_bytes(sv);
-  const unsigned char * const p = sqlite3_value_text(sv);
-  return s3jni_new_jbyteArray(env, p, n);
-}
-
-static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){
-  int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal));
-  jbyteArray jba;
-  const jbyte * pBytes;
-  switch(mode){
-    case SQLITE_UTF16:
-      pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal));
-      break;
-    case SQLITE_UTF16LE:
-      pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal));
-      break;
-    case SQLITE_UTF16BE:
-      pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal));
-      break;
-    default:
-      assert(!"not possible");
-      return NULL;
+S3JniApi(sqlite3_value_free(),void,1value_1free)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  if( sv ){
+    sqlite3_value_free(sv);
   }
-  jba = pBytes
-    ? (*env)->NewByteArray(env, (jsize)nLen)
-    : NULL;
-  if(jba){
-    (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes);
-  }
-  return jba;
 }
 
-JDECL(jbyteArray,1value_1text16)(JENV_CSELF, jobject jpSVal){
-  return value_text16(SQLITE_UTF16, env, jpSVal);
+S3JniApi(sqlite3_value_int(),jint,1value_1int)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  return (jint) (sv ? sqlite3_value_int(sv) : 0);
 }
 
-JDECL(jbyteArray,1value_1text16le)(JENV_CSELF, jobject jpSVal){
-  return value_text16(SQLITE_UTF16LE, env, jpSVal);
+S3JniApi(sqlite3_value_int64(),jlong,1value_1int64)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  return (jlong) (sv ? sqlite3_value_int64(sv) : 0LL);
 }
 
-JDECL(jbyteArray,1value_1text16be)(JENV_CSELF, jobject jpSVal){
-  return value_text16(SQLITE_UTF16BE, env, jpSVal);
+S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  return sv
+    ? sqlite3_value_pointer(sv, ResultJavaValuePtrStr)
+    : 0;
 }
 
-JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){
+S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
+  int const n = p ? sqlite3_value_bytes(sv) : 0;
+  return p ? s3jni_new_jbyteArray(p, n) : 0;
+}
+
+#if 0
+// this impl might prove useful.
+S3JniApi(sqlite3_value_text(),jstring,1value_1text)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
+  int const n = p ? sqlite3_value_bytes(sv) : 0;
+  return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0;
+}
+#endif
+
+S3JniApi(sqlite3_value_text16(),jstring,1value_1text16)(
+  JniArgsEnvClass, jlong jpSVal
+){
+  sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+  const int n = sv ? sqlite3_value_bytes16(sv) : 0;
+  const void * const p = sv ? sqlite3_value_text16(sv) : 0;
+  return p ? s3jni_text16_to_jstring(env, p, n) : 0;
+}
+
+JniDecl(void,1jni_1internal_1details)(JniArgsEnvClass){
   MARKER(("\nVarious bits of internal info:\n"));
   puts("FTS5 is "
 #ifdef SQLITE_ENABLE_FTS5
@@ -3469,29 +4936,63 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){
   puts("sizeofs:");
 #define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T))
   SO(void*);
-  SO(S3JniEnvCache);
+  SO(jmethodID);
+  SO(jfieldID);
+  SO(S3JniEnv);
   SO(S3JniHook);
   SO(S3JniDb);
-  SO(S3JniClassNames);
-  printf("\t(^^^ %u NativePointerHolder subclasses)\n",
-         (unsigned)(sizeof(S3JniClassNames) / sizeof(const char *)));
+  SO(S3JniNphOps);
+  printf("\t(^^^ %u NativePointerHolder/OutputPointer.T types)\n",
+         (unsigned)S3Jni_NphCache_size);
   SO(S3JniGlobal);
+  SO(S3JniGlobal.nph);
+  SO(S3JniGlobal.metrics);
   SO(S3JniAutoExtension);
   SO(S3JniUdf);
+#undef SO
+#ifdef SQLITE_JNI_ENABLE_METRICS
   printf("Cache info:\n");
-  printf("\tNativePointerHolder cache: %u misses, %u hits\n",
-         S3JniGlobal.metrics.nphCacheMisses,
-         S3JniGlobal.metrics.nphCacheHits);
-  printf("\tJNIEnv cache               %u misses, %u hits\n",
-         S3JniGlobal.metrics.envCacheMisses,
-         S3JniGlobal.metrics.envCacheHits);
+  printf("\tJNIEnv cache: %u allocs, %u misses, %u hits\n",
+         SJG.metrics.nEnvAlloc, SJG.metrics.nEnvMiss,
+         SJG.metrics.nEnvHit);
+  printf("Mutex entry:"
+         "\n\tglobal       = %u"
+         "\n\tenv          = %u"
+         "\n\tnph          = %u for S3JniNphOp init"
+         "\n\thook         = %u"
+         "\n\tperDb        = %u"
+         "\n\tautoExt list = %u"
+         "\n\tS3JniUdf     = %u (free-list)"
+         "\n\tmetrics      = %u\n",
+         SJG.metrics.nMutexGlobal, SJG.metrics.nMutexEnv,
+         SJG.metrics.nMutexNph, SJG.metrics.nMutexHook,
+         SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt,
+         SJG.metrics.nMutexUdf, SJG.metrics.nMetrics);
+  puts("Allocs:");
+  printf("\tS3JniDb:  %u alloced (*%u = %u bytes), %u recycled\n",
+         SJG.metrics.nPdbAlloc, (unsigned) sizeof(S3JniDb),
+         (unsigned)(SJG.metrics.nPdbAlloc * sizeof(S3JniDb)),
+         SJG.metrics.nPdbRecycled);
+  printf("\tS3JniUdf: %u alloced (*%u = %u bytes), %u recycled\n",
+         SJG.metrics.nUdfAlloc, (unsigned) sizeof(S3JniUdf),
+         (unsigned)(SJG.metrics.nUdfAlloc * sizeof(S3JniUdf)),
+         SJG.metrics.nUdfRecycled);
+  printf("\tS3JniHook: %u alloced (*%u = %u bytes), %u recycled\n",
+         SJG.metrics.nHookAlloc, (unsigned) sizeof(S3JniHook),
+         (unsigned)(SJG.metrics.nHookAlloc * sizeof(S3JniHook)),
+         SJG.metrics.nHookRecycled);
+  printf("\tS3JniEnv: %u alloced (*%u = %u bytes)\n",
+         SJG.metrics.nEnvAlloc, (unsigned) sizeof(S3JniEnv),
+         (unsigned)(SJG.metrics.nEnvAlloc * sizeof(S3JniEnv)));
   puts("Java-side UDF calls:");
-#define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3JniGlobal.metrics.udf.n##T)
+#define UDF(T) printf("\t%-8s = %u\n", "x" #T, SJG.metrics.udf.n##T)
   UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse);
 #undef UDF
   printf("xDestroy calls across all callback types: %u\n",
-         S3JniGlobal.metrics.nDestroy);
-#undef SO
+         SJG.metrics.nDestroy);
+#else
+  puts("Built without SQLITE_JNI_ENABLE_METRICS.");
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////////
@@ -3500,36 +5001,35 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){
 #ifdef SQLITE_ENABLE_FTS5
 
 /* Creates a verbose JNI Fts5 function name. */
-#define JFuncNameFtsXA(Suffix)                  \
-  Java_org_sqlite_jni_Fts5ExtensionApi_ ## Suffix
-#define JFuncNameFtsApi(Suffix)                  \
-  Java_org_sqlite_jni_fts5_1api_ ## Suffix
-#define JFuncNameFtsTok(Suffix)                  \
-  Java_org_sqlite_jni_fts5_tokenizer_ ## Suffix
+#define JniFuncNameFtsXA(Suffix)                  \
+  Java_org_sqlite_jni_fts5_Fts5ExtensionApi_ ## Suffix
+#define JniFuncNameFtsApi(Suffix)                  \
+  Java_org_sqlite_jni_fts5_fts5_1api_ ## Suffix
+#define JniFuncNameFtsTok(Suffix)                  \
+  Java_org_sqlite_jni_fts5_fts5_tokenizer_ ## Suffix
 
-#define JDECLFtsXA(ReturnType,Suffix)           \
+#define JniDeclFtsXA(ReturnType,Suffix)           \
   JNIEXPORT ReturnType JNICALL                  \
-  JFuncNameFtsXA(Suffix)
-#define JDECLFtsApi(ReturnType,Suffix)          \
+  JniFuncNameFtsXA(Suffix)
+#define JniDeclFtsApi(ReturnType,Suffix)          \
   JNIEXPORT ReturnType JNICALL                  \
-  JFuncNameFtsApi(Suffix)
-#define JDECLFtsTok(ReturnType,Suffix)          \
+  JniFuncNameFtsApi(Suffix)
+#define JniDeclFtsTok(ReturnType,Suffix)          \
   JNIEXPORT ReturnType JNICALL                  \
-  JFuncNameFtsTok(Suffix)
+  JniFuncNameFtsTok(Suffix)
 
-#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_api)
-#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_tokenizer)
-#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Context)
-#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Tokenizer)
-#define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext()
+#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_api))
+#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_tokenizer))
+#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Context))
+#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Tokenizer))
+#define s3jni_ftsext() &sFts5Api/*singleton from sqlite3.c*/
+#define Fts5ExtDecl Fts5ExtensionApi const * const ext = s3jni_ftsext()
 
 /**
    State for binding Java-side FTS5 auxiliary functions.
 */
 typedef struct {
-  JNIEnv * env;         /* env registered from */;
   jobject jObj          /* functor instance */;
-  jclass klazz          /* jObj's class */;
   jobject jUserData     /* 2nd arg to JNI binding of
                            xCreateFunction(), ostensibly the 3rd arg
                            to the lib-level xCreateFunction(), except
@@ -3540,38 +5040,38 @@ typedef struct {
 } Fts5JniAux;
 
 static void Fts5JniAux_free(Fts5JniAux * const s){
-  JNIEnv * const env = s->env;
-  if(env){
+  S3JniDeclLocal_env;
+  if( env ){
     /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/
-    s3jni_call_xDestroy(env, s->jObj, s->klazz);
-    UNREF_G(s->jObj);
-    UNREF_G(s->klazz);
-    UNREF_G(s->jUserData);
+    s3jni_call_xDestroy(s->jObj);
+    S3JniUnrefGlobal(s->jObj);
+    S3JniUnrefGlobal(s->jUserData);
   }
   sqlite3_free(s->zFuncName);
   sqlite3_free(s);
 }
 
 static void Fts5JniAux_xDestroy(void *p){
-  if(p) Fts5JniAux_free(p);
+  if( p ) Fts5JniAux_free(p);
 }
 
 static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){
-  Fts5JniAux * s = sqlite3_malloc(sizeof(Fts5JniAux));
-  if(s){
-    const char * zSig =
-      "(Lorg/sqlite/jni/Fts5ExtensionApi;"
-      "Lorg/sqlite/jni/Fts5Context;"
-      "Lorg/sqlite/jni/sqlite3_context;"
-      "[Lorg/sqlite/jni/sqlite3_value;)V";
+  Fts5JniAux * s = s3jni_malloc( sizeof(Fts5JniAux));
+
+  if( s ){
+    jclass klazz;
     memset(s, 0, sizeof(Fts5JniAux));
-    s->env = env;
-    s->jObj = REF_G(jObj);
-    s->klazz = REF_G((*env)->GetObjectClass(env, jObj));
-    s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig);
-    IFTHREW{
-      EXCEPTION_REPORT;
-      EXCEPTION_CLEAR;
+    s->jObj = S3JniRefGlobal(jObj);
+    klazz = (*env)->GetObjectClass(env, jObj);
+    s->jmid = (*env)->GetMethodID(env, klazz, "call",
+                                  "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;"
+                                  "Lorg/sqlite/jni/fts5/Fts5Context;"
+                                  "Lorg/sqlite/jni/capi/sqlite3_context;"
+                                  "[Lorg/sqlite/jni/capi/sqlite3_value;)V");
+    S3JniUnrefLocal(klazz);
+    S3JniIfThrew{
+      S3JniExceptionReport;
+      S3JniExceptionClear;
       Fts5JniAux_free(s);
       s = 0;
     }
@@ -3579,34 +5079,37 @@ static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){
   return s;
 }
 
-static inline Fts5ExtensionApi const * s3jni_ftsext(void){
-  return &sFts5Api/*singleton from sqlite3.c*/;
+static inline jobject new_java_Fts5Context(JNIEnv * const env, Fts5Context *sv){
+  return NativePointerHolder_new(env, S3JniNph(Fts5Context), sv);
 }
-
-static inline jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){
-  return new_NativePointerHolder_object(env, S3JniClassNames.Fts5Context, sv);
-}
-static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){
-  return new_NativePointerHolder_object(env, S3JniClassNames.fts5_api, sv);
-}
-
-/**
-   Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton
-   instance, or NULL on OOM.
-*/
-static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){
-  S3JniEnvCache * const row = S3JniGlobal_env_cache(env);
-  if( !row->jFtsExt ){
-    row->jFtsExt = new_NativePointerHolder_object(env, S3JniClassNames.Fts5ExtensionApi,
-                                                  s3jni_ftsext());
-    if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt);
-  }
-  return row->jFtsExt;
+static inline jobject new_java_fts5_api(JNIEnv * const env, fts5_api *sv){
+  return NativePointerHolder_new(env, S3JniNph(fts5_api), sv);
 }
 
 /*
-** Return a pointer to the fts5_api instance for database connection
-** db.  If an error occurs, return NULL and leave an error in the
+** Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton
+** instance, or NULL on OOM.
+*/
+static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){
+  if( !SJG.fts5.jExt ){
+    S3JniGlobal_mutex_enter;
+    if( !SJG.fts5.jExt ){
+      jobject const pNPH = NativePointerHolder_new(
+        env, S3JniNph(Fts5ExtensionApi), s3jni_ftsext()
+      );
+      if( pNPH ){
+        SJG.fts5.jExt = S3JniRefGlobal(pNPH);
+        S3JniUnrefLocal(pNPH);
+      }
+    }
+    S3JniGlobal_mutex_leave;
+  }
+  return SJG.fts5.jExt;
+}
+
+/*
+** Returns a pointer to the fts5_api instance for database connection
+** db.  If an error occurs, returns NULL and leaves an error in the
 ** database handle (accessible using sqlite3_errcode()/errmsg()).
 */
 static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){
@@ -3620,54 +5123,68 @@ static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){
   return pRet;
 }
 
-JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+JniDeclFtsApi(jobject,getInstanceForDb)(JniArgsEnvClass,jobject jDb){
+  S3JniDb * const ps = S3JniDb_from_java(jDb);
+#if 0
   jobject rv = 0;
-  if(!ps) return 0;
-  else if(ps->jFtsApi){
-    rv = ps->jFtsApi;
+  if( !ps ) return 0;
+  else if( ps->fts.jApi ){
+    rv = ps->fts.jApi;
   }else{
     fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb);
     if( pApi ){
-      rv = new_fts5_api_wrapper(env, pApi);
-      ps->jFtsApi = rv ? REF_G(rv) : 0;
+      rv = new_java_fts5_api(env, pApi);
+      ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0;
     }
   }
   return rv;
+#else
+  if( ps && !ps->fts.jApi ){
+    S3JniDb_mutex_enter;
+    if( !ps->fts.jApi ){
+      fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb);
+      if( pApi ){
+        jobject const rv = new_java_fts5_api(env, pApi);
+        ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0;
+      }
+    }
+    S3JniDb_mutex_leave;
+  }
+  return ps ? ps->fts.jApi : 0;
+#endif
 }
 
 
-JDECLFtsXA(jobject,getInstance)(JENV_CSELF){
+JniDeclFtsXA(jobject,getInstance)(JniArgsEnvClass){
   return s3jni_getFts5ExensionApi(env);
 }
 
-JDECLFtsXA(jint,xColumnCount)(JENV_OSELF,jobject jCtx){
+JniDeclFtsXA(jint,xColumnCount)(JniArgsEnvObj,jobject jCtx){
   Fts5ExtDecl;
-  return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx));
+  return (jint)ext->xColumnCount(PtrGet_Fts5Context(jCtx));
 }
 
-JDECLFtsXA(jint,xColumnSize)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOut32){
+JniDeclFtsXA(jint,xColumnSize)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOut32){
   Fts5ExtDecl;
   int n1 = 0;
-  int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1);
+  int const rc = ext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1);
   if( 0==rc ) OutputPointer_set_Int32(env, jOut32, n1);
   return rc;
 }
 
-JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol,
+JniDeclFtsXA(jint,xColumnText)(JniArgsEnvObj,jobject jCtx, jint iCol,
                            jobject jOut){
   Fts5ExtDecl;
   const char *pz = 0;
   int pn = 0;
-  int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol,
+  int rc = ext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol,
                              &pz, &pn);
   if( 0==rc ){
-    S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
-    jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0;
+    jstring jstr = pz ? s3jni_utf8_to_jstring( pz, pn) : 0;
     if( pz ){
       if( jstr ){
         OutputPointer_set_String(env, jOut, jstr);
-        UNREF_L(jstr)/*jOut has a reference*/;
+        S3JniUnrefLocal(jstr)/*jOut has a reference*/;
       }else{
         rc = SQLITE_NOMEM;
       }
@@ -3676,17 +5193,17 @@ JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol,
   return (jint)rc;
 }
 
-JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jOut64){
+JniDeclFtsXA(jint,xColumnTotalSize)(JniArgsEnvObj,jobject jCtx, jint iCol, jobject jOut64){
   Fts5ExtDecl;
   sqlite3_int64 nOut = 0;
-  int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut);
+  int const rc = ext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut);
   if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
   return (jint)rc;
 }
 
-/**
-   Proxy for fts5_extension_function instances plugged in via
-   fts5_api::xCreateFunction().
+/*
+** Proxy for fts5_extension_function instances plugged in via
+** fts5_api::xCreateFunction().
 */
 static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi,
                                           Fts5Context *pFts,
@@ -3694,46 +5211,48 @@ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi,
                                           int argc,
                                           sqlite3_value **argv){
   Fts5JniAux * const pAux = pApi->xUserData(pFts);
-  JNIEnv *env;
   jobject jpCx = 0;
   jobjectArray jArgv = 0;
   jobject jpFts = 0;
   jobject jFXA;
   int rc;
+  S3JniDeclLocal_env;
+
   assert(pAux);
-  env = pAux->env;
   jFXA = s3jni_getFts5ExensionApi(env);
   if( !jFXA ) goto error_oom;
-  jpFts = new_Fts5Context_wrapper(env, pFts);
-  if(!jpFts) goto error_oom;
+  jpFts = new_java_Fts5Context(env, pFts);
+  if( !jpFts ) goto error_oom;
   rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv);
-  if(rc) goto error_oom;
+  if( rc ) goto error_oom;
   (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid,
                          jFXA, jpFts, jpCx, jArgv);
-  IFTHREW{
-    EXCEPTION_CLEAR;
-    udf_report_exception(pCx, pAux->zFuncName, "xFunction");
+  S3JniIfThrew{
+    udf_report_exception(env, 1, pCx, pAux->zFuncName, "call");
   }
-  UNREF_L(jpFts);
-  UNREF_L(jpCx);
-  UNREF_L(jArgv);
+  udf_unargs(env, jpCx, argc, jArgv);
+  S3JniUnrefLocal(jpFts);
+  S3JniUnrefLocal(jpCx);
+  S3JniUnrefLocal(jArgv);
   return;
 error_oom:
+  s3jni_db_oom( sqlite3_context_db_handle(pCx) );
   assert( !jArgv );
   assert( !jpCx );
-  UNREF_L(jpFts);
+  S3JniUnrefLocal(jpFts);
   sqlite3_result_error_nomem(pCx);
   return;
 }
 
-JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName,
+JniDeclFtsApi(jint,xCreateFunction)(JniArgsEnvObj, jstring jName,
                                   jobject jUserData, jobject jFunc){
   fts5_api * const pApi = PtrGet_fts5_api(jSelf);
   int rc;
-  char const * zName;
+  char * zName;
   Fts5JniAux * pAux;
+
   assert(pApi);
-  zName = JSTR_TOC(jName);
+  zName = s3jni_jstring_to_utf8( jName, 0);
   if(!zName) return SQLITE_NOMEM;
   pAux = Fts5JniAux_alloc(env, jFunc);
   if( pAux ){
@@ -3744,42 +5263,45 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName,
     rc = SQLITE_NOMEM;
   }
   if( 0==rc ){
-    pAux->jUserData = jUserData ? REF_G(jUserData) : 0;
-    pAux->zFuncName = sqlite3_mprintf("%s", zName)
-      /* OOM here is non-fatal. Ignore it. */;
+    pAux->jUserData = jUserData ? S3JniRefGlobal(jUserData) : 0;
+    pAux->zFuncName = zName;
+  }else{
+    sqlite3_free(zName);
   }
-  JSTR_RELEASE(jName, zName);
   return (jint)rc;
 }
 
 
-typedef struct s3jni_fts5AuxData s3jni_fts5AuxData;
-struct s3jni_fts5AuxData {
-  JNIEnv *env;
+typedef struct S3JniFts5AuxData S3JniFts5AuxData;
+/*
+** TODO: this middle-man struct is no longer necessary. Conider
+** removing it and passing around jObj itself instead.
+*/
+struct S3JniFts5AuxData {
   jobject jObj;
 };
 
-static void s3jni_fts5AuxData_xDestroy(void *x){
-  if(x){
-    s3jni_fts5AuxData * const p = x;
-    if(p->jObj){
-      JNIEnv *env = p->env;
-      s3jni_call_xDestroy(env, p->jObj, 0);
-      UNREF_G(p->jObj);
+static void S3JniFts5AuxData_xDestroy(void *x){
+  if( x ){
+    S3JniFts5AuxData * const p = x;
+    if( p->jObj ){
+      S3JniDeclLocal_env;
+      s3jni_call_xDestroy(p->jObj);
+      S3JniUnrefGlobal(p->jObj);
     }
     sqlite3_free(x);
   }
 }
 
-JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){
+JniDeclFtsXA(jobject,xGetAuxdata)(JniArgsEnvObj,jobject jCtx, jboolean bClear){
   Fts5ExtDecl;
   jobject rv = 0;
-  s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear);
-  if(pAux){
-    if(bClear){
+  S3JniFts5AuxData * const pAux = ext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear);
+  if( pAux ){
+    if( bClear ){
       if( pAux->jObj ){
-        rv = REF_L(pAux->jObj);
-        UNREF_G(pAux->jObj);
+        rv = S3JniRefLocal(pAux->jObj);
+        S3JniUnrefGlobal(pAux->jObj);
       }
       /* Note that we do not call xDestroy() in this case. */
       sqlite3_free(pAux);
@@ -3790,11 +5312,11 @@ JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){
   return rv;
 }
 
-JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase,
+JniDeclFtsXA(jint,xInst)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOutPhrase,
                     jobject jOutCol, jobject jOutOff){
   Fts5ExtDecl;
   int n1 = 0, n2 = 2, n3 = 0;
-  int const rc = fext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3);
+  int const rc = ext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3);
   if( 0==rc ){
     OutputPointer_set_Int32(env, jOutPhrase, n1);
     OutputPointer_set_Int32(env, jOutCol, n2);
@@ -3803,135 +5325,117 @@ JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase,
   return rc;
 }
 
-JDECLFtsXA(jint,xInstCount)(JENV_OSELF,jobject jCtx, jobject jOut32){
+JniDeclFtsXA(jint,xInstCount)(JniArgsEnvObj,jobject jCtx, jobject jOut32){
   Fts5ExtDecl;
   int nOut = 0;
-  int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut);
+  int const rc = ext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut);
   if( 0==rc && jOut32 ) OutputPointer_set_Int32(env, jOut32, nOut);
   return (jint)rc;
 }
 
-JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){
+JniDeclFtsXA(jint,xPhraseCount)(JniArgsEnvObj,jobject jCtx){
   Fts5ExtDecl;
-  return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx));
-}
-
-/**
-   Initializes jc->jPhraseIter if it needed it.
-*/
-static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnvCache * const jc,
-                                  jobject jIter){
-  if(!jc->jPhraseIter.klazz){
-    jclass klazz = (*env)->GetObjectClass(env, jIter);
-    jc->jPhraseIter.klazz = REF_G(klazz);
-    jc->jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J");
-    EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field.");
-    jc->jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "a", "J");
-    EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field.");
-  }
+  return (jint)ext->xPhraseCount(PtrGet_Fts5Context(jCtx));
 }
 
 /* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */
-static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnvCache const * const jc,
-                                    Fts5PhraseIter const * const pSrc,
-                                    jobject jIter){
-  assert(jc->jPhraseIter.klazz);
-  (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidA, (jlong)pSrc->a);
-  EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field.");
-  (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidB, (jlong)pSrc->b);
-  EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.b field.");
+static void s3jni_phraseIter_NToJ(JNIEnv *const env,
+                                  Fts5PhraseIter const * const pSrc,
+                                  jobject jIter){
+  S3JniGlobalType * const g = &S3JniGlobal;
+  assert(g->fts5.jPhraseIter.fidA);
+  (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidA,
+                       S3JniCast_P2L(pSrc->a));
+  S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.a field.");
+  (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB,
+                       S3JniCast_P2L(pSrc->b));
+  S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.b field.");
 }
 
 /* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */
-static void s3jni_phraseIter_JToN(JNIEnv *const env, S3JniEnvCache const * const jc,
-                                  jobject jIter, Fts5PhraseIter * const pDest){
-  assert(jc->jPhraseIter.klazz);
-  pDest->a =
-    (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidA);
-  EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field.");
-  pDest->b =
-    (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidB);
-  EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field.");
+static void s3jni_phraseIter_JToN(JNIEnv *const env,  jobject jIter,
+                                  Fts5PhraseIter * const pDest){
+  S3JniGlobalType * const g = &S3JniGlobal;
+  assert(g->fts5.jPhraseIter.fidA);
+  pDest->a = S3JniCast_L2P(
+    (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA)
+  );
+  S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field.");
+  pDest->b = S3JniCast_L2P(
+    (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidB)
+  );
+  S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field.");
 }
 
-JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase,
+JniDeclFtsXA(jint,xPhraseFirst)(JniArgsEnvObj,jobject jCtx, jint iPhrase,
                             jobject jIter, jobject jOutCol,
                             jobject jOutOff){
   Fts5ExtDecl;
-  S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
   Fts5PhraseIter iter;
   int rc, iCol = 0, iOff = 0;
-  s3jni_phraseIter_init(env, jc, jIter);
-  rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase,
+  rc = ext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase,
                          &iter, &iCol, &iOff);
   if( 0==rc ){
     OutputPointer_set_Int32(env, jOutCol, iCol);
     OutputPointer_set_Int32(env, jOutOff, iOff);
-    s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
+    s3jni_phraseIter_NToJ(env, &iter, jIter);
   }
   return rc;
 }
 
-JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase,
+JniDeclFtsXA(jint,xPhraseFirstColumn)(JniArgsEnvObj,jobject jCtx, jint iPhrase,
                                   jobject jIter, jobject jOutCol){
   Fts5ExtDecl;
-  S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
   Fts5PhraseIter iter;
   int rc, iCol = 0;
-  s3jni_phraseIter_init(env, jc, jIter);
-  rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase,
+  rc = ext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase,
                                 &iter, &iCol);
   if( 0==rc ){
     OutputPointer_set_Int32(env, jOutCol, iCol);
-    s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
+    s3jni_phraseIter_NToJ(env, &iter, jIter);
   }
   return rc;
 }
 
-JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter,
+JniDeclFtsXA(void,xPhraseNext)(JniArgsEnvObj,jobject jCtx, jobject jIter,
                            jobject jOutCol, jobject jOutOff){
   Fts5ExtDecl;
-  S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
   Fts5PhraseIter iter;
   int iCol = 0, iOff = 0;
-  if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/;
-  s3jni_phraseIter_JToN(env, jc, jIter, &iter);
-  fext->xPhraseNext(PtrGet_Fts5Context(jCtx),
-                         &iter, &iCol, &iOff);
+  s3jni_phraseIter_JToN(env, jIter, &iter);
+  ext->xPhraseNext(PtrGet_Fts5Context(jCtx), &iter, &iCol, &iOff);
   OutputPointer_set_Int32(env, jOutCol, iCol);
   OutputPointer_set_Int32(env, jOutOff, iOff);
-  s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
+  s3jni_phraseIter_NToJ(env, &iter, jIter);
 }
 
-JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter,
+JniDeclFtsXA(void,xPhraseNextColumn)(JniArgsEnvObj,jobject jCtx, jobject jIter,
                                  jobject jOutCol){
   Fts5ExtDecl;
-  S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
   Fts5PhraseIter iter;
   int iCol = 0;
-  if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/;
-  s3jni_phraseIter_JToN(env, jc, jIter, &iter);
-  fext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol);
+  s3jni_phraseIter_JToN(env, jIter, &iter);
+  ext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol);
   OutputPointer_set_Int32(env, jOutCol, iCol);
-  s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
+  s3jni_phraseIter_NToJ(env, &iter, jIter);
 }
 
 
-JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){
+JniDeclFtsXA(jint,xPhraseSize)(JniArgsEnvObj,jobject jCtx, jint iPhrase){
   Fts5ExtDecl;
-  return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase);
+  return (jint)ext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase);
 }
 
-/**
-   State for use with xQueryPhrase() and xTokenize().
-*/
+/* State for use with xQueryPhrase() and xTokenize(). */
 struct s3jni_xQueryPhraseState {
-  JNIEnv *env;
-  Fts5ExtensionApi const * fext;
-  S3JniEnvCache const * jc;
-  jmethodID midCallback;
-  jobject jCallback;
-  jobject jFcx;
+  Fts5ExtensionApi const * ext;
+  jmethodID midCallback; /* jCallback->call() method */
+  jobject jCallback;   /* Fts5ExtensionApi.XQueryPhraseCallback instance */
+  jobject jFcx;        /* (Fts5Context*) for xQueryPhrase()
+                          callback. This is NOT the instance that is
+                          passed to xQueryPhrase(), it's the one
+                          created by xQueryPhrase() for use by its
+                          callback. */
   /* State for xTokenize() */
   struct {
     const char * zPrev;
@@ -3942,138 +5446,138 @@ struct s3jni_xQueryPhraseState {
 
 static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi,
                               Fts5Context * pFcx, void *pData){
-  /* TODO: confirm that the Fts5Context passed to this function is
-     guaranteed to be the same one passed to xQueryPhrase(). If it's
-     not, we'll have to create a new wrapper object on every call. */
-  struct s3jni_xQueryPhraseState const * s = pData;
-  JNIEnv * const env = s->env;
+  struct s3jni_xQueryPhraseState * const s = pData;
+  S3JniDeclLocal_env;
+
+  if( !s->jFcx ){
+    s->jFcx = new_java_Fts5Context(env, pFcx);
+    if( !s->jFcx ) return SQLITE_NOMEM;
+  }
   int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
-                                      s->jc->jFtsExt, s->jFcx);
-  IFTHREW{
-    EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase callback");
-    EXCEPTION_CLEAR;
+                                      SJG.fts5.jExt, s->jFcx);
+  S3JniIfThrew{
+    S3JniExceptionWarnCallbackThrew("xQueryPhrase() callback");
+    S3JniExceptionClear;
     rc = SQLITE_ERROR;
   }
   return rc;
 }
 
-JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase,
+JniDeclFtsXA(jint,xQueryPhrase)(JniArgsEnvObj,jobject jFcx, jint iPhrase,
                             jobject jCallback){
   Fts5ExtDecl;
-  S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
+  int rc;
   struct s3jni_xQueryPhraseState s;
   jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
+
   if( !klazz ) return SQLITE_MISUSE;
-  s.env = env;
-  s.jc = jc;
   s.jCallback = jCallback;
-  s.jFcx = jFcx;
-  s.fext = fext;
-  s.midCallback = (*env)->GetMethodID(env, klazz, "xCallback",
-                                      "(Lorg.sqlite.jni.Fts5ExtensionApi;"
-                                      "Lorg.sqlite.jni.Fts5Context;)I");
-  EXCEPTION_IS_FATAL("Could not extract xQueryPhraseCallback.xCallback method.");
-  return (jint)fext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s,
-                                  s3jni_xQueryPhrase);
+  s.jFcx = 0;
+  s.ext = ext;
+  s.midCallback = (*env)->GetMethodID(env, klazz, "call",
+                                      "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;"
+                                      "Lorg/sqlite/jni/fts5/Fts5Context;)I");
+  S3JniUnrefLocal(klazz);
+  S3JniExceptionIsFatal("Could not extract xQueryPhraseCallback.call() method.");
+  rc = ext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s,
+                         s3jni_xQueryPhrase);
+  S3JniUnrefLocal(s.jFcx);
+  return (jint)rc;
 }
 
 
-JDECLFtsXA(jint,xRowCount)(JENV_OSELF,jobject jCtx, jobject jOut64){
+JniDeclFtsXA(jint,xRowCount)(JniArgsEnvObj,jobject jCtx, jobject jOut64){
   Fts5ExtDecl;
   sqlite3_int64 nOut = 0;
-  int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut);
+  int const rc = ext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut);
   if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
   return (jint)rc;
 }
 
-JDECLFtsXA(jlong,xRowid)(JENV_OSELF,jobject jCtx){
+JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){
   Fts5ExtDecl;
-  return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx));
+  return (jlong)ext->xRowid(PtrGet_Fts5Context(jCtx));
 }
 
-JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){
+JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){
   Fts5ExtDecl;
   int rc;
-  s3jni_fts5AuxData * pAux;
-  pAux = sqlite3_malloc(sizeof(*pAux));
-  if(!pAux){
-    if(jAux){
-      // Emulate how xSetAuxdata() behaves when it cannot alloc
-      // its auxdata wrapper.
-      s3jni_call_xDestroy(env, jAux, 0);
+  S3JniFts5AuxData * pAux;
+
+  pAux = s3jni_malloc( sizeof(*pAux));
+  if( !pAux ){
+    if( jAux ){
+      /* Emulate how xSetAuxdata() behaves when it cannot alloc
+      ** its auxdata wrapper. */
+      s3jni_call_xDestroy(jAux);
     }
     return SQLITE_NOMEM;
   }
-  pAux->env = env;
-  pAux->jObj = REF_G(jAux);
-  rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux,
-                         s3jni_fts5AuxData_xDestroy);
+  pAux->jObj = S3JniRefGlobal(jAux);
+  rc = ext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux,
+                         S3JniFts5AuxData_xDestroy);
   return rc;
 }
 
-/**
-   xToken() impl for xTokenize().
-*/
+/* xToken() impl for xTokenize(). */
 static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z,
                                   int nZ, int iStart, int iEnd){
   int rc;
+  S3JniDeclLocal_env;
   struct s3jni_xQueryPhraseState * const s = p;
-  JNIEnv * const env = s->env;
   jbyteArray jba;
-  if( s->tok.zPrev == z && s->tok.nPrev == nZ ){
-    jba = s->tok.jba;
-  }else{
-    if(s->tok.jba){
-      UNREF_L(s->tok.jba);
-    }
-    s->tok.zPrev = z;
-    s->tok.nPrev = nZ;
-    s->tok.jba = (*env)->NewByteArray(env, (jint)nZ);
-    if( !s->tok.jba ) return SQLITE_NOMEM;
-    jba = s->tok.jba;
-    (*env)->SetByteArrayRegion(env, jba, 0, (jint)nZ, (const jbyte*)z);
-  }
+
+  S3JniUnrefLocal(s->tok.jba);
+  s->tok.zPrev = z;
+  s->tok.nPrev = nZ;
+  s->tok.jba = s3jni_new_jbyteArray(z, nZ);
+  if( !s->tok.jba ) return SQLITE_NOMEM;
+  jba = s->tok.jba;
   rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
                                   (jint)tFlags, jba, (jint)iStart,
                                   (jint)iEnd);
+  S3JniIfThrew {
+    S3JniExceptionWarnCallbackThrew("xTokenize() callback");
+    rc = SQLITE_ERROR;
+  }
   return rc;
 }
 
-/**
-   Proxy for Fts5ExtensionApi.xTokenize() and fts5_tokenizer.xTokenize()
+/*
+** Proxy for Fts5ExtensionApi.xTokenize() and
+** fts5_tokenizer.xTokenize()
 */
-static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName,
+static jint s3jni_fts5_xTokenize(JniArgsEnvObj, S3JniNphOp const *pRef,
                                  jint tokFlags, jobject jFcx,
                                  jbyteArray jbaText, jobject jCallback){
   Fts5ExtDecl;
-  S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
   struct s3jni_xQueryPhraseState s;
   int rc = 0;
-  jbyte * const pText = jCallback ? JBA_TOC(jbaText) : 0;
+  jbyte * const pText = jCallback ? s3jni_jbyteArray_bytes(jbaText) : 0;
   jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0;
   jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
+
   if( !klazz ) return SQLITE_MISUSE;
   memset(&s, 0, sizeof(s));
-  s.env = env;
-  s.jc = jc;
   s.jCallback = jCallback;
   s.jFcx = jFcx;
-  s.fext = fext;
-  s.midCallback = (*env)->GetMethodID(env, klazz, "xToken", "(I[BII)I");
-  IFTHREW {
-    EXCEPTION_REPORT;
-    EXCEPTION_CLEAR;
-    JBA_RELEASE(jbaText, pText);
+  s.ext = ext;
+  s.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I[BII)I");
+  S3JniUnrefLocal(klazz);
+  S3JniIfThrew {
+    S3JniExceptionReport;
+    S3JniExceptionClear;
+    s3jni_jbyteArray_release(jbaText, pText);
     return SQLITE_ERROR;
   }
-  s.tok.jba = REF_L(jbaText);
+  s.tok.jba = S3JniRefLocal(jbaText);
   s.tok.zPrev = (const char *)pText;
   s.tok.nPrev = (int)nText;
-  if( zClassName == S3JniClassNames.Fts5ExtensionApi ){
-    rc = fext->xTokenize(PtrGet_Fts5Context(jFcx),
+  if( pRef == S3JniNph(Fts5ExtensionApi) ){
+    rc = ext->xTokenize(PtrGet_Fts5Context(jFcx),
                          (const char *)pText, (int)nText,
                          &s, s3jni_xTokenize_xToken);
-  }else if( zClassName == S3JniClassNames.fts5_tokenizer ){
+  }else if( pRef == S3JniNph(fts5_tokenizer) ){
     fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf);
     rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags,
                          (const char *)pText, (int)nText,
@@ -4081,30 +5585,30 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName,
   }else{
     (*env)->FatalError(env, "This cannot happen. Maintenance required.");
   }
-  if(s.tok.jba){
+  if( s.tok.jba ){
     assert( s.tok.zPrev );
-    UNREF_L(s.tok.jba);
+    S3JniUnrefLocal(s.tok.jba);
   }
-  JBA_RELEASE(jbaText, pText);
+  s3jni_jbyteArray_release(jbaText, pText);
   return (jint)rc;
 }
 
-JDECLFtsXA(jint,xTokenize)(JENV_OSELF,jobject jFcx, jbyteArray jbaText,
-                           jobject jCallback){
-  return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5ExtensionApi,
+JniDeclFtsXA(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jbyteArray jbaText,
+                             jobject jCallback){
+  return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5ExtensionApi),
                               0, jFcx, jbaText, jCallback);
 }
 
-JDECLFtsTok(jint,xTokenize)(JENV_OSELF,jobject jFcx, jint tokFlags,
-                            jbyteArray jbaText, jobject jCallback){
-  return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5Tokenizer,
+JniDeclFtsTok(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jint tokFlags,
+                              jbyteArray jbaText, jobject jCallback){
+  return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5Tokenizer),
                               tokFlags, jFcx, jbaText, jCallback);
 }
 
 
-JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){
+JniDeclFtsXA(jobject,xUserData)(JniArgsEnvObj,jobject jFcx){
   Fts5ExtDecl;
-  Fts5JniAux * const pAux = fext->xUserData(PtrGet_Fts5Context(jFcx));
+  Fts5JniAux * const pAux = ext->xUserData(PtrGet_Fts5Context(jFcx));
   return pAux ? pAux->jUserData : 0;
 }
 
@@ -4114,7 +5618,7 @@ JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){
 // End of the main API bindings. Start of SQLTester bits...
 ////////////////////////////////////////////////////////////////////////
 
-#ifdef S3JNI_ENABLE_SQLTester
+#ifdef SQLITE_JNI_ENABLE_SQLTester
 typedef struct SQLTesterJni SQLTesterJni;
 struct SQLTesterJni {
   sqlite3_int64 nDup;
@@ -4151,9 +5655,10 @@ static void SQLTester_dup_func(
   char *z;
   int n = sqlite3_value_bytes(argv[0]);
   SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context);
+  S3JniDeclLocal_env;
 
   ++p->nDup;
-  if( n>0 && (pOut = sqlite3_malloc( (n+16)&~7 ))!=0 ){
+  if( n>0 && (pOut = s3jni_malloc( (n+16)&~7 ))!=0 ){
     pOut[0] = 0x2bbf4b7c;
     z = (char*)&pOut[1];
     memcpy(z, sqlite3_value_text(argv[0]), n);
@@ -4279,19 +5784,19 @@ static int SQLTester_strnotglob(const char *zGlob, const char *z){
 }
 
 JNIEXPORT jint JNICALL
-Java_org_sqlite_jni_tester_SQLTester_strglob(
-  JENV_CSELF, jbyteArray baG, jbyteArray baT
+Java_org_sqlite_jni_capi_SQLTester_strglob(
+  JniArgsEnvClass, jbyteArray baG, jbyteArray baT
 ){
   int rc = 0;
-  jbyte * const pG = JBA_TOC(baG);
-  jbyte * const pT = pG ? JBA_TOC(baT) : 0;
-  OOM_CHECK(pT);
+  jbyte * const pG = s3jni_jbyteArray_bytes(baG);
+  jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0;
 
+  s3jni_oom_fatal(pT);
   /* Note that we're relying on the byte arrays having been
      NUL-terminated on the Java side. */
   rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT);
-  JBA_RELEASE(baG, pG);
-  JBA_RELEASE(baT, pT);
+  s3jni_jbyteArray_release(baG, pG);
+  s3jni_jbyteArray_release(baT, pT);
   return rc;
 }
 
@@ -4306,115 +5811,89 @@ static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr,
 }
 
 JNIEXPORT void JNICALL
-Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){
+Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions(JniArgsEnvClass){
   sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension );
 }
 
-#endif /* S3JNI_ENABLE_SQLTester */
+#endif /* SQLITE_JNI_ENABLE_SQLTester */
 ////////////////////////////////////////////////////////////////////////
 // End of SQLTester bindings. Start of lower-level bits.
 ////////////////////////////////////////////////////////////////////////
 
-
-/**
-   Uncaches the current JNIEnv from the S3JniGlobal state, clearing any
-   resources owned by that cache entry and making that slot available
-   for re-use. It is important that the Java-side decl of this
-   function be declared as synchronous.
-*/
-JNIEXPORT jboolean JNICALL
-Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){
-  return S3JniGlobal_env_uncache(env) ? JNI_TRUE : JNI_FALSE;
-}
-
-/**
-   Called during static init of the SQLite3Jni class to sync certain
-   compile-time constants to Java-space.
-
-   This routine is part of the reason why we have to #include
-   sqlite3.c instead of sqlite3.h.
+/*
+** Called during static init of the CApi class to set up global
+** state.
 */
 JNIEXPORT void JNICALL
-Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){
-  enum JType {
-    JTYPE_INT,
-    JTYPE_BOOL
-  };
-  typedef struct {
-    const char *zName;
-    enum JType jtype;
-    int value;
-  } ConfigFlagEntry;
-  const ConfigFlagEntry aLimits[] = {
-    {"SQLITE_ENABLE_FTS5", JTYPE_BOOL,
-#ifdef SQLITE_ENABLE_FTS5
-     1
-#else
-     0
-#endif
-    },
-    {"SQLITE_MAX_ALLOCATION_SIZE", JTYPE_INT, SQLITE_MAX_ALLOCATION_SIZE},
-    {"SQLITE_LIMIT_LENGTH", JTYPE_INT, SQLITE_LIMIT_LENGTH},
-    {"SQLITE_MAX_LENGTH", JTYPE_INT, SQLITE_MAX_LENGTH},
-    {"SQLITE_LIMIT_SQL_LENGTH", JTYPE_INT, SQLITE_LIMIT_SQL_LENGTH},
-    {"SQLITE_MAX_SQL_LENGTH", JTYPE_INT, SQLITE_MAX_SQL_LENGTH},
-    {"SQLITE_LIMIT_COLUMN", JTYPE_INT, SQLITE_LIMIT_COLUMN},
-    {"SQLITE_MAX_COLUMN", JTYPE_INT, SQLITE_MAX_COLUMN},
-    {"SQLITE_LIMIT_EXPR_DEPTH", JTYPE_INT, SQLITE_LIMIT_EXPR_DEPTH},
-    {"SQLITE_MAX_EXPR_DEPTH", JTYPE_INT, SQLITE_MAX_EXPR_DEPTH},
-    {"SQLITE_LIMIT_COMPOUND_SELECT", JTYPE_INT, SQLITE_LIMIT_COMPOUND_SELECT},
-    {"SQLITE_MAX_COMPOUND_SELECT", JTYPE_INT, SQLITE_MAX_COMPOUND_SELECT},
-    {"SQLITE_LIMIT_VDBE_OP", JTYPE_INT, SQLITE_LIMIT_VDBE_OP},
-    {"SQLITE_MAX_VDBE_OP", JTYPE_INT, SQLITE_MAX_VDBE_OP},
-    {"SQLITE_LIMIT_FUNCTION_ARG", JTYPE_INT, SQLITE_LIMIT_FUNCTION_ARG},
-    {"SQLITE_MAX_FUNCTION_ARG", JTYPE_INT, SQLITE_MAX_FUNCTION_ARG},
-    {"SQLITE_LIMIT_ATTACHED", JTYPE_INT, SQLITE_LIMIT_ATTACHED},
-    {"SQLITE_MAX_ATTACHED", JTYPE_INT, SQLITE_MAX_ATTACHED},
-    {"SQLITE_LIMIT_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_LIMIT_LIKE_PATTERN_LENGTH},
-    {"SQLITE_MAX_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_MAX_LIKE_PATTERN_LENGTH},
-    {"SQLITE_LIMIT_VARIABLE_NUMBER", JTYPE_INT, SQLITE_LIMIT_VARIABLE_NUMBER},
-    {"SQLITE_MAX_VARIABLE_NUMBER", JTYPE_INT, SQLITE_MAX_VARIABLE_NUMBER},
-    {"SQLITE_LIMIT_TRIGGER_DEPTH", JTYPE_INT, SQLITE_LIMIT_TRIGGER_DEPTH},
-    {"SQLITE_MAX_TRIGGER_DEPTH", JTYPE_INT, SQLITE_MAX_TRIGGER_DEPTH},
-    {"SQLITE_LIMIT_WORKER_THREADS", JTYPE_INT, SQLITE_LIMIT_WORKER_THREADS},
-    {"SQLITE_MAX_WORKER_THREADS", JTYPE_INT, SQLITE_MAX_WORKER_THREADS},
-    {0,0}
-  };
-  jfieldID fieldId;
-  const ConfigFlagEntry * pConfFlag;
+Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){
+  jclass klazz;
 
   memset(&S3JniGlobal, 0, sizeof(S3JniGlobal));
-  if( (*env)->GetJavaVM(env, &S3JniGlobal.jvm) ){
+  if( (*env)->GetJavaVM(env, &SJG.jvm) ){
     (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible.");
     return;
   }
-#if 0
-  /* Just for sanity checking... */
-  (void)S3JniGlobal_env_cache(env);
-  if( !S3JniGlobal.envCache.aHead ){
-    (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache.");
-    return;
+
+  /* Grab references to various global classes and objects... */
+  SJG.g.cLong = S3JniRefGlobal((*env)->FindClass(env,"java/lang/Long"));
+  S3JniExceptionIsFatal("Error getting reference to Long class.");
+  SJG.g.ctorLong1 = (*env)->GetMethodID(env, SJG.g.cLong,
+                                         "<init>", "(J)V");
+  S3JniExceptionIsFatal("Error getting reference to Long constructor.");
+
+  SJG.g.cString = S3JniRefGlobal((*env)->FindClass(env,"java/lang/String"));
+  S3JniExceptionIsFatal("Error getting reference to String class.");
+  SJG.g.ctorStringBA =
+    (*env)->GetMethodID(env, SJG.g.cString,
+                        "<init>", "([BLjava/nio/charset/Charset;)V");
+  S3JniExceptionIsFatal("Error getting reference to String(byte[],Charset) ctor.");
+  SJG.g.stringGetBytes =
+    (*env)->GetMethodID(env, SJG.g.cString,
+                        "getBytes", "(Ljava/nio/charset/Charset;)[B");
+  S3JniExceptionIsFatal("Error getting reference to String.getBytes(Charset).");
+
+  { /* java.nio.charset.StandardCharsets.UTF_8 */
+    jfieldID fUtf8;
+    klazz = (*env)->FindClass(env,"java/nio/charset/StandardCharsets");
+    S3JniExceptionIsFatal("Error getting reference to StandardCharsets class.");
+    fUtf8 = (*env)->GetStaticFieldID(env, klazz, "UTF_8",
+                                     "Ljava/nio/charset/Charset;");
+    S3JniExceptionIsFatal("Error getting StandardCharsets.UTF_8 field.");
+    SJG.g.oCharsetUtf8 =
+      S3JniRefGlobal((*env)->GetStaticObjectField(env, klazz, fUtf8));
+    S3JniExceptionIsFatal("Error getting reference to StandardCharsets.UTF_8.");
+    S3JniUnrefLocal(klazz);
   }
-  assert( 1 == S3JniGlobal.metrics.envCacheMisses );
-  assert( env == S3JniGlobal.envCache.aHead->env );
-  assert( 0 != S3JniGlobal.envCache.aHead->g.cObj );
+
+#ifdef SQLITE_ENABLE_FTS5
+  klazz = (*env)->FindClass(env, "org/sqlite/jni/fts5/Fts5PhraseIter");
+  S3JniExceptionIsFatal("Error getting reference to org.sqlite.jni.fts5.Fts5PhraseIter.");
+  SJG.fts5.jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J");
+  S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field.");
+  SJG.fts5.jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "b", "J");
+  S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field.");
+  S3JniUnrefLocal(klazz);
 #endif
 
-  for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){
-    char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I";
-    fieldId = (*env)->GetStaticFieldID(env, jKlazz, pConfFlag->zName, zSig);
-    EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class.");
-    //MARKER(("Setting %s (field=%p) = %d\n", pConfFlag->zName, fieldId, pConfFlag->value));
-    assert(fieldId);
-    switch(pConfFlag->jtype){
-      case JTYPE_INT:
-        (*env)->SetStaticIntField(env, jKlazz, fieldId, (jint)pConfFlag->value);
-        break;
-      case JTYPE_BOOL:
-        (*env)->SetStaticBooleanField(env, jKlazz, fieldId,
-                                      pConfFlag->value ? JNI_TRUE : JNI_FALSE);
-        break;
-    }
-    EXCEPTION_IS_FATAL("Seting a static member of the SQLite3Jni class failed.");
-  }
+  SJG.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  s3jni_oom_fatal( SJG.mutex );
+  SJG.hook.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  s3jni_oom_fatal( SJG.hook.mutex );
+  SJG.nph.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  s3jni_oom_fatal( SJG.nph.mutex );
+  SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  s3jni_oom_fatal( SJG.envCache.mutex );
+  SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  s3jni_oom_fatal( SJG.perDb.mutex );
+  SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  s3jni_oom_fatal( SJG.autoExt.mutex );
+
+#if S3JNI_METRICS_MUTEX
+  SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  s3jni_oom_fatal( SJG.metrics.mutex );
+#endif
+
+  sqlite3_shutdown()
+    /* So that it becomes legal for Java-level code to call
+    ** sqlite3_config(). */;
 }
diff --git a/libsql-sqlite3/ext/jni/src/c/sqlite3-jni.h b/libsql-sqlite3/ext/jni/src/c/sqlite3-jni.h
index bcb55c4f1c..bf6df7ac94 100644
--- a/libsql-sqlite3/ext/jni/src/c/sqlite3-jni.h
+++ b/libsql-sqlite3/ext/jni/src/c/sqlite3-jni.h
@@ -1,1936 +1,2355 @@
 /* DO NOT EDIT THIS FILE - it is machine generated */
 #include <jni.h>
-/* Header for class org_sqlite_jni_SQLite3Jni */
+/* Header for class org_sqlite_jni_capi_CApi */
 
-#ifndef _Included_org_sqlite_jni_SQLite3Jni
-#define _Included_org_sqlite_jni_SQLite3Jni
+#ifndef _Included_org_sqlite_jni_capi_CApi
+#define _Included_org_sqlite_jni_capi_CApi
 #ifdef __cplusplus
 extern "C" {
 #endif
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS 0L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DENY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DENY 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER 5L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW 6L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER 7L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DELETE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DELETE 9L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX 10L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE 11L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX 12L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE 13L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER 14L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW 15L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER 16L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW 17L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INSERT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INSERT 18L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA
-#define org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA 19L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_READ
-#define org_sqlite_jni_SQLite3Jni_SQLITE_READ 20L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_SELECT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_SELECT 21L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION 22L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE 23L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH 24L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETACH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DETACH 25L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE 26L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX 27L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE 28L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE 29L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE 30L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION 31L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT 32L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE 33L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATIC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STATIC 0LL
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT -1LL
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY 5L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT 0L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC 5L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH 6L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE 7L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS 9L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX 10L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX 11L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE 13L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE 14L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE 15L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG 16L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI 17L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2 18L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2 19L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG 21L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE 22L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE 23L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ 24L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ 25L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL 26L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC 27L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE 28L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE 29L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TEXT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TEXT 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_BLOB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_BLOB 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_NULL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_NULL 5L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME 1000L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE 1001L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY 1002L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG 1007L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP 1008L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE 1009L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE 1010L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML 1013L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL 1014L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW 1015L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX 1019L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED 0L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT 7L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE 9L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS 10L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL 12L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX 12L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF8
-#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF8 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16
-#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT 5L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE 6L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER 7L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY 9L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL 10L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE 11L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME 12L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA 14L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER 15L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME 16L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE 18L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE 19L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED 20L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC 21L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO 22L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE 23L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK 24L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS 25L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU 26L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER 27L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER 28L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE 29L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB 30L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT 34L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION 35L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT 36L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE 37L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES 38L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START 39L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER 40L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE 41L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE 42L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE 0L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING
-#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K 16L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K 32L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K 64L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K 128L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K 256L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND 512L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL 1024L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE 8192L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC 16384L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI 64L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY 128L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX 32768L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX 65536L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE 131072L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE 262144L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE 33554432L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW 16777216L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB 256L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL 2048L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB 512L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL 4096L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB 1024L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL 8192L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL 16384L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL 524288L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE 16L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OK 0L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_PERM
-#define org_sqlite_jni_SQLite3Jni_SQLITE_PERM 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY 5L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED 6L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM
-#define org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM 7L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT 9L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR 10L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT 11L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND
-#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND 12L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FULL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FULL 13L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN 14L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL 15L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY 16L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA
-#define org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA 17L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG 18L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT 19L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH 20L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE 21L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS 22L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH 23L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT 24L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_RANGE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_RANGE 25L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB 26L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE 27L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING
-#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING 28L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROW
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ROW 100L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DONE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DONE 101L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ 257L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY 513L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT 769L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ 266L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ 522L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE 778L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC 1034L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC 1290L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE 1546L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT 1802L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK 2058L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK 2314L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE 2570L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED 2826L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM 3082L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS 3338L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK 3850L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE 4106L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE 4362L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN 4618L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE 4874L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK 5130L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP 5386L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK 5642L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT 5898L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP 6154L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH 6410L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH 6666L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE 6922L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH 7178L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC 7434L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC 7690L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA 8202L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS 8458L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE 262L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB 518L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY 261L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT 517L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT 773L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR 270L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR 526L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH 782L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH 1038L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK 1550L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB 267L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE 523L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX 779L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY 264L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK 520L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK 776L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED 1032L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT 1288L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY 1544L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK 516L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK 275L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK 531L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY 787L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION 1043L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL 1299L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY 1555L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER 1811L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE 2067L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB 2323L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID 2579L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED 2835L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE 3091L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL 283L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK 539L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX 284L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER
-#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER 279L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY 256L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED 0L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE 5L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK 6L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE 7L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT 9L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE 5L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN 6L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS 7L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED
-#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED 99L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY 16L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE 0L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC 2048L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY 524288L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS 2097152L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE 8L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT 16L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE 32L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH 64L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE 65L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB 66L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP 67L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE 68L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT 69L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL 71L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS 72L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT 73L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET 74L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION
-#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT
-#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS 2L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY
-#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS
-#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS 4L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK
-#define org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK 1L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_FAIL
-#define org_sqlite_jni_SQLite3Jni_SQLITE_FAIL 3L
-#undef org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE
-#define org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DENY
+#define org_sqlite_jni_capi_CApi_SQLITE_DENY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IGNORE
+#define org_sqlite_jni_capi_CApi_SQLITE_IGNORE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DELETE
+#define org_sqlite_jni_capi_CApi_SQLITE_DELETE 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INSERT
+#define org_sqlite_jni_capi_CApi_SQLITE_INSERT 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PRAGMA
+#define org_sqlite_jni_capi_CApi_SQLITE_PRAGMA 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_READ 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SELECT
+#define org_sqlite_jni_capi_CApi_SQLITE_SELECT 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION
+#define org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UPDATE
+#define org_sqlite_jni_capi_CApi_SQLITE_UPDATE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ATTACH
+#define org_sqlite_jni_capi_CApi_SQLITE_ATTACH 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DETACH
+#define org_sqlite_jni_capi_CApi_SQLITE_DETACH 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_REINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_REINDEX 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ANALYZE
+#define org_sqlite_jni_capi_CApi_SQLITE_ANALYZE 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE 30L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_FUNCTION 31L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT
+#define org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE 33L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATIC
+#define org_sqlite_jni_capi_CApi_SQLITE_STATIC 0LL
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT
+#define org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT -1LL
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTEGER
+#define org_sqlite_jni_capi_CApi_SQLITE_INTEGER 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FLOAT
+#define org_sqlite_jni_capi_CApi_SQLITE_FLOAT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TEXT
+#define org_sqlite_jni_capi_CApi_SQLITE_TEXT 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BLOB
+#define org_sqlite_jni_capi_CApi_SQLITE_BLOB 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NULL
+#define org_sqlite_jni_capi_CApi_SQLITE_NULL 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME 1000L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE 1001L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY 1002L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG 1007L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP 1008L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE 1009L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE 1010L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML 1013L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL 1014L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW 1015L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX 1019L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF8
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF8 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16LE
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16LE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16BE
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16BE 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB 30L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT 34L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION 35L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT 36L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE 37L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES 38L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START 39L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER 40L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE 41L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE 42L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K 128L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K 256L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND 512L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL 1024L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE 8192L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC 16384L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY 128L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX 32768L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX 65536L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE 131072L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE 262144L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW 16777216L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE 33554432L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT
+#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OK
+#define org_sqlite_jni_capi_CApi_SQLITE_OK 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTERNAL
+#define org_sqlite_jni_capi_CApi_SQLITE_INTERNAL 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PERM
+#define org_sqlite_jni_capi_CApi_SQLITE_PERM 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT
+#define org_sqlite_jni_capi_CApi_SQLITE_ABORT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOMEM
+#define org_sqlite_jni_capi_CApi_SQLITE_NOMEM 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT
+#define org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_FULL 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL
+#define org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_EMPTY
+#define org_sqlite_jni_capi_CApi_SQLITE_EMPTY 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_SCHEMA 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TOOBIG
+#define org_sqlite_jni_capi_CApi_SQLITE_TOOBIG 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_MISMATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_MISMATCH 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_MISUSE
+#define org_sqlite_jni_capi_CApi_SQLITE_MISUSE 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOLFS
+#define org_sqlite_jni_capi_CApi_SQLITE_NOLFS 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH
+#define org_sqlite_jni_capi_CApi_SQLITE_AUTH 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FORMAT
+#define org_sqlite_jni_capi_CApi_SQLITE_FORMAT 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_RANGE
+#define org_sqlite_jni_capi_CApi_SQLITE_RANGE 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTADB
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTADB 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING
+#define org_sqlite_jni_capi_CApi_SQLITE_WARNING 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ROW
+#define org_sqlite_jni_capi_CApi_SQLITE_ROW 100L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DONE
+#define org_sqlite_jni_capi_CApi_SQLITE_DONE 101L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ 257L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY 513L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT 769L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ 266L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ 522L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE 778L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC 1034L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC 1290L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE 1546L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT 1802L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK 2058L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK 2314L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE 2570L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED 2826L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM 3082L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS 3338L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK 3850L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE 4106L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE 4362L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN 4618L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE 4874L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK 5130L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP 5386L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK 5642L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT 5898L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP 6154L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH 6410L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH 6666L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE 6922L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH 7178L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC 7434L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC 7690L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA 8202L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS 8458L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE 262L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB 518L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY 261L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT 517L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT 773L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR 270L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR 526L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH 782L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH 1038L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK 1550L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB 267L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE 523L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX 779L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY 264L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK 520L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK 776L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED 1032L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT 1288L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY 1544L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK 516L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK 275L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK 531L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY 787L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION 1043L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL 1299L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY 1555L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER 1811L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE 2067L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB 2323L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID 2579L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED 2835L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE 3091L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL 283L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK 539L
+#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX 284L
+#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER
+#define org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER 279L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY
+#define org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY 256L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY
+#define org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED 99L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_READ 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC
+#define org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC 2048L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY 524288L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS
+#define org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS 2097152L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE 65L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB 66L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP 67L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE 68L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT 69L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL 71L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS 72L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT 73L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET 74L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FAIL
+#define org_sqlite_jni_capi_CApi_SQLITE_FAIL 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_REPLACE
+#define org_sqlite_jni_capi_CApi_SQLITE_REPLACE 5L
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
+ * Class:     org_sqlite_jni_capi_CApi
  * Method:    init
  * Signature: ()V
  */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_init
   (JNIEnv *, jclass);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    uncacheJniEnv
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_java_uncache_thread
  * Signature: ()Z
  */
-JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_1thread
   (JNIEnv *, jclass);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_auto_extension
- * Signature: (Lorg/sqlite/jni/AutoExtension;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_aggregate_context
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Z)J
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1auto_1extension
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_blob
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1blob
-  (JNIEnv *, jclass, jobject, jint, jbyteArray, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_double
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;ID)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1double
-  (JNIEnv *, jclass, jobject, jint, jdouble);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_int
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int
-  (JNIEnv *, jclass, jobject, jint, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_int64
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int64
-  (JNIEnv *, jclass, jobject, jint, jlong);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_null
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1null
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_parameter_count
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1count
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_parameter_index
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;[B)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1index
-  (JNIEnv *, jclass, jobject, jbyteArray);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_text
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text
-  (JNIEnv *, jclass, jobject, jint, jbyteArray, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_zeroblob
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob
-  (JNIEnv *, jclass, jobject, jint, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_bind_zeroblob64
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob64
-  (JNIEnv *, jclass, jobject, jint, jlong);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_busy_handler
- * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/BusyHandler;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1handler
-  (JNIEnv *, jclass, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_busy_timeout
- * Signature: (Lorg/sqlite/jni/sqlite3;I)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1timeout
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_cancel_auto_extension
- * Signature: (Lorg/sqlite/jni/AutoExtension;)Z
- */
-JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1cancel_1auto_1extension
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_changes
- * Signature: (Lorg/sqlite/jni/sqlite3;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_changes64
- * Signature: (Lorg/sqlite/jni/sqlite3;)J
- */
-JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes64
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_clear_bindings
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1clear_1bindings
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_close
- * Signature: (Lorg/sqlite/jni/sqlite3;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_close_v2
- * Signature: (Lorg/sqlite/jni/sqlite3;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close_1v2
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_blob
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B
- */
-JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1blob
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_bytes
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_bytes16
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes16
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_count
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1count
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_double
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)D
- */
-JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1double
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_int
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_int64
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)J
- */
-JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int64
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_name
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1name
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_database_name
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1database_1name
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_origin_name
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1origin_1name
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_table_name
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1table_1name
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_text16
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text16
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_text
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B
- */
-JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_type
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1type
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_column_value
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Lorg/sqlite/jni/sqlite3_value;
- */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1value
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_collation_needed
- * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CollationNeeded;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1collation_1needed
-  (JNIEnv *, jclass, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_context_db_handle
- * Signature: (Lorg/sqlite/jni/sqlite3_context;)Lorg/sqlite/jni/sqlite3;
- */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1context_1db_1handle
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_commit_hook
- * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CommitHook;)Lorg/sqlite/jni/CommitHook;
- */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1commit_1hook
-  (JNIEnv *, jclass, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_compileoption_get
- * Signature: (I)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1get
-  (JNIEnv *, jclass, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_compileoption_used
- * Signature: (Ljava/lang/String;)Z
- */
-JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1used
-  (JNIEnv *, jclass, jstring);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_create_collation
- * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/Collation;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1collation
-  (JNIEnv *, jclass, jobject, jstring, jint, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_create_function
- * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/SQLFunction;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1function
-  (JNIEnv *, jclass, jobject, jstring, jint, jint, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_data_count
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1data_1count
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_db_filename
- * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename
-  (JNIEnv *, jclass, jobject, jstring);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_db_config
- * Signature: (Lorg/sqlite/jni/sqlite3;IILorg/sqlite/jni/OutputPointer/Int32;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2
-  (JNIEnv *, jclass, jobject, jint, jint, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_db_config
- * Signature: (Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2
-  (JNIEnv *, jclass, jobject, jint, jstring);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_db_status
- * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1status
-  (JNIEnv *, jclass, jobject, jint, jobject, jobject, jboolean);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_errcode
- * Signature: (Lorg/sqlite/jni/sqlite3;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errcode
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_expanded_sql
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1expanded_1sql
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_extended_errcode
- * Signature: (Lorg/sqlite/jni/sqlite3;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1errcode
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_extended_result_codes
- * Signature: (Lorg/sqlite/jni/sqlite3;Z)Z
- */
-JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1result_1codes
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1aggregate_1context
   (JNIEnv *, jclass, jobject, jboolean);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_errmsg
- * Signature: (Lorg/sqlite/jni/sqlite3;)Ljava/lang/String;
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_auto_extension
+ * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)I
  */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errmsg
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1auto_1extension
   (JNIEnv *, jclass, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_backup_finish
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1finish
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_backup_init
+ * Signature: (JLjava/lang/String;JLjava/lang/String;)Lorg/sqlite/jni/capi/sqlite3_backup;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1init
+  (JNIEnv *, jclass, jlong, jstring, jlong, jstring);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_backup_pagecount
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1pagecount
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_backup_remaining
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1remaining
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_backup_step
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1step
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_blob
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1blob
+  (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_double
+ * Signature: (JID)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1double
+  (JNIEnv *, jclass, jlong, jint, jdouble);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_int
+ * Signature: (JII)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int
+  (JNIEnv *, jclass, jlong, jint, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_int64
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int64
+  (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_java_object
+ * Signature: (JILjava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1java_1object
+  (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_null
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1null
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_parameter_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1count
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_parameter_index
+ * Signature: (J[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1index
+  (JNIEnv *, jclass, jlong, jbyteArray);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_parameter_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1name
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_text
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text
+  (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_text16
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text16
+  (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_value
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1value
+  (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_zeroblob
+ * Signature: (JII)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob
+  (JNIEnv *, jclass, jlong, jint, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_bind_zeroblob64
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob64
+  (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_blob_bytes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1bytes
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_blob_close
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1close
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_blob_open
+ * Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;JILorg/sqlite/jni/capi/OutputPointer/sqlite3_blob;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1open
+  (JNIEnv *, jclass, jlong, jstring, jstring, jstring, jlong, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_blob_read
+ * Signature: (J[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read
+  (JNIEnv *, jclass, jlong, jbyteArray, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_blob_reopen
+ * Signature: (JJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1reopen
+  (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_blob_write
+ * Signature: (J[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write
+  (JNIEnv *, jclass, jlong, jbyteArray, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_busy_handler
+ * Signature: (JLorg/sqlite/jni/capi/BusyHandlerCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1handler
+  (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_busy_timeout
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1timeout
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_cancel_auto_extension
+ * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1cancel_1auto_1extension
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_changes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_changes64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes64
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_clear_bindings
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1clear_1bindings
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_close
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_close_v2
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close_1v2
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_blob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1blob
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_bytes
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_bytes16
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes16
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_decltype
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1decltype
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_double
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)D
+ */
+JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1double
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_int
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_int64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int64
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1name
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_database_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_origin_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1origin_1name
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_table_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1table_1name
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_text
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_text16
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text16
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_type
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1type
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_column_value
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Lorg/sqlite/jni/capi/sqlite3_value;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1value
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_collation_needed
+ * Signature: (JLorg/sqlite/jni/capi/CollationNeededCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1collation_1needed
+  (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_commit_hook
+ * Signature: (JLorg/sqlite/jni/capi/CommitHookCallback;)Lorg/sqlite/jni/capi/CommitHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1commit_1hook
+  (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_compileoption_get
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1get
+  (JNIEnv *, jclass, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_compileoption_used
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1used
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_complete
+ * Signature: ([B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1complete
+  (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_config
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__I
+  (JNIEnv *, jclass, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_config
+ * Signature: (Lorg/sqlite/jni/capi/ConfigSqllogCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigSqllogCallback_2
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_config
+ * Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigLogCallback_2
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_context_db_handle
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)Lorg/sqlite/jni/capi/sqlite3;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1context_1db_1handle
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_create_collation
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/capi/CollationCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1collation
+  (JNIEnv *, jclass, jobject, jstring, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_create_function
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/capi/SQLFunction;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1function
+  (JNIEnv *, jclass, jobject, jstring, jint, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_data_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1data_1count
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_db_config
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;IILorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2
+  (JNIEnv *, jclass, jobject, jint, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_db_config
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2
+  (JNIEnv *, jclass, jobject, jint, jstring);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_db_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1name
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_db_filename
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1filename
+  (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_db_handle
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Lorg/sqlite/jni/capi/sqlite3;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1handle
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_db_readonly
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1readonly
+  (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_db_release_memory
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1release_1memory
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_db_status
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1status
+  (JNIEnv *, jclass, jobject, jint, jobject, jobject, jboolean);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_errcode
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errcode
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_errmsg
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errmsg
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_error_offset
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1error_1offset
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
  * Method:    sqlite3_errstr
  * Signature: (I)Ljava/lang/String;
  */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errstr
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errstr
   (JNIEnv *, jclass, jint);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_error_offset
- * Signature: (Lorg/sqlite/jni/sqlite3;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_expanded_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1error_1offset
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1expanded_1sql
   (JNIEnv *, jclass, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_extended_errcode
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1errcode
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_extended_result_codes
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes
+  (JNIEnv *, jclass, jobject, jboolean);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_get_autocommit
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1autocommit
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_get_auxdata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1auxdata
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
  * Method:    sqlite3_finalize
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I
+ * Signature: (J)I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1finalize
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1finalize
+  (JNIEnv *, jclass, jlong);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
+ * Class:     org_sqlite_jni_capi_CApi
  * Method:    sqlite3_initialize
  * Signature: ()I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1initialize
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1initialize
   (JNIEnv *, jclass);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_last_insert_rowid
- * Signature: (Lorg/sqlite/jni/sqlite3;)J
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_interrupt
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)V
  */
-JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1last_1insert_1rowid
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1interrupt
   (JNIEnv *, jclass, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_libversion
- * Signature: ()Ljava/lang/String;
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_is_interrupted
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Z
  */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion
-  (JNIEnv *, jclass);
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1is_1interrupted
+  (JNIEnv *, jclass, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_libversion_number
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_keyword_check
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1check
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_keyword_count
  * Signature: ()I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion_1number
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1count
   (JNIEnv *, jclass);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_open
- * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_keyword_name
+ * Signature: (I)Ljava/lang/String;
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open
-  (JNIEnv *, jclass, jstring, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_open_v2
- * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;ILjava/lang/String;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open_1v2
-  (JNIEnv *, jclass, jstring, jobject, jint, jstring);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_prepare
- * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare
-  (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_prepare_v2
- * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2
-  (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_prepare_v3
- * Signature: (Lorg/sqlite/jni/sqlite3;[BIILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3
-  (JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_progress_handler
- * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/ProgressHandler;)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1progress_1handler
-  (JNIEnv *, jclass, jobject, jint, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_reset
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_reset_auto_extension
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset_1auto_1extension
-  (JNIEnv *, jclass);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_double
- * Signature: (Lorg/sqlite/jni/sqlite3_context;D)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1double
-  (JNIEnv *, jclass, jobject, jdouble);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_error
- * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error
-  (JNIEnv *, jclass, jobject, jbyteArray, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_error_toobig
- * Signature: (Lorg/sqlite/jni/sqlite3_context;)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1toobig
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_error_nomem
- * Signature: (Lorg/sqlite/jni/sqlite3_context;)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1nomem
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_error_code
- * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1code
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_null
- * Signature: (Lorg/sqlite/jni/sqlite3_context;)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1null
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_int
- * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_int64
- * Signature: (Lorg/sqlite/jni/sqlite3_context;J)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int64
-  (JNIEnv *, jclass, jobject, jlong);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_java_object
- * Signature: (Lorg/sqlite/jni/sqlite3_context;Ljava/lang/Object;)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1java_1object
-  (JNIEnv *, jclass, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_value
- * Signature: (Lorg/sqlite/jni/sqlite3_context;Lorg/sqlite/jni/sqlite3_value;)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1value
-  (JNIEnv *, jclass, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_zeroblob
- * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob
-  (JNIEnv *, jclass, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_zeroblob64
- * Signature: (Lorg/sqlite/jni/sqlite3_context;J)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob64
-  (JNIEnv *, jclass, jobject, jlong);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_blob
- * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob
-  (JNIEnv *, jclass, jobject, jbyteArray, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_blob64
- * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJ)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob64
-  (JNIEnv *, jclass, jobject, jbyteArray, jlong);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_text
- * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text
-  (JNIEnv *, jclass, jobject, jbyteArray, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_result_text64
- * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJI)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64
-  (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_status
- * Signature: (ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status
-  (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_status64
- * Signature: (ILorg/sqlite/jni/OutputPointer/Int64;Lorg/sqlite/jni/OutputPointer/Int64;Z)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status64
-  (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_rollback_hook
- * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/RollbackHook;)Lorg/sqlite/jni/RollbackHook;
- */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1rollback_1hook
-  (JNIEnv *, jclass, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_set_authorizer
- * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/Authorizer;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1authorizer
-  (JNIEnv *, jclass, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_set_last_insert_rowid
- * Signature: (Lorg/sqlite/jni/sqlite3;J)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1last_1insert_1rowid
-  (JNIEnv *, jclass, jobject, jlong);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_sleep
- * Signature: (I)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sleep
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1name
   (JNIEnv *, jclass, jint);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_sourceid
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_last_insert_rowid
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1last_1insert_1rowid
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_libversion
  * Signature: ()Ljava/lang/String;
  */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sourceid
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion
   (JNIEnv *, jclass);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_sql
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sql
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_step
- * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1step
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_strglob
- * Signature: ([B[B)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strglob
-  (JNIEnv *, jclass, jbyteArray, jbyteArray);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_strlike
- * Signature: ([B[BI)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strlike
-  (JNIEnv *, jclass, jbyteArray, jbyteArray, jint);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_threadsafe
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_libversion_number
  * Signature: ()I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1threadsafe
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion_1number
   (JNIEnv *, jclass);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_total_changes
- * Signature: (Lorg/sqlite/jni/sqlite3;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_limit
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;II)I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1limit
+  (JNIEnv *, jclass, jobject, jint, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_normalized_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1normalized_1sql
   (JNIEnv *, jclass, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_total_changes64
- * Signature: (Lorg/sqlite/jni/sqlite3;)J
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_open
+ * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;)I
  */
-JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes64
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open
+  (JNIEnv *, jclass, jstring, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_trace_v2
- * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/Tracer;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_open_v2
+ * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;ILjava/lang/String;)I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1trace_1v2
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open_1v2
+  (JNIEnv *, jclass, jstring, jobject, jint, jstring);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_prepare
+ * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare
+  (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_prepare_v2
+ * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v2
+  (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_prepare_v3
+ * Signature: (J[BIILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v3
+  (JNIEnv *, jclass, jlong, jbyteArray, jint, jint, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_preupdate_blobwrite
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1blobwrite
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_preupdate_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1count
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_preupdate_depth
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1depth
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_preupdate_hook
+ * Signature: (JLorg/sqlite/jni/capi/PreupdateHookCallback;)Lorg/sqlite/jni/capi/PreupdateHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1hook
+  (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_preupdate_new
+ * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1new
+  (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_preupdate_old
+ * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1old
+  (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_progress_handler
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/ProgressHandlerCallback;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1progress_1handler
   (JNIEnv *, jclass, jobject, jint, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_update_hook
- * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/UpdateHook;)Lorg/sqlite/jni/UpdateHook;
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_randomness
+ * Signature: ([B)V
  */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1update_1hook
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1randomness
+  (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_release_memory
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1release_1memory
+  (JNIEnv *, jclass, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_reset
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_reset_auto_extension
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset_1auto_1extension
+  (JNIEnv *, jclass);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_double
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;D)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1double
+  (JNIEnv *, jclass, jobject, jdouble);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_error
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error
+  (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_error_toobig
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1toobig
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_error_nomem
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1nomem
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_error_code
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1code
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_null
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_int
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int
+  (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_int64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int64
+  (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_java_object
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/lang/Object;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1object
   (JNIEnv *, jclass, jobject, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_blob
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_value
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Lorg/sqlite/jni/capi/sqlite3_value;)V
  */
-JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1blob
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1value
+  (JNIEnv *, jclass, jobject, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_bytes
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_zeroblob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob
+  (JNIEnv *, jclass, jobject, jint);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_bytes16
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_zeroblob64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes16
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob64
+  (JNIEnv *, jclass, jobject, jlong);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_double
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)D
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_blob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
  */
-JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1double
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob
+  (JNIEnv *, jclass, jobject, jbyteArray, jint);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_dupe
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)Lorg/sqlite/jni/sqlite3_value;
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_blob64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJ)V
  */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1dupe
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob64
+  (JNIEnv *, jclass, jobject, jbyteArray, jlong);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_encoding
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_text
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1encoding
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text
+  (JNIEnv *, jclass, jobject, jbyteArray, jint);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_free
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)V
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_result_text64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJI)V
  */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1free
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text64
+  (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_int
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_rollback_hook
+ * Signature: (JLorg/sqlite/jni/capi/RollbackHookCallback;)Lorg/sqlite/jni/capi/RollbackHookCallback;
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1rollback_1hook
+  (JNIEnv *, jclass, jlong, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_int64
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)J
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_set_authorizer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Lorg/sqlite/jni/capi/AuthorizerCallback;)I
  */
-JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int64
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1authorizer
+  (JNIEnv *, jclass, jobject, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_java_object
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/Object;
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_set_auxdata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;ILjava/lang/Object;)V
  */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1java_1object
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1auxdata
+  (JNIEnv *, jclass, jobject, jint, jobject);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_text
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String;
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_set_last_insert_rowid
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;J)V
  */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text
-  (JNIEnv *, jclass, jobject);
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1last_1insert_1rowid
+  (JNIEnv *, jclass, jobject, jlong);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_text_utf8
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B
- */
-JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text_1utf8
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_text16
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B
- */
-JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_text16le
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B
- */
-JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16le
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_text16be
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B
- */
-JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16be
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_type
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1type
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_numeric_type
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1numeric_1type
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_nochange
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1nochange
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_frombind
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1frombind
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_value_subtype
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1subtype
-  (JNIEnv *, jclass, jobject);
-
-/*
- * Class:     org_sqlite_jni_SQLite3Jni
+ * Class:     org_sqlite_jni_capi_CApi
  * Method:    sqlite3_shutdown
  * Signature: ()I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1shutdown
   (JNIEnv *, jclass);
 
 /*
- * Class:     org_sqlite_jni_SQLite3Jni
- * Method:    sqlite3_do_something_for_developer
- * Signature: ()V
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_sleep
+ * Signature: (I)I
  */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1do_1something_1for_1developer
-  (JNIEnv *, jclass);
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sleep
+  (JNIEnv *, jclass, jint);
 
-#ifdef __cplusplus
-}
-#endif
-#endif
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class org_sqlite_jni_Fts5ExtensionApi */
-
-#ifndef _Included_org_sqlite_jni_Fts5ExtensionApi
-#define _Included_org_sqlite_jni_Fts5ExtensionApi
-#ifdef __cplusplus
-extern "C" {
-#endif
 /*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    getInstance
- * Signature: ()Lorg/sqlite/jni/Fts5ExtensionApi;
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_sourceid
+ * Signature: ()Ljava/lang/String;
  */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_getInstance
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sourceid
   (JNIEnv *, jclass);
 
 /*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xColumnCount
- * Signature: (Lorg/sqlite/jni/Fts5Context;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnCount
-  (JNIEnv *, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xColumnSize
- * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnSize
-  (JNIEnv *, jobject, jobject, jint, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xColumnText
- * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/String;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnText
-  (JNIEnv *, jobject, jobject, jint, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xColumnTotalSize
- * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int64;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnTotalSize
-  (JNIEnv *, jobject, jobject, jint, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xGetAuxdata
- * Signature: (Lorg/sqlite/jni/Fts5Context;Z)Ljava/lang/Object;
- */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xGetAuxdata
-  (JNIEnv *, jobject, jobject, jboolean);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xInst
- * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInst
-  (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xInstCount
- * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int32;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInstCount
-  (JNIEnv *, jobject, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xPhraseCount
- * Signature: (Lorg/sqlite/jni/Fts5Context;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseCount
-  (JNIEnv *, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xPhraseFirst
- * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirst
-  (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xPhraseFirstColumn
- * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirstColumn
-  (JNIEnv *, jobject, jobject, jint, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xPhraseNext
- * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNext
-  (JNIEnv *, jobject, jobject, jobject, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xPhraseNextColumn
- * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNextColumn
-  (JNIEnv *, jobject, jobject, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xPhraseSize
- * Signature: (Lorg/sqlite/jni/Fts5Context;I)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseSize
-  (JNIEnv *, jobject, jobject, jint);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xQueryPhrase
- * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5ExtensionApi/xQueryPhraseCallback;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xQueryPhrase
-  (JNIEnv *, jobject, jobject, jint, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xRowCount
- * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int64;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowCount
-  (JNIEnv *, jobject, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xRowid
- * Signature: (Lorg/sqlite/jni/Fts5Context;)J
- */
-JNIEXPORT jlong JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowid
-  (JNIEnv *, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xSetAuxdata
- * Signature: (Lorg/sqlite/jni/Fts5Context;Ljava/lang/Object;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xSetAuxdata
-  (JNIEnv *, jobject, jobject, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xTokenize
- * Signature: (Lorg/sqlite/jni/Fts5Context;[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xTokenize
-  (JNIEnv *, jobject, jobject, jbyteArray, jobject);
-
-/*
- * Class:     org_sqlite_jni_Fts5ExtensionApi
- * Method:    xUserData
- * Signature: (Lorg/sqlite/jni/Fts5Context;)Ljava/lang/Object;
- */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xUserData
-  (JNIEnv *, jobject, jobject);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class org_sqlite_jni_fts5_api */
-
-#ifndef _Included_org_sqlite_jni_fts5_api
-#define _Included_org_sqlite_jni_fts5_api
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class:     org_sqlite_jni_fts5_api
- * Method:    getInstanceForDb
- * Signature: (Lorg/sqlite/jni/sqlite3;)Lorg/sqlite/jni/fts5_api;
- */
-JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_1api_getInstanceForDb
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sql
   (JNIEnv *, jclass, jobject);
 
 /*
- * Class:     org_sqlite_jni_fts5_api
- * Method:    xCreateFunction
- * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5_extension_function;)I
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_status
+ * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1api_xCreateFunction
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status
+  (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_status64
+ * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int64;Lorg/sqlite/jni/capi/OutputPointer/Int64;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status64
+  (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_step
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1step
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_stmt_busy
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1busy
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_stmt_explain
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1explain
+  (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_stmt_isexplain
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1isexplain
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_stmt_readonly
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1readonly
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_stmt_status
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;IZ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1status
+  (JNIEnv *, jclass, jobject, jint, jboolean);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_strglob
+ * Signature: ([B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strglob
+  (JNIEnv *, jclass, jbyteArray, jbyteArray);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_strlike
+ * Signature: ([B[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strlike
+  (JNIEnv *, jclass, jbyteArray, jbyteArray, jint);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_system_errno
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1system_1errno
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_table_column_metadata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1table_1column_1metadata
+  (JNIEnv *, jclass, jobject, jstring, jstring, jstring, jobject, jobject, jobject, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_threadsafe
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1threadsafe
+  (JNIEnv *, jclass);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_total_changes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_total_changes64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes64
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_trace_v2
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/TraceV2Callback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1trace_1v2
+  (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_txn_state
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1txn_1state
+  (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_update_hook
+ * Signature: (JLorg/sqlite/jni/capi/UpdateHookCallback;)Lorg/sqlite/jni/capi/UpdateHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1update_1hook
+  (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_blob
+ * Signature: (J)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1blob
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_bytes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_bytes16
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes16
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_double
+ * Signature: (J)D
+ */
+JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1double
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_dup
+ * Signature: (J)Lorg/sqlite/jni/capi/sqlite3_value;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1dup
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_encoding
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1encoding
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_free
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1free
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_frombind
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1frombind
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_int
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_int64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int64
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_java_object
+ * Signature: (J)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1object
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_nochange
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nochange
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_numeric_type
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1numeric_1type
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_subtype
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1subtype
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_text
+ * Signature: (J)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_text16
+ * Signature: (J)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text16
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_value_type
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1type
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sqlite_jni_capi_CApi
+ * Method:    sqlite3_jni_internal_details
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1internal_1details
+  (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_sqlite_jni_capi_SQLTester */
+
+#ifndef _Included_org_sqlite_jni_capi_SQLTester
+#define _Included_org_sqlite_jni_capi_SQLTester
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     org_sqlite_jni_capi_SQLTester
+ * Method:    strglob
+ * Signature: ([B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_SQLTester_strglob
+  (JNIEnv *, jclass, jbyteArray, jbyteArray);
+
+/*
+ * Class:     org_sqlite_jni_capi_SQLTester
+ * Method:    installCustomExtensions
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions
+  (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_sqlite_jni_fts5_Fts5ExtensionApi */
+
+#ifndef _Included_org_sqlite_jni_fts5_Fts5ExtensionApi
+#define _Included_org_sqlite_jni_fts5_Fts5ExtensionApi
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    getInstance
+ * Signature: ()Lorg/sqlite/jni/fts5/Fts5ExtensionApi;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_getInstance
+  (JNIEnv *, jclass);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xColumnCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnCount
+  (JNIEnv *, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xColumnSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnSize
+  (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xColumnText
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnText
+  (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xColumnTotalSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int64;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnTotalSize
+  (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xGetAuxdata
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Z)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xGetAuxdata
+  (JNIEnv *, jobject, jobject, jboolean);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xInst
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInst
+  (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xInstCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInstCount
+  (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xPhraseCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseCount
+  (JNIEnv *, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xPhraseFirst
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirst
+  (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xPhraseFirstColumn
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirstColumn
+  (JNIEnv *, jobject, jobject, jint, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xPhraseNext
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNext
+  (JNIEnv *, jobject, jobject, jobject, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xPhraseNextColumn
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNextColumn
+  (JNIEnv *, jobject, jobject, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xPhraseSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseSize
+  (JNIEnv *, jobject, jobject, jint);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xQueryPhrase
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5ExtensionApi/XQueryPhraseCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xQueryPhrase
+  (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xRowCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int64;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowCount
+  (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xRowid
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowid
+  (JNIEnv *, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xSetAuxdata
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Ljava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xSetAuxdata
+  (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xTokenize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xTokenize
+  (JNIEnv *, jobject, jobject, jbyteArray, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method:    xUserData
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xUserData
+  (JNIEnv *, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_sqlite_jni_fts5_fts5_api */
+
+#ifndef _Included_org_sqlite_jni_fts5_fts5_api
+#define _Included_org_sqlite_jni_fts5_fts5_api
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_sqlite_jni_fts5_fts5_api_iVersion
+#define org_sqlite_jni_fts5_fts5_api_iVersion 2L
+/*
+ * Class:     org_sqlite_jni_fts5_fts5_api
+ * Method:    getInstanceForDb
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Lorg/sqlite/jni/fts5/fts5_api;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_fts5_1api_getInstanceForDb
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_fts5_fts5_api
+ * Method:    xCreateFunction
+ * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5/fts5_extension_function;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1api_xCreateFunction
   (JNIEnv *, jobject, jstring, jobject, jobject);
 
 #ifdef __cplusplus
@@ -1939,51 +2358,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1api_xCreateFunction
 #endif
 /* DO NOT EDIT THIS FILE - it is machine generated */
 #include <jni.h>
-/* Header for class org_sqlite_jni_fts5_tokenizer */
+/* Header for class org_sqlite_jni_fts5_fts5_tokenizer */
 
-#ifndef _Included_org_sqlite_jni_fts5_tokenizer
-#define _Included_org_sqlite_jni_fts5_tokenizer
+#ifndef _Included_org_sqlite_jni_fts5_fts5_tokenizer
+#define _Included_org_sqlite_jni_fts5_fts5_tokenizer
 #ifdef __cplusplus
 extern "C" {
 #endif
 /*
- * Class:     org_sqlite_jni_fts5_tokenizer
+ * Class:     org_sqlite_jni_fts5_fts5_tokenizer
  * Method:    xTokenize
- * Signature: (Lorg/sqlite/jni/Fts5Tokenizer;I[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Tokenizer;I[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I
  */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1tokenizer_xTokenize
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1tokenizer_xTokenize
   (JNIEnv *, jobject, jobject, jint, jbyteArray, jobject);
 
 #ifdef __cplusplus
 }
 #endif
 #endif
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class org_sqlite_jni_tester_SQLTester */
-
-#ifndef _Included_org_sqlite_jni_tester_SQLTester
-#define _Included_org_sqlite_jni_tester_SQLTester
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class:     org_sqlite_jni_tester_SQLTester
- * Method:    strglob
- * Signature: ([B[B)I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_tester_SQLTester_strglob
-  (JNIEnv *, jclass, jbyteArray, jbyteArray);
-
-/*
- * Class:     org_sqlite_jni_tester_SQLTester
- * Method:    installCustomExtensions
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions
-  (JNIEnv *, jclass);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Collation.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Collation.java
index a05b8ef9ef..99d3fb0351 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Collation.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Collation.java
@@ -1,5 +1,5 @@
 /*
-** 2023-07-22
+** 2023-08-25
 **
 ** The author disclaims copyright to this source code.  In place of
 ** a legal notice, here is a blessing:
@@ -11,18 +11,16 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 /**
+   Callback for use with {@link CApi#sqlite3_preupdate_hook}.
 */
-public abstract class Collation {
+public interface PreupdateHookCallback extends CallbackProxy {
   /**
-     Must compare the given byte arrays using memcmp() semantics.
+     Must function as described for the C-level sqlite3_preupdate_hook()
+     callback.
   */
-  public abstract int xCompare(byte[] lhs, byte[] rhs);
-  /**
-     Called by SQLite when the collation is destroyed. If a Collation
-     requires custom cleanup, override this method.
-  */
-  public void xDestroy() {}
+  void call(sqlite3 db, int op, String dbName, String dbTable,
+            long iKey1, long iKey2 );
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/CollationNeeded.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/CollationNeeded.java
index 85214a1d27..fe61fe5065 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/CollationNeeded.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/CollationNeeded.java
@@ -1,5 +1,5 @@
 /*
-** 2023-07-30
+** 2023-08-25
 **
 ** The author disclaims copyright to this source code.  In place of
 ** a legal notice, here is a blessing:
@@ -11,18 +11,18 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 /**
-   Callback proxy for use with sqlite3_collation_needed().
+   Callback for use with {@link CApi#sqlite3_collation_needed}.
 */
-public interface CollationNeeded {
+public interface CollationNeededCallback extends CallbackProxy {
   /**
      Has the same semantics as the C-level sqlite3_create_collation()
      callback.
 
-     If it throws, the exception message is passed on to the db and
+     <p>If it throws, the exception message is passed on to the db and
      the exception is suppressed.
   */
-  int xCollationNeeded(sqlite3 db, int eTextRep, String collationName);
+  int call(sqlite3 db, int eTextRep, String collationName);
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/CommitHook.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/CommitHook.java
index eaa75a0040..3aa514f314 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/CommitHook.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/CommitHook.java
@@ -1,5 +1,5 @@
 /*
-** 2023-07-22
+** 2023-08-04
 **
 ** The author disclaims copyright to this source code.  In place of
 ** a legal notice, here is a blessing:
@@ -11,15 +11,12 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.fts5;
+
 
 /**
-   Callback proxy for use with sqlite3_commit_hook().
+   Callback type for use with xTokenize() variants.
 */
-public interface CommitHook {
-  /**
-     Works as documented for the sqlite3_commit_hook() callback.
-     Must not throw.
-  */
-  int xCommitHook();
+public interface XTokenizeCallback {
+  int call(int tFlags, byte[] txt, int iStart, int iEnd);
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5.java
index 102cf575a8..0dceeafd2e 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5.java
@@ -11,24 +11,18 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.fts5;
 
 /**
    INCOMPLETE AND COMPLETELY UNTESTED.
 
-   A wrapper for communicating C-level (fts5_api*) instances with
-   Java. These wrappers do not own their associated pointer, they
-   simply provide a type-safe way to communicate it between Java and C
-   via JNI.
+   A utility object for holding FTS5-specific types and constants
+   which are used by multiple FTS5 classes.
 */
 public final class Fts5 {
   /* Not used */
   private Fts5(){}
 
-  //! Callback type for use with xTokenize() variants
-  public static interface xTokenizeCallback {
-    int xToken(int tFlags, byte txt[], int iStart, int iEnd);
-  }
 
   public static final int FTS5_TOKENIZE_QUERY    = 0x0001;
   public static final int FTS5_TOKENIZE_PREFIX   = 0x0002;
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5Context.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5Context.java
index e78f67d556..439b477910 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5Context.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5Context.java
@@ -11,7 +11,8 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.*;
 
 /**
    A wrapper for communicating C-level (Fts5Context*) instances with
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java
index ac041e3001..594f3eaad6 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java
@@ -11,76 +11,87 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.fts5;
 import java.nio.charset.StandardCharsets;
+import org.sqlite.jni.capi.*;
+import org.sqlite.jni.annotation.*;
 
 /**
-   ALMOST COMPLETELY UNTESTED.
-
-   FAR FROM COMPLETE and the feasibility of binding this to Java
-   is still undetermined. This might be removed.
 */
 public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi> {
   //! Only called from JNI
   private Fts5ExtensionApi(){}
-  private int iVersion = 2;
+  private final int iVersion = 2;
 
   /* Callback type for used by xQueryPhrase(). */
-  public static interface xQueryPhraseCallback {
-    int xCallback(Fts5ExtensionApi fapi, Fts5Context cx);
+  public static interface XQueryPhraseCallback {
+    int call(Fts5ExtensionApi fapi, Fts5Context cx);
   }
 
   /**
      Returns the singleton instance of this class.
   */
-  public static synchronized native Fts5ExtensionApi getInstance();
+  public static native Fts5ExtensionApi getInstance();
 
-  public synchronized native int xColumnCount(@NotNull Fts5Context fcx);
-  public synchronized native int xColumnSize(@NotNull Fts5Context cx, int iCol,
+  public native int xColumnCount(@NotNull Fts5Context fcx);
+
+  public native int xColumnSize(@NotNull Fts5Context cx, int iCol,
                                 @NotNull OutputPointer.Int32 pnToken);
-  public synchronized native int xColumnText(@NotNull Fts5Context cx, int iCol,
+
+  public native int xColumnText(@NotNull Fts5Context cx, int iCol,
                                 @NotNull OutputPointer.String txt);
-  public synchronized native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
+
+  public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
                                      @NotNull OutputPointer.Int64 pnToken);
-  public synchronized native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
-  public synchronized native int xInst(@NotNull Fts5Context cx, int iIdx,
+
+  public native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
+
+  public native int xInst(@NotNull Fts5Context cx, int iIdx,
                           @NotNull OutputPointer.Int32 piPhrase,
                           @NotNull OutputPointer.Int32 piCol,
                           @NotNull OutputPointer.Int32 piOff);
-  public synchronized native int xInstCount(@NotNull Fts5Context fcx,
+
+  public native int xInstCount(@NotNull Fts5Context fcx,
                                @NotNull OutputPointer.Int32 pnInst);
-  public synchronized native int xPhraseCount(@NotNull Fts5Context fcx);
-  public synchronized native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
+
+  public native int xPhraseCount(@NotNull Fts5Context fcx);
+
+  public native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
                                  @NotNull Fts5PhraseIter iter,
                                  @NotNull OutputPointer.Int32 iCol,
                                  @NotNull OutputPointer.Int32 iOff);
-  public synchronized native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
+
+  public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
                                        @NotNull Fts5PhraseIter iter,
                                        @NotNull OutputPointer.Int32 iCol);
-  public synchronized native void xPhraseNext(@NotNull Fts5Context cx,
+  public native void xPhraseNext(@NotNull Fts5Context cx,
                                  @NotNull Fts5PhraseIter iter,
                                  @NotNull OutputPointer.Int32 iCol,
                                  @NotNull OutputPointer.Int32 iOff);
-  public synchronized native void xPhraseNextColumn(@NotNull Fts5Context cx,
+  public native void xPhraseNextColumn(@NotNull Fts5Context cx,
                                        @NotNull Fts5PhraseIter iter,
                                        @NotNull OutputPointer.Int32 iCol);
-  public synchronized native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
-  public synchronized native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
-                                 @NotNull xQueryPhraseCallback callback);
-  public synchronized native int xRowCount(@NotNull Fts5Context fcx,
+  public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
+
+  public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
+                                 @NotNull XQueryPhraseCallback callback);
+  public native int xRowCount(@NotNull Fts5Context fcx,
                               @NotNull OutputPointer.Int64 nRow);
-  public synchronized native long xRowid(@NotNull Fts5Context cx);
+
+  public native long xRowid(@NotNull Fts5Context cx);
   /* Note that the JNI binding lacks the C version's xDelete()
      callback argument. Instead, if pAux has an xDestroy() method, it
      is called if the FTS5 API finalizes the aux state (including if
      allocation of storage for the auxdata fails). Any reference to
      pAux held by the JNI layer will be relinquished regardless of
      whether pAux has an xDestroy() method. */
-  public synchronized native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
-  public synchronized native int xTokenize(@NotNull Fts5Context cx, @NotNull byte pText[],
-                              @NotNull Fts5.xTokenizeCallback callback);
 
-  public synchronized native Object xUserData(Fts5Context cx);
+  public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
+
+  public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
+                              @NotNull XTokenizeCallback callback);
+
+  public native Object xUserData(Fts5Context cx);
   //^^^ returns the pointer passed as the 3rd arg to the C-level
-  // fts5_api::xCreateFunction.
+  // fts5_api::xCreateFunction().
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java
index eb4e05fdf8..5774eb5936 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java
@@ -11,7 +11,8 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
 
 /**
    A wrapper for C-level Fts5PhraseIter. They are only modified and
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java
index 0d266a13d8..b72e5d0fc0 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java
@@ -11,7 +11,8 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
 
 /**
    INCOMPLETE AND COMPLETELY UNTESTED.
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/NativePointerHolder.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/NativePointerHolder.java
index afe2618a00..e82909e424 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/NativePointerHolder.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/NativePointerHolder.java
@@ -11,7 +11,7 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 /**
    A helper for passing pointers between JNI C code and Java, in
@@ -23,11 +23,24 @@ package org.sqlite.jni;
    NativePointerHolder is not inadvertently passed to an incompatible
    function signature.
 
-   These objects do not _own_ the pointer they refer to.  They are
+   These objects do not own the pointer they refer to.  They are
    intended simply to communicate that pointer between C and Java.
 */
 public class NativePointerHolder<ContextType> {
   //! Only set from JNI, where access permissions don't matter.
-  private long nativePointer = 0;
+  private volatile long nativePointer = 0;
+  /**
+     For use ONLY by package-level APIs which act as proxies for
+     close/finalize operations. Such ops must call this to zero out
+     the pointer so that this object is not carrying a stale
+     pointer. This function returns the prior value of the pointer and
+     sets it to 0.
+  */
+  final long clearNativePointer() {
+    final long rv = nativePointer;
+    nativePointer= 0;
+    return rv;
+  }
+
   public final long getNativePointer(){ return nativePointer; }
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/ProgressHandler.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/ProgressHandler.java
index c806eebca0..464baa2e3d 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/ProgressHandler.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/ProgressHandler.java
@@ -1,5 +1,5 @@
 /*
-** 2023-07-22
+** 2023-08-25
 **
 ** The author disclaims copyright to this source code.  In place of
 ** a legal notice, here is a blessing:
@@ -11,17 +11,17 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 /**
-   Callback proxy for use with sqlite3_progress_handler().
+   Callback for use with {@link CApi#sqlite3_progress_handler}.
 */
-public interface ProgressHandler {
+public interface ProgressHandlerCallback extends CallbackProxy {
   /**
-     Works as documented for the sqlite3_progress_handler() callback.
+     Works as documented for the C-level sqlite3_progress_handler() callback.
 
-     If it throws, the exception message is passed on to the db and
+     <p>If it throws, the exception message is passed on to the db and
      the exception is suppressed.
   */
-  int xCallback();
+  int call();
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/RollbackHook.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/RollbackHook.java
index 4ce3cb93e3..5ce17e718a 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/RollbackHook.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/RollbackHook.java
@@ -1,5 +1,5 @@
 /*
-** 2023-07-22
+** 2023-08-25
 **
 ** The author disclaims copyright to this source code.  In place of
 ** a legal notice, here is a blessing:
@@ -11,15 +11,15 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 /**
-   Callback proxy for use with sqlite3_rollback_hook().
+   Callback for use with {@link CApi#sqlite3_rollback_hook}.
 */
-public interface RollbackHook {
+public interface RollbackHookCallback extends CallbackProxy {
   /**
-     Works as documented for the sqlite3_rollback_hook() callback.
-     Must not throw.
+     Works as documented for the C-level sqlite3_rollback_hook()
+     callback.
   */
-  void xRollbackHook();
+  void call();
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/UpdateHook.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/UpdateHook.java
index 171e2bdb41..24373bdf2b 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/UpdateHook.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/UpdateHook.java
@@ -1,5 +1,5 @@
 /*
-** 2023-07-22
+** 2023-08-25
 **
 ** The author disclaims copyright to this source code.  In place of
 ** a legal notice, here is a blessing:
@@ -11,15 +11,15 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 /**
-   Callback proxy for use with sqlite3_update_hook().
+   Callback for use with {@link CApi#sqlite3_commit_hook}.
 */
-public interface UpdateHook {
+public interface CommitHookCallback extends CallbackProxy {
   /**
-     Works as documented for the sqlite3_update_hook() callback.
-     Must not throw.
+     Works as documented for the C-level sqlite3_commit_hook()
+     callback.  Must not throw.
   */
-  void xUpdateHook(int opId, String dbName, String tableName, long rowId);
+  int call();
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/ValueHolder.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/ValueHolder.java
index 7f6a463ba5..b3f03ac867 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/ValueHolder.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/ValueHolder.java
@@ -1,5 +1,5 @@
 /*
-** 2023-07-21
+** 2023-10-16
 **
 ** The author disclaims copyright to this source code.  In place of
 ** a legal notice, here is a blessing:
@@ -9,12 +9,12 @@
 **    May you share freely, never taking more than you give.
 **
 *************************************************************************
-** This file is part of the JNI bindings for the sqlite3 C API.
+** This file contains a set of tests for the sqlite3 JNI bindings.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 /**
-   A helper class which simply holds a single value. Its current use
+   A helper class which simply holds a single value. Its primary use
    is for communicating values out of anonymous classes, as doing so
    requires a "final" reference.
 */
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/annotation/NotNull.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/annotation/NotNull.java
new file mode 100644
index 0000000000..3b4c1c7af1
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/annotation/NotNull.java
@@ -0,0 +1,59 @@
+/*
+** 2023-09-27
+**
+** 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 houses the NotNull annotaion for the sqlite3 C API.
+*/
+package org.sqlite.jni.annotation;
+
+/**
+   This annotation is for flagging parameters which may not legally be
+   null or point to closed/finalized C-side resources.
+
+   <p>In the case of Java types which map directly to C struct types
+   (e.g. {@link org.sqlite.jni.sqlite3}, {@link
+   org.sqlite.jni.sqlite3_stmt}, and {@link
+   org.sqlite.jni.sqlite3_context}), a closed/finalized resource is
+   also considered to be null for purposes this annotation because the
+   C-side effect of passing such a handle is the same as if null is
+   passed.</p>
+
+   <p>When used in the context of Java interfaces which are called
+   from the C APIs, this annotation communicates that the C API will
+   never pass a null value to the callback for that parameter.</p>
+
+   <p>Passing a null, for this annotation's definition of null, for
+   any parameter marked with this annoation specifically invokes
+   undefined behavior.</p>
+
+   <p>Passing 0 (i.e. C NULL) or a negative value for any long-type
+   parameter marked with this annoation specifically invokes undefined
+   behavior. Such values are treated as C pointers in the JNI
+   layer.</p>
+
+   <p>Note that the C-style API does not throw any exceptions on its
+   own because it has a no-throw policy in order to retain its C-style
+   semantics, but it may trigger NullPointerExceptions (or similar) if
+   passed a null for a parameter flagged with this annotation.</p>
+
+   <p>This annotation is informational only. No policy is in place to
+   programmatically ensure that NotNull is conformed to in client
+   code.</p>
+
+   <p>This annotation is solely for the use by the classes in the
+   org.sqlite package and subpackages, but is made public so that
+   javadoc will link to it from the annotated functions. It is not
+   part of the public API and client-level code must not rely on
+   it.</p>
+*/
+@java.lang.annotation.Documented
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
+public @interface NotNull{}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/annotation/Nullable.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/annotation/Nullable.java
new file mode 100644
index 0000000000..ddc8502d67
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/annotation/Nullable.java
@@ -0,0 +1,32 @@
+/*
+** 2023-09-27
+**
+** 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 houses the Nullable annotaion for the sqlite3 C API.
+*/
+package org.sqlite.jni.annotation;
+
+/**
+   This annotation is for flagging parameters which may legally be
+   null, noting that they may behave differently if passed null but
+   are prepared to expect null as a value. When used in the context of
+   callback methods which are called into from the C APIs, this
+   annotation communicates that the C API may pass a null value to the
+   callback.
+
+   <p>This annotation is solely for the use by the classes in this
+   package but is made public so that javadoc will link to it from the
+   annotated functions. It is not part of the public API and
+   client-level code must not rely on it.
+*/
+@java.lang.annotation.Documented
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
+public @interface Nullable{}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/annotation/package-info.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/annotation/package-info.java
new file mode 100644
index 0000000000..20ac7a3017
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/annotation/package-info.java
@@ -0,0 +1,17 @@
+/*
+** 2023-09-27
+**
+** 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 package houses annotations specific to the JNI bindings of the
+   SQLite3 C API.
+*/
+package org.sqlite.jni.annotation;
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java
new file mode 100644
index 0000000000..925536636e
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java
@@ -0,0 +1,34 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+   An implementation of {@link CollationCallback} which provides a
+   no-op xDestroy() method.
+*/
+public abstract class AbstractCollationCallback
+  implements CollationCallback, XDestroyCallback {
+  /**
+     Must compare the given byte arrays and return the result using
+     {@code memcmp()} semantics.
+  */
+  public abstract int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
+
+  /**
+     Optionally override to be notified when the UDF is finalized by
+     SQLite. This implementation does nothing.
+  */
+  public void xDestroy(){}
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java
new file mode 100644
index 0000000000..89c4f27421
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java
@@ -0,0 +1,72 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+   A SQLFunction implementation for aggregate functions.  Its T is the
+   data type of its "accumulator" state, an instance of which is
+   intended to be be managed using the getAggregateState() and
+   takeAggregateState() methods.
+*/
+public abstract class AggregateFunction<T> implements SQLFunction {
+
+  /**
+     As for the xStep() argument of the C API's
+     sqlite3_create_function().  If this function throws, the
+     exception is not propagated and a warning might be emitted to a
+     debugging channel.
+  */
+  public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
+
+  /**
+     As for the xFinal() argument of the C API's sqlite3_create_function().
+     If this function throws, it is translated into an sqlite3_result_error().
+  */
+  public abstract void xFinal(sqlite3_context cx);
+
+  /**
+     Optionally override to be notified when the UDF is finalized by
+     SQLite.
+  */
+  public void xDestroy() {}
+
+  /** Per-invocation state for the UDF. */
+  private final SQLFunction.PerContextState<T> map =
+    new SQLFunction.PerContextState<>();
+
+  /**
+     To be called from the implementation's xStep() method, as well
+     as the xValue() and xInverse() methods of the {@link WindowFunction}
+     subclass, to fetch the current per-call UDF state. On the
+     first call to this method for any given sqlite3_context
+     argument, the context is set to the given initial value. On all other
+     calls, the 2nd argument is ignored.
+
+     @see SQLFunction.PerContextState#getAggregateState
+  */
+  protected final ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
+    return map.getAggregateState(cx, initialValue);
+  }
+
+  /**
+     To be called from the implementation's xFinal() method to fetch
+     the final state of the UDF and remove its mapping.
+
+     see SQLFunction.PerContextState#takeAggregateState
+  */
+  protected final T takeAggregateState(sqlite3_context cx){
+    return map.takeAggregateState(cx);
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java
new file mode 100644
index 0000000000..ce7c6fca6d
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java
@@ -0,0 +1,28 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.*;
+
+/**
+   Callback for use with {@link CApi#sqlite3_set_authorizer}.
+*/
+public interface AuthorizerCallback extends CallbackProxy {
+  /**
+     Must function as described for the C-level
+     sqlite3_set_authorizer() callback.
+  */
+  int call(int opId, @Nullable String s1, @Nullable String s2,
+           @Nullable String s3, @Nullable String s4);
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java
new file mode 100644
index 0000000000..7a54132d29
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java
@@ -0,0 +1,40 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for use with the {@link CApi#sqlite3_auto_extension}
+   family of APIs.
+*/
+public interface AutoExtensionCallback extends CallbackProxy {
+  /**
+     Must function as described for a C-level
+     sqlite3_auto_extension() callback.
+
+     <p>This callback may throw and the exception's error message will
+     be set as the db's error string.
+
+     <p>Tips for implementations:
+
+     <p>- Opening a database from an auto-extension handler will lead to
+     an endless recursion of the auto-handler triggering itself
+     indirectly for each newly-opened database.
+
+     <p>- If this routine is stateful, it may be useful to make the
+     overridden method synchronized.
+
+     <p>- Results are undefined if the given db is closed by an auto-extension.
+  */
+  int call(sqlite3 db);
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java
new file mode 100644
index 0000000000..00223f0b66
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for use with {@link CApi#sqlite3_busy_handler}.
+*/
+public interface BusyHandlerCallback extends CallbackProxy {
+  /**
+     Must function as documented for the C-level
+     sqlite3_busy_handler() callback argument, minus the (void*)
+     argument the C-level function requires.
+  */
+  int call(int n);
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CApi.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CApi.java
new file mode 100644
index 0000000000..302cdb760e
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CApi.java
@@ -0,0 +1,2449 @@
+/*
+** 2023-07-21
+**
+** 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 declares JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import java.nio.charset.StandardCharsets;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import org.sqlite.jni.annotation.*;
+import java.util.Arrays;
+
+/**
+  This class contains the entire C-style sqlite3 JNI API binding,
+  minus a few bits and pieces declared in other files. For client-side
+  use, a static import is recommended:
+
+  <pre>{@code
+  import static org.sqlite.jni.capi.CApi.*;
+  }</pre>
+
+  <p>The C-side part can be found in sqlite3-jni.c.
+
+  <p>This class is package-private in order to keep Java clients from
+  having direct access to the low-level C-style APIs, a design
+  decision made by Java developers based on the C-style API being
+  riddled with opportunities for Java developers to proverbially shoot
+  themselves in the foot with. Third-party copies of this code may
+  eliminate that guard by simply changing this class from
+  package-private to public. Its methods which are intended to be
+  exposed that way are all public.
+
+  <p>Only functions which materially differ from their C counterparts
+  are documented here, and only those material differences are
+  documented. The C documentation is otherwise applicable for these
+  APIs:
+
+  <p><a href="https://sqlite.org/c3ref/intro.html">https://sqlite.org/c3ref/intro.html</a>
+
+  <p>A handful of Java-specific APIs have been added which are
+  documented here. A number of convenience overloads are provided
+  which are not documented but whose semantics map 1-to-1 in an
+  intuitive manner. e.g. {@link
+  #sqlite3_result_set(sqlite3_context,int)} is equivalent to {@link
+  #sqlite3_result_int}, and sqlite3_result_set() has many
+  type-specific overloads.
+
+  <p>Notes regarding Java's Modified UTF-8 vs standard UTF-8:
+
+  <p>SQLite internally uses UTF-8 encoding, whereas Java natively uses
+  UTF-16.  Java JNI has routines for converting to and from UTF-8,
+  but JNI uses what its docs call modified UTF-8 (see links below)
+  Care must be taken when converting Java strings to or from standard
+  UTF-8 to ensure that the proper conversion is performed. In short,
+  Java's {@code String.getBytes(StandardCharsets.UTF_8)} performs the proper
+  conversion in Java, and there are no JNI C APIs for that conversion
+  (JNI's {@code NewStringUTF()} requires its input to be in MUTF-8).
+
+  <p>The known consequences and limitations this discrepancy places on
+  the SQLite3 JNI binding include:
+
+  <ul>
+
+  <li>C functions which take C-style strings without a length argument
+  require special care when taking input from Java. In particular,
+  Java strings converted to byte arrays for encoding purposes are not
+  NUL-terminated, and conversion to a Java byte array must sometimes
+  be careful to add one. Functions which take a length do not require
+  this so long as the length is provided. Search the CApi class
+  for "\0" for many examples.
+
+  </ul>
+
+  <p>Further reading:
+
+  <p><a href="https://stackoverflow.com/questions/57419723">https://stackoverflow.com/questions/57419723</a>
+  <p><a href="https://stackoverflow.com/questions/7921016">https://stackoverflow.com/questions/7921016</a>
+  <p><a href="https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/">https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/</a>
+  <p><a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode">https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode</a>
+  <p><a href="https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8">https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8</a>
+
+*/
+public final class CApi {
+  static {
+    System.loadLibrary("sqlite3-jni");
+  }
+  //! Not used
+  private CApi(){}
+  //! Called from static init code.
+  private static native void init();
+
+  /**
+     Returns a nul-terminated copy of s as a UTF-8-encoded byte array,
+     or null if s is null.
+  */
+  private static byte[] nulTerminateUtf8(String s){
+    return null==s ? null : (s+"\0").getBytes(StandardCharsets.UTF_8);
+  }
+
+  /**
+     Each thread which uses the SQLite3 JNI APIs should call
+     sqlite3_jni_uncache_thread() when it is done with the library -
+     either right before it terminates or when it finishes using the
+     SQLite API.  This will clean up any cached per-thread info.
+
+     <p>This process does not close any databases or finalize
+     any prepared statements because their ownership does not depend on
+     a given thread.  For proper library behavior, and to
+     avoid C-side leaks, be sure to finalize all statements and close
+     all databases before calling this function.
+
+     <p>Calling this from the main application thread is not strictly
+     required. Additional threads must call this before ending or they
+     will leak cache entries in the C heap, which in turn may keep
+     numerous Java-side global references active.
+
+     <p>This routine returns false without side effects if the current
+     JNIEnv is not cached, else returns true, but this information is
+     primarily for testing of the JNI bindings and is not information
+     which client-level code can use to make any informed decisions.
+  */
+  public static native boolean sqlite3_java_uncache_thread();
+
+  //////////////////////////////////////////////////////////////////////
+  // Maintenance reminder: please keep the sqlite3_.... functions
+  // alphabetized.  The SQLITE_... values. on the other hand, are
+  // grouped by category.
+
+  /**
+     Functions exactly like the native form except that (A) the 2nd
+     argument is a boolean instead of an int and (B) the returned
+     value is not a pointer address and is only intended for use as a
+     per-UDF-call lookup key in a higher-level data structure.
+
+     <p>Passing a true second argument is analogous to passing some
+     unspecified small, non-0 positive value to the C API and passing
+     false is equivalent to passing 0 to the C API.
+
+     <p>Like the C API, it returns 0 if allocation fails or if
+     initialize is false and no prior aggregate context was allocated
+     for cx.  If initialize is true then it returns 0 only on
+     allocation error. In all casses, 0 is considered the sentinel
+     "not a key" value.
+  */
+  public static native long sqlite3_aggregate_context(sqlite3_context cx, boolean initialize);
+
+  /**
+     Functions almost as documented for the C API, with these
+     exceptions:
+
+     <p>- The callback interface is shorter because of
+     cross-language differences. Specifically, 3rd argument to the C
+     auto-extension callback interface is unnecessary here.
+
+     <p>The C API docs do not specifically say so, but if the list of
+     auto-extensions is manipulated from an auto-extension, it is
+     undefined which, if any, auto-extensions will subsequently
+     execute for the current database. That is, doing so will result
+     in unpredictable, but not undefined, behavior.
+
+     <p>See the AutoExtension class docs for more information.
+  */
+  public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback);
+
+  static native int sqlite3_backup_finish(@NotNull long ptrToBackup);
+
+  public static int sqlite3_backup_finish(@NotNull sqlite3_backup b){
+    return sqlite3_backup_finish(b.clearNativePointer());
+  }
+
+  static native sqlite3_backup sqlite3_backup_init(
+    @NotNull long ptrToDbDest, @NotNull String destTableName,
+    @NotNull long ptrToDbSrc, @NotNull String srcTableName
+  );
+
+  public static sqlite3_backup sqlite3_backup_init(
+    @NotNull sqlite3 dbDest, @NotNull String destTableName,
+    @NotNull sqlite3 dbSrc, @NotNull String srcTableName
+  ){
+    return sqlite3_backup_init( dbDest.getNativePointer(), destTableName,
+                                dbSrc.getNativePointer(), srcTableName );
+  }
+
+  static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup);
+
+  public static int sqlite3_backup_pagecount(@NotNull sqlite3_backup b){
+    return sqlite3_backup_pagecount(b.getNativePointer());
+  }
+
+  static native int sqlite3_backup_remaining(@NotNull long ptrToBackup);
+
+  public static int sqlite3_backup_remaining(@NotNull sqlite3_backup b){
+    return sqlite3_backup_remaining(b.getNativePointer());
+  }
+
+  static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage);
+
+  public static int sqlite3_backup_step(@NotNull sqlite3_backup b, int nPage){
+    return sqlite3_backup_step(b.getNativePointer(), nPage);
+  }
+
+  static native int sqlite3_bind_blob(
+    @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int n
+  );
+
+  /**
+     If n is negative, SQLITE_MISUSE is returned. If n>data.length
+     then n is silently truncated to data.length.
+  */
+  static int sqlite3_bind_blob(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
+  ){
+    return sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, n);
+  }
+
+  public static int sqlite3_bind_blob(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
+  ){
+    return (null==data)
+      ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+      : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length);
+  }
+
+  static native int sqlite3_bind_double(
+    @NotNull long ptrToStmt, int ndx, double v
+  );
+
+  public static int sqlite3_bind_double(
+    @NotNull sqlite3_stmt stmt, int ndx, double v
+  ){
+    return sqlite3_bind_double(stmt.getNativePointer(), ndx, v);
+  }
+
+  static native int sqlite3_bind_int(
+    @NotNull long ptrToStmt, int ndx, int v
+  );
+
+  public static int sqlite3_bind_int(
+    @NotNull sqlite3_stmt stmt, int ndx, int v
+  ){
+    return sqlite3_bind_int(stmt.getNativePointer(), ndx, v);
+  }
+
+  static native int sqlite3_bind_int64(
+    @NotNull long ptrToStmt, int ndx, long v
+  );
+
+  public static int sqlite3_bind_int64(@NotNull sqlite3_stmt stmt, int ndx, long v){
+    return sqlite3_bind_int64( stmt.getNativePointer(), ndx, v );
+  }
+
+  static native int sqlite3_bind_java_object(
+    @NotNull long ptrToStmt, int ndx, @Nullable Object o
+  );
+
+  /**
+     Binds the given object at the given index. If o is null then this behaves like
+     sqlite3_bind_null().
+
+     @see #sqlite3_result_java_object
+  */
+  public static int sqlite3_bind_java_object(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable Object o
+  ){
+    return sqlite3_bind_java_object(stmt.getNativePointer(), ndx, o);
+  }
+
+  static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx);
+
+  public static int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx){
+    return sqlite3_bind_null(stmt.getNativePointer(), ndx);
+  }
+
+  static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt);
+
+  public static int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt){
+    return sqlite3_bind_parameter_count(stmt.getNativePointer());
+  }
+
+  /**
+     Requires that paramName be a NUL-terminated UTF-8 string.
+
+     This overload is private because: (A) to keep users from
+     inadvertently passing non-NUL-terminated byte arrays (an easy
+     thing to do). (B) it is cheaper to NUL-terminate the
+     String-to-byte-array conversion in the public-facing Java-side
+     overload than to do that in C, so that signature is the
+     public-facing one.
+  */
+  private static native int sqlite3_bind_parameter_index(
+    @NotNull long ptrToStmt, @NotNull byte[] paramName
+  );
+
+  public static int sqlite3_bind_parameter_index(
+    @NotNull sqlite3_stmt stmt, @NotNull String paramName
+  ){
+    final byte[] utf8 = nulTerminateUtf8(paramName);
+    return null==utf8 ? 0 : sqlite3_bind_parameter_index(stmt.getNativePointer(), utf8);
+  }
+
+  static native String sqlite3_bind_parameter_name(
+    @NotNull long ptrToStmt, int index
+  );
+
+  public static String sqlite3_bind_parameter_name(@NotNull sqlite3_stmt stmt, int index){
+    return sqlite3_bind_parameter_name(stmt.getNativePointer(), index);
+  }
+
+  static native int sqlite3_bind_text(
+    @NotNull long ptrToStmt, int ndx, @Nullable byte[] utf8, int maxBytes
+  );
+
+  /**
+     Works like the C-level sqlite3_bind_text() but assumes
+     SQLITE_TRANSIENT for the final C API parameter. The byte array is
+     assumed to be in UTF-8 encoding.
+
+     <p>If data is not null and maxBytes>utf8.length then maxBytes is
+     silently truncated to utf8.length. If maxBytes is negative then
+     results are undefined if data is not null and does not contain a
+     NUL byte.
+  */
+  static int sqlite3_bind_text(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8, int maxBytes
+  ){
+    return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, maxBytes);
+  }
+
+  /**
+     Converts data, if not null, to a UTF-8-encoded byte array and
+     binds it as such, returning the result of the C-level
+     sqlite3_bind_null() or sqlite3_bind_text().
+  */
+  public static int sqlite3_bind_text(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
+  ){
+    if( null==data ) return sqlite3_bind_null(stmt.getNativePointer(), ndx);
+    final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8);
+    return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length);
+  }
+
+  /**
+     Requires that utf8 be null or in UTF-8 encoding.
+  */
+  public static int sqlite3_bind_text(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8
+  ){
+    return ( null==utf8 )
+      ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+      : sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length);
+  }
+
+  static native int sqlite3_bind_text16(
+    @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int maxBytes
+  );
+
+  /**
+     Identical to the sqlite3_bind_text() overload with the same
+     signature but requires that its input be encoded in UTF-16 in
+     platform byte order.
+  */
+  static int sqlite3_bind_text16(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
+  ){
+    return sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, maxBytes);
+  }
+
+  /**
+     Converts its string argument to UTF-16 and binds it as such, returning
+     the result of the C-side function of the same name. The 3rd argument
+     may be null.
+  */
+  public static int sqlite3_bind_text16(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
+  ){
+    if(null == data) return sqlite3_bind_null(stmt, ndx);
+    final byte[] bytes = data.getBytes(StandardCharsets.UTF_16);
+    return sqlite3_bind_text16(stmt.getNativePointer(), ndx, bytes, bytes.length);
+  }
+
+  /**
+     Requires that data be null or in UTF-16 encoding in platform byte
+     order. Returns the result of the C-level sqlite3_bind_null() or
+     sqlite3_bind_text16().
+  */
+  public static int sqlite3_bind_text16(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
+  ){
+    return (null == data)
+      ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+      : sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, data.length);
+  }
+
+  static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue);
+
+  /**
+     Functions like the C-level sqlite3_bind_value(), or
+     sqlite3_bind_null() if val is null.
+  */
+  public static int sqlite3_bind_value(@NotNull sqlite3_stmt stmt, int ndx, sqlite3_value val){
+    return sqlite3_bind_value(stmt.getNativePointer(), ndx,
+                              null==val ? 0L : val.getNativePointer());
+  }
+
+  static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n);
+
+  public static int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n){
+    return sqlite3_bind_zeroblob(stmt.getNativePointer(), ndx, n);
+  }
+
+  static native int sqlite3_bind_zeroblob64(
+    @NotNull long ptrToStmt, int ndx, long n
+  );
+
+  public static int sqlite3_bind_zeroblob64(@NotNull sqlite3_stmt stmt, int ndx, long n){
+    return sqlite3_bind_zeroblob64(stmt.getNativePointer(), ndx, n);
+  }
+
+  static native int sqlite3_blob_bytes(@NotNull long ptrToBlob);
+
+  public static int sqlite3_blob_bytes(@NotNull sqlite3_blob blob){
+    return sqlite3_blob_bytes(blob.getNativePointer());
+  }
+
+  static native int sqlite3_blob_close(@Nullable long ptrToBlob);
+
+  public static int sqlite3_blob_close(@Nullable sqlite3_blob blob){
+    return sqlite3_blob_close(blob.clearNativePointer());
+  }
+
+  static native int sqlite3_blob_open(
+    @NotNull long ptrToDb, @NotNull String dbName,
+    @NotNull String tableName, @NotNull String columnName,
+    long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
+  );
+
+  public static int sqlite3_blob_open(
+    @NotNull sqlite3 db, @NotNull String dbName,
+    @NotNull String tableName, @NotNull String columnName,
+    long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
+  ){
+    return sqlite3_blob_open(db.getNativePointer(), dbName, tableName,
+                             columnName, iRow, flags, out);
+  }
+
+  /**
+     Convenience overload.
+  */
+  public static sqlite3_blob sqlite3_blob_open(
+    @NotNull sqlite3 db, @NotNull String dbName,
+    @NotNull String tableName, @NotNull String columnName,
+    long iRow, int flags ){
+    final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob();
+    sqlite3_blob_open(db.getNativePointer(), dbName, tableName, columnName,
+                      iRow, flags, out);
+    return out.take();
+  };
+
+  static native int sqlite3_blob_read(
+    @NotNull long ptrToBlob, @NotNull byte[] target, int iOffset
+  );
+
+  public static int sqlite3_blob_read(
+    @NotNull sqlite3_blob b, @NotNull byte[] target, int iOffset
+  ){
+    return sqlite3_blob_read(b.getNativePointer(), target, iOffset);
+  }
+
+  static native int sqlite3_blob_reopen(
+    @NotNull long ptrToBlob, long newRowId
+  );
+
+  public static int sqlite3_blob_reopen(@NotNull sqlite3_blob b, long newRowId){
+    return sqlite3_blob_reopen(b.getNativePointer(), newRowId);
+  }
+
+  static native int sqlite3_blob_write(
+    @NotNull long ptrToBlob, @NotNull byte[] bytes, int iOffset
+  );
+
+  public static int sqlite3_blob_write(
+    @NotNull sqlite3_blob b, @NotNull byte[] bytes, int iOffset
+  ){
+    return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset);
+  }
+
+  static native int sqlite3_busy_handler(
+    @NotNull long ptrToDb, @Nullable BusyHandlerCallback handler
+  );
+
+  /**
+     As for the C-level function of the same name, with a
+     BusyHandlerCallback instance in place of a callback
+     function. Pass it a null handler to clear the busy handler.
+  */
+  public static int sqlite3_busy_handler(
+    @NotNull sqlite3 db, @Nullable BusyHandlerCallback handler
+  ){
+    return sqlite3_busy_handler(db.getNativePointer(), handler);
+  }
+
+  static native int sqlite3_busy_timeout(@NotNull long ptrToDb, int ms);
+
+  public static int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms){
+    return sqlite3_busy_timeout(db.getNativePointer(), ms);
+  }
+
+  public static native boolean sqlite3_cancel_auto_extension(
+    @NotNull AutoExtensionCallback ax
+  );
+
+  static native int sqlite3_changes(@NotNull long ptrToDb);
+
+  public static int sqlite3_changes(@NotNull sqlite3 db){
+    return sqlite3_changes(db.getNativePointer());
+  }
+
+  static native long sqlite3_changes64(@NotNull long ptrToDb);
+
+  public static long sqlite3_changes64(@NotNull sqlite3 db){
+    return sqlite3_changes64(db.getNativePointer());
+  }
+
+  static native int sqlite3_clear_bindings(@NotNull long ptrToStmt);
+
+  public static int sqlite3_clear_bindings(@NotNull sqlite3_stmt stmt){
+    return sqlite3_clear_bindings(stmt.getNativePointer());
+  }
+
+  static native int sqlite3_close(@Nullable long ptrToDb);
+
+  public static int sqlite3_close(@Nullable sqlite3 db){
+    int rc = 0;
+    if( null!=db ){
+      rc = sqlite3_close(db.getNativePointer());
+      if( 0==rc ) db.clearNativePointer();
+    }
+    return rc;
+  }
+
+  static native int sqlite3_close_v2(@Nullable long ptrToDb);
+
+  public static int sqlite3_close_v2(@Nullable sqlite3 db){
+    return db==null ? 0 : sqlite3_close_v2(db.clearNativePointer());
+  }
+
+  public static native byte[] sqlite3_column_blob(
+    @NotNull sqlite3_stmt stmt, int ndx
+  );
+
+  static native int sqlite3_column_bytes(@NotNull long ptrToStmt, int ndx);
+
+  public static int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx){
+    return sqlite3_column_bytes(stmt.getNativePointer(), ndx);
+  }
+
+  static native int sqlite3_column_bytes16(@NotNull long ptrToStmt, int ndx);
+
+  public static int sqlite3_column_bytes16(@NotNull sqlite3_stmt stmt, int ndx){
+    return sqlite3_column_bytes16(stmt.getNativePointer(), ndx);
+  }
+
+  static native int sqlite3_column_count(@NotNull long ptrToStmt);
+
+  public static int sqlite3_column_count(@NotNull sqlite3_stmt stmt){
+    return sqlite3_column_count(stmt.getNativePointer());
+  }
+
+  static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx);
+
+  public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){
+    return sqlite3_column_decltype(stmt.getNativePointer(), ndx);
+  }
+
+  public static native double sqlite3_column_double(
+    @NotNull sqlite3_stmt stmt, int ndx
+  );
+
+  public static native int sqlite3_column_int(
+    @NotNull sqlite3_stmt stmt, int ndx
+  );
+
+  public static native long sqlite3_column_int64(
+    @NotNull sqlite3_stmt stmt, int ndx
+  );
+
+  static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx);
+
+  public static String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx){
+    return sqlite3_column_name(stmt.getNativePointer(), ndx);
+  }
+
+  static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
+
+  public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){
+    return sqlite3_column_database_name(stmt.getNativePointer(), ndx);
+  }
+
+  static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx);
+
+  public static String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx){
+    return sqlite3_column_origin_name(stmt.getNativePointer(), ndx);
+  }
+
+  static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx);
+
+  public static String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx){
+    return sqlite3_column_table_name(stmt.getNativePointer(), ndx);
+  }
+
+  /**
+     Functions identially to the C API, and this note is just to
+     stress that the returned bytes are encoded as UTF-8. It returns
+     null if the underlying C-level sqlite3_column_text() returns NULL
+     or on allocation error.
+
+     @see #sqlite3_column_text16(sqlite3_stmt,int)
+  */
+  public static native byte[] sqlite3_column_text(
+    @NotNull sqlite3_stmt stmt, int ndx
+  );
+
+  public static native String sqlite3_column_text16(
+    @NotNull sqlite3_stmt stmt, int ndx
+  );
+
+  // The real utility of this function is questionable.
+  // /**
+  //    Returns a Java value representation based on the value of
+  //    sqlite_value_type(). For integer types it returns either Integer
+  //    or Long, depending on whether the value will fit in an
+  //    Integer. For floating-point values it always returns type Double.
+
+  //    If the column was bound using sqlite3_result_java_object() then
+  //    that value, as an Object, is returned.
+  // */
+  // public static Object sqlite3_column_to_java(@NotNull sqlite3_stmt stmt,
+  //                                             int ndx){
+  //   sqlite3_value v = sqlite3_column_value(stmt, ndx);
+  //   Object rv = null;
+  //   if(null == v) return v;
+  //   v = sqlite3_value_dup(v)/*need a protected value*/;
+  //   if(null == v) return v /* OOM error in C */;
+  //   if(112/* 'p' */ == sqlite3_value_subtype(v)){
+  //     rv = sqlite3_value_java_object(v);
+  //   }else{
+  //     switch(sqlite3_value_type(v)){
+  //       case SQLITE_INTEGER: {
+  //         final long i = sqlite3_value_int64(v);
+  //         rv = (i<=0x7fffffff && i>=-0x7fffffff-1)
+  //           ? new Integer((int)i) : new Long(i);
+  //         break;
+  //       }
+  //       case SQLITE_FLOAT: rv = new Double(sqlite3_value_double(v)); break;
+  //       case SQLITE_BLOB: rv = sqlite3_value_blob(v); break;
+  //       case SQLITE_TEXT: rv = sqlite3_value_text16(v); break;
+  //       default: break;
+  //     }
+  //   }
+  //   sqlite3_value_free(v);
+  //   return rv;
+  // }
+
+  static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx);
+
+  public static int sqlite3_column_type(@NotNull sqlite3_stmt stmt, int ndx){
+    return sqlite3_column_type(stmt.getNativePointer(), ndx);
+  }
+
+  public static native sqlite3_value sqlite3_column_value(
+    @NotNull sqlite3_stmt stmt, int ndx
+  );
+
+  static native int sqlite3_collation_needed(
+    @NotNull long ptrToDb, @Nullable CollationNeededCallback callback
+  );
+
+  /**
+     This functions like C's sqlite3_collation_needed16() because
+     Java's string type is inherently compatible with that interface.
+  */
+  public static int sqlite3_collation_needed(
+    @NotNull sqlite3 db, @Nullable CollationNeededCallback callback
+  ){
+    return sqlite3_collation_needed(db.getNativePointer(), callback);
+  }
+
+  static native CommitHookCallback sqlite3_commit_hook(
+    @NotNull long ptrToDb, @Nullable CommitHookCallback hook
+  );
+
+  public static CommitHookCallback sqlite3_commit_hook(
+    @NotNull sqlite3 db, @Nullable CommitHookCallback hook
+  ){
+    return sqlite3_commit_hook(db.getNativePointer(), hook);
+  }
+
+  public static native String sqlite3_compileoption_get(int n);
+
+  public static native boolean sqlite3_compileoption_used(String optName);
+
+  /**
+     This implementation is private because it's too easy to pass it
+     non-NUL-terminated byte arrays from client code.
+  */
+  private static native int sqlite3_complete(
+    @NotNull byte[] nulTerminatedUtf8Sql
+  );
+
+  /**
+     Unlike the C API, this returns SQLITE_MISUSE if its argument is
+     null (as opposed to invoking UB).
+  */
+  public static int sqlite3_complete(@NotNull String sql){
+    return sqlite3_complete( nulTerminateUtf8(sql) );
+  }
+
+
+  /**
+     <p>Works like in the C API with the exception that it only supports
+     the following subset of configution flags:
+
+     <p>SQLITE_CONFIG_SINGLETHREAD
+     SQLITE_CONFIG_MULTITHREAD
+     SQLITE_CONFIG_SERIALIZED
+
+     <p>Others may be added in the future. It returns SQLITE_MISUSE if
+     given an argument it does not handle.
+
+     <p>Note that sqlite3_config() is not threadsafe with regards to
+     the rest of the library. This must not be called when any other
+     library APIs are being called.
+  */
+  public static native int sqlite3_config(int op);
+
+  /**
+     If the native library was built with SQLITE_ENABLE_SQLLOG defined
+     then this acts as a proxy for C's
+     sqlite3_config(SQLITE_ENABLE_SQLLOG,...). This sets or clears the
+     logger. If installation of a logger fails, any previous logger is
+     retained.
+
+     <p>If not built with SQLITE_ENABLE_SQLLOG defined, this returns
+     SQLITE_MISUSE.
+
+     <p>Note that sqlite3_config() is not threadsafe with regards to
+     the rest of the library. This must not be called when any other
+     library APIs are being called.
+  */
+  public static native int sqlite3_config( @Nullable ConfigSqllogCallback logger );
+
+  /**
+     The sqlite3_config() overload for handling the SQLITE_CONFIG_LOG
+     option.
+  */
+  public static native int sqlite3_config( @Nullable ConfigLogCallback logger );
+
+  /**
+     Unlike the C API, this returns null if its argument is
+     null (as opposed to invoking UB).
+  */
+  public static native sqlite3 sqlite3_context_db_handle(
+    @NotNull sqlite3_context cx
+  );
+
+  public static native int sqlite3_create_collation(
+    @NotNull sqlite3 db, @NotNull String name, int eTextRep,
+    @NotNull CollationCallback col
+  );
+
+  /**
+     The Java counterpart to the C-native sqlite3_create_function(),
+     sqlite3_create_function_v2(), and
+     sqlite3_create_window_function(). Which one it behaves like
+     depends on which methods the final argument implements. See
+     SQLFunction's subclasses (ScalarFunction, AggregateFunction<T>,
+     and WindowFunction<T>) for details.
+
+     <p>Unlike the C API, this returns SQLITE_MISUSE null if its db or
+     functionName arguments are null (as opposed to invoking UB).
+  */
+  public static native int sqlite3_create_function(
+    @NotNull sqlite3 db, @NotNull String functionName,
+    int nArg, int eTextRep, @NotNull SQLFunction func
+  );
+
+  static native int sqlite3_data_count(@NotNull long ptrToStmt);
+
+  public static int sqlite3_data_count(@NotNull sqlite3_stmt stmt){
+    return sqlite3_data_count(stmt.getNativePointer());
+  }
+
+  /**
+     Overload for sqlite3_db_config() calls which take (int,int*)
+     variadic arguments. Returns SQLITE_MISUSE if op is not one of the
+     SQLITE_DBCONFIG_... options which uses this call form.
+
+     <p>Unlike the C API, this returns SQLITE_MISUSE if its db argument
+     are null (as opposed to invoking UB).
+  */
+  public static native int sqlite3_db_config(
+    @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out
+  );
+
+  /**
+     Overload for sqlite3_db_config() calls which take a (const char*)
+     variadic argument. As of SQLite3 v3.43 the only such option is
+     SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op is not
+     SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be
+     extended in future versions.
+  */
+  public static native int sqlite3_db_config(
+    @NotNull sqlite3 db, int op, @NotNull String val
+  );
+
+  private static native String sqlite3_db_name(@NotNull long ptrToDb, int ndx);
+
+  public static String sqlite3_db_name(@NotNull sqlite3 db, int ndx){
+    return null==db ? null : sqlite3_db_name(db.getNativePointer(), ndx);
+  }
+
+
+  public static native String sqlite3_db_filename(
+    @NotNull sqlite3 db, @NotNull String dbName
+  );
+
+  public static native sqlite3 sqlite3_db_handle(@NotNull sqlite3_stmt stmt);
+
+  public static native int sqlite3_db_readonly(@NotNull sqlite3 db, String dbName);
+
+  public static native int sqlite3_db_release_memory(sqlite3 db);
+
+  public static native int sqlite3_db_status(
+    @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent,
+    @NotNull OutputPointer.Int32 pHighwater, boolean reset
+  );
+
+  public static native int sqlite3_errcode(@NotNull sqlite3 db);
+
+  public static native String sqlite3_errmsg(@NotNull sqlite3 db);
+
+  static native int sqlite3_error_offset(@NotNull long ptrToDb);
+
+  /**
+     Note that the returned byte offset values assume UTF-8-encoded
+     inputs, so won't always match character offsets in Java Strings.
+  */
+  public static int sqlite3_error_offset(@NotNull sqlite3 db){
+    return sqlite3_error_offset(db.getNativePointer());
+  }
+
+  public static native String sqlite3_errstr(int resultCode);
+
+  public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt);
+
+  static native int sqlite3_extended_errcode(@NotNull long ptrToDb);
+
+  public static int sqlite3_extended_errcode(@NotNull sqlite3 db){
+    return sqlite3_extended_errcode(db.getNativePointer());
+  }
+
+  public static native boolean sqlite3_extended_result_codes(
+    @NotNull sqlite3 db, boolean onoff
+  );
+
+  static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb);
+
+  public static boolean sqlite3_get_autocommit(@NotNull sqlite3 db){
+    return sqlite3_get_autocommit(db.getNativePointer());
+  }
+
+  public static native Object sqlite3_get_auxdata(
+    @NotNull sqlite3_context cx, int n
+  );
+
+  static native int sqlite3_finalize(long ptrToStmt);
+
+  public static int sqlite3_finalize(@NotNull sqlite3_stmt stmt){
+    return null==stmt ? 0 : sqlite3_finalize(stmt.clearNativePointer());
+  }
+
+  public static native int sqlite3_initialize();
+
+  public static native void sqlite3_interrupt(@NotNull sqlite3 db);
+
+  public static native boolean sqlite3_is_interrupted(@NotNull sqlite3 db);
+
+  public static native boolean sqlite3_keyword_check(@NotNull String word);
+
+  public static native int sqlite3_keyword_count();
+
+  public static native String sqlite3_keyword_name(int index);
+
+
+  public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db);
+
+  public static native String sqlite3_libversion();
+
+  public static native int sqlite3_libversion_number();
+
+  public static native int sqlite3_limit(@NotNull sqlite3 db, int id, int newVal);
+
+  /**
+     Only available if built with SQLITE_ENABLE_NORMALIZE. If not, it always
+     returns null.
+  */
+  public static native String sqlite3_normalized_sql(@NotNull sqlite3_stmt stmt);
+
+  /**
+     Works like its C counterpart and makes the native pointer of the
+     underling (sqlite3*) object available via
+     ppDb.getNativePointer(). That pointer is necessary for looking up
+     the JNI-side native, but clients need not pay it any
+     heed. Passing the object to sqlite3_close() or sqlite3_close_v2()
+     will clear that pointer mapping.
+
+     <p>Recall that even if opening fails, the output pointer might be
+     non-null. Any error message about the failure will be in that
+     object and it is up to the caller to sqlite3_close() that
+     db handle.
+  */
+  public static native int sqlite3_open(
+    @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb
+  );
+
+  /**
+     Convenience overload which returns its db handle directly. The returned
+     object might not have been successfully opened: use sqlite3_errcode() to
+     check whether it is in an error state.
+
+     <p>Ownership of the returned value is passed to the caller, who must eventually
+     pass it to sqlite3_close() or sqlite3_close_v2().
+  */
+  public static sqlite3 sqlite3_open(@Nullable String filename){
+    final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+    sqlite3_open(filename, out);
+    return out.take();
+  };
+
+  public static native int sqlite3_open_v2(
+    @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb,
+    int flags, @Nullable String zVfs
+  );
+
+  /**
+     Has the same semantics as the sqlite3-returning sqlite3_open()
+     but uses sqlite3_open_v2() instead of sqlite3_open().
+  */
+  public static sqlite3 sqlite3_open_v2(@Nullable String filename, int flags,
+                                        @Nullable String zVfs){
+    final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+    sqlite3_open_v2(filename, out, flags, zVfs);
+    return out.take();
+  };
+
+  /**
+     The sqlite3_prepare() family of functions require slightly
+     different signatures than their native counterparts, but (A) they
+     retain functionally equivalent semantics and (B) overloading
+     allows us to install several convenience forms.
+
+     <p>All of them which take their SQL in the form of a byte[] require
+     that it be in UTF-8 encoding unless explicitly noted otherwise.
+
+     <p>The forms which take a "tail" output pointer return (via that
+     output object) the index into their SQL byte array at which the
+     end of the first SQL statement processed by the call was
+     found. That's fundamentally how the C APIs work but making use of
+     that value requires more copying of the input SQL into
+     consecutively smaller arrays in order to consume all of
+     it. (There is an example of doing that in this project's Tester1
+     class.) For that vast majority of uses, that capability is not
+     necessary, however, and overloads are provided which gloss over
+     that.
+
+     <p>Results are undefined if maxBytes>sqlUtf8.length.
+
+     <p>This routine is private because its maxBytes value is not
+     strictly necessary in the Java interface, as sqlUtf8.length tells
+     us the information we need. Making this public would give clients
+     more ways to shoot themselves in the foot without providing any
+     real utility.
+  */
+  private static native int sqlite3_prepare(
+    @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+    @NotNull OutputPointer.sqlite3_stmt outStmt,
+    @Nullable OutputPointer.Int32 pTailOffset
+  );
+
+  /**
+     Works like the canonical sqlite3_prepare() but its "tail" output
+     argument is returned as the index offset into the given
+     UTF-8-encoded byte array at which SQL parsing stopped. The
+     semantics are otherwise identical to the C API counterpart.
+
+     <p>Several overloads provided simplified call signatures.
+  */
+  public static int sqlite3_prepare(
+    @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+    @NotNull OutputPointer.sqlite3_stmt outStmt,
+    @Nullable OutputPointer.Int32 pTailOffset
+  ){
+    return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+                           outStmt, pTailOffset);
+  }
+
+  public static int sqlite3_prepare(
+    @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+    @NotNull OutputPointer.sqlite3_stmt outStmt
+  ){
+    return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+                           outStmt, null);
+  }
+
+  public static int sqlite3_prepare(
+    @NotNull sqlite3 db, @NotNull String sql,
+    @NotNull OutputPointer.sqlite3_stmt outStmt
+  ){
+    final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+    return sqlite3_prepare(db.getNativePointer(), utf8, utf8.length,
+                           outStmt, null);
+  }
+
+  /**
+     Convenience overload which returns its statement handle directly,
+     or null on error or when reading only whitespace or
+     comments. sqlite3_errcode() can be used to determine whether
+     there was an error or the input was empty. Ownership of the
+     returned object is passed to the caller, who must eventually pass
+     it to sqlite3_finalize().
+  */
+  public static sqlite3_stmt sqlite3_prepare(
+    @NotNull sqlite3 db, @NotNull String sql
+  ){
+    final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+    sqlite3_prepare(db, sql, out);
+    return out.take();
+  }
+  /**
+     @see #sqlite3_prepare
+  */
+  private static native int sqlite3_prepare_v2(
+    @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+    @NotNull OutputPointer.sqlite3_stmt outStmt,
+    @Nullable OutputPointer.Int32 pTailOffset
+  );
+
+  /**
+     Works like the canonical sqlite3_prepare_v2() but its "tail"
+     output paramter is returned as the index offset into the given
+     byte array at which SQL parsing stopped.
+  */
+  public static int sqlite3_prepare_v2(
+    @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+    @NotNull OutputPointer.sqlite3_stmt outStmt,
+    @Nullable OutputPointer.Int32 pTailOffset
+  ){
+    return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+                              outStmt, pTailOffset);
+  }
+
+  public static int sqlite3_prepare_v2(
+    @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+    @NotNull OutputPointer.sqlite3_stmt outStmt
+  ){
+    return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+                              outStmt, null);
+  }
+
+  public static int sqlite3_prepare_v2(
+    @NotNull sqlite3 db, @NotNull String sql,
+    @NotNull OutputPointer.sqlite3_stmt outStmt
+  ){
+    final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+    return sqlite3_prepare_v2(db.getNativePointer(), utf8, utf8.length,
+                              outStmt, null);
+  }
+
+  /**
+     Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+     but uses sqlite3_prepare_v2().
+  */
+  public static sqlite3_stmt sqlite3_prepare_v2(
+    @NotNull sqlite3 db, @NotNull String sql
+  ){
+    final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+    sqlite3_prepare_v2(db, sql, out);
+    return out.take();
+  }
+
+  /**
+     @see #sqlite3_prepare
+  */
+  private static native int sqlite3_prepare_v3(
+    @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+    int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt,
+    @Nullable OutputPointer.Int32 pTailOffset
+  );
+
+  /**
+     Works like the canonical sqlite3_prepare_v2() but its "tail"
+     output paramter is returned as the index offset into the given
+     byte array at which SQL parsing stopped.
+  */
+  public static int sqlite3_prepare_v3(
+    @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags,
+    @NotNull OutputPointer.sqlite3_stmt outStmt,
+    @Nullable OutputPointer.Int32 pTailOffset
+  ){
+    return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+                              prepFlags, outStmt, pTailOffset);
+  }
+
+  /**
+     Convenience overload which elides the seldom-used pTailOffset
+     parameter.
+  */
+  public static int sqlite3_prepare_v3(
+    @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags,
+    @NotNull OutputPointer.sqlite3_stmt outStmt
+  ){
+    return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+                              prepFlags, outStmt, null);
+  }
+
+  /**
+     Convenience overload which elides the seldom-used pTailOffset
+     parameter and converts the given string to UTF-8 before passing
+     it on.
+  */
+  public static int sqlite3_prepare_v3(
+    @NotNull sqlite3 db, @NotNull String sql, int prepFlags,
+    @NotNull OutputPointer.sqlite3_stmt outStmt
+  ){
+    final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+    return sqlite3_prepare_v3(db.getNativePointer(), utf8, utf8.length,
+                              prepFlags, outStmt, null);
+  }
+
+  /**
+     Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+     but uses sqlite3_prepare_v3().
+  */
+  public static sqlite3_stmt sqlite3_prepare_v3(
+    @NotNull sqlite3 db, @NotNull String sql, int prepFlags
+  ){
+    final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+    sqlite3_prepare_v3(db, sql, prepFlags, out);
+    return out.take();
+  }
+
+  /**
+     A convenience wrapper around sqlite3_prepare_v3() which accepts
+     an arbitrary amount of input provided as a UTF-8-encoded byte
+     array.  It loops over the input bytes looking for
+     statements. Each one it finds is passed to p.call(), passing
+     ownership of it to that function. If p.call() returns 0, looping
+     continues, else the loop stops.
+
+     <p>If p.call() throws, the exception is propagated.
+
+     <p>How each statement is handled, including whether it is finalized
+     or not, is up to the callback object. e.g. the callback might
+     collect them for later use. If it does not collect them then it
+     must finalize them. See PrepareMultiCallback.Finalize for a
+     simple proxy which does that.
+  */
+  public static int sqlite3_prepare_multi(
+    @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+    int preFlags,
+    @NotNull PrepareMultiCallback p){
+    final OutputPointer.Int32 oTail = new OutputPointer.Int32();
+    int pos = 0, n = 1;
+    byte[] sqlChunk = sqlUtf8;
+    int rc = 0;
+    final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+    while(0==rc && pos<sqlChunk.length){
+      sqlite3_stmt stmt = null;
+      if(pos > 0){
+        sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+                                      sqlChunk.length);
+      }
+      if( 0==sqlChunk.length ) break;
+      rc = sqlite3_prepare_v3(db, sqlChunk, preFlags, outStmt, oTail);
+      if( 0!=rc ) break;
+      pos = oTail.value;
+      stmt = outStmt.take();
+      if( null == stmt ){
+        // empty statement was parsed.
+        continue;
+      }
+      rc = p.call(stmt);
+    }
+    return rc;
+  }
+
+  /**
+     Convenience overload which accepts its SQL as a String and uses
+     no statement-preparation flags.
+  */
+  public static int sqlite3_prepare_multi(
+    @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+    @NotNull PrepareMultiCallback p){
+    return sqlite3_prepare_multi(db, sqlUtf8, 0, p);
+  }
+
+  /**
+     Convenience overload which accepts its SQL as a String.
+  */
+  public static int sqlite3_prepare_multi(
+    @NotNull sqlite3 db, @NotNull String sql, int prepFlags,
+    @NotNull PrepareMultiCallback p){
+    return sqlite3_prepare_multi(
+      db, sql.getBytes(StandardCharsets.UTF_8), prepFlags, p
+    );
+  }
+
+  /**
+     Convenience overload which accepts its SQL as a String and uses
+     no statement-preparation flags.
+  */
+  public static int sqlite3_prepare_multi(
+    @NotNull sqlite3 db, @NotNull String sql,
+    @NotNull PrepareMultiCallback p){
+    return sqlite3_prepare_multi(db, sql, 0, p);
+  }
+
+  /**
+     Convenience overload which accepts its SQL as a String
+     array. They will be concatenated together as-is, with no
+     separator, and passed on to one of the other overloads.
+  */
+  public static int sqlite3_prepare_multi(
+    @NotNull sqlite3 db, @NotNull String[] sql, int prepFlags,
+    @NotNull PrepareMultiCallback p){
+    return sqlite3_prepare_multi(db, String.join("",sql), prepFlags, p);
+  }
+
+  /**
+     Convenience overload which uses no statement-preparation flags.
+  */
+  public static int sqlite3_prepare_multi(
+    @NotNull sqlite3 db, @NotNull String[] sql,
+    @NotNull PrepareMultiCallback p){
+    return sqlite3_prepare_multi(db, sql, 0, p);
+  }
+
+  static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb);
+
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+     acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns
+     SQLITE_MISUSE with no side effects.
+  */
+  public static int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db){
+    return sqlite3_preupdate_blobwrite(db.getNativePointer());
+  }
+
+  static native int sqlite3_preupdate_count(@NotNull long ptrToDb);
+
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+     acts as a proxy for C's sqlite3_preupdate_count(), else it returns
+     SQLITE_MISUSE with no side effects.
+  */
+  public static int sqlite3_preupdate_count(@NotNull sqlite3 db){
+    return sqlite3_preupdate_count(db.getNativePointer());
+  }
+
+  static native int sqlite3_preupdate_depth(@NotNull long ptrToDb);
+
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+     acts as a proxy for C's sqlite3_preupdate_depth(), else it returns
+     SQLITE_MISUSE with no side effects.
+  */
+  public static int sqlite3_preupdate_depth(@NotNull sqlite3 db){
+    return sqlite3_preupdate_depth(db.getNativePointer());
+  }
+
+  static native PreupdateHookCallback sqlite3_preupdate_hook(
+    @NotNull long ptrToDb, @Nullable PreupdateHookCallback hook
+  );
+
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+     acts as a proxy for C's sqlite3_preupdate_hook(), else it returns null
+     with no side effects.
+  */
+  public static PreupdateHookCallback sqlite3_preupdate_hook(
+    @NotNull sqlite3 db, @Nullable PreupdateHookCallback hook
+  ){
+    return sqlite3_preupdate_hook(db.getNativePointer(), hook);
+  }
+
+  static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col,
+                                                 @NotNull OutputPointer.sqlite3_value out);
+
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+     this acts as a proxy for C's sqlite3_preupdate_new(), else it
+     returns SQLITE_MISUSE with no side effects.
+  */
+  public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col,
+                                          @NotNull OutputPointer.sqlite3_value out){
+    return sqlite3_preupdate_new(db.getNativePointer(), col, out);
+  }
+
+  /**
+     Convenience wrapper for the 3-arg sqlite3_preupdate_new() which returns
+     null on error.
+  */
+  public static sqlite3_value sqlite3_preupdate_new(@NotNull sqlite3 db, int col){
+    final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+    sqlite3_preupdate_new(db.getNativePointer(), col, out);
+    return out.take();
+  }
+
+  static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col,
+                                                 @NotNull OutputPointer.sqlite3_value out);
+
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+     this acts as a proxy for C's sqlite3_preupdate_old(), else it
+     returns SQLITE_MISUSE with no side effects.
+  */
+  public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col,
+                                          @NotNull OutputPointer.sqlite3_value out){
+    return sqlite3_preupdate_old(db.getNativePointer(), col, out);
+  }
+
+  /**
+     Convenience wrapper for the 3-arg sqlite3_preupdate_old() which returns
+     null on error.
+  */
+  public static sqlite3_value sqlite3_preupdate_old(@NotNull sqlite3 db, int col){
+    final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+    sqlite3_preupdate_old(db.getNativePointer(), col, out);
+    return out.take();
+  }
+
+  public static native void sqlite3_progress_handler(
+    @NotNull sqlite3 db, int n, @Nullable ProgressHandlerCallback h
+  );
+
+  public static native void sqlite3_randomness(byte[] target);
+
+  public static native int sqlite3_release_memory(int n);
+
+  public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt);
+
+  /**
+     Works like the C API except that it has no side effects if auto
+     extensions are currently running. (The JNI-level list of
+     extensions cannot be manipulated while it is being traversed.)
+  */
+  public static native void sqlite3_reset_auto_extension();
+
+  public static native void sqlite3_result_double(
+    @NotNull sqlite3_context cx, double v
+  );
+
+  /**
+     The main sqlite3_result_error() impl of which all others are
+     proxies. eTextRep must be one of SQLITE_UTF8 or SQLITE_UTF16 and
+     msg must be encoded correspondingly. Any other eTextRep value
+     results in the C-level sqlite3_result_error() being called with a
+     complaint about the invalid argument.
+  */
+  static native void sqlite3_result_error(
+    @NotNull sqlite3_context cx, @NotNull byte[] msg, int eTextRep
+  );
+
+  public static void sqlite3_result_error(
+    @NotNull sqlite3_context cx, @NotNull byte[] utf8
+  ){
+    sqlite3_result_error(cx, utf8, SQLITE_UTF8);
+  }
+
+  public static void sqlite3_result_error(
+    @NotNull sqlite3_context cx, @NotNull String msg
+  ){
+    final byte[] utf8 = msg.getBytes(StandardCharsets.UTF_8);
+    sqlite3_result_error(cx, utf8, SQLITE_UTF8);
+  }
+
+  public static void sqlite3_result_error16(
+    @NotNull sqlite3_context cx, @NotNull byte[] utf16
+  ){
+    sqlite3_result_error(cx, utf16, SQLITE_UTF16);
+  }
+
+  public static void sqlite3_result_error16(
+    @NotNull sqlite3_context cx, @NotNull String msg
+  ){
+    final byte[] utf16 = msg.getBytes(StandardCharsets.UTF_16);
+    sqlite3_result_error(cx, utf16, SQLITE_UTF16);
+  }
+
+  /**
+     Equivalent to passing e.toString() to {@link
+     #sqlite3_result_error(sqlite3_context,String)}.  Note that
+     toString() is used instead of getMessage() because the former
+     prepends the exception type name to the message.
+  */
+  public static void sqlite3_result_error(
+    @NotNull sqlite3_context cx, @NotNull Exception e
+  ){
+    sqlite3_result_error(cx, e.toString());
+  }
+
+  public static native void sqlite3_result_error_toobig(
+    @NotNull sqlite3_context cx
+  );
+
+  public static native void sqlite3_result_error_nomem(
+    @NotNull sqlite3_context cx
+  );
+
+  public static native void sqlite3_result_error_code(
+    @NotNull sqlite3_context cx, int c
+  );
+
+  public static native void sqlite3_result_null(
+    @NotNull sqlite3_context cx
+  );
+
+  public static native void sqlite3_result_int(
+    @NotNull sqlite3_context cx, int v
+  );
+
+  public static native void sqlite3_result_int64(
+    @NotNull sqlite3_context cx, long v
+  );
+
+  /**
+     Binds the SQL result to the given object, or {@link
+     #sqlite3_result_null} if {@code o} is null. Use {@link
+     #sqlite3_value_java_object} to fetch it.
+
+     <p>This is implemented in terms of C's sqlite3_result_pointer(),
+     but that function is not exposed to JNI because (A)
+     cross-language semantic mismatch and (B) Java doesn't need that
+     argument for its intended purpose (type safety).
+
+     <p>Note that there is no sqlite3_column_java_object(), as the
+     C-level API has no sqlite3_column_pointer() to proxy.
+
+     @see #sqlite3_value_java_object
+     @see #sqlite3_bind_java_object
+  */
+  public static native void sqlite3_result_java_object(
+    @NotNull sqlite3_context cx, @NotNull Object o
+  );
+
+  public static void sqlite3_result_set(
+    @NotNull sqlite3_context cx, @NotNull Boolean v
+  ){
+    sqlite3_result_int(cx, v ? 1 : 0);
+  }
+
+  public static void sqlite3_result_set(
+    @NotNull sqlite3_context cx, boolean v
+  ){
+    sqlite3_result_int(cx, v ? 1 : 0);
+  }
+
+  public static void sqlite3_result_set(
+    @NotNull sqlite3_context cx, @NotNull Double v
+  ){
+    sqlite3_result_double(cx, v);
+  }
+
+  public static void sqlite3_result_set(
+    @NotNull sqlite3_context cx, double v
+  ){
+    sqlite3_result_double(cx, v);
+  }
+
+  public static void sqlite3_result_set(
+    @NotNull sqlite3_context cx, @NotNull Integer v
+  ){
+    sqlite3_result_int(cx, v);
+  }
+
+  public static void sqlite3_result_set(@NotNull sqlite3_context cx, int v){
+    sqlite3_result_int(cx, v);
+  }
+
+  public static void sqlite3_result_set(
+    @NotNull sqlite3_context cx, @NotNull Long v
+  ){
+    sqlite3_result_int64(cx, v);
+  }
+
+  public static void sqlite3_result_set(
+    @NotNull sqlite3_context cx, long v
+  ){
+    sqlite3_result_int64(cx, v);
+  }
+
+  public static void sqlite3_result_set(
+    @NotNull sqlite3_context cx, @Nullable String v
+  ){
+    if( null==v ) sqlite3_result_null(cx);
+    else sqlite3_result_text(cx, v);
+  }
+
+  public static void sqlite3_result_set(
+    @NotNull sqlite3_context cx, @Nullable byte[] blob
+  ){
+    if( null==blob ) sqlite3_result_null(cx);
+    else sqlite3_result_blob(cx, blob, blob.length);
+  }
+
+  public static native void sqlite3_result_value(
+    @NotNull sqlite3_context cx, @NotNull sqlite3_value v
+  );
+
+  public static native void sqlite3_result_zeroblob(
+    @NotNull sqlite3_context cx, int n
+  );
+
+  public static native int sqlite3_result_zeroblob64(
+    @NotNull sqlite3_context cx, long n
+  );
+
+  /**
+     This overload is private because its final parameter is arguably
+     unnecessary in Java.
+  */
+  private static native void sqlite3_result_blob(
+    @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen
+  );
+
+  public static void sqlite3_result_blob(
+    @NotNull sqlite3_context cx, @Nullable byte[] blob
+  ){
+    sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length));
+  }
+
+  /**
+     Binds the given text using C's sqlite3_result_blob64() unless:
+
+     <ul>
+
+     <li>@param blob is null: translates to sqlite3_result_null()</li>
+
+     <li>@param blob is too large: translates to
+     sqlite3_result_error_toobig()</li>
+
+     </ul>
+
+     <p>If @param maxLen is larger than blob.length, it is truncated
+     to that value. If it is negative, results are undefined.</p>
+
+     <p>This overload is private because its final parameter is
+     arguably unnecessary in Java.</p>
+  */
+  private static native void sqlite3_result_blob64(
+    @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen
+  );
+
+  public static void sqlite3_result_blob64(
+    @NotNull sqlite3_context cx, @Nullable byte[] blob
+  ){
+    sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length));
+  }
+
+  /**
+     This overload is private because its final parameter is
+     arguably unnecessary in Java.
+  */
+  private static native void sqlite3_result_text(
+    @NotNull sqlite3_context cx, @Nullable byte[] utf8, int maxLen
+  );
+
+  public static void sqlite3_result_text(
+    @NotNull sqlite3_context cx, @Nullable byte[] utf8
+  ){
+    sqlite3_result_text(cx, utf8, null==utf8 ? 0 : utf8.length);
+  }
+
+  public static void sqlite3_result_text(
+    @NotNull sqlite3_context cx, @Nullable String text
+  ){
+    if(null == text) sqlite3_result_null(cx);
+    else{
+      final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8);
+      sqlite3_result_text(cx, utf8, utf8.length);
+    }
+  }
+
+  /**
+     Binds the given text using C's sqlite3_result_text64() unless:
+
+     <ul>
+
+     <li>text is null: translates to a call to sqlite3_result_null()</li>
+
+     <li>text is too large: translates to a call to
+     {@link #sqlite3_result_error_toobig}</li>
+
+     <li>The @param encoding argument has an invalid value: translates to
+     {@link sqlite3_result_error_code} with code SQLITE_FORMAT.</li>
+
+     </ul>
+
+     If maxLength (in bytes, not characters) is larger than
+     text.length, it is silently truncated to text.length. If it is
+     negative, results are undefined. If text is null, the subsequent
+     arguments are ignored.
+
+     This overload is private because its maxLength parameter is
+     arguably unnecessary in Java.
+  */
+  private static native void sqlite3_result_text64(
+    @NotNull sqlite3_context cx, @Nullable byte[] text,
+    long maxLength, int encoding
+  );
+
+  /**
+     Sets the current UDF result to the given bytes, which are assumed
+     be encoded in UTF-16 using the platform's byte order.
+  */
+  public static void sqlite3_result_text16(
+    @NotNull sqlite3_context cx, @Nullable byte[] utf16
+  ){
+    if(null == utf16) sqlite3_result_null(cx);
+    else sqlite3_result_text64(cx, utf16, utf16.length, SQLITE_UTF16);
+  }
+
+  public static void sqlite3_result_text16(
+    @NotNull sqlite3_context cx, @Nullable String text
+  ){
+    if(null == text) sqlite3_result_null(cx);
+    else{
+      final byte[] b = text.getBytes(StandardCharsets.UTF_16);
+      sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16);
+    }
+  }
+
+  static native RollbackHookCallback sqlite3_rollback_hook(
+    @NotNull long ptrToDb, @Nullable RollbackHookCallback hook
+  );
+
+  public static RollbackHookCallback sqlite3_rollback_hook(
+    @NotNull sqlite3 db, @Nullable RollbackHookCallback hook
+  ){
+    return sqlite3_rollback_hook(db.getNativePointer(), hook);
+  }
+
+  public static native int sqlite3_set_authorizer(
+    @NotNull sqlite3 db, @Nullable AuthorizerCallback auth
+  );
+
+  public static native void sqlite3_set_auxdata(
+    @NotNull sqlite3_context cx, int n, @Nullable Object data
+  );
+
+  public static native void sqlite3_set_last_insert_rowid(
+    @NotNull sqlite3 db, long rowid
+  );
+
+
+  /**
+     In addition to calling the C-level sqlite3_shutdown(), the JNI
+     binding also cleans up all stale per-thread state managed by the
+     library, as well as any registered auto-extensions, and frees up
+     various bits of memory. Calling this while database handles or
+     prepared statements are still active will leak resources. Trying
+     to use those objects after this routine is called invoked
+     undefined behavior.
+  */
+  public static synchronized native int sqlite3_shutdown();
+
+  public static native int sqlite3_sleep(int ms);
+
+  public static native String sqlite3_sourceid();
+
+  public static native String sqlite3_sql(@NotNull sqlite3_stmt stmt);
+
+  //! Consider removing this. We can use sqlite3_status64() instead,
+  // or use that one's impl with this one's name.
+  public static native int sqlite3_status(
+    int op, @NotNull OutputPointer.Int32 pCurrent,
+    @NotNull OutputPointer.Int32 pHighwater, boolean reset
+  );
+
+  public static native int sqlite3_status64(
+    int op, @NotNull OutputPointer.Int64 pCurrent,
+    @NotNull OutputPointer.Int64 pHighwater, boolean reset
+  );
+
+  public static native int sqlite3_step(@NotNull sqlite3_stmt stmt);
+
+  public static native boolean sqlite3_stmt_busy(@NotNull sqlite3_stmt stmt);
+
+  static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op);
+
+  public static int sqlite3_stmt_explain(@NotNull sqlite3_stmt stmt, int op){
+    return sqlite3_stmt_explain(stmt.getNativePointer(), op);
+  }
+
+  static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt);
+
+  public static int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt){
+    return sqlite3_stmt_isexplain(stmt.getNativePointer());
+  }
+
+  public static native boolean sqlite3_stmt_readonly(@NotNull sqlite3_stmt stmt);
+
+  public static native int sqlite3_stmt_status(
+    @NotNull sqlite3_stmt stmt, int op, boolean reset
+  );
+
+  /**
+     Internal impl of the public sqlite3_strglob() method. Neither
+     argument may be null and both must be NUL-terminated UTF-8.
+
+     This overload is private because: (A) to keep users from
+     inadvertently passing non-NUL-terminated byte arrays (an easy
+     thing to do). (B) it is cheaper to NUL-terminate the
+     String-to-byte-array conversion in the Java implementation
+     (sqlite3_strglob(String,String)) than to do that in C, so that
+     signature is the public-facing one.
+  */
+  private static native int sqlite3_strglob(
+    @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8
+  );
+
+  public static int sqlite3_strglob(
+    @NotNull String glob, @NotNull String txt
+  ){
+    return sqlite3_strglob(nulTerminateUtf8(glob),
+                           nulTerminateUtf8(txt));
+  }
+
+  /**
+     The LIKE counterpart of the private sqlite3_strglob() method.
+  */
+  private static native int sqlite3_strlike(
+    @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8,
+    int escChar
+  );
+
+  public static int sqlite3_strlike(
+    @NotNull String glob, @NotNull String txt, char escChar
+  ){
+    return sqlite3_strlike(nulTerminateUtf8(glob),
+                           nulTerminateUtf8(txt),
+                           (int)escChar);
+  }
+
+  static native int sqlite3_system_errno(@NotNull long ptrToDb);
+
+  public static int sqlite3_system_errno(@NotNull sqlite3 db){
+    return sqlite3_system_errno(db.getNativePointer());
+  }
+
+  public static native int sqlite3_table_column_metadata(
+    @NotNull sqlite3 db, @NotNull String zDbName,
+    @NotNull String zTableName, @NotNull String zColumnName,
+    @Nullable OutputPointer.String pzDataType,
+    @Nullable OutputPointer.String pzCollSeq,
+    @Nullable OutputPointer.Bool pNotNull,
+    @Nullable OutputPointer.Bool pPrimaryKey,
+    @Nullable OutputPointer.Bool pAutoinc
+  );
+
+  /**
+     Convenience overload which returns its results via a single
+     output object. If this function returns non-0 (error), the the
+     contents of the output object are not modified.
+  */
+  public static int sqlite3_table_column_metadata(
+    @NotNull sqlite3 db, @NotNull String zDbName,
+    @NotNull String zTableName, @NotNull String zColumnName,
+    @NotNull TableColumnMetadata out){
+    return sqlite3_table_column_metadata(
+      db, zDbName, zTableName, zColumnName,
+      out.pzDataType, out.pzCollSeq, out.pNotNull,
+      out.pPrimaryKey, out.pAutoinc);
+  }
+
+  /**
+     Convenience overload which returns the column metadata object on
+     success and null on error.
+  */
+  public static TableColumnMetadata sqlite3_table_column_metadata(
+    @NotNull sqlite3 db, @NotNull String zDbName,
+    @NotNull String zTableName, @NotNull String zColumnName){
+    final TableColumnMetadata out = new TableColumnMetadata();
+    return 0==sqlite3_table_column_metadata(
+      db, zDbName, zTableName, zColumnName, out
+    ) ? out : null;
+  }
+
+  public static native int sqlite3_threadsafe();
+
+  static native int sqlite3_total_changes(@NotNull long ptrToDb);
+
+  public static int sqlite3_total_changes(@NotNull sqlite3 db){
+    return sqlite3_total_changes(db.getNativePointer());
+  }
+
+  static native long sqlite3_total_changes64(@NotNull long ptrToDb);
+
+  public static long sqlite3_total_changes64(@NotNull sqlite3 db){
+    return sqlite3_total_changes64(db.getNativePointer());
+  }
+
+  /**
+     Works like C's sqlite3_trace_v2() except that the 3rd argument to that
+     function is elided here because the roles of that functions' 3rd and 4th
+     arguments are encapsulated in the final argument to this function.
+
+     <p>Unlike the C API, which is documented as always returning 0,
+     this implementation returns non-0 if initialization of the tracer
+     mapping state fails (e.g. on OOM).
+  */
+  public static native int sqlite3_trace_v2(
+    @NotNull sqlite3 db, int traceMask, @Nullable TraceV2Callback tracer
+  );
+
+  public static native int sqlite3_txn_state(
+    @NotNull sqlite3 db, @Nullable String zSchema
+  );
+
+  static native UpdateHookCallback sqlite3_update_hook(
+    @NotNull long ptrToDb, @Nullable UpdateHookCallback hook
+  );
+
+  public static UpdateHookCallback sqlite3_update_hook(
+    @NotNull sqlite3 db, @Nullable UpdateHookCallback hook
+  ){
+    return sqlite3_update_hook(db.getNativePointer(), hook);
+  }
+
+  /*
+     Note that:
+
+     void * sqlite3_user_data(sqlite3_context*)
+
+     Is not relevant in the JNI binding, as its feature is replaced by
+     the ability to pass an object, including any relevant state, to
+     sqlite3_create_function().
+  */
+
+  static native byte[] sqlite3_value_blob(@NotNull long ptrToValue);
+
+  public static byte[] sqlite3_value_blob(@NotNull sqlite3_value v){
+    return sqlite3_value_blob(v.getNativePointer());
+  }
+
+  static native int sqlite3_value_bytes(@NotNull long ptrToValue);
+
+  public static int sqlite3_value_bytes(@NotNull sqlite3_value v){
+    return sqlite3_value_bytes(v.getNativePointer());
+  }
+
+  static native int sqlite3_value_bytes16(@NotNull long ptrToValue);
+
+  public static int sqlite3_value_bytes16(@NotNull sqlite3_value v){
+    return sqlite3_value_bytes16(v.getNativePointer());
+  }
+
+  static native double sqlite3_value_double(@NotNull long ptrToValue);
+
+  public static double sqlite3_value_double(@NotNull sqlite3_value v){
+    return sqlite3_value_double(v.getNativePointer());
+  }
+
+  static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue);
+
+  public static sqlite3_value sqlite3_value_dup(@NotNull sqlite3_value v){
+    return sqlite3_value_dup(v.getNativePointer());
+  }
+
+  static native int sqlite3_value_encoding(@NotNull long ptrToValue);
+
+  public static int sqlite3_value_encoding(@NotNull sqlite3_value v){
+    return sqlite3_value_encoding(v.getNativePointer());
+  }
+
+  static native void sqlite3_value_free(@Nullable long ptrToValue);
+
+  public static void sqlite3_value_free(@Nullable sqlite3_value v){
+    sqlite3_value_free(v.getNativePointer());
+  }
+
+  static native boolean sqlite3_value_frombind(@NotNull long ptrToValue);
+
+  public static boolean sqlite3_value_frombind(@NotNull sqlite3_value v){
+    return sqlite3_value_frombind(v.getNativePointer());
+  }
+
+  static native int sqlite3_value_int(@NotNull long ptrToValue);
+
+  public static int sqlite3_value_int(@NotNull sqlite3_value v){
+    return sqlite3_value_int(v.getNativePointer());
+  }
+
+  static native long sqlite3_value_int64(@NotNull long ptrToValue);
+
+  public static long sqlite3_value_int64(@NotNull sqlite3_value v){
+    return sqlite3_value_int64(v.getNativePointer());
+  }
+
+  static native Object sqlite3_value_java_object(@NotNull long ptrToValue);
+
+  /**
+     If the given value was set using {@link
+     #sqlite3_result_java_object} then this function returns that
+     object, else it returns null.
+
+     <p>It is up to the caller to inspect the object to determine its
+     type, and cast it if necessary.
+  */
+  public static Object sqlite3_value_java_object(@NotNull sqlite3_value v){
+    return sqlite3_value_java_object(v.getNativePointer());
+  }
+
+  /**
+     A variant of sqlite3_value_java_object() which returns the
+     fetched object cast to T if the object is an instance of the
+     given Class, else it returns null.
+  */
+  @SuppressWarnings("unchecked")
+  public static <T> T sqlite3_value_java_casted(@NotNull sqlite3_value v,
+                                                @NotNull Class<T> type){
+    final Object o = sqlite3_value_java_object(v);
+    return type.isInstance(o) ? (T)o : null;
+  }
+
+  static native int sqlite3_value_nochange(@NotNull long ptrToValue);
+
+  public static int sqlite3_value_nochange(@NotNull sqlite3_value v){
+    return sqlite3_value_nochange(v.getNativePointer());
+  }
+
+  static native int sqlite3_value_numeric_type(@NotNull long ptrToValue);
+
+  public static int sqlite3_value_numeric_type(@NotNull sqlite3_value v){
+    return sqlite3_value_numeric_type(v.getNativePointer());
+  }
+
+  static native int sqlite3_value_subtype(@NotNull long ptrToValue);
+
+  public static int sqlite3_value_subtype(@NotNull sqlite3_value v){
+    return sqlite3_value_subtype(v.getNativePointer());
+  }
+
+  static native byte[] sqlite3_value_text(@NotNull long ptrToValue);
+
+  /**
+     Functions identially to the C API, and this note is just to
+     stress that the returned bytes are encoded as UTF-8. It returns
+     null if the underlying C-level sqlite3_value_text() returns NULL
+     or on allocation error.
+  */
+  public static byte[] sqlite3_value_text(@NotNull sqlite3_value v){
+    return sqlite3_value_text(v.getNativePointer());
+  }
+
+  static native String sqlite3_value_text16(@NotNull long ptrToValue);
+
+  public static String sqlite3_value_text16(@NotNull sqlite3_value v){
+    return sqlite3_value_text16(v.getNativePointer());
+  }
+
+  static native int sqlite3_value_type(@NotNull long ptrToValue);
+
+  public static int sqlite3_value_type(@NotNull sqlite3_value v){
+    return sqlite3_value_type(v.getNativePointer());
+  }
+
+  /**
+     This is NOT part of the public API. It exists solely as a place
+     for this code's developers to collect internal metrics and such.
+     It has no stable interface. It may go way or change behavior at
+     any time.
+  */
+  public static native void sqlite3_jni_internal_details();
+
+  //////////////////////////////////////////////////////////////////////
+  // SQLITE_... constants follow...
+
+  // version info
+  public static final int SQLITE_VERSION_NUMBER = sqlite3_libversion_number();
+  public static final String SQLITE_VERSION = sqlite3_libversion();
+  public static final String SQLITE_SOURCE_ID = sqlite3_sourceid();
+
+  // access
+  public static final int SQLITE_ACCESS_EXISTS = 0;
+  public static final int SQLITE_ACCESS_READWRITE = 1;
+  public static final int SQLITE_ACCESS_READ = 2;
+
+  // authorizer
+  public static final int SQLITE_DENY = 1;
+  public static final int SQLITE_IGNORE = 2;
+  public static final int SQLITE_CREATE_INDEX = 1;
+  public static final int SQLITE_CREATE_TABLE = 2;
+  public static final int SQLITE_CREATE_TEMP_INDEX = 3;
+  public static final int SQLITE_CREATE_TEMP_TABLE = 4;
+  public static final int SQLITE_CREATE_TEMP_TRIGGER = 5;
+  public static final int SQLITE_CREATE_TEMP_VIEW = 6;
+  public static final int SQLITE_CREATE_TRIGGER = 7;
+  public static final int SQLITE_CREATE_VIEW = 8;
+  public static final int SQLITE_DELETE = 9;
+  public static final int SQLITE_DROP_INDEX = 10;
+  public static final int SQLITE_DROP_TABLE = 11;
+  public static final int SQLITE_DROP_TEMP_INDEX = 12;
+  public static final int SQLITE_DROP_TEMP_TABLE = 13;
+  public static final int SQLITE_DROP_TEMP_TRIGGER = 14;
+  public static final int SQLITE_DROP_TEMP_VIEW = 15;
+  public static final int SQLITE_DROP_TRIGGER = 16;
+  public static final int SQLITE_DROP_VIEW = 17;
+  public static final int SQLITE_INSERT = 18;
+  public static final int SQLITE_PRAGMA = 19;
+  public static final int SQLITE_READ = 20;
+  public static final int SQLITE_SELECT = 21;
+  public static final int SQLITE_TRANSACTION = 22;
+  public static final int SQLITE_UPDATE = 23;
+  public static final int SQLITE_ATTACH = 24;
+  public static final int SQLITE_DETACH = 25;
+  public static final int SQLITE_ALTER_TABLE = 26;
+  public static final int SQLITE_REINDEX = 27;
+  public static final int SQLITE_ANALYZE = 28;
+  public static final int SQLITE_CREATE_VTABLE = 29;
+  public static final int SQLITE_DROP_VTABLE = 30;
+  public static final int SQLITE_FUNCTION = 31;
+  public static final int SQLITE_SAVEPOINT = 32;
+  public static final int SQLITE_RECURSIVE = 33;
+
+  // blob finalizers: these should, because they are treated as
+  // special pointer values in C, ideally have the same sizeof() as
+  // the platform's (void*), but we can't know that size from here.
+  public static final long SQLITE_STATIC = 0;
+  public static final long SQLITE_TRANSIENT = -1;
+
+  // changeset
+  public static final int SQLITE_CHANGESETSTART_INVERT = 2;
+  public static final int SQLITE_CHANGESETAPPLY_NOSAVEPOINT = 1;
+  public static final int SQLITE_CHANGESETAPPLY_INVERT = 2;
+  public static final int SQLITE_CHANGESETAPPLY_IGNORENOOP = 4;
+  public static final int SQLITE_CHANGESET_DATA = 1;
+  public static final int SQLITE_CHANGESET_NOTFOUND = 2;
+  public static final int SQLITE_CHANGESET_CONFLICT = 3;
+  public static final int SQLITE_CHANGESET_CONSTRAINT = 4;
+  public static final int SQLITE_CHANGESET_FOREIGN_KEY = 5;
+  public static final int SQLITE_CHANGESET_OMIT = 0;
+  public static final int SQLITE_CHANGESET_REPLACE = 1;
+  public static final int SQLITE_CHANGESET_ABORT = 2;
+
+  // config
+  public static final int SQLITE_CONFIG_SINGLETHREAD = 1;
+  public static final int SQLITE_CONFIG_MULTITHREAD = 2;
+  public static final int SQLITE_CONFIG_SERIALIZED = 3;
+  public static final int SQLITE_CONFIG_MALLOC = 4;
+  public static final int SQLITE_CONFIG_GETMALLOC = 5;
+  public static final int SQLITE_CONFIG_SCRATCH = 6;
+  public static final int SQLITE_CONFIG_PAGECACHE = 7;
+  public static final int SQLITE_CONFIG_HEAP = 8;
+  public static final int SQLITE_CONFIG_MEMSTATUS = 9;
+  public static final int SQLITE_CONFIG_MUTEX = 10;
+  public static final int SQLITE_CONFIG_GETMUTEX = 11;
+  public static final int SQLITE_CONFIG_LOOKASIDE = 13;
+  public static final int SQLITE_CONFIG_PCACHE = 14;
+  public static final int SQLITE_CONFIG_GETPCACHE = 15;
+  public static final int SQLITE_CONFIG_LOG = 16;
+  public static final int SQLITE_CONFIG_URI = 17;
+  public static final int SQLITE_CONFIG_PCACHE2 = 18;
+  public static final int SQLITE_CONFIG_GETPCACHE2 = 19;
+  public static final int SQLITE_CONFIG_COVERING_INDEX_SCAN = 20;
+  public static final int SQLITE_CONFIG_SQLLOG = 21;
+  public static final int SQLITE_CONFIG_MMAP_SIZE = 22;
+  public static final int SQLITE_CONFIG_WIN32_HEAPSIZE = 23;
+  public static final int SQLITE_CONFIG_PCACHE_HDRSZ = 24;
+  public static final int SQLITE_CONFIG_PMASZ = 25;
+  public static final int SQLITE_CONFIG_STMTJRNL_SPILL = 26;
+  public static final int SQLITE_CONFIG_SMALL_MALLOC = 27;
+  public static final int SQLITE_CONFIG_SORTERREF_SIZE = 28;
+  public static final int SQLITE_CONFIG_MEMDB_MAXSIZE = 29;
+
+  // data types
+  public static final int SQLITE_INTEGER = 1;
+  public static final int SQLITE_FLOAT = 2;
+  public static final int SQLITE_TEXT = 3;
+  public static final int SQLITE_BLOB = 4;
+  public static final int SQLITE_NULL = 5;
+
+  // db config
+  public static final int SQLITE_DBCONFIG_MAINDBNAME = 1000;
+  public static final int SQLITE_DBCONFIG_LOOKASIDE = 1001;
+  public static final int SQLITE_DBCONFIG_ENABLE_FKEY = 1002;
+  public static final int SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003;
+  public static final int SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004;
+  public static final int SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005;
+  public static final int SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006;
+  public static final int SQLITE_DBCONFIG_ENABLE_QPSG = 1007;
+  public static final int SQLITE_DBCONFIG_TRIGGER_EQP = 1008;
+  public static final int SQLITE_DBCONFIG_RESET_DATABASE = 1009;
+  public static final int SQLITE_DBCONFIG_DEFENSIVE = 1010;
+  public static final int SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011;
+  public static final int SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012;
+  public static final int SQLITE_DBCONFIG_DQS_DML = 1013;
+  public static final int SQLITE_DBCONFIG_DQS_DDL = 1014;
+  public static final int SQLITE_DBCONFIG_ENABLE_VIEW = 1015;
+  public static final int SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016;
+  public static final int SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017;
+  public static final int SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018;
+  public static final int SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019;
+  public static final int SQLITE_DBCONFIG_MAX = 1019;
+
+  // db status
+  public static final int SQLITE_DBSTATUS_LOOKASIDE_USED = 0;
+  public static final int SQLITE_DBSTATUS_CACHE_USED = 1;
+  public static final int SQLITE_DBSTATUS_SCHEMA_USED = 2;
+  public static final int SQLITE_DBSTATUS_STMT_USED = 3;
+  public static final int SQLITE_DBSTATUS_LOOKASIDE_HIT = 4;
+  public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5;
+  public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6;
+  public static final int SQLITE_DBSTATUS_CACHE_HIT = 7;
+  public static final int SQLITE_DBSTATUS_CACHE_MISS = 8;
+  public static final int SQLITE_DBSTATUS_CACHE_WRITE = 9;
+  public static final int SQLITE_DBSTATUS_DEFERRED_FKS = 10;
+  public static final int SQLITE_DBSTATUS_CACHE_USED_SHARED = 11;
+  public static final int SQLITE_DBSTATUS_CACHE_SPILL = 12;
+  public static final int SQLITE_DBSTATUS_MAX = 12;
+
+  // encodings
+  public static final int SQLITE_UTF8 = 1;
+  public static final int SQLITE_UTF16LE = 2;
+  public static final int SQLITE_UTF16BE = 3;
+  public static final int SQLITE_UTF16 = 4;
+  public static final int SQLITE_UTF16_ALIGNED = 8;
+
+  // fcntl
+  public static final int SQLITE_FCNTL_LOCKSTATE = 1;
+  public static final int SQLITE_FCNTL_GET_LOCKPROXYFILE = 2;
+  public static final int SQLITE_FCNTL_SET_LOCKPROXYFILE = 3;
+  public static final int SQLITE_FCNTL_LAST_ERRNO = 4;
+  public static final int SQLITE_FCNTL_SIZE_HINT = 5;
+  public static final int SQLITE_FCNTL_CHUNK_SIZE = 6;
+  public static final int SQLITE_FCNTL_FILE_POINTER = 7;
+  public static final int SQLITE_FCNTL_SYNC_OMITTED = 8;
+  public static final int SQLITE_FCNTL_WIN32_AV_RETRY = 9;
+  public static final int SQLITE_FCNTL_PERSIST_WAL = 10;
+  public static final int SQLITE_FCNTL_OVERWRITE = 11;
+  public static final int SQLITE_FCNTL_VFSNAME = 12;
+  public static final int SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13;
+  public static final int SQLITE_FCNTL_PRAGMA = 14;
+  public static final int SQLITE_FCNTL_BUSYHANDLER = 15;
+  public static final int SQLITE_FCNTL_TEMPFILENAME = 16;
+  public static final int SQLITE_FCNTL_MMAP_SIZE = 18;
+  public static final int SQLITE_FCNTL_TRACE = 19;
+  public static final int SQLITE_FCNTL_HAS_MOVED = 20;
+  public static final int SQLITE_FCNTL_SYNC = 21;
+  public static final int SQLITE_FCNTL_COMMIT_PHASETWO = 22;
+  public static final int SQLITE_FCNTL_WIN32_SET_HANDLE = 23;
+  public static final int SQLITE_FCNTL_WAL_BLOCK = 24;
+  public static final int SQLITE_FCNTL_ZIPVFS = 25;
+  public static final int SQLITE_FCNTL_RBU = 26;
+  public static final int SQLITE_FCNTL_VFS_POINTER = 27;
+  public static final int SQLITE_FCNTL_JOURNAL_POINTER = 28;
+  public static final int SQLITE_FCNTL_WIN32_GET_HANDLE = 29;
+  public static final int SQLITE_FCNTL_PDB = 30;
+  public static final int SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31;
+  public static final int SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32;
+  public static final int SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33;
+  public static final int SQLITE_FCNTL_LOCK_TIMEOUT = 34;
+  public static final int SQLITE_FCNTL_DATA_VERSION = 35;
+  public static final int SQLITE_FCNTL_SIZE_LIMIT = 36;
+  public static final int SQLITE_FCNTL_CKPT_DONE = 37;
+  public static final int SQLITE_FCNTL_RESERVE_BYTES = 38;
+  public static final int SQLITE_FCNTL_CKPT_START = 39;
+  public static final int SQLITE_FCNTL_EXTERNAL_READER = 40;
+  public static final int SQLITE_FCNTL_CKSM_FILE = 41;
+  public static final int SQLITE_FCNTL_RESET_CACHE = 42;
+
+  // flock
+  public static final int SQLITE_LOCK_NONE = 0;
+  public static final int SQLITE_LOCK_SHARED = 1;
+  public static final int SQLITE_LOCK_RESERVED = 2;
+  public static final int SQLITE_LOCK_PENDING = 3;
+  public static final int SQLITE_LOCK_EXCLUSIVE = 4;
+
+  // iocap
+  public static final int SQLITE_IOCAP_ATOMIC = 1;
+  public static final int SQLITE_IOCAP_ATOMIC512 = 2;
+  public static final int SQLITE_IOCAP_ATOMIC1K = 4;
+  public static final int SQLITE_IOCAP_ATOMIC2K = 8;
+  public static final int SQLITE_IOCAP_ATOMIC4K = 16;
+  public static final int SQLITE_IOCAP_ATOMIC8K = 32;
+  public static final int SQLITE_IOCAP_ATOMIC16K = 64;
+  public static final int SQLITE_IOCAP_ATOMIC32K = 128;
+  public static final int SQLITE_IOCAP_ATOMIC64K = 256;
+  public static final int SQLITE_IOCAP_SAFE_APPEND = 512;
+  public static final int SQLITE_IOCAP_SEQUENTIAL = 1024;
+  public static final int SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 2048;
+  public static final int SQLITE_IOCAP_POWERSAFE_OVERWRITE = 4096;
+  public static final int SQLITE_IOCAP_IMMUTABLE = 8192;
+  public static final int SQLITE_IOCAP_BATCH_ATOMIC = 16384;
+
+  // limits
+  public static final int SQLITE_LIMIT_LENGTH = 0;
+  public static final int SQLITE_LIMIT_SQL_LENGTH = 1;
+  public static final int SQLITE_LIMIT_COLUMN = 2;
+  public static final int SQLITE_LIMIT_EXPR_DEPTH = 3;
+  public static final int SQLITE_LIMIT_COMPOUND_SELECT = 4;
+  public static final int SQLITE_LIMIT_VDBE_OP = 5;
+  public static final int SQLITE_LIMIT_FUNCTION_ARG = 6;
+  public static final int SQLITE_LIMIT_ATTACHED = 7;
+  public static final int SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8;
+  public static final int SQLITE_LIMIT_VARIABLE_NUMBER = 9;
+  public static final int SQLITE_LIMIT_TRIGGER_DEPTH = 10;
+  public static final int SQLITE_LIMIT_WORKER_THREADS = 11;
+
+  // open flags
+
+  public static final int SQLITE_OPEN_READONLY     = 0x00000001  /* Ok for sqlite3_open_v2() */;
+  public static final int SQLITE_OPEN_READWRITE    = 0x00000002  /* Ok for sqlite3_open_v2() */;
+  public static final int SQLITE_OPEN_CREATE       = 0x00000004  /* Ok for sqlite3_open_v2() */;
+  //public static final int SQLITE_OPEN_DELETEONCLOSE  = 0x00000008  /* VFS only */;
+  //public static final int SQLITE_OPEN_EXCLUSIVE  = 0x00000010  /* VFS only */;
+  //public static final int SQLITE_OPEN_AUTOPROXY  = 0x00000020  /* VFS only */;
+  public static final int SQLITE_OPEN_URI          = 0x00000040  /* Ok for sqlite3_open_v2() */;
+  public static final int SQLITE_OPEN_MEMORY       = 0x00000080  /* Ok for sqlite3_open_v2() */;
+  //public static final int SQLITE_OPEN_MAIN_DB    = 0x00000100  /* VFS only */;
+  //public static final int SQLITE_OPEN_TEMP_DB    = 0x00000200  /* VFS only */;
+  //public static final int SQLITE_OPEN_TRANSIENT_DB  = 0x00000400  /* VFS only */;
+  //public static final int SQLITE_OPEN_MAIN_JOURNAL  = 0x00000800  /* VFS only */;
+  //public static final int SQLITE_OPEN_TEMP_JOURNAL  = 0x00001000  /* VFS only */;
+  //public static final int SQLITE_OPEN_SUBJOURNAL    = 0x00002000  /* VFS only */;
+  //public static final int SQLITE_OPEN_SUPER_JOURNAL = 0x00004000  /* VFS only */;
+  public static final int SQLITE_OPEN_NOMUTEX       = 0x00008000  /* Ok for sqlite3_open_v2() */;
+  public static final int SQLITE_OPEN_FULLMUTEX     = 0x00010000  /* Ok for sqlite3_open_v2() */;
+  public static final int SQLITE_OPEN_SHAREDCACHE   = 0x00020000  /* Ok for sqlite3_open_v2() */;
+  public static final int SQLITE_OPEN_PRIVATECACHE  = 0x00040000  /* Ok for sqlite3_open_v2() */;
+  //public static final int SQLITE_OPEN_WAL         = 0x00080000  /* VFS only */;
+  public static final int SQLITE_OPEN_NOFOLLOW      = 0x01000000  /* Ok for sqlite3_open_v2() */;
+  public static final int SQLITE_OPEN_EXRESCODE     = 0x02000000  /* Extended result codes */;
+
+  // prepare flags
+  public static final int SQLITE_PREPARE_PERSISTENT = 1;
+  public static final int SQLITE_PREPARE_NORMALIZE = 2;
+  public static final int SQLITE_PREPARE_NO_VTAB = 4;
+
+  // result codes
+  public static final int SQLITE_OK = 0;
+  public static final int SQLITE_ERROR = 1;
+  public static final int SQLITE_INTERNAL = 2;
+  public static final int SQLITE_PERM = 3;
+  public static final int SQLITE_ABORT = 4;
+  public static final int SQLITE_BUSY = 5;
+  public static final int SQLITE_LOCKED = 6;
+  public static final int SQLITE_NOMEM = 7;
+  public static final int SQLITE_READONLY = 8;
+  public static final int SQLITE_INTERRUPT = 9;
+  public static final int SQLITE_IOERR = 10;
+  public static final int SQLITE_CORRUPT = 11;
+  public static final int SQLITE_NOTFOUND = 12;
+  public static final int SQLITE_FULL = 13;
+  public static final int SQLITE_CANTOPEN = 14;
+  public static final int SQLITE_PROTOCOL = 15;
+  public static final int SQLITE_EMPTY = 16;
+  public static final int SQLITE_SCHEMA = 17;
+  public static final int SQLITE_TOOBIG = 18;
+  public static final int SQLITE_CONSTRAINT = 19;
+  public static final int SQLITE_MISMATCH = 20;
+  public static final int SQLITE_MISUSE = 21;
+  public static final int SQLITE_NOLFS = 22;
+  public static final int SQLITE_AUTH = 23;
+  public static final int SQLITE_FORMAT = 24;
+  public static final int SQLITE_RANGE = 25;
+  public static final int SQLITE_NOTADB = 26;
+  public static final int SQLITE_NOTICE = 27;
+  public static final int SQLITE_WARNING = 28;
+  public static final int SQLITE_ROW = 100;
+  public static final int SQLITE_DONE = 101;
+  public static final int SQLITE_ERROR_MISSING_COLLSEQ = 257;
+  public static final int SQLITE_ERROR_RETRY = 513;
+  public static final int SQLITE_ERROR_SNAPSHOT = 769;
+  public static final int SQLITE_IOERR_READ = 266;
+  public static final int SQLITE_IOERR_SHORT_READ = 522;
+  public static final int SQLITE_IOERR_WRITE = 778;
+  public static final int SQLITE_IOERR_FSYNC = 1034;
+  public static final int SQLITE_IOERR_DIR_FSYNC = 1290;
+  public static final int SQLITE_IOERR_TRUNCATE = 1546;
+  public static final int SQLITE_IOERR_FSTAT = 1802;
+  public static final int SQLITE_IOERR_UNLOCK = 2058;
+  public static final int SQLITE_IOERR_RDLOCK = 2314;
+  public static final int SQLITE_IOERR_DELETE = 2570;
+  public static final int SQLITE_IOERR_BLOCKED = 2826;
+  public static final int SQLITE_IOERR_NOMEM = 3082;
+  public static final int SQLITE_IOERR_ACCESS = 3338;
+  public static final int SQLITE_IOERR_CHECKRESERVEDLOCK = 3594;
+  public static final int SQLITE_IOERR_LOCK = 3850;
+  public static final int SQLITE_IOERR_CLOSE = 4106;
+  public static final int SQLITE_IOERR_DIR_CLOSE = 4362;
+  public static final int SQLITE_IOERR_SHMOPEN = 4618;
+  public static final int SQLITE_IOERR_SHMSIZE = 4874;
+  public static final int SQLITE_IOERR_SHMLOCK = 5130;
+  public static final int SQLITE_IOERR_SHMMAP = 5386;
+  public static final int SQLITE_IOERR_SEEK = 5642;
+  public static final int SQLITE_IOERR_DELETE_NOENT = 5898;
+  public static final int SQLITE_IOERR_MMAP = 6154;
+  public static final int SQLITE_IOERR_GETTEMPPATH = 6410;
+  public static final int SQLITE_IOERR_CONVPATH = 6666;
+  public static final int SQLITE_IOERR_VNODE = 6922;
+  public static final int SQLITE_IOERR_AUTH = 7178;
+  public static final int SQLITE_IOERR_BEGIN_ATOMIC = 7434;
+  public static final int SQLITE_IOERR_COMMIT_ATOMIC = 7690;
+  public static final int SQLITE_IOERR_ROLLBACK_ATOMIC = 7946;
+  public static final int SQLITE_IOERR_DATA = 8202;
+  public static final int SQLITE_IOERR_CORRUPTFS = 8458;
+  public static final int SQLITE_LOCKED_SHAREDCACHE = 262;
+  public static final int SQLITE_LOCKED_VTAB = 518;
+  public static final int SQLITE_BUSY_RECOVERY = 261;
+  public static final int SQLITE_BUSY_SNAPSHOT = 517;
+  public static final int SQLITE_BUSY_TIMEOUT = 773;
+  public static final int SQLITE_CANTOPEN_NOTEMPDIR = 270;
+  public static final int SQLITE_CANTOPEN_ISDIR = 526;
+  public static final int SQLITE_CANTOPEN_FULLPATH = 782;
+  public static final int SQLITE_CANTOPEN_CONVPATH = 1038;
+  public static final int SQLITE_CANTOPEN_SYMLINK = 1550;
+  public static final int SQLITE_CORRUPT_VTAB = 267;
+  public static final int SQLITE_CORRUPT_SEQUENCE = 523;
+  public static final int SQLITE_CORRUPT_INDEX = 779;
+  public static final int SQLITE_READONLY_RECOVERY = 264;
+  public static final int SQLITE_READONLY_CANTLOCK = 520;
+  public static final int SQLITE_READONLY_ROLLBACK = 776;
+  public static final int SQLITE_READONLY_DBMOVED = 1032;
+  public static final int SQLITE_READONLY_CANTINIT = 1288;
+  public static final int SQLITE_READONLY_DIRECTORY = 1544;
+  public static final int SQLITE_ABORT_ROLLBACK = 516;
+  public static final int SQLITE_CONSTRAINT_CHECK = 275;
+  public static final int SQLITE_CONSTRAINT_COMMITHOOK = 531;
+  public static final int SQLITE_CONSTRAINT_FOREIGNKEY = 787;
+  public static final int SQLITE_CONSTRAINT_FUNCTION = 1043;
+  public static final int SQLITE_CONSTRAINT_NOTNULL = 1299;
+  public static final int SQLITE_CONSTRAINT_PRIMARYKEY = 1555;
+  public static final int SQLITE_CONSTRAINT_TRIGGER = 1811;
+  public static final int SQLITE_CONSTRAINT_UNIQUE = 2067;
+  public static final int SQLITE_CONSTRAINT_VTAB = 2323;
+  public static final int SQLITE_CONSTRAINT_ROWID = 2579;
+  public static final int SQLITE_CONSTRAINT_PINNED = 2835;
+  public static final int SQLITE_CONSTRAINT_DATATYPE = 3091;
+  public static final int SQLITE_NOTICE_RECOVER_WAL = 283;
+  public static final int SQLITE_NOTICE_RECOVER_ROLLBACK = 539;
+  public static final int SQLITE_WARNING_AUTOINDEX = 284;
+  public static final int SQLITE_AUTH_USER = 279;
+  public static final int SQLITE_OK_LOAD_PERMANENTLY = 256;
+
+  // serialize
+  public static final int SQLITE_SERIALIZE_NOCOPY = 1;
+  public static final int SQLITE_DESERIALIZE_FREEONCLOSE = 1;
+  public static final int SQLITE_DESERIALIZE_READONLY = 4;
+  public static final int SQLITE_DESERIALIZE_RESIZEABLE = 2;
+
+  // session
+  public static final int SQLITE_SESSION_CONFIG_STRMSIZE = 1;
+  public static final int SQLITE_SESSION_OBJCONFIG_SIZE = 1;
+
+  // sqlite3 status
+  public static final int SQLITE_STATUS_MEMORY_USED = 0;
+  public static final int SQLITE_STATUS_PAGECACHE_USED = 1;
+  public static final int SQLITE_STATUS_PAGECACHE_OVERFLOW = 2;
+  public static final int SQLITE_STATUS_MALLOC_SIZE = 5;
+  public static final int SQLITE_STATUS_PARSER_STACK = 6;
+  public static final int SQLITE_STATUS_PAGECACHE_SIZE = 7;
+  public static final int SQLITE_STATUS_MALLOC_COUNT = 9;
+
+  // stmt status
+  public static final int SQLITE_STMTSTATUS_FULLSCAN_STEP = 1;
+  public static final int SQLITE_STMTSTATUS_SORT = 2;
+  public static final int SQLITE_STMTSTATUS_AUTOINDEX = 3;
+  public static final int SQLITE_STMTSTATUS_VM_STEP = 4;
+  public static final int SQLITE_STMTSTATUS_REPREPARE = 5;
+  public static final int SQLITE_STMTSTATUS_RUN = 6;
+  public static final int SQLITE_STMTSTATUS_FILTER_MISS = 7;
+  public static final int SQLITE_STMTSTATUS_FILTER_HIT = 8;
+  public static final int SQLITE_STMTSTATUS_MEMUSED = 99;
+
+  // sync flags
+  public static final int SQLITE_SYNC_NORMAL = 2;
+  public static final int SQLITE_SYNC_FULL = 3;
+  public static final int SQLITE_SYNC_DATAONLY = 16;
+
+  // tracing flags
+  public static final int SQLITE_TRACE_STMT = 1;
+  public static final int SQLITE_TRACE_PROFILE = 2;
+  public static final int SQLITE_TRACE_ROW = 4;
+  public static final int SQLITE_TRACE_CLOSE = 8;
+
+  // transaction state
+  public static final int SQLITE_TXN_NONE = 0;
+  public static final int SQLITE_TXN_READ = 1;
+  public static final int SQLITE_TXN_WRITE = 2;
+
+  // udf flags
+  public static final int SQLITE_DETERMINISTIC = 0x000000800;
+  public static final int SQLITE_DIRECTONLY    = 0x000080000;
+  public static final int SQLITE_INNOCUOUS     = 0x000200000;
+
+  // virtual tables
+  public static final int SQLITE_INDEX_SCAN_UNIQUE = 1;
+  public static final int SQLITE_INDEX_CONSTRAINT_EQ = 2;
+  public static final int SQLITE_INDEX_CONSTRAINT_GT = 4;
+  public static final int SQLITE_INDEX_CONSTRAINT_LE = 8;
+  public static final int SQLITE_INDEX_CONSTRAINT_LT = 16;
+  public static final int SQLITE_INDEX_CONSTRAINT_GE = 32;
+  public static final int SQLITE_INDEX_CONSTRAINT_MATCH = 64;
+  public static final int SQLITE_INDEX_CONSTRAINT_LIKE = 65;
+  public static final int SQLITE_INDEX_CONSTRAINT_GLOB = 66;
+  public static final int SQLITE_INDEX_CONSTRAINT_REGEXP = 67;
+  public static final int SQLITE_INDEX_CONSTRAINT_NE = 68;
+  public static final int SQLITE_INDEX_CONSTRAINT_ISNOT = 69;
+  public static final int SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70;
+  public static final int SQLITE_INDEX_CONSTRAINT_ISNULL = 71;
+  public static final int SQLITE_INDEX_CONSTRAINT_IS = 72;
+  public static final int SQLITE_INDEX_CONSTRAINT_LIMIT = 73;
+  public static final int SQLITE_INDEX_CONSTRAINT_OFFSET = 74;
+  public static final int SQLITE_INDEX_CONSTRAINT_FUNCTION = 150;
+  public static final int SQLITE_VTAB_CONSTRAINT_SUPPORT = 1;
+  public static final int SQLITE_VTAB_INNOCUOUS = 2;
+  public static final int SQLITE_VTAB_DIRECTONLY = 3;
+  public static final int SQLITE_VTAB_USES_ALL_SCHEMAS = 4;
+  public static final int SQLITE_ROLLBACK = 1;
+  public static final int SQLITE_FAIL = 3;
+  public static final int SQLITE_REPLACE = 5;
+  static {
+    // This MUST come after the SQLITE_MAX_... values or else
+    // attempting to modify them silently fails.
+    init();
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java
new file mode 100644
index 0000000000..0495702561
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java
@@ -0,0 +1,44 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+/**
+   This marker interface exists soley for use as a documentation and
+   class-grouping tool. It should be applied to interfaces or
+   classes which have a call() method implementing some specific
+   callback interface on behalf of the C library.
+
+   <p>Unless very explicitely documented otherwise, callbacks must
+   never throw. Any which do throw but should not might trigger debug
+   output regarding the error, but the exception will not be
+   propagated.  For callback interfaces which support returning error
+   info to the core, the JNI binding will convert any exceptions to
+   C-level error information. For callback interfaces which do not
+   support, all exceptions will necessarily be suppressed in order to
+   retain the C-style no-throw semantics.
+
+   <p>Callbacks of this style follow a common naming convention:
+
+   <p>1) They use the UpperCamelCase form of the C function they're
+   proxying for, minus the {@code sqlite3_} prefix, plus a {@code
+   Callback} suffix. e.g. {@code sqlite3_busy_handler()}'s callback is
+   named {@code BusyHandlerCallback}. Exceptions are made where that
+   would potentially be ambiguous, e.g. {@link ConfigSqllogCallback}
+   instead of {@code ConfigCallback} because the {@code
+   sqlite3_config()} interface may need to support more callback types
+   in the future.
+
+   <p>2) They all have a {@code call()} method but its signature is
+   callback-specific.
+*/
+public interface CallbackProxy {}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java
new file mode 100644
index 0000000000..ed8bd09475
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java
@@ -0,0 +1,35 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+   Callback for use with {@link CApi#sqlite3_create_collation}.
+
+   @see AbstractCollationCallback
+*/
+public interface CollationCallback
+  extends CallbackProxy, XDestroyCallback {
+  /**
+     Must compare the given byte arrays and return the result using
+     {@code memcmp()} semantics.
+  */
+  int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
+
+  /**
+     Called by SQLite when the collation is destroyed. If a collation
+     requires custom cleanup, override this method.
+  */
+  void xDestroy();
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java
new file mode 100644
index 0000000000..fe61fe5065
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java
@@ -0,0 +1,28 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for use with {@link CApi#sqlite3_collation_needed}.
+*/
+public interface CollationNeededCallback extends CallbackProxy {
+  /**
+     Has the same semantics as the C-level sqlite3_create_collation()
+     callback.
+
+     <p>If it throws, the exception message is passed on to the db and
+     the exception is suppressed.
+  */
+  int call(sqlite3 db, int eTextRep, String collationName);
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
new file mode 100644
index 0000000000..24373bdf2b
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
@@ -0,0 +1,25 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for use with {@link CApi#sqlite3_commit_hook}.
+*/
+public interface CommitHookCallback extends CallbackProxy {
+  /**
+     Works as documented for the C-level sqlite3_commit_hook()
+     callback.  Must not throw.
+  */
+  int call();
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java
new file mode 100644
index 0000000000..6513b0730d
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java
@@ -0,0 +1,25 @@
+/*
+** 2023-08-23
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   A callback for use with sqlite3_config().
+*/
+public interface ConfigLogCallback {
+  /**
+     Must function as described for a C-level callback for
+     {@link CApi#sqlite3_config(ConfigLogCallback)}, with the slight signature change.
+  */
+  void call(int errCode, String msg);
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java
new file mode 100644
index 0000000000..df753e6513
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java
@@ -0,0 +1,25 @@
+/*
+** 2023-08-23
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   A callback for use with sqlite3_config().
+*/
+public interface ConfigSqllogCallback {
+  /**
+     Must function as described for a C-level callback for
+     {@link CApi#sqlite3_config(ConfigSqllogCallback)}, with the slight signature change.
+  */
+  void call(sqlite3 db, String msg, int msgType );
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java
new file mode 100644
index 0000000000..e82909e424
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java
@@ -0,0 +1,46 @@
+/*
+** 2023-07-21
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   A helper for passing pointers between JNI C code and Java, in
+   particular for output pointers of high-level object types in the
+   sqlite3 C API, e.g. (sqlite3**) and (sqlite3_stmt**).  This is
+   intended to be subclassed and the ContextType is intended to be the
+   class which is doing the subclassing. The intent of the ContextType
+   is strictly to provide some level of type safety by avoiding that
+   NativePointerHolder is not inadvertently passed to an incompatible
+   function signature.
+
+   These objects do not own the pointer they refer to.  They are
+   intended simply to communicate that pointer between C and Java.
+*/
+public class NativePointerHolder<ContextType> {
+  //! Only set from JNI, where access permissions don't matter.
+  private volatile long nativePointer = 0;
+  /**
+     For use ONLY by package-level APIs which act as proxies for
+     close/finalize operations. Such ops must call this to zero out
+     the pointer so that this object is not carrying a stale
+     pointer. This function returns the prior value of the pointer and
+     sets it to 0.
+  */
+  final long clearNativePointer() {
+    final long rv = nativePointer;
+    nativePointer= 0;
+    return rv;
+  }
+
+  public final long getNativePointer(){ return nativePointer; }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java
new file mode 100644
index 0000000000..60b9025386
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java
@@ -0,0 +1,231 @@
+/*
+** 2023-07-21
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Helper classes for handling JNI output pointers.
+
+   <p>We do not use a generic OutputPointer<T> because working with those
+   from the native JNI code is unduly quirky due to a lack of
+   autoboxing at that level.
+
+   <p>The usage is similar for all of thes types:
+
+   <pre>{@code
+   OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+   assert( null==out.get() );
+   int rc = sqlite3_open(":memory:", out);
+   if( 0!=rc ) ... error;
+   assert( null!=out.get() );
+   sqlite3 db = out.take();
+   assert( null==out.get() );
+   }</pre>
+
+   <p>With the minor exception that the primitive types permit direct
+   access to the object's value via the `value` property, whereas the
+   JNI-level opaque types do not permit client-level code to set that
+   property.
+
+   <p>Warning: do not share instances of these classes across
+   threads. Doing so may lead to corrupting sqlite3-internal state.
+*/
+public final class OutputPointer {
+
+  /**
+     Output pointer for use with routines, such as sqlite3_open(),
+     which return a database handle via an output pointer. These
+     pointers can only be set by the JNI layer, not by client-level
+     code.
+  */
+  public static final class sqlite3 {
+    private org.sqlite.jni.capi.sqlite3 value;
+    /** Initializes with a null value. */
+    public sqlite3(){value = null;}
+    /** Sets the current value to null. */
+    public void clear(){value = null;}
+    /** Returns the current value. */
+    public final org.sqlite.jni.capi.sqlite3 get(){return value;}
+    /** Equivalent to calling get() then clear(). */
+    public final org.sqlite.jni.capi.sqlite3 take(){
+      final org.sqlite.jni.capi.sqlite3 v = value;
+      value = null;
+      return v;
+    }
+  }
+
+  /**
+     Output pointer for sqlite3_blob_open(). These
+     pointers can only be set by the JNI layer, not by client-level
+     code.
+  */
+  public static final class sqlite3_blob {
+    private org.sqlite.jni.capi.sqlite3_blob value;
+    /** Initializes with a null value. */
+    public sqlite3_blob(){value = null;}
+    /** Sets the current value to null. */
+    public void clear(){value = null;}
+    /** Returns the current value. */
+    public final org.sqlite.jni.capi.sqlite3_blob get(){return value;}
+    /** Equivalent to calling get() then clear(). */
+    public final org.sqlite.jni.capi.sqlite3_blob take(){
+      final org.sqlite.jni.capi.sqlite3_blob v = value;
+      value = null;
+      return v;
+    }
+  }
+
+  /**
+     Output pointer for use with routines, such as sqlite3_prepare(),
+     which return a statement handle via an output pointer. These
+     pointers can only be set by the JNI layer, not by client-level
+     code.
+  */
+  public static final class sqlite3_stmt {
+    private org.sqlite.jni.capi.sqlite3_stmt value;
+    /** Initializes with a null value. */
+    public sqlite3_stmt(){value = null;}
+    /** Sets the current value to null. */
+    public void clear(){value = null;}
+    /** Returns the current value. */
+    public final org.sqlite.jni.capi.sqlite3_stmt get(){return value;}
+    /** Equivalent to calling get() then clear(). */
+    public final org.sqlite.jni.capi.sqlite3_stmt take(){
+      final org.sqlite.jni.capi.sqlite3_stmt v = value;
+      value = null;
+      return v;
+    }
+  }
+
+  /**
+     Output pointer for use with routines, such as sqlite3_prepupdate_new(),
+     which return a sqlite3_value handle via an output pointer. These
+     pointers can only be set by the JNI layer, not by client-level
+     code.
+  */
+  public static final class sqlite3_value {
+    private org.sqlite.jni.capi.sqlite3_value value;
+    /** Initializes with a null value. */
+    public sqlite3_value(){value = null;}
+    /** Sets the current value to null. */
+    public void clear(){value = null;}
+    /** Returns the current value. */
+    public final org.sqlite.jni.capi.sqlite3_value get(){return value;}
+    /** Equivalent to calling get() then clear(). */
+    public final org.sqlite.jni.capi.sqlite3_value take(){
+      final org.sqlite.jni.capi.sqlite3_value v = value;
+      value = null;
+      return v;
+    }
+  }
+
+  /**
+     Output pointer for use with native routines which return booleans
+     via integer output pointers.
+  */
+  public static final class Bool {
+    /**
+       This is public for ease of use. Accessors are provided for
+       consistency with the higher-level types.
+    */
+    public boolean value;
+    /** Initializes with the value 0. */
+    public Bool(){this(false);}
+    /** Initializes with the value v. */
+    public Bool(boolean v){value = v;}
+    /** Returns the current value. */
+    public final boolean get(){return value;}
+    /** Sets the current value to v. */
+    public final void set(boolean v){value = v;}
+  }
+
+  /**
+     Output pointer for use with native routines which return integers via
+     output pointers.
+  */
+  public static final class Int32 {
+    /**
+       This is public for ease of use. Accessors are provided for
+       consistency with the higher-level types.
+    */
+    public int value;
+    /** Initializes with the value 0. */
+    public Int32(){this(0);}
+    /** Initializes with the value v. */
+    public Int32(int v){value = v;}
+    /** Returns the current value. */
+    public final int get(){return value;}
+    /** Sets the current value to v. */
+    public final void set(int v){value = v;}
+  }
+
+  /**
+     Output pointer for use with native routines which return 64-bit integers
+     via output pointers.
+  */
+  public static final class Int64 {
+    /**
+       This is public for ease of use. Accessors are provided for
+       consistency with the higher-level types.
+    */
+    public long value;
+    /** Initializes with the value 0. */
+    public Int64(){this(0);}
+    /** Initializes with the value v. */
+    public Int64(long v){value = v;}
+    /** Returns the current value. */
+    public final long get(){return value;}
+    /** Sets the current value. */
+    public final void set(long v){value = v;}
+  }
+
+  /**
+     Output pointer for use with native routines which return strings via
+     output pointers.
+  */
+  public static final class String {
+    /**
+       This is public for ease of use. Accessors are provided for
+       consistency with the higher-level types.
+    */
+    public java.lang.String value;
+    /** Initializes with a null value. */
+    public String(){this(null);}
+    /** Initializes with the value v. */
+    public String(java.lang.String v){value = v;}
+    /** Returns the current value. */
+    public final java.lang.String get(){return value;}
+    /** Sets the current value. */
+    public final void set(java.lang.String v){value = v;}
+  }
+
+  /**
+     Output pointer for use with native routines which return byte
+     arrays via output pointers.
+  */
+  public static final class ByteArray {
+    /**
+       This is public for ease of use. Accessors are provided for
+       consistency with the higher-level types.
+    */
+    public byte[] value;
+    /** Initializes with the value null. */
+    public ByteArray(){this(null);}
+    /** Initializes with the value v. */
+    public ByteArray(byte[] v){value = v;}
+    /** Returns the current value. */
+    public final byte[] get(){return value;}
+    /** Sets the current value. */
+    public final void set(byte[] v){value = v;}
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java
new file mode 100644
index 0000000000..1c805a9b16
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java
@@ -0,0 +1,78 @@
+/*
+** 2023-09-13
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for use with {@link CApi#sqlite3_prepare_multi}.
+*/
+public interface PrepareMultiCallback extends CallbackProxy {
+
+  /**
+     Gets passed a sqlite3_stmt which it may handle in arbitrary ways,
+     transfering ownership of it to this function.
+
+     sqlite3_prepare_multi() will _not_ finalize st - it is up
+     to the call() implementation how st is handled.
+
+     Must return 0 on success or an SQLITE_... code on error.
+
+     See the {@link Finalize} class for a wrapper which finalizes the
+     statement after calling a proxy PrepareMultiCallback.
+  */
+  int call(sqlite3_stmt st);
+
+  /**
+     A PrepareMultiCallback impl which wraps a separate impl and finalizes
+     any sqlite3_stmt passed to its callback.
+  */
+  public static final class Finalize implements PrepareMultiCallback {
+    private PrepareMultiCallback p;
+    /**
+       p is the proxy to call() when this.call() is called.
+    */
+    public Finalize( PrepareMultiCallback p ){
+      this.p = p;
+    }
+    /**
+       Calls the call() method of the proxied callback and either returns its
+       result or propagates an exception. Either way, it passes its argument to
+       sqlite3_finalize() before returning.
+    */
+    @Override public int call(sqlite3_stmt st){
+      try {
+        return this.p.call(st);
+      }finally{
+        CApi.sqlite3_finalize(st);
+      }
+    }
+  }
+
+  /**
+     A PrepareMultiCallback impl which steps entirely through a result set,
+     ignoring all non-error results.
+  */
+  public static final class StepAll implements PrepareMultiCallback {
+    public StepAll(){}
+    /**
+       Calls sqlite3_step() on st until it returns something other than
+       SQLITE_ROW. If the final result is SQLITE_DONE then 0 is returned,
+       else the result of the final step is returned.
+    */
+    @Override public int call(sqlite3_stmt st){
+      int rc = CApi.SQLITE_DONE;
+      while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(st)) ){}
+      return CApi.SQLITE_DONE==rc ? 0 : rc;
+    }
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java
new file mode 100644
index 0000000000..99d3fb0351
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for use with {@link CApi#sqlite3_preupdate_hook}.
+*/
+public interface PreupdateHookCallback extends CallbackProxy {
+  /**
+     Must function as described for the C-level sqlite3_preupdate_hook()
+     callback.
+  */
+  void call(sqlite3 db, int op, String dbName, String dbTable,
+            long iKey1, long iKey2 );
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java
new file mode 100644
index 0000000000..464baa2e3d
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java
@@ -0,0 +1,27 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for use with {@link CApi#sqlite3_progress_handler}.
+*/
+public interface ProgressHandlerCallback extends CallbackProxy {
+  /**
+     Works as documented for the C-level sqlite3_progress_handler() callback.
+
+     <p>If it throws, the exception message is passed on to the db and
+     the exception is suppressed.
+  */
+  int call();
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ResultCode.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ResultCode.java
new file mode 100644
index 0000000000..5a8b2e6a18
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ResultCode.java
@@ -0,0 +1,155 @@
+/*
+** 2023-07-21
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   This enum contains all of the core and "extended" result codes used
+   by the sqlite3 library. It is provided not for use with the C-style
+   API (with which it won't work) but for higher-level code which may
+   find it useful to map SQLite result codes to human-readable names.
+*/
+public enum ResultCode {
+  SQLITE_OK(CApi.SQLITE_OK),
+  SQLITE_ERROR(CApi.SQLITE_ERROR),
+  SQLITE_INTERNAL(CApi.SQLITE_INTERNAL),
+  SQLITE_PERM(CApi.SQLITE_PERM),
+  SQLITE_ABORT(CApi.SQLITE_ABORT),
+  SQLITE_BUSY(CApi.SQLITE_BUSY),
+  SQLITE_LOCKED(CApi.SQLITE_LOCKED),
+  SQLITE_NOMEM(CApi.SQLITE_NOMEM),
+  SQLITE_READONLY(CApi.SQLITE_READONLY),
+  SQLITE_INTERRUPT(CApi.SQLITE_INTERRUPT),
+  SQLITE_IOERR(CApi.SQLITE_IOERR),
+  SQLITE_CORRUPT(CApi.SQLITE_CORRUPT),
+  SQLITE_NOTFOUND(CApi.SQLITE_NOTFOUND),
+  SQLITE_FULL(CApi.SQLITE_FULL),
+  SQLITE_CANTOPEN(CApi.SQLITE_CANTOPEN),
+  SQLITE_PROTOCOL(CApi.SQLITE_PROTOCOL),
+  SQLITE_EMPTY(CApi.SQLITE_EMPTY),
+  SQLITE_SCHEMA(CApi.SQLITE_SCHEMA),
+  SQLITE_TOOBIG(CApi.SQLITE_TOOBIG),
+  SQLITE_CONSTRAINT(CApi.SQLITE_CONSTRAINT),
+  SQLITE_MISMATCH(CApi.SQLITE_MISMATCH),
+  SQLITE_MISUSE(CApi.SQLITE_MISUSE),
+  SQLITE_NOLFS(CApi.SQLITE_NOLFS),
+  SQLITE_AUTH(CApi.SQLITE_AUTH),
+  SQLITE_FORMAT(CApi.SQLITE_FORMAT),
+  SQLITE_RANGE(CApi.SQLITE_RANGE),
+  SQLITE_NOTADB(CApi.SQLITE_NOTADB),
+  SQLITE_NOTICE(CApi.SQLITE_NOTICE),
+  SQLITE_WARNING(CApi.SQLITE_WARNING),
+  SQLITE_ROW(CApi.SQLITE_ROW),
+  SQLITE_DONE(CApi.SQLITE_DONE),
+  SQLITE_ERROR_MISSING_COLLSEQ(CApi.SQLITE_ERROR_MISSING_COLLSEQ),
+  SQLITE_ERROR_RETRY(CApi.SQLITE_ERROR_RETRY),
+  SQLITE_ERROR_SNAPSHOT(CApi.SQLITE_ERROR_SNAPSHOT),
+  SQLITE_IOERR_READ(CApi.SQLITE_IOERR_READ),
+  SQLITE_IOERR_SHORT_READ(CApi.SQLITE_IOERR_SHORT_READ),
+  SQLITE_IOERR_WRITE(CApi.SQLITE_IOERR_WRITE),
+  SQLITE_IOERR_FSYNC(CApi.SQLITE_IOERR_FSYNC),
+  SQLITE_IOERR_DIR_FSYNC(CApi.SQLITE_IOERR_DIR_FSYNC),
+  SQLITE_IOERR_TRUNCATE(CApi.SQLITE_IOERR_TRUNCATE),
+  SQLITE_IOERR_FSTAT(CApi.SQLITE_IOERR_FSTAT),
+  SQLITE_IOERR_UNLOCK(CApi.SQLITE_IOERR_UNLOCK),
+  SQLITE_IOERR_RDLOCK(CApi.SQLITE_IOERR_RDLOCK),
+  SQLITE_IOERR_DELETE(CApi.SQLITE_IOERR_DELETE),
+  SQLITE_IOERR_BLOCKED(CApi.SQLITE_IOERR_BLOCKED),
+  SQLITE_IOERR_NOMEM(CApi.SQLITE_IOERR_NOMEM),
+  SQLITE_IOERR_ACCESS(CApi.SQLITE_IOERR_ACCESS),
+  SQLITE_IOERR_CHECKRESERVEDLOCK(CApi.SQLITE_IOERR_CHECKRESERVEDLOCK),
+  SQLITE_IOERR_LOCK(CApi.SQLITE_IOERR_LOCK),
+  SQLITE_IOERR_CLOSE(CApi.SQLITE_IOERR_CLOSE),
+  SQLITE_IOERR_DIR_CLOSE(CApi.SQLITE_IOERR_DIR_CLOSE),
+  SQLITE_IOERR_SHMOPEN(CApi.SQLITE_IOERR_SHMOPEN),
+  SQLITE_IOERR_SHMSIZE(CApi.SQLITE_IOERR_SHMSIZE),
+  SQLITE_IOERR_SHMLOCK(CApi.SQLITE_IOERR_SHMLOCK),
+  SQLITE_IOERR_SHMMAP(CApi.SQLITE_IOERR_SHMMAP),
+  SQLITE_IOERR_SEEK(CApi.SQLITE_IOERR_SEEK),
+  SQLITE_IOERR_DELETE_NOENT(CApi.SQLITE_IOERR_DELETE_NOENT),
+  SQLITE_IOERR_MMAP(CApi.SQLITE_IOERR_MMAP),
+  SQLITE_IOERR_GETTEMPPATH(CApi.SQLITE_IOERR_GETTEMPPATH),
+  SQLITE_IOERR_CONVPATH(CApi.SQLITE_IOERR_CONVPATH),
+  SQLITE_IOERR_VNODE(CApi.SQLITE_IOERR_VNODE),
+  SQLITE_IOERR_AUTH(CApi.SQLITE_IOERR_AUTH),
+  SQLITE_IOERR_BEGIN_ATOMIC(CApi.SQLITE_IOERR_BEGIN_ATOMIC),
+  SQLITE_IOERR_COMMIT_ATOMIC(CApi.SQLITE_IOERR_COMMIT_ATOMIC),
+  SQLITE_IOERR_ROLLBACK_ATOMIC(CApi.SQLITE_IOERR_ROLLBACK_ATOMIC),
+  SQLITE_IOERR_DATA(CApi.SQLITE_IOERR_DATA),
+  SQLITE_IOERR_CORRUPTFS(CApi.SQLITE_IOERR_CORRUPTFS),
+  SQLITE_LOCKED_SHAREDCACHE(CApi.SQLITE_LOCKED_SHAREDCACHE),
+  SQLITE_LOCKED_VTAB(CApi.SQLITE_LOCKED_VTAB),
+  SQLITE_BUSY_RECOVERY(CApi.SQLITE_BUSY_RECOVERY),
+  SQLITE_BUSY_SNAPSHOT(CApi.SQLITE_BUSY_SNAPSHOT),
+  SQLITE_BUSY_TIMEOUT(CApi.SQLITE_BUSY_TIMEOUT),
+  SQLITE_CANTOPEN_NOTEMPDIR(CApi.SQLITE_CANTOPEN_NOTEMPDIR),
+  SQLITE_CANTOPEN_ISDIR(CApi.SQLITE_CANTOPEN_ISDIR),
+  SQLITE_CANTOPEN_FULLPATH(CApi.SQLITE_CANTOPEN_FULLPATH),
+  SQLITE_CANTOPEN_CONVPATH(CApi.SQLITE_CANTOPEN_CONVPATH),
+  SQLITE_CANTOPEN_SYMLINK(CApi.SQLITE_CANTOPEN_SYMLINK),
+  SQLITE_CORRUPT_VTAB(CApi.SQLITE_CORRUPT_VTAB),
+  SQLITE_CORRUPT_SEQUENCE(CApi.SQLITE_CORRUPT_SEQUENCE),
+  SQLITE_CORRUPT_INDEX(CApi.SQLITE_CORRUPT_INDEX),
+  SQLITE_READONLY_RECOVERY(CApi.SQLITE_READONLY_RECOVERY),
+  SQLITE_READONLY_CANTLOCK(CApi.SQLITE_READONLY_CANTLOCK),
+  SQLITE_READONLY_ROLLBACK(CApi.SQLITE_READONLY_ROLLBACK),
+  SQLITE_READONLY_DBMOVED(CApi.SQLITE_READONLY_DBMOVED),
+  SQLITE_READONLY_CANTINIT(CApi.SQLITE_READONLY_CANTINIT),
+  SQLITE_READONLY_DIRECTORY(CApi.SQLITE_READONLY_DIRECTORY),
+  SQLITE_ABORT_ROLLBACK(CApi.SQLITE_ABORT_ROLLBACK),
+  SQLITE_CONSTRAINT_CHECK(CApi.SQLITE_CONSTRAINT_CHECK),
+  SQLITE_CONSTRAINT_COMMITHOOK(CApi.SQLITE_CONSTRAINT_COMMITHOOK),
+  SQLITE_CONSTRAINT_FOREIGNKEY(CApi.SQLITE_CONSTRAINT_FOREIGNKEY),
+  SQLITE_CONSTRAINT_FUNCTION(CApi.SQLITE_CONSTRAINT_FUNCTION),
+  SQLITE_CONSTRAINT_NOTNULL(CApi.SQLITE_CONSTRAINT_NOTNULL),
+  SQLITE_CONSTRAINT_PRIMARYKEY(CApi.SQLITE_CONSTRAINT_PRIMARYKEY),
+  SQLITE_CONSTRAINT_TRIGGER(CApi.SQLITE_CONSTRAINT_TRIGGER),
+  SQLITE_CONSTRAINT_UNIQUE(CApi.SQLITE_CONSTRAINT_UNIQUE),
+  SQLITE_CONSTRAINT_VTAB(CApi.SQLITE_CONSTRAINT_VTAB),
+  SQLITE_CONSTRAINT_ROWID(CApi.SQLITE_CONSTRAINT_ROWID),
+  SQLITE_CONSTRAINT_PINNED(CApi.SQLITE_CONSTRAINT_PINNED),
+  SQLITE_CONSTRAINT_DATATYPE(CApi.SQLITE_CONSTRAINT_DATATYPE),
+  SQLITE_NOTICE_RECOVER_WAL(CApi.SQLITE_NOTICE_RECOVER_WAL),
+  SQLITE_NOTICE_RECOVER_ROLLBACK(CApi.SQLITE_NOTICE_RECOVER_ROLLBACK),
+  SQLITE_WARNING_AUTOINDEX(CApi.SQLITE_WARNING_AUTOINDEX),
+  SQLITE_AUTH_USER(CApi.SQLITE_AUTH_USER),
+  SQLITE_OK_LOAD_PERMANENTLY(CApi.SQLITE_OK_LOAD_PERMANENTLY);
+
+  public final int value;
+
+  ResultCode(int rc){
+    value = rc;
+    ResultCodeMap.set(rc, this);
+  }
+
+  /**
+     Returns the entry from this enum for the given result code, or
+     null if no match is found.
+  */
+  public static ResultCode getEntryForInt(int rc){
+    return ResultCodeMap.get(rc);
+  }
+
+  /**
+     Internal level of indirection required because we cannot initialize
+     static enum members in an enum before the enum constructor is
+     invoked.
+  */
+  private static final class ResultCodeMap {
+    private static final java.util.Map<Integer,ResultCode> i2e
+      = new java.util.HashMap<>();
+    private static void set(int rc, ResultCode e){ i2e.put(rc, e); }
+    private static ResultCode get(int rc){ return i2e.get(rc); }
+  }
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java
new file mode 100644
index 0000000000..5ce17e718a
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java
@@ -0,0 +1,25 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for use with {@link CApi#sqlite3_rollback_hook}.
+*/
+public interface RollbackHookCallback extends CallbackProxy {
+  /**
+     Works as documented for the C-level sqlite3_rollback_hook()
+     callback.
+  */
+  void call();
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java
new file mode 100644
index 0000000000..4806e2fc0c
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java
@@ -0,0 +1,103 @@
+/*
+** 2023-07-22
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   SQLFunction is used in conjunction with the
+   sqlite3_create_function() JNI-bound API to give that native code
+   access to the callback functions needed in order to implement SQL
+   functions in Java.
+
+   <p>
+
+   This class is not used by itself, but is a marker base class. The
+   three UDF types are modelled by the inner classes Scalar,
+   Aggregate<T>, and Window<T>. Most simply, clients may subclass
+   those, or create anonymous classes from them, to implement
+   UDFs. Clients are free to create their own classes for use with
+   UDFs, so long as they conform to the public interfaces defined by
+   those three classes. The JNI layer only actively relies on the
+   SQLFunction base class and the method names and signatures used by
+   the UDF callback interfaces.
+*/
+public interface SQLFunction {
+
+  /**
+     PerContextState assists aggregate and window functions in
+     managing their accumulator state across calls to the UDF's
+     callbacks.
+
+     <p>T must be of a type which can be legally stored as a value in
+     java.util.HashMap<KeyType,T>.
+
+     <p>If a given aggregate or window function is called multiple times
+     in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
+     then the clients need some way of knowing which call is which so
+     that they can map their state between their various UDF callbacks
+     and reset it via xFinal(). This class takes care of such
+     mappings.
+
+     <p>This class works by mapping
+     sqlite3_context.getAggregateContext() to a single piece of
+     state, of a client-defined type (the T part of this class), which
+     persists across a "matching set" of the UDF's callbacks.
+
+     <p>This class is a helper providing commonly-needed functionality
+     - it is not required for use with aggregate or window functions.
+     Client UDFs are free to perform such mappings using custom
+     approaches. The provided {@link AggregateFunction} and {@link
+     WindowFunction} classes use this.
+  */
+  public static final class PerContextState<T> {
+    private final java.util.Map<Long,ValueHolder<T>> map
+      = new java.util.HashMap<>();
+
+    /**
+       Should be called from a UDF's xStep(), xValue(), and xInverse()
+       methods, passing it that method's first argument and an initial
+       value for the persistent state. If there is currently no
+       mapping for the given context within the map, one is created
+       using the given initial value, else the existing one is used
+       and the 2nd argument is ignored.  It returns a ValueHolder<T>
+       which can be used to modify that state directly without
+       requiring that the client update the underlying map's entry.
+
+       <p>The caller is obligated to eventually call
+       takeAggregateState() to clear the mapping.
+    */
+    public ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
+      final Long key = cx.getAggregateContext(true);
+      ValueHolder<T> rc = null==key ? null : map.get(key);
+      if( null==rc ){
+        map.put(key, rc = new ValueHolder<>(initialValue));
+      }
+      return rc;
+    }
+
+    /**
+       Should be called from a UDF's xFinal() method and passed that
+       method's first argument. This function removes the value
+       associated with cx.getAggregateContext() from the map and
+       returns it, returning null if no other UDF method has been
+       called to set up such a mapping. The latter condition will be
+       the case if a UDF is used in a statement which has no result
+       rows.
+    */
+    public T takeAggregateState(sqlite3_context cx){
+      final ValueHolder<T> h = map.remove(cx.getAggregateContext(false));
+      return null==h ? null : h.value;
+    }
+  }
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/SQLTester.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/SQLTester.java
new file mode 100644
index 0000000000..81d6106be7
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/SQLTester.java
@@ -0,0 +1,1433 @@
+/*
+** 2023-08-08
+**
+** 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 the main application entry pointer for the
+** SQLTester framework.
+*/
+package org.sqlite.jni.capi;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.*;
+import static org.sqlite.jni.capi.CApi.*;
+
+/**
+   Modes for how to escape (or not) column values and names from
+   SQLTester.execSql() to the result buffer output.
+*/
+enum ResultBufferMode {
+  //! Do not append to result buffer
+  NONE,
+  //! Append output escaped.
+  ESCAPED,
+  //! Append output as-is
+  ASIS
+};
+
+/**
+   Modes to specify how to emit multi-row output from
+   SQLTester.execSql() to the result buffer.
+*/
+enum ResultRowMode {
+  //! Keep all result rows on one line, space-separated.
+  ONELINE,
+  //! Add a newline between each result row.
+  NEWLINE
+};
+
+/**
+   Base exception type for test-related failures.
+*/
+class SQLTesterException extends RuntimeException {
+  private boolean bFatal = false;
+
+  SQLTesterException(String msg){
+    super(msg);
+  }
+
+  protected SQLTesterException(String msg, boolean fatal){
+    super(msg);
+    bFatal = fatal;
+  }
+
+  /**
+     Indicates whether the framework should consider this exception
+     type as immediately fatal to the test run or not.
+  */
+  final boolean isFatal(){ return bFatal; }
+}
+
+class DbException extends SQLTesterException {
+  DbException(sqlite3 db, int rc, boolean closeDb){
+    super("DB error #"+rc+": "+sqlite3_errmsg(db),true);
+    if( closeDb ) sqlite3_close_v2(db);
+  }
+  DbException(sqlite3 db, int rc){
+    this(db, rc, false);
+  }
+}
+
+/**
+   Generic test-failed exception.
+ */
+class TestScriptFailed extends SQLTesterException {
+  public TestScriptFailed(TestScript ts, String msg){
+    super(ts.getOutputPrefix()+": "+msg, true);
+  }
+}
+
+/**
+   Thrown when an unknown test command is encountered in a script.
+*/
+class UnknownCommand extends SQLTesterException {
+  public UnknownCommand(TestScript ts, String cmd){
+    super(ts.getOutputPrefix()+": unknown command: "+cmd, false);
+  }
+}
+
+/**
+   Thrown when an "incompatible directive" is found in a script.  This
+   can be the presence of a C-preprocessor construct, specific
+   metadata tags within a test script's header, or specific test
+   constructs which are incompatible with this particular
+   implementation.
+*/
+class IncompatibleDirective extends SQLTesterException {
+  public IncompatibleDirective(TestScript ts, String line){
+    super(ts.getOutputPrefix()+": incompatible directive: "+line, false);
+  }
+}
+
+/**
+   Console output utility class.
+*/
+class Outer {
+  private int verbosity = 0;
+
+  static void out(Object val){
+    System.out.print(val);
+  }
+
+  Outer out(Object... vals){
+    for(Object v : vals) out(v);
+    return this;
+  }
+
+  Outer outln(Object... vals){
+    out(vals).out("\n");
+    return this;
+  }
+
+  Outer verbose(Object... vals){
+    if(verbosity>0){
+      out("VERBOSE",(verbosity>1 ? "+: " : ": ")).outln(vals);
+    }
+    return this;
+  }
+
+  void setVerbosity(int level){
+    verbosity = level;
+  }
+
+  int getVerbosity(){
+    return verbosity;
+  }
+
+  public boolean isVerbose(){return verbosity > 0;}
+
+}
+
+/**
+   <p>This class provides an application which aims to implement the
+   rudimentary SQL-driven test tool described in the accompanying
+   {@code test-script-interpreter.md}.
+
+   <p>This class is an internal testing tool, not part of the public
+   interface but is (A) in the same package as the library because
+   access permissions require it to be so and (B) the JDK8 javadoc
+   offers no way to filter individual classes out of the doc
+   generation process (it can only exclude packages, but see (A)).
+
+   <p>An instance of this application provides a core set of services
+   which TestScript instances use for processing testing logic.
+   TestScripts, in turn, delegate the concrete test work to Command
+   objects, which the TestScript parses on their behalf.
+*/
+public class SQLTester {
+  //! List of input script files.
+  private final java.util.List<String> listInFiles = new ArrayList<>();
+  //! Console output utility.
+  private final Outer outer = new Outer();
+  //! Test input buffer.
+  private final StringBuilder inputBuffer = new StringBuilder();
+  //! Test result buffer.
+  private final StringBuilder resultBuffer = new StringBuilder();
+  //! Buffer for REQUIRED_PROPERTIES pragmas.
+  private final StringBuilder dbInitSql = new StringBuilder();
+  //! Output representation of SQL NULL.
+  private String nullView = "nil";
+  //! Total tests run.
+  private int nTotalTest = 0;
+  //! Total test script files run.
+  private int nTestFile = 0;
+  //! Number of scripts which were aborted.
+  private int nAbortedScript = 0;
+  //! Incremented by test case handlers
+  private int nTest = 0;
+  //! True to enable column name output from execSql()
+  private boolean emitColNames;
+  //! True to keep going regardless of how a test fails.
+  private boolean keepGoing = false;
+  //! The list of available db handles.
+  private final sqlite3[] aDb = new sqlite3[7];
+  //! Index into aDb of the current db.
+  private int iCurrentDb = 0;
+  //! Name of the default db, re-created for each script.
+  private final String initialDbName = "test.db";
+
+
+  public SQLTester(){
+    reset();
+  }
+
+  void setVerbosity(int level){
+    this.outer.setVerbosity( level );
+  }
+  int getVerbosity(){
+    return this.outer.getVerbosity();
+  }
+  boolean isVerbose(){
+    return this.outer.isVerbose();
+  }
+
+  void outputColumnNames(boolean b){ emitColNames = b; }
+
+  void verbose(Object... vals){
+    outer.verbose(vals);
+  }
+
+  void outln(Object... vals){
+    outer.outln(vals);
+  }
+
+  void out(Object... vals){
+    outer.out(vals);
+  }
+
+  //! Adds the given test script to the to-test list.
+  public void addTestScript(String filename){
+    listInFiles.add(filename);
+    //verbose("Added file ",filename);
+  }
+
+  private void setupInitialDb() throws DbException {
+    if( null==aDb[0] ){
+      Util.unlink(initialDbName);
+      openDb(0, initialDbName, true);
+    }else{
+      outln("WARNING: setupInitialDb() unexpectedly ",
+            "triggered while it is opened.");
+    }
+  }
+
+  static final String[] startEmoji = {
+    "🚴", "πŸ„", "πŸ‡", "🀸", "β›Ή", "🏊", "β›·", "πŸ§—", "πŸ‹"
+  };
+  static final int nStartEmoji = startEmoji.length;
+  static int iStartEmoji = 0;
+
+  private static String nextStartEmoji(){
+    return startEmoji[iStartEmoji++ % nStartEmoji];
+  }
+
+  public void runTests() throws Exception {
+    final long tStart = System.currentTimeMillis();
+    for(String f : listInFiles){
+      reset();
+      ++nTestFile;
+      final TestScript ts = new TestScript(f);
+      outln(nextStartEmoji(), " starting [",f,"]");
+      boolean threw = false;
+      final long timeStart = System.currentTimeMillis();
+      try{
+        ts.run(this);
+      }catch(SQLTesterException e){
+        threw = true;
+        outln("πŸ”₯EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage());
+        ++nAbortedScript;
+        if( keepGoing ) outln("Continuing anyway becaure of the keep-going option.");
+        else if( e.isFatal() ) throw e;
+      }finally{
+        final long timeEnd = System.currentTimeMillis();
+        outln("🏁",(threw ? "❌" : "βœ…")," ",nTest," test(s) in ",
+              (timeEnd-timeStart),"ms.");
+      }
+    }
+    final long tEnd = System.currentTimeMillis();
+    outln("Total run-time: ",(tEnd-tStart),"ms");
+    Util.unlink(initialDbName);
+  }
+
+  private StringBuilder clearBuffer(StringBuilder b){
+    b.setLength(0);;
+    return b;
+  }
+
+  StringBuilder clearInputBuffer(){
+    return clearBuffer(inputBuffer);
+  }
+
+  StringBuilder clearResultBuffer(){
+    return clearBuffer(resultBuffer);
+  }
+
+  StringBuilder getInputBuffer(){ return inputBuffer; }
+
+  void appendInput(String n, boolean addNL){
+    inputBuffer.append(n);
+    if(addNL) inputBuffer.append('\n');
+  }
+
+  void appendResult(String n, boolean addNL){
+    resultBuffer.append(n);
+    if(addNL) resultBuffer.append('\n');
+  }
+
+  void appendDbInitSql(String n) throws DbException {
+    dbInitSql.append(n).append('\n');
+    if( null!=getCurrentDb() ){
+      //outln("RUNNING DB INIT CODE: ",n);
+      execSql(null, true, ResultBufferMode.NONE, null, n);
+    }
+  }
+  String getDbInitSql(){ return dbInitSql.toString(); }
+
+  String getInputText(){ return inputBuffer.toString(); }
+
+  String getResultText(){ return resultBuffer.toString(); }
+
+  private String takeBuffer(StringBuilder b){
+    final String rc = b.toString();
+    clearBuffer(b);
+    return rc;
+  }
+
+  String takeInputBuffer(){ return takeBuffer(inputBuffer); }
+
+  String takeResultBuffer(){ return takeBuffer(resultBuffer); }
+
+  int getCurrentDbId(){ return iCurrentDb; }
+
+  SQLTester affirmDbId(int n) throws IndexOutOfBoundsException {
+    if(n<0 || n>=aDb.length){
+      throw new IndexOutOfBoundsException("illegal db number: "+n);
+    }
+    return this;
+  }
+
+  sqlite3 setCurrentDb(int n) throws Exception{
+    affirmDbId(n);
+    iCurrentDb = n;
+    return this.aDb[n];
+  }
+
+  sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; }
+
+  sqlite3 getDbById(int id) throws Exception{
+    return affirmDbId(id).aDb[id];
+  }
+
+  void closeDb(int id) {
+    final sqlite3 db = affirmDbId(id).aDb[id];
+    if( null != db ){
+      sqlite3_close_v2(db);
+      aDb[id] = null;
+    }
+  }
+
+  void closeDb() { closeDb(iCurrentDb); }
+
+  void closeAllDbs(){
+    for(int i = 0; i<aDb.length; ++i){
+      sqlite3_close_v2(aDb[i]);
+      aDb[i] = null;
+    }
+  }
+
+  sqlite3 openDb(String name, boolean createIfNeeded) throws DbException {
+    closeDb();
+    int flags = SQLITE_OPEN_READWRITE;
+    if( createIfNeeded ) flags |= SQLITE_OPEN_CREATE;
+    final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+    int rc = sqlite3_open_v2(name, out, flags, null);
+    final sqlite3 db = out.take();
+    if( 0==rc && dbInitSql.length() > 0){
+      //outln("RUNNING DB INIT CODE: ",dbInitSql.toString());
+      rc = execSql(db, false, ResultBufferMode.NONE,
+                   null, dbInitSql.toString());
+    }
+    if( 0!=rc ){
+      throw new DbException(db, rc, true);
+    }
+    return aDb[iCurrentDb] = db;
+  }
+
+  sqlite3 openDb(int slot, String name, boolean createIfNeeded) throws DbException {
+    affirmDbId(slot);
+    iCurrentDb = slot;
+    return openDb(name, createIfNeeded);
+  }
+
+  /**
+     Resets all tester context state except for that related to
+     tracking running totals.
+  */
+  void reset(){
+    clearInputBuffer();
+    clearResultBuffer();
+    clearBuffer(dbInitSql);
+    closeAllDbs();
+    nTest = 0;
+    nullView = "nil";
+    emitColNames = false;
+    iCurrentDb = 0;
+    //dbInitSql.append("SELECT 1;");
+  }
+
+  void setNullValue(String v){nullView = v;}
+
+  /**
+     If true, encountering an unknown command in a script causes the
+     remainder of the script to be skipped, rather than aborting the
+     whole script run.
+  */
+  boolean skipUnknownCommands(){
+    // Currently hard-coded. Potentially a flag someday.
+    return true;
+  }
+
+  void incrementTestCounter(){ ++nTest; ++nTotalTest; }
+
+  //! "Special" characters - we have to escape output if it contains any.
+  static final Pattern patternSpecial = Pattern.compile(
+    "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]"
+  );
+  //! Either of '{' or '}'.
+  static final Pattern patternSquiggly = Pattern.compile("[{}]");
+
+  /**
+     Returns v or some escaped form of v, as defined in the tester's
+     spec doc.
+  */
+  String escapeSqlValue(String v){
+    if( "".equals(v) ) return "{}";
+    Matcher m = patternSpecial.matcher(v);
+    if( !m.find() ){
+      return v  /* no escaping needed */;
+    }
+    m = patternSquiggly.matcher(v);
+    if( !m.find() ){
+      return "{"+v+"}";
+    }
+    final StringBuilder sb = new StringBuilder("\"");
+    final int n = v.length();
+    for(int i = 0; i < n; ++i){
+      final char ch = v.charAt(i);
+      switch(ch){
+        case '\\': sb.append("\\\\"); break;
+        case '"': sb.append("\\\""); break;
+        default:
+          //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch));
+          if( (int)ch < 32 ) sb.append(String.format("\\%03o", (int)ch));
+          else sb.append(ch);
+          break;
+      }
+    }
+    sb.append("\"");
+    return sb.toString();
+  }
+
+  private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){
+    sb.append(org.sqlite.jni.capi.ResultCode.getEntryForInt(rc)).append(' ');
+    final String msg = escapeSqlValue(sqlite3_errmsg(db));
+    if( '{' == msg.charAt(0) ){
+      sb.append(msg);
+    }else{
+      sb.append('{').append(msg).append('}');
+    }
+  }
+
+  /**
+     Runs SQL on behalf of test commands and outputs the results following
+     the very specific rules of the test framework.
+
+     If db is null, getCurrentDb() is assumed. If throwOnError is true then
+     any db-side error will result in an exception, else they result in
+     the db's result code.
+
+     appendMode specifies how/whether to append results to the result
+     buffer. rowMode specifies whether to output all results in a
+     single line or one line per row. If appendMode is
+     ResultBufferMode.NONE then rowMode is ignored and may be null.
+  */
+  public int execSql(sqlite3 db, boolean throwOnError,
+                     ResultBufferMode appendMode, ResultRowMode rowMode,
+                     String sql) throws SQLTesterException {
+    if( null==db && null==aDb[0] ){
+      // Delay opening of the initial db to enable tests to change its
+      // name and inject on-connect code via, e.g., the MEMDB
+      // directive.  this setup as the potential to misinteract with
+      // auto-extension timing and must be done carefully.
+      setupInitialDb();
+    }
+    final OutputPointer.Int32 oTail = new OutputPointer.Int32();
+    final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+    final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+    if( null==db ) db = getCurrentDb();
+    int pos = 0, n = 1;
+    byte[] sqlChunk = sqlUtf8;
+    int rc = 0;
+    sqlite3_stmt stmt = null;
+    int spacing = 0 /* emit a space for --result if>0 */ ;
+    final StringBuilder sb = (ResultBufferMode.NONE==appendMode)
+      ? null : resultBuffer;
+    //outln("sqlChunk len= = ",sqlChunk.length);
+    try{
+      while(pos < sqlChunk.length){
+        if(pos > 0){
+          sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+                                        sqlChunk.length);
+        }
+        if( 0==sqlChunk.length ) break;
+        rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+        /*outln("PREPARE rc ",rc," oTail=",oTail.get(),": ",
+          new String(sqlChunk,StandardCharsets.UTF_8),"\n<EOSQL>");*/
+        if( 0!=rc ){
+          if(throwOnError){
+            throw new DbException(db, rc);
+          }else if( null!=sb ){
+            appendDbErr(db, sb, rc);
+          }
+          break;
+        }
+        pos = oTail.value;
+        stmt = outStmt.take();
+        if( null == stmt ){
+          // empty statement was parsed.
+          continue;
+        }
+        if( null!=sb ){
+          // Add the output to the result buffer...
+          final int nCol = sqlite3_column_count(stmt);
+          String colName = null, val = null;
+          while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){
+            for(int i = 0; i < nCol; ++i){
+              if( spacing++ > 0 ) sb.append(' ');
+              if( emitColNames ){
+                colName = sqlite3_column_name(stmt, i);
+                switch(appendMode){
+                  case ASIS:
+                    sb.append( colName );
+                    break;
+                  case ESCAPED:
+                    sb.append( escapeSqlValue(colName) );
+                    break;
+                  default:
+                    throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
+                }
+                sb.append(' ');
+              }
+              val = sqlite3_column_text16(stmt, i);
+              if( null==val ){
+                sb.append( nullView );
+                continue;
+              }
+              switch(appendMode){
+                case ASIS:
+                  sb.append( val );
+                  break;
+                case ESCAPED:
+                  sb.append( escapeSqlValue(val) );
+                  break;
+                default:
+                  throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
+              }
+            }
+            if( ResultRowMode.NEWLINE == rowMode ){
+              spacing = 0;
+              sb.append('\n');
+            }
+          }
+        }else{
+          while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){}
+        }
+        sqlite3_finalize(stmt);
+        stmt = null;
+        if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
+        else if( rc!=0 ){
+          if( null!=sb ){
+            appendDbErr(db, sb, rc);
+          }
+          break;
+        }
+      }
+    }finally{
+      sqlite3_reset(stmt
+        /* In order to trigger an exception in the
+           INSERT...RETURNING locking scenario:
+           https://sqlite.org/forum/forumpost/36f7a2e7494897df */);
+      sqlite3_finalize(stmt);
+    }
+    if( 0!=rc && throwOnError ){
+      throw new DbException(db, rc);
+    }
+    return rc;
+  }
+
+  public static void main(String[] argv) throws Exception{
+    installCustomExtensions();
+    boolean dumpInternals = false;
+    final SQLTester t = new SQLTester();
+    for(String a : argv){
+      if(a.startsWith("-")){
+        final String flag = a.replaceFirst("-+","");
+        if( flag.equals("verbose") ){
+          // Use --verbose up to 3 times
+          t.setVerbosity(t.getVerbosity() + 1);
+        }else if( flag.equals("keep-going") ){
+          t.keepGoing = true;
+        }else if( flag.equals("internals") ){
+          dumpInternals = true;
+        }else{
+          throw new IllegalArgumentException("Unhandled flag: "+flag);
+        }
+        continue;
+      }
+      t.addTestScript(a);
+    }
+    final AutoExtensionCallback ax = new AutoExtensionCallback() {
+        private final SQLTester tester = t;
+        @Override public int call(sqlite3 db){
+          final String init = tester.getDbInitSql();
+          if( !init.isEmpty() ){
+            tester.execSql(db, true, ResultBufferMode.NONE, null, init);
+          }
+          return 0;
+        }
+      };
+    sqlite3_auto_extension(ax);
+    try {
+      t.runTests();
+    }finally{
+      sqlite3_cancel_auto_extension(ax);
+      t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s).");
+      if( t.nAbortedScript > 0 ){
+        t.outln("Aborted ",t.nAbortedScript," script(s).");
+      }
+      if( dumpInternals ){
+        sqlite3_jni_internal_details();
+      }
+    }
+  }
+
+  /**
+     Internal impl of the public strglob() method. Neither argument
+     may be NULL and both _MUST_ be NUL-terminated.
+  */
+  private static native int strglob(byte[] glob, byte[] txt);
+
+  /**
+     Works essentially the same as sqlite3_strglob() except that the
+     glob character '#' matches a sequence of one or more digits.  It
+     does not match when it appears at the start or middle of a series
+     of digits, e.g. "#23" or "1#3", but will match at the end,
+     e.g. "12#".
+  */
+  static int strglob(String glob, String txt){
+    return strglob(
+      (glob+"\0").getBytes(StandardCharsets.UTF_8),
+      (txt+"\0").getBytes(StandardCharsets.UTF_8)
+    );
+  }
+
+  /**
+     Sets up C-side components needed by the test framework. This must
+     not be called until main() is triggered so that it does not
+     interfere with library clients who don't use this class.
+  */
+  static native void installCustomExtensions();
+  static {
+    System.loadLibrary("sqlite3-jni")
+      /* Interestingly, when SQLTester is the main app, we have to
+         load that lib from here. The same load from CApi does
+         not happen early enough. Without this,
+         installCustomExtensions() is an unresolved symbol. */;
+  }
+
+}
+
+/**
+   General utilities for the SQLTester bits.
+*/
+final class Util {
+
+  //! Throws a new T, appending all msg args into a string for the message.
+  static void toss(Class<? extends Exception> errorType, Object... msg) throws Exception {
+    StringBuilder sb = new StringBuilder();
+    for(Object s : msg) sb.append(s);
+    final java.lang.reflect.Constructor<? extends Exception> ctor =
+      errorType.getConstructor(String.class);
+    throw ctor.newInstance(sb.toString());
+  }
+
+  static void toss(Object... msg) throws Exception{
+    toss(RuntimeException.class, msg);
+  }
+
+  //! Tries to delete the given file, silently ignoring failure.
+  static void unlink(String filename){
+    try{
+      final java.io.File f = new java.io.File(filename);
+      f.delete();
+    }catch(Exception e){
+      /* ignore */
+    }
+  }
+
+  /**
+     Appends all entries in argv[1..end] into a space-separated
+     string, argv[0] is not included because it's expected to be a
+     command name.
+  */
+  static String argvToString(String[] argv){
+    StringBuilder sb = new StringBuilder();
+    for(int i = 1; i < argv.length; ++i ){
+      if( i>1 ) sb.append(" ");
+      sb.append( argv[i] );
+    }
+    return sb.toString();
+  }
+
+}
+
+/**
+   Base class for test script commands. It provides a set of utility
+   APIs for concrete command implementations.
+
+   Each subclass must have a public no-arg ctor and must implement
+   the process() method which is abstract in this class.
+
+   Commands are intended to be stateless, except perhaps for counters
+   and similar internals. Specifically, no state which changes the
+   behavior between any two invocations of process() should be
+   retained.
+*/
+abstract class Command {
+  protected Command(){}
+
+  /**
+     Must process one command-unit of work and either return
+     (on success) or throw (on error).
+
+     The first two arguments specify the context of the test. The TestScript
+     provides the content of the test and the SQLTester providers the sandbox
+     in which that script is being evaluated.
+
+     argv is a list with the command name followed by any arguments to
+     that command. The argcCheck() method from this class provides
+     very basic argc validation.
+  */
+  public abstract void process(
+    SQLTester st, TestScript ts, String[] argv
+  ) throws Exception;
+
+  /**
+     If argv.length-1 (-1 because the command's name is in argv[0]) does not
+     fall in the inclusive range (min,max) then this function throws. Use
+     a max value of -1 to mean unlimited.
+  */
+  protected final void argcCheck(TestScript ts, String[] argv, int min, int max) throws Exception{
+    int argc = argv.length-1;
+    if(argc<min || (max>=0 && argc>max)){
+      if( min==max ){
+        ts.toss(argv[0]," requires exactly ",min," argument(s)");
+      }else if(max>0){
+        ts.toss(argv[0]," requires ",min,"-",max," arguments.");
+      }else{
+        ts.toss(argv[0]," requires at least ",min," arguments.");
+      }
+    }
+  }
+
+  /**
+     Equivalent to argcCheck(argv,argc,argc).
+  */
+  protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{
+    argcCheck(ts, argv, argc, argc);
+  }
+}
+
+//! --close command
+class CloseDbCommand extends Command {
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+    argcCheck(ts,argv,0,1);
+    Integer id;
+    if(argv.length>1){
+      String arg = argv[1];
+      if("all".equals(arg)){
+        t.closeAllDbs();
+        return;
+      }
+      else{
+        id = Integer.parseInt(arg);
+      }
+    }else{
+      id = t.getCurrentDbId();
+    }
+    t.closeDb(id);
+  }
+}
+
+//! --column-names command
+class ColumnNamesCommand extends Command {
+  public void process(
+    SQLTester st, TestScript ts, String[] argv
+  ) throws Exception{
+    argcCheck(ts,argv,1);
+    st.outputColumnNames( Integer.parseInt(argv[1])!=0 );
+  }
+}
+
+//! --db command
+class DbCommand extends Command {
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+    argcCheck(ts,argv,1);
+    t.setCurrentDb( Integer.parseInt(argv[1]) );
+  }
+}
+
+//! --glob command
+class GlobCommand extends Command {
+  private boolean negate = false;
+  public GlobCommand(){}
+  protected GlobCommand(boolean negate){ this.negate = negate; }
+
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+    argcCheck(ts,argv,1,-1);
+    t.incrementTestCounter();
+    final String sql = t.takeInputBuffer();
+    int rc = t.execSql(null, true, ResultBufferMode.ESCAPED,
+                       ResultRowMode.ONELINE, sql);
+    final String result = t.getResultText();
+    final String sArgs = Util.argvToString(argv);
+    //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs);
+    final String glob = Util.argvToString(argv);
+    rc = SQLTester.strglob(glob, result);
+    if( (negate && 0==rc) || (!negate && 0!=rc) ){
+      ts.toss(argv[0], " mismatch: ", glob," vs input: ",result);
+    }
+  }
+}
+
+//! --json command
+class JsonCommand extends ResultCommand {
+  public JsonCommand(){ super(ResultBufferMode.ASIS); }
+}
+
+//! --json-block command
+class JsonBlockCommand extends TableResultCommand {
+  public JsonBlockCommand(){ super(true); }
+}
+
+//! --new command
+class NewDbCommand extends OpenDbCommand {
+  public NewDbCommand(){ super(true); }
+}
+
+//! Placeholder dummy/no-op commands
+class NoopCommand extends Command {
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+  }
+}
+
+//! --notglob command
+class NotGlobCommand extends GlobCommand {
+  public NotGlobCommand(){
+    super(true);
+  }
+}
+
+//! --null command
+class NullCommand extends Command {
+  public void process(
+    SQLTester st, TestScript ts, String[] argv
+  ) throws Exception{
+    argcCheck(ts,argv,1);
+    st.setNullValue( argv[1] );
+  }
+}
+
+//! --open command
+class OpenDbCommand extends Command {
+  private boolean createIfNeeded = false;
+  public OpenDbCommand(){}
+  protected OpenDbCommand(boolean c){createIfNeeded = c;}
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+    argcCheck(ts,argv,1);
+    t.openDb(argv[1], createIfNeeded);
+  }
+}
+
+//! --print command
+class PrintCommand extends Command {
+  public void process(
+    SQLTester st, TestScript ts, String[] argv
+  ) throws Exception{
+    st.out(ts.getOutputPrefix(),": ");
+    if( 1==argv.length ){
+      st.out( st.getInputText() );
+    }else{
+      st.outln( Util.argvToString(argv) );
+    }
+  }
+}
+
+//! --result command
+class ResultCommand extends Command {
+  private final ResultBufferMode bufferMode;
+  protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; }
+  public ResultCommand(){ this(ResultBufferMode.ESCAPED); }
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+    argcCheck(ts,argv,0,-1);
+    t.incrementTestCounter();
+    final String sql = t.takeInputBuffer();
+    //ts.verbose2(argv[0]," SQL =\n",sql);
+    int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql);
+    final String result = t.getResultText().trim();
+    final String sArgs = argv.length>1 ? Util.argvToString(argv) : "";
+    if( !result.equals(sArgs) ){
+      t.outln(argv[0]," FAILED comparison. Result buffer:\n",
+              result,"\nExpected result:\n",sArgs);
+      ts.toss(argv[0]+" comparison failed.");
+    }
+  }
+}
+
+//! --run command
+class RunCommand extends Command {
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+    argcCheck(ts,argv,0,1);
+    final sqlite3 db = (1==argv.length)
+      ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) );
+    final String sql = t.takeInputBuffer();
+    final int rc = t.execSql(db, false, ResultBufferMode.NONE,
+                             ResultRowMode.ONELINE, sql);
+    if( 0!=rc && t.isVerbose() ){
+      String msg = sqlite3_errmsg(db);
+      ts.verbose1(argv[0]," non-fatal command error #",rc,": ",
+                  msg,"\nfor SQL:\n",sql);
+    }
+  }
+}
+
+//! --tableresult command
+class TableResultCommand extends Command {
+  private final boolean jsonMode;
+  protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; }
+  public TableResultCommand(){ this(false); }
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+    argcCheck(ts,argv,0);
+    t.incrementTestCounter();
+    String body = ts.fetchCommandBody(t);
+    if( null==body ) ts.toss("Missing ",argv[0]," body.");
+    body = body.trim();
+    if( !body.endsWith("\n--end") ){
+      ts.toss(argv[0], " must be terminated with --end.");
+    }else{
+      body = body.substring(0, body.length()-6);
+    }
+    final String[] globs = body.split("\\s*\\n\\s*");
+    if( globs.length < 1 ){
+      ts.toss(argv[0], " requires 1 or more ",
+              (jsonMode ? "json snippets" : "globs"),".");
+    }
+    final String sql = t.takeInputBuffer();
+    t.execSql(null, true,
+              jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED,
+              ResultRowMode.NEWLINE, sql);
+    final String rbuf = t.getResultText();
+    final String[] res = rbuf.split("\n");
+    if( res.length != globs.length ){
+      ts.toss(argv[0], " failure: input has ", res.length,
+              " row(s) but expecting ",globs.length);
+    }
+    for(int i = 0; i < res.length; ++i){
+      final String glob = globs[i].replaceAll("\\s+"," ").trim();
+      //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>");
+      if( jsonMode ){
+        if( !glob.equals(res[i]) ){
+          ts.toss(argv[0], " json <<",glob, ">> does not match: <<",
+                  res[i],">>");
+        }
+      }else if( 0 != SQLTester.strglob(glob, res[i]) ){
+        ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>");
+      }
+    }
+  }
+}
+
+//! --testcase command
+class TestCaseCommand extends Command {
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+    argcCheck(ts,argv,1);
+    ts.setTestCaseName(argv[1]);
+    t.clearResultBuffer();
+    t.clearInputBuffer();
+  }
+}
+
+//! --verbosity command
+class VerbosityCommand extends Command {
+  public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+    argcCheck(ts,argv,1);
+    ts.setVerbosity( Integer.parseInt(argv[1]) );
+  }
+}
+
+class CommandDispatcher {
+
+  private static java.util.Map<String,Command> commandMap =
+    new java.util.HashMap<>();
+
+  /**
+     Returns a (cached) instance mapped to name, or null if no match
+     is found.
+  */
+  static Command getCommandByName(String name){
+    Command rv = commandMap.get(name);
+    if( null!=rv ) return rv;
+    switch(name){
+      case "close":        rv = new CloseDbCommand(); break;
+      case "column-names": rv = new ColumnNamesCommand(); break;
+      case "db":           rv = new DbCommand(); break;
+      case "glob":         rv = new GlobCommand(); break;
+      case "json":         rv = new JsonCommand(); break;
+      case "json-block":   rv = new JsonBlockCommand(); break;
+      case "new":          rv = new NewDbCommand(); break;
+      case "notglob":      rv = new NotGlobCommand(); break;
+      case "null":         rv = new NullCommand(); break;
+      case "oom":          rv = new NoopCommand(); break;
+      case "open":         rv = new OpenDbCommand(); break;
+      case "print":        rv = new PrintCommand(); break;
+      case "result":       rv = new ResultCommand(); break;
+      case "run":          rv = new RunCommand(); break;
+      case "tableresult":  rv = new TableResultCommand(); break;
+      case "testcase":     rv = new TestCaseCommand(); break;
+      case "verbosity":    rv = new VerbosityCommand(); break;
+      default: rv = null; break;
+    }
+    if( null!=rv ) commandMap.put(name, rv);
+    return rv;
+  }
+
+  /**
+     Treats argv[0] as a command name, looks it up with
+     getCommandByName(), and calls process() on that instance, passing
+     it arguments given to this function.
+  */
+  static void dispatch(SQLTester tester, TestScript ts, String[] argv) throws Exception{
+    final Command cmd = getCommandByName(argv[0]);
+    if(null == cmd){
+      throw new UnknownCommand(ts, argv[0]);
+    }
+    cmd.process(tester, ts, argv);
+  }
+}
+
+
+/**
+   This class represents a single test script. It handles (or
+   delegates) its the reading-in and parsing, but the details of
+   evaluation are delegated elsewhere.
+*/
+class TestScript {
+  //! input file
+  private String filename = null;
+  //! Name pulled from the SCRIPT_MODULE_NAME directive of the file
+  private String moduleName = null;
+  //! Current test case name.
+  private String testCaseName = null;
+  //! Content buffer state.
+  private final Cursor cur = new Cursor();
+  //! Utility for console output.
+  private final Outer outer = new Outer();
+
+  //! File content and parse state.
+  private static final class Cursor {
+    private final StringBuilder sb = new StringBuilder();
+    byte[] src = null;
+    //! Current position in this.src.
+    int pos = 0;
+    //! Current line number. Starts at 0 for internal reasons and will
+    // line up with 1-based reality once parsing starts.
+    int lineNo = 0 /* yes, zero */;
+    //! Putback value for this.pos.
+    int putbackPos = 0;
+    //! Putback line number
+    int putbackLineNo = 0;
+    //! Peeked-to pos, used by peekLine() and consumePeeked().
+    int peekedPos = 0;
+    //! Peeked-to line number.
+    int peekedLineNo = 0;
+
+    //! Restore parsing state to the start of the stream.
+    void rewind(){
+      sb.setLength(0);
+      pos = lineNo = putbackPos = putbackLineNo = peekedPos = peekedLineNo = 0
+        /* kinda missing memset() about now. */;
+    }
+  }
+
+  private byte[] readFile(String filename) throws Exception {
+    return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename));
+  }
+
+  /**
+     Initializes the script with the content of the given file.
+     Throws if it cannot read the file.
+  */
+  public TestScript(String filename) throws Exception{
+    this.filename = filename;
+    setVerbosity(2);
+    cur.src = readFile(filename);
+  }
+
+  public String getFilename(){
+    return filename;
+  }
+
+  public String getModuleName(){
+    return moduleName;
+  }
+
+  /**
+     Verbosity level 0 produces no debug/verbose output. Level 1 produces
+     some and level 2 produces more.
+   */
+  public void setVerbosity(int level){
+    outer.setVerbosity(level);
+  }
+
+  public String getOutputPrefix(){
+    String rc = "["+(moduleName==null ? "<unnamed>" : moduleName)+"]";
+    if( null!=testCaseName ) rc += "["+testCaseName+"]";
+    if( null!=filename ) rc += "["+filename+"]";
+    return rc + " line "+ cur.lineNo;
+  }
+
+  static final String[] verboseLabel = {"πŸ”ˆ",/*"πŸ”‰",*/"πŸ”Š","πŸ“’"};
+  //! Output vals only if level<=current verbosity level.
+  private TestScript verboseN(int level, Object... vals){
+    final int verbosity = outer.getVerbosity();
+    if(verbosity>=level){
+      outer.out( verboseLabel[level-1], getOutputPrefix(), " ",level,": "
+      ).outln(vals);
+    }
+    return this;
+  }
+
+  TestScript verbose1(Object... vals){return verboseN(1,vals);}
+  TestScript verbose2(Object... vals){return verboseN(2,vals);}
+  TestScript verbose3(Object... vals){return verboseN(3,vals);}
+
+  private void reset(){
+    testCaseName = null;
+    cur.rewind();
+  }
+
+  void setTestCaseName(String n){ testCaseName = n; }
+
+  /**
+     Returns the next line from the buffer, minus the trailing EOL.
+
+     Returns null when all input is consumed. Throws if it reads
+     illegally-encoded input, e.g. (non-)characters in the range
+     128-256.
+  */
+  String getLine(){
+    if( cur.pos==cur.src.length ){
+      return null /* EOF */;
+    }
+    cur.putbackPos = cur.pos;
+    cur.putbackLineNo = cur.lineNo;
+    cur.sb.setLength(0);
+    final boolean skipLeadingWs = false;
+    byte b = 0, prevB = 0;
+    int i = cur.pos;
+    if(skipLeadingWs) {
+      /* Skip any leading spaces, including newlines. This will eliminate
+         blank lines. */
+      for(; i < cur.src.length; ++i, prevB=b){
+        b = cur.src[i];
+        switch((int)b){
+          case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue;
+          case 10/*NL*/: ++cur.lineNo; continue;
+          default: break;
+        }
+        break;
+      }
+      if( i==cur.src.length ){
+        return null /* EOF */;
+      }
+    }
+    boolean doBreak = false;
+    final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */;
+    int nChar = 0 /* number of bytes in the char */;
+    for(; i < cur.src.length && !doBreak; ++i){
+      b = cur.src[i];
+      switch( (int)b ){
+        case 13/*CR*/: continue;
+        case 10/*NL*/:
+          ++cur.lineNo;
+          if(cur.sb.length()>0) doBreak = true;
+          // Else it's an empty string
+          break;
+        default:
+          /* Multi-byte chars need to be gathered up and appended at
+             one time. Appending individual bytes to the StringBuffer
+             appends their integer value. */
+          nChar = 1;
+          switch( b & 0xF0 ){
+            case 0xC0: nChar = 2; break;
+            case 0xE0: nChar = 3; break;
+            case 0xF0: nChar = 4; break;
+            default:
+              if( b > 127 ) this.toss("Invalid character (#"+(int)b+").");
+              break;
+          }
+          if( 1==nChar ){
+            cur.sb.append((char)b);
+          }else{
+            for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x];
+            cur.sb.append(new String(Arrays.copyOf(aChar, nChar),
+                                      StandardCharsets.UTF_8));
+            i += nChar-1;
+          }
+          break;
+      }
+    }
+    cur.pos = i;
+    final String rv = cur.sb.toString();
+    if( i==cur.src.length && 0==rv.length() ){
+      return null /* EOF */;
+    }
+    return rv;
+  }/*getLine()*/
+
+  /**
+     Fetches the next line then resets the cursor to its pre-call
+     state. consumePeeked() can be used to consume this peeked line
+     without having to re-parse it.
+  */
+  String peekLine(){
+    final int oldPos = cur.pos;
+    final int oldPB = cur.putbackPos;
+    final int oldPBL = cur.putbackLineNo;
+    final int oldLine = cur.lineNo;
+    try{ return getLine(); }
+    finally{
+      cur.peekedPos = cur.pos;
+      cur.peekedLineNo = cur.lineNo;
+      cur.pos = oldPos;
+      cur.lineNo = oldLine;
+      cur.putbackPos = oldPB;
+      cur.putbackLineNo = oldPBL;
+    }
+  }
+
+  /**
+     Only valid after calling peekLine() and before calling getLine().
+     This places the cursor to the position it would have been at had
+     the peekLine() had been fetched with getLine().
+  */
+  void consumePeeked(){
+    cur.pos = cur.peekedPos;
+    cur.lineNo = cur.peekedLineNo;
+  }
+
+  /**
+     Restores the cursor to the position it had before the previous
+     call to getLine().
+  */
+  void putbackLine(){
+    cur.pos = cur.putbackPos;
+    cur.lineNo = cur.putbackLineNo;
+  }
+
+  private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{
+    if( true ) return false;
+    int nOk = 0;
+    for(String rp : props){
+      verbose1("REQUIRED_PROPERTIES: ",rp);
+      switch(rp){
+        case "RECURSIVE_TRIGGERS":
+          t.appendDbInitSql("pragma recursive_triggers=on;");
+          ++nOk;
+          break;
+        case "TEMPSTORE_FILE":
+          /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+             which we just happen to know is the case */
+          t.appendDbInitSql("pragma temp_store=1;");
+          ++nOk;
+          break;
+        case "TEMPSTORE_MEM":
+          /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+             which we just happen to know is the case */
+          t.appendDbInitSql("pragma temp_store=0;");
+          ++nOk;
+          break;
+        case "AUTOVACUUM":
+          t.appendDbInitSql("pragma auto_vacuum=full;");
+          ++nOk;
+        case "INCRVACUUM":
+          t.appendDbInitSql("pragma auto_vacuum=incremental;");
+          ++nOk;
+        default:
+          break;
+      }
+    }
+    return props.length == nOk;
+  }
+
+  private static final Pattern patternRequiredProperties =
+    Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$");
+  private static final Pattern patternScriptModuleName =
+    Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$");
+  private static final Pattern patternMixedModuleName =
+    Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$");
+  private static final Pattern patternCommand =
+    Pattern.compile("^--(([a-z-]+)( .*)?)$");
+
+  /**
+     Looks for "directives." If a compatible one is found, it is
+     processed and this function returns. If an incompatible one is found,
+     a description of it is returned and processing of the test must
+     end immediately.
+  */
+  private void checkForDirective(
+    SQLTester tester, String line
+  ) throws IncompatibleDirective {
+    if(line.startsWith("#")){
+      throw new IncompatibleDirective(this, "C-preprocessor input: "+line);
+    }else if(line.startsWith("---")){
+      new IncompatibleDirective(this, "triple-dash: "+line);
+    }
+    Matcher m = patternScriptModuleName.matcher(line);
+    if( m.find() ){
+      moduleName = m.group(1);
+      return;
+    }
+    m = patternRequiredProperties.matcher(line);
+    if( m.find() ){
+      final String rp = m.group(1);
+      if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
+        throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
+      }
+    }
+    m = patternMixedModuleName.matcher(line);
+    if( m.find() ){
+      throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3));
+    }
+    if( line.indexOf("\n|")>=0 ){
+      throw new IncompatibleDirective(this, "newline-pipe combination.");
+    }
+    return;
+  }
+
+  boolean isCommandLine(String line, boolean checkForImpl){
+    final Matcher m = patternCommand.matcher(line);
+    boolean rc = m.find();
+    if( rc && checkForImpl ){
+      rc = null!=CommandDispatcher.getCommandByName(m.group(2));
+    }
+    return rc;
+  }
+
+  /**
+     If line looks like a command, returns an argv for that command
+     invocation, else returns null.
+  */
+  String[] getCommandArgv(String line){
+    final Matcher m = patternCommand.matcher(line);
+    return m.find() ? m.group(1).trim().split("\\s+") : null;
+  }
+
+  /**
+     Fetches lines until the next recognized command. Throws if
+     checkForDirective() does.  Returns null if there is no input or
+     it's only whitespace. The returned string retains all whitespace.
+
+     Note that "subcommands", --command-like constructs in the body
+     which do not match a known command name are considered to be
+     content, not commands.
+  */
+  String fetchCommandBody(SQLTester tester){
+    final StringBuilder sb = new StringBuilder();
+    String line;
+    while( (null != (line = peekLine())) ){
+      checkForDirective(tester, line);
+      if( isCommandLine(line, true) ) break;
+      else {
+        sb.append(line).append("\n");
+        consumePeeked();
+      }
+    }
+    line = sb.toString();
+    return line.trim().isEmpty() ? null : line;
+  }
+
+  private void processCommand(SQLTester t, String[] argv) throws Exception{
+    verbose1("running command: ",argv[0], " ", Util.argvToString(argv));
+    if(outer.getVerbosity()>1){
+      final String input = t.getInputText();
+      if( !input.isEmpty() ) verbose3("Input buffer = ",input);
+    }
+    CommandDispatcher.dispatch(t, this, argv);
+  }
+
+  void toss(Object... msg) throws TestScriptFailed {
+    StringBuilder sb = new StringBuilder();
+    for(Object s : msg) sb.append(s);
+    throw new TestScriptFailed(this, sb.toString());
+  }
+
+  /**
+     Runs this test script in the context of the given tester object.
+  */
+  public boolean run(SQLTester tester) throws Exception {
+    reset();
+    setVerbosity(tester.getVerbosity());
+    String line, directive;
+    String[] argv;
+    while( null != (line = getLine()) ){
+      verbose3("input line: ",line);
+      checkForDirective(tester, line);
+      argv = getCommandArgv(line);
+      if( null!=argv ){
+        processCommand(tester, argv);
+        continue;
+      }
+      tester.appendInput(line,true);
+    }
+    return true;
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java
new file mode 100644
index 0000000000..95541bdcba
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java
@@ -0,0 +1,33 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+   A SQLFunction implementation for scalar functions.
+*/
+public abstract class ScalarFunction implements SQLFunction {
+  /**
+     As for the xFunc() argument of the C API's
+     sqlite3_create_function(). If this function throws, it is
+     translated into an sqlite3_result_error().
+  */
+  public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args);
+
+  /**
+     Optionally override to be notified when the UDF is finalized by
+     SQLite. This default implementation does nothing.
+  */
+  public void xDestroy() {}
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java
new file mode 100644
index 0000000000..d8b6226ac9
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java
@@ -0,0 +1,35 @@
+/*
+** 2023-07-21
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   A wrapper object for use with sqlite3_table_column_metadata().
+   They are populated only via that interface.
+*/
+public final class TableColumnMetadata {
+  OutputPointer.Bool pNotNull = new OutputPointer.Bool();
+  OutputPointer.Bool pPrimaryKey = new OutputPointer.Bool();
+  OutputPointer.Bool pAutoinc = new OutputPointer.Bool();
+  OutputPointer.String pzCollSeq = new OutputPointer.String();
+  OutputPointer.String pzDataType = new OutputPointer.String();
+
+  public TableColumnMetadata(){
+  }
+
+  public String getDataType(){ return pzDataType.value; }
+  public String getCollation(){ return pzCollSeq.value; }
+  public boolean isNotNull(){ return pNotNull.value; }
+  public boolean isPrimaryKey(){ return pPrimaryKey.value; }
+  public boolean isAutoincrement(){ return pAutoinc.value; }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/Tester1.java
new file mode 100644
index 0000000000..6fb28e65b9
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/Tester1.java
@@ -0,0 +1,1976 @@
+/*
+** 2023-07-21
+**
+** 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 a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.capi;
+import static org.sqlite.jni.capi.CApi.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+   An annotation for Tester1 tests which we do not want to run in
+   reflection-driven test mode because either they are not suitable
+   for multi-threaded threaded mode or we have to control their execution
+   order.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface ManualTest{}
+/**
+   Annotation for Tester1 tests which mark those which must be skipped
+   in multi-threaded mode.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface SingleThreadOnly{}
+
+public class Tester1 implements Runnable {
+  //! True when running in multi-threaded mode.
+  private static boolean mtMode = false;
+  //! True to sleep briefly between tests.
+  private static boolean takeNaps = false;
+  //! True to shuffle the order of the tests.
+  private static boolean shuffle = false;
+  //! True to dump the list of to-run tests to stdout.
+  private static boolean listRunTests = false;
+  //! True to squelch all out() and outln() output.
+  private static boolean quietMode = false;
+  //! Total number of runTests() calls.
+  private static int nTestRuns = 0;
+  //! List of test*() methods to run.
+  private static List<java.lang.reflect.Method> testMethods = null;
+  //! List of exceptions collected by run()
+  private static List<Exception> listErrors = new ArrayList<>();
+  private static final class Metrics {
+    //! Number of times createNewDb() (or equivalent) is invoked.
+    volatile int dbOpen = 0;
+  }
+
+  private Integer tId;
+
+  Tester1(Integer id){
+    tId = id;
+  }
+
+  static final Metrics metrics = new Metrics();
+
+  public static synchronized void outln(){
+    if( !quietMode ){
+      System.out.println("");
+    }
+  }
+
+  public static synchronized void outPrefix(){
+    if( !quietMode ){
+      System.out.print(Thread.currentThread().getName()+": ");
+    }
+  }
+
+  public static synchronized void outln(Object val){
+    if( !quietMode ){
+      outPrefix();
+      System.out.println(val);
+    }
+  }
+
+  public static synchronized void out(Object val){
+    if( !quietMode ){
+      System.out.print(val);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public static synchronized void out(Object... vals){
+    if( !quietMode ){
+      outPrefix();
+      for(Object v : vals) out(v);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public static synchronized void outln(Object... vals){
+    if( !quietMode ){
+      out(vals); out("\n");
+    }
+  }
+
+  static volatile int affirmCount = 0;
+  public static synchronized int affirm(Boolean v, String comment){
+    ++affirmCount;
+    if( false ) assert( v /* prefer assert over exception if it's enabled because
+                 the JNI layer sometimes has to suppress exceptions,
+                 so they might be squelched on their way back to the
+                 top. */);
+    if( !v ) throw new RuntimeException(comment);
+    return affirmCount;
+  }
+
+  public static void affirm(Boolean v){
+    affirm(v, "Affirmation failed.");
+  }
+
+  @SingleThreadOnly /* because it's thread-agnostic */
+  private void test1(){
+    affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
+  }
+
+  public static sqlite3 createNewDb(){
+    final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+    int rc = sqlite3_open(":memory:", out);
+    ++metrics.dbOpen;
+    sqlite3 db = out.take();
+    if( 0!=rc ){
+      final String msg =
+        null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db);
+      sqlite3_close(db);
+      throw new RuntimeException("Opening db failed: "+msg);
+    }
+    affirm( null == out.get() );
+    affirm( 0 != db.getNativePointer() );
+    rc = sqlite3_busy_timeout(db, 2000);
+    affirm( 0 == rc );
+    return db;
+  }
+
+  public static void execSql(sqlite3 db, String[] sql){
+    execSql(db, String.join("", sql));
+  }
+
+  public static int execSql(sqlite3 db, boolean throwOnError, String sql){
+    OutputPointer.Int32 oTail = new OutputPointer.Int32();
+    final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+    int pos = 0, n = 1;
+    byte[] sqlChunk = sqlUtf8;
+    int rc = 0;
+    sqlite3_stmt stmt = null;
+    final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+    while(pos < sqlChunk.length){
+      if(pos > 0){
+        sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+                                      sqlChunk.length);
+      }
+      if( 0==sqlChunk.length ) break;
+      rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+      if(throwOnError) affirm(0 == rc);
+      else if( 0!=rc ) break;
+      pos = oTail.value;
+      stmt = outStmt.take();
+      if( null == stmt ){
+        // empty statement was parsed.
+        continue;
+      }
+      affirm(0 != stmt.getNativePointer());
+      while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){
+      }
+      sqlite3_finalize(stmt);
+      affirm(0 == stmt.getNativePointer());
+      if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){
+        break;
+      }
+    }
+    sqlite3_finalize(stmt);
+    if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
+    if( 0!=rc && throwOnError){
+      throw new RuntimeException("db op failed with rc="
+                                 +rc+": "+sqlite3_errmsg(db));
+    }
+    return rc;
+  }
+
+  public static void execSql(sqlite3 db, String sql){
+    execSql(db, true, sql);
+  }
+
+  public static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
+    final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+    int rc = sqlite3_prepare_v2(db, sql, outStmt);
+    if( throwOnError ){
+      affirm( 0 == rc );
+    }
+    final sqlite3_stmt rv = outStmt.take();
+    affirm( null == outStmt.get() );
+    if( throwOnError ){
+      affirm( 0 != rv.getNativePointer() );
+    }
+    return rv;
+  }
+
+  public static sqlite3_stmt prepare(sqlite3 db, String sql){
+    return prepare(db, true, sql);
+  }
+
+  private void showCompileOption(){
+    int i = 0;
+    String optName;
+    outln("compile options:");
+    for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
+      outln("\t"+optName+"\t (used="+
+            sqlite3_compileoption_used(optName)+")");
+    }
+  }
+
+  private void testCompileOption(){
+    int i = 0;
+    String optName;
+    for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
+    }
+    affirm( i > 10 );
+    affirm( null==sqlite3_compileoption_get(-1) );
+  }
+
+  private void testOpenDb1(){
+    final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+    int rc = sqlite3_open(":memory:", out);
+    ++metrics.dbOpen;
+    sqlite3 db = out.get();
+    affirm(0 == rc);
+    affirm(db.getNativePointer()!=0);
+    sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null)
+      /* This function has different mangled names in jdk8 vs jdk19,
+         and this call is here to ensure that the build fails
+         if it cannot find both names. */;
+
+    affirm( 0==sqlite3_db_readonly(db,"main") );
+    affirm( 0==sqlite3_db_readonly(db,null) );
+    affirm( 0>sqlite3_db_readonly(db,"nope") );
+    affirm( 0>sqlite3_db_readonly(null,null) );
+    affirm( 0==sqlite3_last_insert_rowid(null) );
+
+    // These interrupt checks are only to make sure that the JNI binding
+    // has the proper exported symbol names. They don't actually test
+    // anything useful.
+    affirm( !sqlite3_is_interrupted(db) );
+    sqlite3_interrupt(db);
+    affirm( sqlite3_is_interrupted(db) );
+    sqlite3_close_v2(db);
+    affirm(0 == db.getNativePointer());
+  }
+
+  private void testOpenDb2(){
+    final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+    int rc = sqlite3_open_v2(":memory:", out,
+                             SQLITE_OPEN_READWRITE
+                             | SQLITE_OPEN_CREATE, null);
+    ++metrics.dbOpen;
+    affirm(0 == rc);
+    sqlite3 db = out.get();
+    affirm(0 != db.getNativePointer());
+    sqlite3_close_v2(db);
+    affirm(0 == db.getNativePointer());
+  }
+
+  private void testPrepare123(){
+    sqlite3 db = createNewDb();
+    int rc;
+    final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+    rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt);
+    affirm(0 == rc);
+    sqlite3_stmt stmt = outStmt.take();
+    affirm(0 != stmt.getNativePointer());
+    affirm( !sqlite3_stmt_readonly(stmt) );
+    affirm( db == sqlite3_db_handle(stmt) );
+    rc = sqlite3_step(stmt);
+    affirm(SQLITE_DONE == rc);
+    sqlite3_finalize(stmt);
+    affirm( null == sqlite3_db_handle(stmt) );
+    affirm(0 == stmt.getNativePointer());
+
+    { /* Demonstrate how to use the "zTail" option of
+         sqlite3_prepare() family of functions. */
+      OutputPointer.Int32 oTail = new OutputPointer.Int32();
+      final byte[] sqlUtf8 =
+        "CREATE TABLE t2(a); INSERT INTO t2(a) VALUES(1),(2),(3)"
+        .getBytes(StandardCharsets.UTF_8);
+      int pos = 0, n = 1;
+      byte[] sqlChunk = sqlUtf8;
+      while(pos < sqlChunk.length){
+        if(pos > 0){
+          sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length);
+        }
+        //outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos);
+        if( 0==sqlChunk.length ) break;
+        rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+        affirm(0 == rc);
+        stmt = outStmt.get();
+        pos = oTail.value;
+        /*outln("SQL tail pos = "+pos+". Chunk = "+
+              (new String(Arrays.copyOfRange(sqlChunk,0,pos),
+              StandardCharsets.UTF_8)));*/
+        switch(n){
+          case 1: affirm(19 == pos); break;
+          case 2: affirm(36 == pos); break;
+          default: affirm( false /* can't happen */ );
+
+        }
+        ++n;
+        affirm(0 != stmt.getNativePointer());
+        rc = sqlite3_step(stmt);
+        affirm(SQLITE_DONE == rc);
+        sqlite3_finalize(stmt);
+        affirm(0 == stmt.getNativePointer());
+      }
+    }
+
+
+    rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)",
+                            SQLITE_PREPARE_NORMALIZE, outStmt);
+    affirm(0 == rc);
+    stmt = outStmt.get();
+    affirm(0 != stmt.getNativePointer());
+    sqlite3_finalize(stmt);
+    affirm(0 == stmt.getNativePointer() );
+
+    affirm( 0==sqlite3_errcode(db) );
+    stmt = sqlite3_prepare(db, "intentional error");
+    affirm( null==stmt );
+    affirm( 0!=sqlite3_errcode(db) );
+    affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") );
+    sqlite3_finalize(stmt);
+    stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only");
+    affirm( null==stmt );
+    affirm( 0==sqlite3_errcode(db) );
+    sqlite3_close_v2(db);
+  }
+
+  private void testBindFetchInt(){
+    sqlite3 db = createNewDb();
+    execSql(db, "CREATE TABLE t(a)");
+
+    sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(:a);");
+    affirm(1 == sqlite3_bind_parameter_count(stmt));
+    final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a");
+    affirm(1 == paramNdx);
+    affirm( ":a".equals(sqlite3_bind_parameter_name(stmt, paramNdx)));
+    int total1 = 0;
+    long rowid = -1;
+    int changes = sqlite3_changes(db);
+    int changesT = sqlite3_total_changes(db);
+    long changes64 = sqlite3_changes64(db);
+    long changesT64 = sqlite3_total_changes64(db);
+    int rc;
+    for(int i = 99; i < 102; ++i ){
+      total1 += i;
+      rc = sqlite3_bind_int(stmt, paramNdx, i);
+      affirm(0 == rc);
+      rc = sqlite3_step(stmt);
+      sqlite3_reset(stmt);
+      affirm(SQLITE_DONE == rc);
+      long x = sqlite3_last_insert_rowid(db);
+      affirm(x > rowid);
+      rowid = x;
+    }
+    sqlite3_finalize(stmt);
+    affirm(300 == total1);
+    affirm(sqlite3_changes(db) > changes);
+    affirm(sqlite3_total_changes(db) > changesT);
+    affirm(sqlite3_changes64(db) > changes64);
+    affirm(sqlite3_total_changes64(db) > changesT64);
+    stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+    affirm( sqlite3_stmt_readonly(stmt) );
+    affirm( !sqlite3_stmt_busy(stmt) );
+    int total2 = 0;
+    while( SQLITE_ROW == sqlite3_step(stmt) ){
+      affirm( sqlite3_stmt_busy(stmt) );
+      total2 += sqlite3_column_int(stmt, 0);
+      sqlite3_value sv = sqlite3_column_value(stmt, 0);
+      affirm( null != sv );
+      affirm( 0 != sv.getNativePointer() );
+      affirm( SQLITE_INTEGER == sqlite3_value_type(sv) );
+    }
+    affirm( !sqlite3_stmt_busy(stmt) );
+    sqlite3_finalize(stmt);
+    affirm(total1 == total2);
+
+    // sqlite3_value_frombind() checks...
+    stmt = prepare(db, "SELECT 1, ?");
+    sqlite3_bind_int(stmt, 1, 2);
+    rc = sqlite3_step(stmt);
+    affirm( SQLITE_ROW==rc );
+    affirm( !sqlite3_value_frombind(sqlite3_column_value(stmt, 0)) );
+    affirm( sqlite3_value_frombind(sqlite3_column_value(stmt, 1)) );
+    sqlite3_finalize(stmt);
+
+    sqlite3_close_v2(db);
+    affirm(0 == db.getNativePointer());
+  }
+
+  private void testBindFetchInt64(){
+    try (sqlite3 db = createNewDb()){
+      execSql(db, "CREATE TABLE t(a)");
+      sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+      long total1 = 0;
+      for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){
+        total1 += i;
+        sqlite3_bind_int64(stmt, 1, i);
+        sqlite3_step(stmt);
+        sqlite3_reset(stmt);
+      }
+      sqlite3_finalize(stmt);
+      stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+      long total2 = 0;
+      while( SQLITE_ROW == sqlite3_step(stmt) ){
+        total2 += sqlite3_column_int64(stmt, 0);
+      }
+      sqlite3_finalize(stmt);
+      affirm(total1 == total2);
+      //sqlite3_close_v2(db);
+    }
+  }
+
+  private void testBindFetchDouble(){
+    try (sqlite3 db = createNewDb()){
+      execSql(db, "CREATE TABLE t(a)");
+      sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+      double total1 = 0;
+      for(double i = 1.5; i < 5.0; i = i + 1.0 ){
+        total1 += i;
+        sqlite3_bind_double(stmt, 1, i);
+        sqlite3_step(stmt);
+        sqlite3_reset(stmt);
+      }
+      sqlite3_finalize(stmt);
+      stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+      double total2 = 0;
+      int counter = 0;
+      while( SQLITE_ROW == sqlite3_step(stmt) ){
+        ++counter;
+        total2 += sqlite3_column_double(stmt, 0);
+      }
+      affirm(4 == counter);
+      sqlite3_finalize(stmt);
+      affirm(total2<=total1+0.01 && total2>=total1-0.01);
+      //sqlite3_close_v2(db);
+    }
+  }
+
+  private void testBindFetchText(){
+    sqlite3 db = createNewDb();
+    execSql(db, "CREATE TABLE t(a)");
+    sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+    String[] list1 = { "hell🀩", "wπŸ˜ƒrld", "!🀩" };
+    int rc;
+    int n = 0;
+    for( String e : list1 ){
+      rc = (0==n)
+        ? sqlite3_bind_text(stmt, 1, e)
+        : sqlite3_bind_text16(stmt, 1, e);
+      affirm(0 == rc);
+      rc = sqlite3_step(stmt);
+      affirm(SQLITE_DONE==rc);
+      sqlite3_reset(stmt);
+    }
+    sqlite3_finalize(stmt);
+    stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+    StringBuilder sbuf = new StringBuilder();
+    n = 0;
+    while( SQLITE_ROW == sqlite3_step(stmt) ){
+      final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0));
+      final String txt = sqlite3_column_text16(stmt, 0);
+      sbuf.append( txt );
+      affirm( txt.equals(new String(
+                           sqlite3_column_text(stmt, 0),
+                           StandardCharsets.UTF_8
+                         )) );
+      affirm( txt.length() < sqlite3_value_bytes(sv) );
+      affirm( txt.equals(new String(
+                           sqlite3_value_text(sv),
+                           StandardCharsets.UTF_8)) );
+      affirm( txt.length() == sqlite3_value_bytes16(sv)/2 );
+      affirm( txt.equals(sqlite3_value_text16(sv)) );
+      sqlite3_value_free(sv);
+      ++n;
+    }
+    sqlite3_finalize(stmt);
+    affirm(3 == n);
+    affirm("wπŸ˜ƒrldhell🀩!🀩".equals(sbuf.toString()));
+
+    try( sqlite3_stmt stmt2 = prepare(db, "SELECT ?, ?") ){
+      rc = sqlite3_bind_text(stmt2, 1, "");
+      affirm( 0==rc );
+      rc = sqlite3_bind_text(stmt2, 2, (String)null);
+      affirm( 0==rc );
+      rc = sqlite3_step(stmt2);
+      affirm( SQLITE_ROW==rc );
+      byte[] colBa = sqlite3_column_text(stmt2, 0);
+      affirm( 0==colBa.length );
+      colBa = sqlite3_column_text(stmt2, 1);
+      affirm( null==colBa );
+      //sqlite3_finalize(stmt);
+    }
+
+    if(true){
+      sqlite3_close_v2(db);
+    }else{
+      // Let the Object.finalize() override deal with it.
+    }
+  }
+
+  private void testBindFetchBlob(){
+    sqlite3 db = createNewDb();
+    execSql(db, "CREATE TABLE t(a)");
+    sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+    byte[] list1 = { 0x32, 0x33, 0x34 };
+    int rc = sqlite3_bind_blob(stmt, 1, list1);
+    affirm( 0==rc );
+    rc = sqlite3_step(stmt);
+    affirm(SQLITE_DONE == rc);
+    sqlite3_finalize(stmt);
+    stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+    int n = 0;
+    int total = 0;
+    while( SQLITE_ROW == sqlite3_step(stmt) ){
+      byte[] blob = sqlite3_column_blob(stmt, 0);
+      affirm(3 == blob.length);
+      int i = 0;
+      for(byte b : blob){
+        affirm(b == list1[i++]);
+        total += b;
+      }
+      ++n;
+    }
+    sqlite3_finalize(stmt);
+    affirm(1 == n);
+    affirm(total == 0x32 + 0x33 + 0x34);
+    sqlite3_close_v2(db);
+  }
+
+  private void testSql(){
+    sqlite3 db = createNewDb();
+    sqlite3_stmt stmt = prepare(db, "SELECT 1");
+    affirm( "SELECT 1".equals(sqlite3_sql(stmt)) );
+    sqlite3_finalize(stmt);
+    stmt = prepare(db, "SELECT ?");
+    sqlite3_bind_text(stmt, 1, "hellπŸ˜ƒ");
+    final String expect = "SELECT 'hellπŸ˜ƒ'";
+    affirm( expect.equals(sqlite3_expanded_sql(stmt)) );
+    String n = sqlite3_normalized_sql(stmt);
+    affirm( null==n || "SELECT?;".equals(n) );
+    sqlite3_finalize(stmt);
+    sqlite3_close(db);
+  }
+
+  private void testCollation(){
+    final sqlite3 db = createNewDb();
+    execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+    final ValueHolder<Integer> xDestroyCalled = new ValueHolder<>(0);
+    final CollationCallback myCollation = new CollationCallback() {
+        private String myState =
+          "this is local state. There is much like it, but this is mine.";
+        @Override
+        // Reverse-sorts its inputs...
+        public int call(byte[] lhs, byte[] rhs){
+          int len = lhs.length > rhs.length ? rhs.length : lhs.length;
+          int c = 0, i = 0;
+          for(i = 0; i < len; ++i){
+            c = lhs[i] - rhs[i];
+            if(0 != c) break;
+          }
+          if(0==c){
+            if(i < lhs.length) c = 1;
+            else if(i < rhs.length) c = -1;
+          }
+          return -c;
+        }
+        @Override
+        public void xDestroy() {
+          // Just demonstrates that xDestroy is called.
+          ++xDestroyCalled.value;
+        }
+      };
+    final CollationNeededCallback collLoader = new CollationNeededCallback(){
+        @Override
+        public int call(sqlite3 dbArg, int eTextRep, String collationName){
+          affirm(dbArg == db/* as opposed to a temporary object*/);
+          return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
+        }
+      };
+    int rc = sqlite3_collation_needed(db, collLoader);
+    affirm( 0 == rc );
+    rc = sqlite3_collation_needed(db, collLoader);
+    affirm( 0 == rc /* Installing the same object again is a no-op */);
+    sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi");
+    int counter = 0;
+    while( SQLITE_ROW == sqlite3_step(stmt) ){
+      final String val = sqlite3_column_text16(stmt, 0);
+      ++counter;
+      //outln("REVERSI'd row#"+counter+": "+val);
+      switch(counter){
+        case 1: affirm("c".equals(val)); break;
+        case 2: affirm("b".equals(val)); break;
+        case 3: affirm("a".equals(val)); break;
+      }
+    }
+    affirm(3 == counter);
+    sqlite3_finalize(stmt);
+    stmt = prepare(db, "SELECT a FROM t ORDER BY a");
+    counter = 0;
+    while( SQLITE_ROW == sqlite3_step(stmt) ){
+      final String val = sqlite3_column_text16(stmt, 0);
+      ++counter;
+      //outln("Non-REVERSI'd row#"+counter+": "+val);
+      switch(counter){
+        case 3: affirm("c".equals(val)); break;
+        case 2: affirm("b".equals(val)); break;
+        case 1: affirm("a".equals(val)); break;
+      }
+    }
+    affirm(3 == counter);
+    sqlite3_finalize(stmt);
+    affirm( 0 == xDestroyCalled.value );
+    rc = sqlite3_collation_needed(db, null);
+    affirm( 0 == rc );
+    sqlite3_close_v2(db);
+    affirm( 0 == db.getNativePointer() );
+    affirm( 1 == xDestroyCalled.value );
+  }
+
+  @SingleThreadOnly /* because it's thread-agnostic */
+  private void testToUtf8(){
+    /**
+       https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
+
+       Let's ensure that we can convert to standard UTF-8 in Java code
+       (noting that the JNI native API has no way to do this).
+    */
+    final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8);
+    affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */);
+  }
+
+  private void testStatus(){
+    final OutputPointer.Int64 cur64 = new OutputPointer.Int64();
+    final OutputPointer.Int64 high64 = new OutputPointer.Int64();
+    final OutputPointer.Int32 cur32 = new OutputPointer.Int32();
+    final OutputPointer.Int32 high32 = new OutputPointer.Int32();
+    final sqlite3 db = createNewDb();
+    execSql(db, "create table t(a); insert into t values(1),(2),(3)");
+
+    int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false);
+    affirm( 0 == rc );
+    affirm( cur32.value > 0 );
+    affirm( high32.value >= cur32.value );
+
+    rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false);
+    affirm( 0 == rc );
+    affirm( cur64.value > 0 );
+    affirm( high64.value >= cur64.value );
+
+    cur32.value = 0;
+    high32.value = 1;
+    rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false);
+    affirm( 0 == rc );
+    affirm( cur32.value > 0 );
+    affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ );
+
+    sqlite3_close_v2(db);
+  }
+
+  private void testUdf1(){
+    final sqlite3 db = createNewDb();
+    // These ValueHolders are just to confirm that the func did what we want...
+    final ValueHolder<Boolean> xDestroyCalled = new ValueHolder<>(false);
+    final ValueHolder<Integer> xFuncAccum = new ValueHolder<>(0);
+    final ValueHolder<sqlite3_value[]> neverEverDoThisInClientCode = new ValueHolder<>(null);
+    final ValueHolder<sqlite3_context> neverEverDoThisInClientCode2 = new ValueHolder<>(null);
+
+    // Create an SQLFunction instance using one of its 3 subclasses:
+    // Scalar, Aggregate, or Window:
+    SQLFunction func =
+      // Each of the 3 subclasses requires a different set of
+      // functions, all of which must be implemented.  Anonymous
+      // classes are a convenient way to implement these.
+      new ScalarFunction(){
+        public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+          affirm(db == sqlite3_context_db_handle(cx));
+          if( null==neverEverDoThisInClientCode.value ){
+            /* !!!NEVER!!! hold a reference to an sqlite3_value or
+               sqlite3_context object like this in client code! They
+               are ONLY legal for the duration of their single
+               call. We do it here ONLY to test that the defenses
+               against clients doing this are working. */
+            neverEverDoThisInClientCode2.value = cx;
+            neverEverDoThisInClientCode.value = args;
+          }
+          int result = 0;
+          for( sqlite3_value v : args ) result += sqlite3_value_int(v);
+          xFuncAccum.value += result;// just for post-run testing
+          sqlite3_result_int(cx, result);
+        }
+        /* OPTIONALLY override xDestroy... */
+        public void xDestroy(){
+          xDestroyCalled.value = true;
+        }
+      };
+
+    // Register and use the function...
+    int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
+    affirm(0 == rc);
+    affirm(0 == xFuncAccum.value);
+    final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)");
+    int n = 0;
+    while( SQLITE_ROW == sqlite3_step(stmt) ){
+      affirm( 6 == sqlite3_column_int(stmt, 0) );
+      ++n;
+    }
+    sqlite3_finalize(stmt);
+    affirm(1 == n);
+    affirm(6 == xFuncAccum.value);
+    affirm( !xDestroyCalled.value );
+    affirm( null!=neverEverDoThisInClientCode.value );
+    affirm( null!=neverEverDoThisInClientCode2.value );
+    affirm( 0<neverEverDoThisInClientCode.value.length );
+    affirm( 0==neverEverDoThisInClientCode2.value.getNativePointer() );
+    for( sqlite3_value sv : neverEverDoThisInClientCode.value ){
+      affirm( 0==sv.getNativePointer() );
+    }
+    sqlite3_close_v2(db);
+    affirm( xDestroyCalled.value );
+  }
+
+  private void testUdfThrows(){
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Integer> xFuncAccum = new ValueHolder<>(0);
+
+    SQLFunction funcAgg = new AggregateFunction<Integer>(){
+        @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
+          /** Throwing from here should emit loud noise on stdout or stderr
+              but the exception is supressed because we have no way to inform
+              sqlite about it from these callbacks. */
+          //throw new RuntimeException("Throwing from an xStep");
+        }
+        @Override public void xFinal(sqlite3_context cx){
+          throw new RuntimeException("Throwing from an xFinal");
+        }
+      };
+    int rc = sqlite3_create_function(db, "myagg", 1, SQLITE_UTF8, funcAgg);
+    affirm(0 == rc);
+    affirm(0 == xFuncAccum.value);
+    sqlite3_stmt stmt = prepare(db, "SELECT myagg(1)");
+    rc = sqlite3_step(stmt);
+    sqlite3_finalize(stmt);
+    affirm( 0 != rc );
+    affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 );
+
+    SQLFunction funcSc = new ScalarFunction(){
+        @Override public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+          throw new RuntimeException("Throwing from an xFunc");
+        }
+      };
+    rc = sqlite3_create_function(db, "mysca", 0, SQLITE_UTF8, funcSc);
+    affirm(0 == rc);
+    affirm(0 == xFuncAccum.value);
+    stmt = prepare(db, "SELECT mysca()");
+    rc = sqlite3_step(stmt);
+    sqlite3_finalize(stmt);
+    affirm( 0 != rc );
+    affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 );
+    rc = sqlite3_create_function(db, "mysca", 1, -1, funcSc);
+    affirm( SQLITE_FORMAT==rc, "invalid encoding value." );
+    sqlite3_close_v2(db);
+  }
+
+  @SingleThreadOnly
+  private void testUdfJavaObject(){
+    affirm( !mtMode );
+    final sqlite3 db = createNewDb();
+    final ValueHolder<sqlite3> testResult = new ValueHolder<>(db);
+    final ValueHolder<Integer> boundObj = new ValueHolder<>(42);
+    final SQLFunction func = new ScalarFunction(){
+        public void xFunc(sqlite3_context cx, sqlite3_value args[]){
+          sqlite3_result_java_object(cx, testResult.value);
+          affirm( sqlite3_value_java_object(args[0]) == boundObj );
+        }
+      };
+    int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
+    affirm(0 == rc);
+    sqlite3_stmt stmt = prepare(db, "select myfunc(?)");
+    affirm( 0 != stmt.getNativePointer() );
+    affirm( testResult.value == db );
+    rc = sqlite3_bind_java_object(stmt, 1, boundObj);
+    affirm( 0==rc );
+    int n = 0;
+    if( SQLITE_ROW == sqlite3_step(stmt) ){
+      final sqlite3_value v = sqlite3_column_value(stmt, 0);
+      affirm( testResult.value == sqlite3_value_java_object(v) );
+      affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) );
+      affirm( testResult.value ==
+              sqlite3_value_java_casted(v, testResult.value.getClass()) );
+      affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) );
+      affirm( null == sqlite3_value_java_casted(v, String.class) );
+      ++n;
+    }
+    sqlite3_finalize(stmt);
+    affirm( 1 == n );
+    affirm( 0==sqlite3_db_release_memory(db) );
+    sqlite3_close_v2(db);
+  }
+
+  private void testUdfAggregate(){
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Boolean> xFinalNull =
+      // To confirm that xFinal() is called with no aggregate state
+      // when the corresponding result set is empty.
+      new ValueHolder<>(false);
+    SQLFunction func = new AggregateFunction<Integer>(){
+        @Override
+        public void xStep(sqlite3_context cx, sqlite3_value[] args){
+          final ValueHolder<Integer> agg = this.getAggregateState(cx, 0);
+          agg.value += sqlite3_value_int(args[0]);
+          affirm( agg == this.getAggregateState(cx, 0) );
+        }
+        @Override
+        public void xFinal(sqlite3_context cx){
+          final Integer v = this.takeAggregateState(cx);
+          if(null == v){
+            xFinalNull.value = true;
+            sqlite3_result_null(cx);
+          }else{
+            sqlite3_result_int(cx, v);
+          }
+        }
+      };
+    execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)");
+    int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func);
+    affirm(0 == rc);
+    sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t");
+    affirm( 0==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
+    int n = 0;
+    if( SQLITE_ROW == sqlite3_step(stmt) ){
+      int v = sqlite3_column_int(stmt, 0);
+      affirm( 6 == v );
+      int v2 = sqlite3_column_int(stmt, 1);
+      affirm( 30+v == v2 );
+      ++n;
+    }
+    affirm( 1==n );
+    affirm(!xFinalNull.value);
+    sqlite3_reset(stmt);
+    affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
+    // Ensure that the accumulator is reset on subsequent calls...
+    n = 0;
+    if( SQLITE_ROW == sqlite3_step(stmt) ){
+      final int v = sqlite3_column_int(stmt, 0);
+      affirm( 6 == v );
+      ++n;
+    }
+    sqlite3_finalize(stmt);
+    affirm( 1==n );
+
+    stmt = prepare(db, "select myfunc(a), myfunc(a+a) from t order by a");
+    n = 0;
+    while( SQLITE_ROW == sqlite3_step(stmt) ){
+      final int c0 = sqlite3_column_int(stmt, 0);
+      final int c1 = sqlite3_column_int(stmt, 1);
+      ++n;
+      affirm( 6 == c0 );
+      affirm( 12 == c1 );
+    }
+    sqlite3_finalize(stmt);
+    affirm( 1 == n );
+    affirm(!xFinalNull.value);
+
+    execSql(db, "SELECT myfunc(1) WHERE 0");
+    affirm(xFinalNull.value);
+    sqlite3_close_v2(db);
+  }
+
+  private void testUdfWindow(){
+    final sqlite3 db = createNewDb();
+    /* Example window function, table, and results taken from:
+       https://sqlite.org/windowfunctions.html#udfwinfunc */
+    final SQLFunction func = new WindowFunction<Integer>(){
+
+        private void xStepInverse(sqlite3_context cx, int v){
+          this.getAggregateState(cx,0).value += v;
+        }
+        @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
+          this.xStepInverse(cx, sqlite3_value_int(args[0]));
+        }
+        @Override public void xInverse(sqlite3_context cx, sqlite3_value[] args){
+          this.xStepInverse(cx, -sqlite3_value_int(args[0]));
+        }
+
+        private void xFinalValue(sqlite3_context cx, Integer v){
+          if(null == v) sqlite3_result_null(cx);
+          else sqlite3_result_int(cx, v);
+        }
+        @Override public void xFinal(sqlite3_context cx){
+          xFinalValue(cx, this.takeAggregateState(cx));
+        }
+        @Override public void xValue(sqlite3_context cx){
+          xFinalValue(cx, this.getAggregateState(cx,null).value);
+        }
+      };
+    int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func);
+    affirm( 0 == rc );
+    execSql(db, new String[] {
+        "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
+        "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
+      });
+    final sqlite3_stmt stmt = prepare(db,
+                         "SELECT x, winsumint(y) OVER ("+
+                         "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+
+                         ") AS sum_y "+
+                         "FROM twin ORDER BY x;");
+    affirm( 0 == rc );
+    int n = 0;
+    while( SQLITE_ROW == sqlite3_step(stmt) ){
+      final String s = sqlite3_column_text16(stmt, 0);
+      final int i = sqlite3_column_int(stmt, 1);
+      switch(++n){
+        case 1: affirm( "a".equals(s) && 9==i ); break;
+        case 2: affirm( "b".equals(s) && 12==i ); break;
+        case 3: affirm( "c".equals(s) && 16==i ); break;
+        case 4: affirm( "d".equals(s) && 12==i ); break;
+        case 5: affirm( "e".equals(s) && 9==i ); break;
+        default: affirm( false /* cannot happen */ );
+      }
+    }
+    sqlite3_finalize(stmt);
+    affirm( 5 == n );
+    sqlite3_close_v2(db);
+  }
+
+  private void listBoundMethods(){
+    if(false){
+      final java.lang.reflect.Field[] declaredFields =
+        CApi.class.getDeclaredFields();
+      outln("Bound constants:\n");
+      for(java.lang.reflect.Field field : declaredFields) {
+        if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
+          outln("\t",field.getName());
+        }
+      }
+    }
+    final java.lang.reflect.Method[] declaredMethods =
+      CApi.class.getDeclaredMethods();
+    final java.util.List<String> funcList = new java.util.ArrayList<>();
+    for(java.lang.reflect.Method m : declaredMethods){
+      if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0){
+        final String name = m.getName();
+        if(name.startsWith("sqlite3_")){
+          funcList.add(name);
+        }
+      }
+    }
+    int count = 0;
+    java.util.Collections.sort(funcList);
+    for(String n : funcList){
+      ++count;
+      outln("\t",n,"()");
+    }
+    outln(count," functions named sqlite3_*.");
+  }
+
+  private void testTrace(){
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    /* Ensure that characters outside of the UTF BMP survive the trip
+       from Java to sqlite3 and back to Java. (At no small efficiency
+       penalty.) */
+    final String nonBmpChar = "πŸ˜ƒ";
+    int rc = sqlite3_trace_v2(
+      db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE
+          | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE,
+      new TraceV2Callback(){
+        @Override public int call(int traceFlag, Object pNative, Object x){
+          ++counter.value;
+          //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName());
+          switch(traceFlag){
+            case SQLITE_TRACE_STMT:
+              affirm(pNative instanceof sqlite3_stmt);
+              //outln("TRACE_STMT sql = "+x);
+              affirm(x instanceof String);
+              affirm( ((String)x).indexOf(nonBmpChar) > 0 );
+              break;
+            case SQLITE_TRACE_PROFILE:
+              affirm(pNative instanceof sqlite3_stmt);
+              affirm(x instanceof Long);
+              //outln("TRACE_PROFILE time = "+x);
+              break;
+            case SQLITE_TRACE_ROW:
+              affirm(pNative instanceof sqlite3_stmt);
+              affirm(null == x);
+              //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0));
+              break;
+            case SQLITE_TRACE_CLOSE:
+              affirm(pNative instanceof sqlite3);
+              affirm(null == x);
+              break;
+            default:
+              affirm(false /*cannot happen*/);
+              break;
+          }
+          return 0;
+        }
+      });
+    affirm( 0==rc );
+    execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+
+            "SELECT 'w"+nonBmpChar+"orld'");
+    affirm( 6 == counter.value );
+    sqlite3_close_v2(db);
+    affirm( 7 == counter.value );
+  }
+
+  @SingleThreadOnly /* because threads inherently break this test */
+  private static void testBusy(){
+    final String dbName = "_busy-handler.db";
+    final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
+    final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+
+    int rc = sqlite3_open(dbName, outDb);
+    ++metrics.dbOpen;
+    affirm( 0 == rc );
+    final sqlite3 db1 = outDb.get();
+    execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
+    rc = sqlite3_open(dbName, outDb);
+    ++metrics.dbOpen;
+    affirm( 0 == rc );
+    affirm( outDb.get() != db1 );
+    final sqlite3 db2 = outDb.get();
+
+    affirm( "main".equals( sqlite3_db_name(db1, 0) ) );
+    rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo");
+    affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) );
+    affirm( "foo".equals( sqlite3_db_name(db1, 0) ) );
+
+    final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
+    BusyHandlerCallback handler = new BusyHandlerCallback(){
+        @Override public int call(int n){
+          //outln("busy handler #"+n);
+          return n > 2 ? 0 : ++xBusyCalled.value;
+        }
+      };
+    rc = sqlite3_busy_handler(db2, handler);
+    affirm(0 == rc);
+
+    // Force a locked condition...
+    execSql(db1, "BEGIN EXCLUSIVE");
+    rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt);
+    affirm( SQLITE_BUSY == rc);
+    affirm( null == outStmt.get() );
+    affirm( 3 == xBusyCalled.value );
+    sqlite3_close_v2(db1);
+    sqlite3_close_v2(db2);
+    try{
+      final java.io.File f = new java.io.File(dbName);
+      f.delete();
+    }catch(Exception e){
+      /* ignore */
+    }
+  }
+
+  private void testProgress(){
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    sqlite3_progress_handler(db, 1, new ProgressHandlerCallback(){
+        @Override public int call(){
+          ++counter.value;
+          return 0;
+        }
+      });
+    execSql(db, "SELECT 1; SELECT 2;");
+    affirm( counter.value > 0 );
+    int nOld = counter.value;
+    sqlite3_progress_handler(db, 0, null);
+    execSql(db, "SELECT 1; SELECT 2;");
+    affirm( nOld == counter.value );
+    sqlite3_close_v2(db);
+  }
+
+  private void testCommitHook(){
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    final ValueHolder<Integer> hookResult = new ValueHolder<>(0);
+    final CommitHookCallback theHook = new CommitHookCallback(){
+        @Override public int call(){
+          ++counter.value;
+          return hookResult.value;
+        }
+      };
+    CommitHookCallback oldHook = sqlite3_commit_hook(db, theHook);
+    affirm( null == oldHook );
+    execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+    affirm( 2 == counter.value );
+    execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;");
+    affirm( 2 == counter.value /* NOT invoked if no changes are made */ );
+    execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;");
+    affirm( 3 == counter.value );
+    oldHook = sqlite3_commit_hook(db, theHook);
+    affirm( theHook == oldHook );
+    execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;");
+    affirm( 4 == counter.value );
+    oldHook = sqlite3_commit_hook(db, null);
+    affirm( theHook == oldHook );
+    execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;");
+    affirm( 4 == counter.value );
+    oldHook = sqlite3_commit_hook(db, null);
+    affirm( null == oldHook );
+    execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;");
+    affirm( 4 == counter.value );
+
+    final CommitHookCallback newHook = new CommitHookCallback(){
+        @Override public int call(){return 0;}
+      };
+    oldHook = sqlite3_commit_hook(db, newHook);
+    affirm( null == oldHook );
+    execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;");
+    affirm( 4 == counter.value );
+    oldHook = sqlite3_commit_hook(db, theHook);
+    affirm( newHook == oldHook );
+    execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;");
+    affirm( 5 == counter.value );
+    hookResult.value = SQLITE_ERROR;
+    int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
+    affirm( SQLITE_CONSTRAINT == rc );
+    affirm( 6 == counter.value );
+    sqlite3_close_v2(db);
+  }
+
+  private void testUpdateHook(){
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    final ValueHolder<Integer> expectedOp = new ValueHolder<>(0);
+    final UpdateHookCallback theHook = new UpdateHookCallback(){
+        @Override
+        public void call(int opId, String dbName, String tableName, long rowId){
+          ++counter.value;
+          if( 0!=expectedOp.value ){
+            affirm( expectedOp.value == opId );
+          }
+        }
+      };
+    UpdateHookCallback oldHook = sqlite3_update_hook(db, theHook);
+    affirm( null == oldHook );
+    expectedOp.value = SQLITE_INSERT;
+    execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+    affirm( 3 == counter.value );
+    expectedOp.value = SQLITE_UPDATE;
+    execSql(db, "update t set a='d' where a='c';");
+    affirm( 4 == counter.value );
+    oldHook = sqlite3_update_hook(db, theHook);
+    affirm( theHook == oldHook );
+    expectedOp.value = SQLITE_DELETE;
+    execSql(db, "DELETE FROM t where a='d'");
+    affirm( 5 == counter.value );
+    oldHook = sqlite3_update_hook(db, null);
+    affirm( theHook == oldHook );
+    execSql(db, "update t set a='e' where a='b';");
+    affirm( 5 == counter.value );
+    oldHook = sqlite3_update_hook(db, null);
+    affirm( null == oldHook );
+
+    final UpdateHookCallback newHook = new UpdateHookCallback(){
+        @Override public void call(int opId, String dbName, String tableName, long rowId){
+        }
+      };
+    oldHook = sqlite3_update_hook(db, newHook);
+    affirm( null == oldHook );
+    execSql(db, "update t set a='h' where a='a'");
+    affirm( 5 == counter.value );
+    oldHook = sqlite3_update_hook(db, theHook);
+    affirm( newHook == oldHook );
+    expectedOp.value = SQLITE_UPDATE;
+    execSql(db, "update t set a='i' where a='h'");
+    affirm( 6 == counter.value );
+    sqlite3_close_v2(db);
+  }
+
+  /**
+     This test is functionally identical to testUpdateHook(), only with a
+     different callback type.
+  */
+  private void testPreUpdateHook(){
+    if( !sqlite3_compileoption_used("ENABLE_PREUPDATE_HOOK") ){
+      //outln("Skipping testPreUpdateHook(): no pre-update hook support.");
+      return;
+    }
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    final ValueHolder<Integer> expectedOp = new ValueHolder<>(0);
+    final PreupdateHookCallback theHook = new PreupdateHookCallback(){
+        @Override
+        public void call(sqlite3 db, int opId, String dbName, String dbTable,
+                         long iKey1, long iKey2 ){
+          ++counter.value;
+          switch( opId ){
+            case SQLITE_UPDATE:
+              affirm( 0 < sqlite3_preupdate_count(db) );
+              affirm( null != sqlite3_preupdate_new(db, 0) );
+              affirm( null != sqlite3_preupdate_old(db, 0) );
+              break;
+            case SQLITE_INSERT:
+              affirm( null != sqlite3_preupdate_new(db, 0) );
+              break;
+            case SQLITE_DELETE:
+              affirm( null != sqlite3_preupdate_old(db, 0) );
+              break;
+            default:
+              break;
+          }
+          if( 0!=expectedOp.value ){
+            affirm( expectedOp.value == opId );
+          }
+        }
+      };
+    PreupdateHookCallback oldHook = sqlite3_preupdate_hook(db, theHook);
+    affirm( null == oldHook );
+    expectedOp.value = SQLITE_INSERT;
+    execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+    affirm( 3 == counter.value );
+    expectedOp.value = SQLITE_UPDATE;
+    execSql(db, "update t set a='d' where a='c';");
+    affirm( 4 == counter.value );
+    oldHook = sqlite3_preupdate_hook(db, theHook);
+    affirm( theHook == oldHook );
+    expectedOp.value = SQLITE_DELETE;
+    execSql(db, "DELETE FROM t where a='d'");
+    affirm( 5 == counter.value );
+    oldHook = sqlite3_preupdate_hook(db, null);
+    affirm( theHook == oldHook );
+    execSql(db, "update t set a='e' where a='b';");
+    affirm( 5 == counter.value );
+    oldHook = sqlite3_preupdate_hook(db, null);
+    affirm( null == oldHook );
+
+    final PreupdateHookCallback newHook = new PreupdateHookCallback(){
+        @Override
+        public void call(sqlite3 db, int opId, String dbName,
+                         String tableName, long iKey1, long iKey2){
+        }
+      };
+    oldHook = sqlite3_preupdate_hook(db, newHook);
+    affirm( null == oldHook );
+    execSql(db, "update t set a='h' where a='a'");
+    affirm( 5 == counter.value );
+    oldHook = sqlite3_preupdate_hook(db, theHook);
+    affirm( newHook == oldHook );
+    expectedOp.value = SQLITE_UPDATE;
+    execSql(db, "update t set a='i' where a='h'");
+    affirm( 6 == counter.value );
+
+    sqlite3_close_v2(db);
+  }
+
+  private void testRollbackHook(){
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    final RollbackHookCallback theHook = new RollbackHookCallback(){
+        @Override public void call(){
+          ++counter.value;
+        }
+      };
+    RollbackHookCallback oldHook = sqlite3_rollback_hook(db, theHook);
+    affirm( null == oldHook );
+    execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+    affirm( 0 == counter.value );
+    execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;");
+    affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ );
+
+    final RollbackHookCallback newHook = new RollbackHookCallback(){
+        @Override public void call(){return;}
+      };
+    oldHook = sqlite3_rollback_hook(db, newHook);
+    affirm( theHook == oldHook );
+    execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+    affirm( 1 == counter.value );
+    oldHook = sqlite3_rollback_hook(db, theHook);
+    affirm( newHook == oldHook );
+    execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+    affirm( 2 == counter.value );
+    int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+    affirm( 0 == rc );
+    affirm( 3 == counter.value );
+    sqlite3_close_v2(db);
+  }
+
+  /**
+     If FTS5 is available, runs FTS5 tests, else returns with no side
+     effects. If it is available but loading of the FTS5 bits fails,
+     it throws.
+  */
+  @SuppressWarnings("unchecked")
+  @SingleThreadOnly /* because the Fts5 parts are not yet known to be
+                       thread-safe */
+  private void testFts5() throws Exception {
+    if( !sqlite3_compileoption_used("ENABLE_FTS5") ){
+      //outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests.");
+      return;
+    }
+    Exception err = null;
+    try {
+      Class t = Class.forName("org.sqlite.jni.fts5.TesterFts5");
+      java.lang.reflect.Constructor ctor = t.getConstructor();
+      ctor.setAccessible(true);
+      final long timeStart = System.currentTimeMillis();
+      ctor.newInstance() /* will run all tests */;
+      final long timeEnd = System.currentTimeMillis();
+      outln("FTS5 Tests done in ",(timeEnd - timeStart),"ms");
+    }catch(ClassNotFoundException e){
+      outln("FTS5 classes not loaded.");
+      err = e;
+    }catch(NoSuchMethodException e){
+      outln("FTS5 tester ctor not found.");
+      err = e;
+    }catch(Exception e){
+      outln("Instantiation of FTS5 tester threw.");
+      err = e;
+    }
+    if( null != err ){
+      outln("Exception: "+err);
+      err.printStackTrace();
+      throw err;
+    }
+  }
+
+  private void testAuthorizer(){
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    final ValueHolder<Integer> authRc = new ValueHolder<>(0);
+    final AuthorizerCallback auth = new AuthorizerCallback(){
+        public int call(int op, String s0, String s1, String s2, String s3){
+          ++counter.value;
+          //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3);
+          return authRc.value;
+        }
+      };
+    execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+    sqlite3_set_authorizer(db, auth);
+    execSql(db, "UPDATE t SET a=1");
+    affirm( 1 == counter.value );
+    authRc.value = SQLITE_DENY;
+    int rc = execSql(db, false, "UPDATE t SET a=2");
+    affirm( SQLITE_AUTH==rc );
+    // TODO: expand these tests considerably
+    sqlite3_close(db);
+  }
+
+  @SingleThreadOnly /* because multiple threads legitimately make these
+                       results unpredictable */
+  private synchronized void testAutoExtension(){
+    final ValueHolder<Integer> val = new ValueHolder<>(0);
+    final ValueHolder<String> toss = new ValueHolder<>(null);
+    final AutoExtensionCallback ax = new AutoExtensionCallback(){
+        @Override public int call(sqlite3 db){
+          ++val.value;
+          if( null!=toss.value ){
+            throw new RuntimeException(toss.value);
+          }
+          return 0;
+        }
+      };
+    int rc = sqlite3_auto_extension( ax );
+    affirm( 0==rc );
+    sqlite3_close(createNewDb());
+    affirm( 1==val.value );
+    sqlite3_close(createNewDb());
+    affirm( 2==val.value );
+    sqlite3_reset_auto_extension();
+    sqlite3_close(createNewDb());
+    affirm( 2==val.value );
+    rc = sqlite3_auto_extension( ax );
+    affirm( 0==rc );
+    // Must not add a new entry
+    rc = sqlite3_auto_extension( ax );
+    affirm( 0==rc );
+    sqlite3_close( createNewDb() );
+    affirm( 3==val.value );
+
+    sqlite3 db = createNewDb();
+    affirm( 4==val.value );
+    execSql(db, "ATTACH ':memory:' as foo");
+    affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
+    sqlite3_close(db);
+    db = null;
+
+    affirm( sqlite3_cancel_auto_extension(ax) );
+    affirm( !sqlite3_cancel_auto_extension(ax) );
+    sqlite3_close(createNewDb());
+    affirm( 4==val.value );
+    rc = sqlite3_auto_extension( ax );
+    affirm( 0==rc );
+    Exception err = null;
+    toss.value = "Throwing from auto_extension.";
+    try{
+      sqlite3_close(createNewDb());
+    }catch(Exception e){
+      err = e;
+    }
+    affirm( err!=null );
+    affirm( err.getMessage().indexOf(toss.value)>0 );
+    toss.value = null;
+
+    val.value = 0;
+    final AutoExtensionCallback ax2 = new AutoExtensionCallback(){
+        @Override public synchronized int call(sqlite3 db){
+          ++val.value;
+          return 0;
+        }
+      };
+    rc = sqlite3_auto_extension( ax2 );
+    affirm( 0 == rc );
+    sqlite3_close(createNewDb());
+    affirm( 2 == val.value );
+    affirm( sqlite3_cancel_auto_extension(ax) );
+    affirm( !sqlite3_cancel_auto_extension(ax) );
+    sqlite3_close(createNewDb());
+    affirm( 3 == val.value );
+    rc = sqlite3_auto_extension( ax );
+    affirm( 0 == rc );
+    sqlite3_close(createNewDb());
+    affirm( 5 == val.value );
+    affirm( sqlite3_cancel_auto_extension(ax2) );
+    affirm( !sqlite3_cancel_auto_extension(ax2) );
+    sqlite3_close(createNewDb());
+    affirm( 6 == val.value );
+    rc = sqlite3_auto_extension( ax2 );
+    affirm( 0 == rc );
+    sqlite3_close(createNewDb());
+    affirm( 8 == val.value );
+
+    sqlite3_reset_auto_extension();
+    sqlite3_close(createNewDb());
+    affirm( 8 == val.value );
+    affirm( !sqlite3_cancel_auto_extension(ax) );
+    affirm( !sqlite3_cancel_auto_extension(ax2) );
+    sqlite3_close(createNewDb());
+    affirm( 8 == val.value );
+  }
+
+
+  private void testColumnMetadata(){
+    final sqlite3 db = createNewDb();
+    execSql(db, new String[] {
+        "CREATE TABLE t(a duck primary key not null collate noCase); ",
+        "INSERT INTO t(a) VALUES(1),(2),(3);"
+      });
+    OutputPointer.Bool bNotNull = new OutputPointer.Bool();
+    OutputPointer.Bool bPrimaryKey = new OutputPointer.Bool();
+    OutputPointer.Bool bAutoinc = new OutputPointer.Bool();
+    OutputPointer.String zCollSeq = new OutputPointer.String();
+    OutputPointer.String zDataType = new OutputPointer.String();
+    int rc = sqlite3_table_column_metadata(
+      db, "main", "t", "a", zDataType, zCollSeq,
+      bNotNull, bPrimaryKey, bAutoinc);
+    affirm( 0==rc );
+    affirm( bPrimaryKey.value );
+    affirm( !bAutoinc.value );
+    affirm( bNotNull.value );
+    affirm( "noCase".equals(zCollSeq.value) );
+    affirm( "duck".equals(zDataType.value) );
+
+    TableColumnMetadata m =
+      sqlite3_table_column_metadata(db, "main", "t", "a");
+    affirm( null != m );
+    affirm( bPrimaryKey.value == m.isPrimaryKey() );
+    affirm( bAutoinc.value == m.isAutoincrement() );
+    affirm( bNotNull.value == m.isNotNull() );
+    affirm( zCollSeq.value.equals(m.getCollation()) );
+    affirm( zDataType.value.equals(m.getDataType()) );
+
+    affirm( null == sqlite3_table_column_metadata(db, "nope", "t", "a") );
+    affirm( null == sqlite3_table_column_metadata(db, "main", "nope", "a") );
+
+    m = sqlite3_table_column_metadata(db, "main", "t", null)
+      /* Check only for existence of table */;
+    affirm( null != m );
+    affirm( m.isPrimaryKey() );
+    affirm( !m.isAutoincrement() );
+    affirm( !m.isNotNull() );
+    affirm( "BINARY".equalsIgnoreCase(m.getCollation()) );
+    affirm( "INTEGER".equalsIgnoreCase(m.getDataType()) );
+
+    sqlite3_close_v2(db);
+  }
+
+  private void testTxnState(){
+    final sqlite3 db = createNewDb();
+    affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+    affirm( sqlite3_get_autocommit(db) );
+    execSql(db, "BEGIN;");
+    affirm( !sqlite3_get_autocommit(db) );
+    affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+    execSql(db, "SELECT * FROM sqlite_schema;");
+    affirm( SQLITE_TXN_READ == sqlite3_txn_state(db, "main") );
+    execSql(db, "CREATE TABLE t(a);");
+    affirm( SQLITE_TXN_WRITE ==  sqlite3_txn_state(db, null) );
+    execSql(db, "ROLLBACK;");
+    affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+    sqlite3_close_v2(db);
+  }
+
+
+  private void testExplain(){
+    final sqlite3 db = createNewDb();
+    sqlite3_stmt stmt = prepare(db,"SELECT 1");
+
+    affirm( 0 == sqlite3_stmt_isexplain(stmt) );
+    int rc = sqlite3_stmt_explain(stmt, 1);
+    affirm( 1 == sqlite3_stmt_isexplain(stmt) );
+    rc = sqlite3_stmt_explain(stmt, 2);
+    affirm( 2 == sqlite3_stmt_isexplain(stmt) );
+    sqlite3_finalize(stmt);
+    sqlite3_close_v2(db);
+  }
+
+  private void testLimit(){
+    final sqlite3 db = createNewDb();
+    int v;
+
+    v = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1);
+    affirm( v > 0 );
+    affirm( v == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, v-1) );
+    affirm( v-1 == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1) );
+    sqlite3_close_v2(db);
+  }
+
+  private void testComplete(){
+    affirm( 0==sqlite3_complete("select 1") );
+    affirm( 0!=sqlite3_complete("select 1;") );
+    affirm( 0!=sqlite3_complete("nope 'nope' 'nope' 1;"), "Yup" );
+  }
+
+  private void testKeyword(){
+    final int n = sqlite3_keyword_count();
+    affirm( n>0 );
+    affirm( !sqlite3_keyword_check("_nope_") );
+    affirm( sqlite3_keyword_check("seLect") );
+    affirm( null!=sqlite3_keyword_name(0) );
+    affirm( null!=sqlite3_keyword_name(n-1) );
+    affirm( null==sqlite3_keyword_name(n) );
+  }
+
+  private void testBackup(){
+    final sqlite3 dbDest = createNewDb();
+
+    try (sqlite3 dbSrc = createNewDb()) {
+      execSql(dbSrc, new String[]{
+          "pragma page_size=512; VACUUM;",
+          "create table t(a);",
+          "insert into t(a) values(1),(2),(3);"
+        });
+      affirm( null==sqlite3_backup_init(dbSrc,"main",dbSrc,"main") );
+      try (sqlite3_backup b = sqlite3_backup_init(dbDest,"main",dbSrc,"main")) {
+        affirm( null!=b );
+        affirm( b.getNativePointer()!=0 );
+        int rc;
+        while( SQLITE_DONE!=(rc = sqlite3_backup_step(b, 1)) ){
+          affirm( 0==rc );
+        }
+        affirm( sqlite3_backup_pagecount(b) > 0 );
+        rc = sqlite3_backup_finish(b);
+        affirm( 0==rc );
+        affirm( b.getNativePointer()==0 );
+      }
+    }
+
+    try (sqlite3_stmt stmt = prepare(dbDest,"SELECT sum(a) from t")) {
+      sqlite3_step(stmt);
+      affirm( sqlite3_column_int(stmt,0) == 6 );
+    }
+    sqlite3_close_v2(dbDest);
+  }
+
+  private void testRandomness(){
+    byte[] foo = new byte[20];
+    int i = 0;
+    for( byte b : foo ){
+      i += b;
+    }
+    affirm( i==0 );
+    sqlite3_randomness(foo);
+    for( byte b : foo ){
+      if(b!=0) ++i;
+    }
+    affirm( i!=0, "There's a very slight chance that 0 is actually correct." );
+  }
+
+  private void testBlobOpen(){
+    final sqlite3 db = createNewDb();
+
+    execSql(db, "CREATE TABLE T(a BLOB);"
+            +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');"
+    );
+    final OutputPointer.sqlite3_blob pOut = new OutputPointer.sqlite3_blob();
+    int rc = sqlite3_blob_open(db, "main", "t", "a",
+                               sqlite3_last_insert_rowid(db), 1, pOut);
+    affirm( 0==rc );
+    sqlite3_blob b = pOut.take();
+    affirm( null!=b );
+    affirm( 0!=b.getNativePointer() );
+    affirm( 3==sqlite3_blob_bytes(b) );
+    rc = sqlite3_blob_write( b, new byte[] {100, 101, 102 /*"DEF"*/}, 0);
+    affirm( 0==rc );
+    rc = sqlite3_blob_close(b);
+    affirm( 0==rc );
+    rc = sqlite3_blob_close(b);
+    affirm( 0!=rc );
+    affirm( 0==b.getNativePointer() );
+    sqlite3_stmt stmt = prepare(db,"SELECT length(a), a FROM t ORDER BY a");
+    affirm( SQLITE_ROW == sqlite3_step(stmt) );
+    affirm( 3 == sqlite3_column_int(stmt,0) );
+    affirm( "def".equals(sqlite3_column_text16(stmt,1)) );
+    sqlite3_finalize(stmt);
+
+    b = sqlite3_blob_open(db, "main", "t", "a",
+                          sqlite3_last_insert_rowid(db), 1);
+    affirm( null!=b );
+    rc = sqlite3_blob_reopen(b, 2);
+    affirm( 0==rc );
+    final byte[] tgt = new byte[3];
+    rc = sqlite3_blob_read(b, tgt, 0);
+    affirm( 0==rc );
+    affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
+    rc = sqlite3_blob_close(b);
+    affirm( 0==rc );
+    sqlite3_close_v2(db);
+  }
+
+  private void testPrepareMulti(){
+    final sqlite3 db = createNewDb();
+    final String[] sql = {
+      "create table t(","a)",
+      "; insert into t(a) values(1),(2),(3);",
+      "select a from t;"
+    };
+    final List<sqlite3_stmt> liStmt = new ArrayList<sqlite3_stmt>();
+    final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
+    PrepareMultiCallback m = new PrepareMultiCallback() {
+        @Override public int call(sqlite3_stmt st){
+          liStmt.add(st);
+          return proxy.call(st);
+        }
+      };
+    int rc = sqlite3_prepare_multi(db, sql, m);
+    affirm( 0==rc );
+    affirm( liStmt.size() == 3 );
+    for( sqlite3_stmt st : liStmt ){
+      sqlite3_finalize(st);
+    }
+    sqlite3_close_v2(db);
+  }
+
+  /* Copy/paste/rename this to add new tests. */
+  private void _testTemplate(){
+    final sqlite3 db = createNewDb();
+    sqlite3_stmt stmt = prepare(db,"SELECT 1");
+    sqlite3_finalize(stmt);
+    sqlite3_close_v2(db);
+  }
+
+
+  @ManualTest /* we really only want to run this test manually */
+  private void testSleep(){
+    out("Sleeping briefly... ");
+    sqlite3_sleep(600);
+    outln("Woke up.");
+  }
+
+  private void nap() throws InterruptedException {
+    if( takeNaps ){
+      Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
+    }
+  }
+
+  @ManualTest /* because we only want to run this test on demand */
+  private void testFail(){
+    affirm( false, "Intentional failure." );
+  }
+
+  private void runTests(boolean fromThread) throws Exception {
+    if(false) showCompileOption();
+    List<java.lang.reflect.Method> mlist = testMethods;
+    affirm( null!=mlist );
+    if( shuffle ){
+      mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
+      java.util.Collections.shuffle(mlist);
+    }
+    if( listRunTests ){
+      synchronized(this.getClass()){
+        if( !fromThread ){
+          out("Initial test"," list: ");
+          for(java.lang.reflect.Method m : testMethods){
+            out(m.getName()+" ");
+          }
+          outln();
+          outln("(That list excludes some which are hard-coded to run.)");
+        }
+        out("Running"," tests: ");
+        for(java.lang.reflect.Method m : mlist){
+          out(m.getName()+" ");
+        }
+        outln();
+      }
+    }
+    for(java.lang.reflect.Method m : mlist){
+      nap();
+      try{
+        m.invoke(this);
+      }catch(java.lang.reflect.InvocationTargetException e){
+        outln("FAILURE: ",m.getName(),"(): ", e.getCause());
+        throw e;
+      }
+    }
+    synchronized( this.getClass() ){
+      ++nTestRuns;
+    }
+  }
+
+  public void run() {
+    try {
+      runTests(0!=this.tId);
+    }catch(Exception e){
+      synchronized( listErrors ){
+        listErrors.add(e);
+      }
+    }finally{
+      affirm( sqlite3_java_uncache_thread() );
+      affirm( !sqlite3_java_uncache_thread() );
+    }
+  }
+
+  /**
+     Runs the basic sqlite3 JNI binding sanity-check suite.
+
+     CLI flags:
+
+     -q|-quiet: disables most test output.
+
+     -t|-thread N: runs the tests in N threads
+      concurrently. Default=1.
+
+     -r|-repeat N: repeats the tests in a loop N times, each one
+      consisting of the -thread value's threads.
+
+     -shuffle: randomizes the order of most of the test functions.
+
+     -naps: sleep small random intervals between tests in order to add
+     some chaos for cross-thread contention.
+
+     -list-tests: outputs the list of tests being run, minus some
+      which are hard-coded. This is noisy in multi-threaded mode.
+
+     -fail: forces an exception to be thrown during the test run.  Use
+     with -shuffle to make its appearance unpredictable.
+
+     -v: emit some developer-mode info at the end.
+  */
+  public static void main(String[] args) throws Exception {
+    Integer nThread = 1;
+    boolean doSomethingForDev = false;
+    Integer nRepeat = 1;
+    boolean forceFail = false;
+    boolean sqlLog = false;
+    boolean configLog = false;
+    boolean squelchTestOutput = false;
+    for( int i = 0; i < args.length; ){
+      String arg = args[i++];
+      if(arg.startsWith("-")){
+        arg = arg.replaceFirst("-+","");
+        if(arg.equals("v")){
+          doSomethingForDev = true;
+          //listBoundMethods();
+        }else if(arg.equals("t") || arg.equals("thread")){
+          nThread = Integer.parseInt(args[i++]);
+        }else if(arg.equals("r") || arg.equals("repeat")){
+          nRepeat = Integer.parseInt(args[i++]);
+        }else if(arg.equals("shuffle")){
+          shuffle = true;
+        }else if(arg.equals("list-tests")){
+          listRunTests = true;
+        }else if(arg.equals("fail")){
+          forceFail = true;
+        }else if(arg.equals("sqllog")){
+          sqlLog = true;
+        }else if(arg.equals("configlog")){
+          configLog = true;
+        }else if(arg.equals("naps")){
+          takeNaps = true;
+        }else if(arg.equals("q") || arg.equals("quiet")){
+          squelchTestOutput = true;
+        }else{
+          throw new IllegalArgumentException("Unhandled flag:"+arg);
+        }
+      }
+    }
+
+    if( sqlLog ){
+      if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){
+        final ConfigSqllogCallback log = new ConfigSqllogCallback() {
+            @Override public void call(sqlite3 db, String msg, int op){
+              switch(op){
+                case 0: outln("Opening db: ",db); break;
+                case 1: outln("SQL ",db,": ",msg); break;
+                case 2: outln("Closing db: ",db); break;
+              }
+            }
+          };
+        int rc = sqlite3_config( log );
+        affirm( 0==rc );
+        rc = sqlite3_config( (ConfigSqllogCallback)null );
+        affirm( 0==rc );
+        rc = sqlite3_config( log );
+        affirm( 0==rc );
+      }else{
+        outln("WARNING: -sqllog is not active because library was built ",
+              "without SQLITE_ENABLE_SQLLOG.");
+      }
+    }
+    if( configLog ){
+      final ConfigLogCallback log = new ConfigLogCallback() {
+          @Override public void call(int code, String msg){
+            outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg);
+          };
+        };
+      int rc = sqlite3_config( log );
+      affirm( 0==rc );
+      rc = sqlite3_config( (ConfigLogCallback)null );
+      affirm( 0==rc );
+      rc = sqlite3_config( log );
+      affirm( 0==rc );
+    }
+
+    quietMode = squelchTestOutput;
+    outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
+          "you are very likely seeing the side effects of a known openjdk8 ",
+          "bug. It is unsightly but does not affect the library.");
+
+    {
+      // Build list of tests to run from the methods named test*().
+      testMethods = new ArrayList<>();
+      int nSkipped = 0;
+      for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
+        final String name = m.getName();
+        if( name.equals("testFail") ){
+          if( forceFail ){
+            testMethods.add(m);
+          }
+        }else if( !m.isAnnotationPresent( ManualTest.class ) ){
+          if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
+            if( 0==nSkipped++ ){
+              out("Skipping tests in multi-thread mode:");
+            }
+            out(" "+name+"()");
+          }else if( name.startsWith("test") ){
+            testMethods.add(m);
+          }
+        }
+      }
+      if( nSkipped>0 ) out("\n");
+    }
+
+    final long timeStart = System.currentTimeMillis();
+    int nLoop = 0;
+    switch( sqlite3_threadsafe() ){ /* Sanity checking */
+      case 0:
+        affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
+                "Could not switch to single-thread mode." );
+        affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
+                "Could switch to multithread mode."  );
+        affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
+                "Could not switch to serialized threading mode."  );
+        outln("This is a single-threaded build. Not using threads.");
+        nThread = 1;
+        break;
+      case 1:
+      case 2:
+        affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
+                "Could not switch to single-thread mode." );
+        affirm( 0==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
+                "Could not switch to multithread mode."  );
+        affirm( 0==sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
+                "Could not switch to serialized threading mode."  );
+        break;
+      default:
+        affirm( false, "Unhandled SQLITE_THREADSAFE value." );
+    }
+    outln("libversion_number: ",
+          sqlite3_libversion_number(),"\n",
+          sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n",
+          "SQLITE_THREADSAFE=",sqlite3_threadsafe());
+    final boolean showLoopCount = (nRepeat>1 && nThread>1);
+    if( showLoopCount ){
+      outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
+    }
+    if( takeNaps ) outln("Napping between tests is enabled.");
+    for( int n = 0; n < nRepeat; ++n ){
+      ++nLoop;
+      if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
+      if( nThread<=1 ){
+        new Tester1(0).runTests(false);
+        continue;
+      }
+      Tester1.mtMode = true;
+      final ExecutorService ex = Executors.newFixedThreadPool( nThread );
+      for( int i = 0; i < nThread; ++i ){
+        ex.submit( new Tester1(i), i );
+      }
+      ex.shutdown();
+      try{
+        ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS);
+        ex.shutdownNow();
+      }catch (InterruptedException ie){
+        ex.shutdownNow();
+        Thread.currentThread().interrupt();
+      }
+      if( !listErrors.isEmpty() ){
+        quietMode = false;
+        outln("TEST ERRORS:");
+        Exception err = null;
+        for( Exception e : listErrors ){
+          e.printStackTrace();
+          if( null==err ) err = e;
+        }
+        if( null!=err ) throw err;
+      }
+    }
+    if( showLoopCount ) outln();
+    quietMode = false;
+
+    final long timeEnd = System.currentTimeMillis();
+    outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
+    outln("\tAssertions checked: ",affirmCount);
+    outln("\tDatabases opened: ",metrics.dbOpen);
+    if( doSomethingForDev ){
+      sqlite3_jni_internal_details();
+    }
+    affirm( 0==sqlite3_release_memory(1) );
+    sqlite3_shutdown();
+    int nMethods = 0;
+    int nNatives = 0;
+    final java.lang.reflect.Method[] declaredMethods =
+      CApi.class.getDeclaredMethods();
+    for(java.lang.reflect.Method m : declaredMethods){
+      final int mod = m.getModifiers();
+      if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
+        final String name = m.getName();
+        if(name.startsWith("sqlite3_")){
+          ++nMethods;
+          if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
+            ++nNatives;
+          }
+        }
+      }
+    }
+    outln("\tCApi.sqlite3_*() methods: "+
+          nMethods+" total, with "+
+          nNatives+" native, "+
+          (nMethods - nNatives)+" Java"
+    );
+    outln("\tTotal test time = "
+          +(timeEnd - timeStart)+"ms");
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java
new file mode 100644
index 0000000000..56465a2c0a
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java
@@ -0,0 +1,50 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.Nullable;
+
+/**
+   Callback for use with {@link CApi#sqlite3_trace_v2}.
+*/
+public interface TraceV2Callback extends CallbackProxy {
+  /**
+     Called by sqlite3 for various tracing operations, as per
+     sqlite3_trace_v2(). Note that this interface elides the 2nd
+     argument to the native trace callback, as that role is better
+     filled by instance-local state.
+
+     <p>These callbacks may throw, in which case their exceptions are
+     converted to C-level error information.
+
+     <p>The 2nd argument to this function, if non-null, will be a an
+     sqlite3 or sqlite3_stmt object, depending on the first argument
+     (see below).
+
+     <p>The final argument to this function is the "X" argument
+     documented for sqlite3_trace() and sqlite3_trace_v2(). Its type
+     depends on value of the first argument:
+
+     <p>- SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a String
+     containing the prepared SQL.
+
+     <p>- SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long
+     holding an approximate number of nanoseconds the statement took
+     to run.
+
+     <p>- SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null.
+
+     <p>- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
+  */
+  int call(int traceFlag, Object pNative, @Nullable Object pX);
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java
new file mode 100644
index 0000000000..33d72a5dd2
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java
@@ -0,0 +1,25 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for use with {@link CApi#sqlite3_update_hook}.
+*/
+public interface UpdateHookCallback extends CallbackProxy {
+  /**
+     Must function as described for the C-level sqlite3_update_hook()
+     callback.
+  */
+  void call(int opId, String dbName, String tableName, long rowId);
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java
new file mode 100644
index 0000000000..b3f03ac867
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java
@@ -0,0 +1,25 @@
+/*
+** 2023-10-16
+**
+** 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 a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   A helper class which simply holds a single value. Its primary use
+   is for communicating values out of anonymous classes, as doing so
+   requires a "final" reference.
+*/
+public class ValueHolder<T> {
+  public T value;
+  public ValueHolder(){}
+  public ValueHolder(T v){value = v;}
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java
new file mode 100644
index 0000000000..eaf1bb9a35
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java
@@ -0,0 +1,39 @@
+/*
+** 2023-08-25
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+   A SQLFunction implementation for window functions.  Note that
+   WindowFunction inherits from {@link AggregateFunction} and each
+   instance is required to implement the inherited abstract methods
+   from that class. See {@link AggregateFunction} for information on
+   managing the UDF's invocation-specific state.
+*/
+public abstract class WindowFunction<T> extends AggregateFunction<T> {
+
+  /**
+     As for the xInverse() argument of the C API's
+     sqlite3_create_window_function(). If this function throws, the
+     exception is not propagated and a warning might be emitted
+     to a debugging channel.
+  */
+  public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args);
+
+  /**
+     As for the xValue() argument of the C API's sqlite3_create_window_function().
+     See xInverse() for the fate of any exceptions this throws.
+  */
+  public abstract void xValue(sqlite3_context cx);
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java
new file mode 100644
index 0000000000..372e4ec8d0
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java
@@ -0,0 +1,37 @@
+/*
+** 2023-07-21
+**
+** 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 declares JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   Callback for a hook called by SQLite when certain client-provided
+   state are destroyed. It gets its name from the pervasive use of
+   the symbol name xDestroy() for this purpose in the C API
+   documentation.
+*/
+public interface XDestroyCallback {
+  /**
+     Must perform any cleanup required by this object. Must not
+     throw. Must not call back into the sqlite3 API, else it might
+     invoke a deadlock.
+
+     WARNING: as a rule, it is never safe to register individual
+     instances with this interface multiple times in the
+     library. e.g., do not register the same CollationCallback with
+     multiple arities or names using sqlite3_create_collation().  If
+     this rule is violated, the library will eventually try to free
+     each individual reference, leading to memory corruption or a
+     crash via duplicate free().
+  */
+  public void xDestroy();
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/package-info.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/package-info.java
new file mode 100644
index 0000000000..127f380675
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/package-info.java
@@ -0,0 +1,89 @@
+/**
+   This package houses a JNI binding to the SQLite3 C API.
+
+   <p>The primary interfaces are in {@link
+   org.sqlite.jni.capi.CApi}.</p>
+
+   <h1>API Goals and Requirements</h1>
+
+   <ul>
+
+     <li>A 1-to-1(-ish) mapping of the C API to Java via JNI, insofar
+     as cross-language semantics allow for. A closely-related goal is
+     that <a href='https://sqlite.org/c3ref/intro.html'>the C
+     documentation</a> should be usable as-is, insofar as possible,
+     for most of the JNI binding. As a rule, undocumented symbols in
+     the Java interface behave as documented for their C API
+     counterpart. Only semantic differences and Java-specific features
+     are documented here.</li>
+
+     <li>Support Java as far back as version 8 (2014).</li>
+
+     <li>Environment-independent. Should work everywhere both Java and
+     SQLite3 do.</li>
+
+     <li>No 3rd-party dependencies beyond the JDK. That includes no
+     build-level dependencies for specific IDEs and toolchains.  We
+     welcome the addition of build files for arbitrary environments
+     insofar as they neither interfere with each other nor become a
+     maintenance burden for the sqlite developers.</li>
+
+  </ul>
+
+  <h2>Non-Goals</h2>
+
+  <ul>
+
+    <li>Creation of high-level OO wrapper APIs. Clients are free to
+    create them off of the C-style API.</li>
+
+    <li>Support for mixed-mode operation, where client code accesses
+    SQLite both via the Java-side API and the C API via their own
+    native code. In such cases, proxy functionalities (primarily
+    callback handler wrappers of all sorts) may fail because the
+    C-side use of the SQLite APIs will bypass those proxies.</li>
+
+  </ul>
+
+   <h1>State of this API</h1>
+
+   <p>As of version 3.43, this software is in "tech preview" form. We
+   tentatively plan to stamp it as stable with the 3.44 release.</p>
+
+   <h1>Threading Considerations</h1>
+
+   <p>This API is, if built with SQLITE_THREADSAFE set to 1 or 2,
+   thread-safe, insofar as the C API guarantees, with some addenda:</p>
+
+   <ul>
+
+     <li>It is not legal to use Java-facing SQLite3 resource handles
+     (sqlite3, sqlite3_stmt, etc) from multiple threads concurrently,
+     nor to use any database-specific resources concurrently in a
+     thread separate from the one the database is currently in use
+     in. i.e. do not use a sqlite3_stmt in thread #2 when thread #1 is
+     using the database which prepared that handle.
+
+     <br>Violating this will eventually corrupt the JNI-level bindings
+     between Java's and C's view of the database. This is a limitation
+     of the JNI bindings, not the lower-level library.
+     </li>
+
+     <li>It is legal to use a given handle, and database-specific
+     resources, across threads, so long as no two threads pass
+     resources owned by the same database into the library
+     concurrently.
+     </li>
+
+   </ul>
+
+   <p>Any number of threads may, of course, create and use any number
+   of database handles they wish. Care only needs to be taken when
+   those handles or their associated resources cross threads, or...</p>
+
+   <p>When built with SQLITE_THREADSAFE=0 then no threading guarantees
+   are provided and multi-threaded use of the library will provoke
+   undefined behavior.</p>
+
+*/
+package org.sqlite.jni.capi;
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3.java
new file mode 100644
index 0000000000..901317f0ef
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3.java
@@ -0,0 +1,43 @@
+/*
+** 2023-07-21
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   A wrapper for communicating C-level (sqlite3*) instances with
+   Java. These wrappers do not own their associated pointer, they
+   simply provide a type-safe way to communicate it between Java
+   and C via JNI.
+*/
+public final class sqlite3 extends NativePointerHolder<sqlite3>
+ implements AutoCloseable {
+
+  // Only invoked from JNI
+  private sqlite3(){}
+
+  public String toString(){
+    final long ptr = getNativePointer();
+    if( 0==ptr ){
+      return sqlite3.class.getSimpleName()+"@null";
+    }
+    final String fn = CApi.sqlite3_db_filename(this, "main");
+    return sqlite3.class.getSimpleName()
+      +"@"+String.format("0x%08x",ptr)
+      +"["+((null == fn) ? "<unnamed>" : fn)+"]"
+      ;
+  }
+
+  @Override public void close(){
+    CApi.sqlite3_close_v2(this.clearNativePointer());
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java
new file mode 100644
index 0000000000..0ef75c17eb
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java
@@ -0,0 +1,31 @@
+/*
+** 2023-09-03
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   A wrapper for passing C-level (sqlite3_backup*) instances around in
+   Java. These wrappers do not own their associated pointer, they
+   simply provide a type-safe way to communicate it between Java and C
+   via JNI.
+*/
+public final class sqlite3_backup extends NativePointerHolder<sqlite3_backup>
+  implements AutoCloseable {
+  // Only invoked from JNI.
+  private sqlite3_backup(){}
+
+  @Override public void close(){
+    CApi.sqlite3_backup_finish(this);
+  }
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java
new file mode 100644
index 0000000000..1b96c18b06
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java
@@ -0,0 +1,31 @@
+/*
+** 2023-09-03
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   A wrapper for passing C-level (sqlite3_blob*) instances around in
+   Java. These wrappers do not own their associated pointer, they
+   simply provide a type-safe way to communicate it between Java and C
+   via JNI.
+*/
+public final class sqlite3_blob extends NativePointerHolder<sqlite3_blob>
+  implements AutoCloseable {
+  // Only invoked from JNI.
+  private sqlite3_blob(){}
+
+  @Override public void close(){
+    CApi.sqlite3_blob_close(this.clearNativePointer());
+  }
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java
new file mode 100644
index 0000000000..82ec49af16
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java
@@ -0,0 +1,79 @@
+/*
+** 2023-07-21
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   sqlite3_context instances are used in conjunction with user-defined
+   SQL functions (a.k.a. UDFs).
+*/
+public final class sqlite3_context extends NativePointerHolder<sqlite3_context> {
+  private Long aggregateContext = null;
+
+  /**
+     getAggregateContext() corresponds to C's
+     sqlite3_aggregate_context(), with a slightly different interface
+     to account for cross-language differences. It serves the same
+     purposes in a slightly different way: it provides a key which is
+     stable across invocations of a UDF's callbacks, such that all
+     calls into those callbacks can determine which "set" of those
+     calls they belong to.
+
+     <p>Note that use of this method is not a requirement for proper use
+     of this class. sqlite3_aggregate_context() can also be used.
+
+     <p>If the argument is true and the aggregate context has not yet
+     been set up, it will be initialized and fetched on demand, else it
+     won't. The intent is that xStep(), xValue(), and xInverse()
+     methods pass true and xFinal() methods pass false.
+
+     <p>This function treats numeric 0 as null, always returning null instead
+     of 0.
+
+     <p>If this object is being used in the context of an aggregate or
+     window UDF, this function returns a non-0 value which is distinct
+     for each set of UDF callbacks from a single invocation of the
+     UDF, otherwise it returns 0. The returned value is only only
+     valid within the context of execution of a single SQL statement,
+     and must not be re-used by future invocations of the UDF in
+     different SQL statements.
+
+     <p>Consider this SQL, where MYFUNC is a user-defined aggregate function:
+
+     <pre>{@code
+     SELECT MYFUNC(A), MYFUNC(B) FROM T;
+     }</pre>
+
+     <p>The xStep() and xFinal() methods of the callback need to be able
+     to differentiate between those two invocations in order to
+     perform their work properly. The value returned by
+     getAggregateContext() will be distinct for each of those
+     invocations of MYFUNC() and is intended to be used as a lookup
+     key for mapping callback invocations to whatever client-defined
+     state is needed by the UDF.
+
+     <p>There is one case where this will return null in the context
+     of an aggregate or window function: if the result set has no
+     rows, the UDF's xFinal() will be called without any other x...()
+     members having been called. In that one case, no aggregate
+     context key will have been generated. xFinal() implementations
+     need to be prepared to accept that condition as legal.
+  */
+  public synchronized Long getAggregateContext(boolean initIfNeeded){
+      if( aggregateContext==null ){
+        aggregateContext = CApi.sqlite3_aggregate_context(this, initIfNeeded);
+        if( !initIfNeeded && null==aggregateContext ) aggregateContext = 0L;
+      }
+      return (null==aggregateContext || 0!=aggregateContext) ? aggregateContext : null;
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java
new file mode 100644
index 0000000000..3b8b71f8a5
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java
@@ -0,0 +1,30 @@
+/*
+** 2023-07-21
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+   A wrapper for communicating C-level (sqlite3_stmt*) instances with
+   Java. These wrappers do not own their associated pointer, they
+   simply provide a type-safe way to communicate it between Java and C
+   via JNI.
+*/
+public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt>
+  implements AutoCloseable {
+  // Only invoked from JNI.
+  private sqlite3_stmt(){}
+
+  @Override public void close(){
+    CApi.sqlite3_finalize(this.clearNativePointer());
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java
new file mode 100644
index 0000000000..a4772f0f63
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java
@@ -0,0 +1,19 @@
+/*
+** 2023-07-21
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+public final class sqlite3_value extends NativePointerHolder<sqlite3_value> {
+  //! Invoked only from JNI.
+  private sqlite3_value(){}
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5.java
new file mode 100644
index 0000000000..0dceeafd2e
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5.java
@@ -0,0 +1,32 @@
+/*
+** 2023-08-05
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+
+/**
+   INCOMPLETE AND COMPLETELY UNTESTED.
+
+   A utility object for holding FTS5-specific types and constants
+   which are used by multiple FTS5 classes.
+*/
+public final class Fts5 {
+  /* Not used */
+  private Fts5(){}
+
+
+  public static final int FTS5_TOKENIZE_QUERY    = 0x0001;
+  public static final int FTS5_TOKENIZE_PREFIX   = 0x0002;
+  public static final int FTS5_TOKENIZE_DOCUMENT = 0x0004;
+  public static final int FTS5_TOKENIZE_AUX      = 0x0008;
+  public static final int FTS5_TOKEN_COLOCATED   = 0x0001;
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java
new file mode 100644
index 0000000000..439b477910
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java
@@ -0,0 +1,24 @@
+/*
+** 2023-08-04
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.*;
+
+/**
+   A wrapper for communicating C-level (Fts5Context*) instances with
+   Java. These wrappers do not own their associated pointer, they
+   simply provide a type-safe way to communicate it between Java and C
+   via JNI.
+*/
+public final class Fts5Context extends NativePointerHolder<Fts5Context> {
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java
new file mode 100644
index 0000000000..594f3eaad6
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java
@@ -0,0 +1,97 @@
+/*
+** 2023-08-04
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import java.nio.charset.StandardCharsets;
+import org.sqlite.jni.capi.*;
+import org.sqlite.jni.annotation.*;
+
+/**
+*/
+public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi> {
+  //! Only called from JNI
+  private Fts5ExtensionApi(){}
+  private final int iVersion = 2;
+
+  /* Callback type for used by xQueryPhrase(). */
+  public static interface XQueryPhraseCallback {
+    int call(Fts5ExtensionApi fapi, Fts5Context cx);
+  }
+
+  /**
+     Returns the singleton instance of this class.
+  */
+  public static native Fts5ExtensionApi getInstance();
+
+  public native int xColumnCount(@NotNull Fts5Context fcx);
+
+  public native int xColumnSize(@NotNull Fts5Context cx, int iCol,
+                                @NotNull OutputPointer.Int32 pnToken);
+
+  public native int xColumnText(@NotNull Fts5Context cx, int iCol,
+                                @NotNull OutputPointer.String txt);
+
+  public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
+                                     @NotNull OutputPointer.Int64 pnToken);
+
+  public native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
+
+  public native int xInst(@NotNull Fts5Context cx, int iIdx,
+                          @NotNull OutputPointer.Int32 piPhrase,
+                          @NotNull OutputPointer.Int32 piCol,
+                          @NotNull OutputPointer.Int32 piOff);
+
+  public native int xInstCount(@NotNull Fts5Context fcx,
+                               @NotNull OutputPointer.Int32 pnInst);
+
+  public native int xPhraseCount(@NotNull Fts5Context fcx);
+
+  public native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
+                                 @NotNull Fts5PhraseIter iter,
+                                 @NotNull OutputPointer.Int32 iCol,
+                                 @NotNull OutputPointer.Int32 iOff);
+
+  public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
+                                       @NotNull Fts5PhraseIter iter,
+                                       @NotNull OutputPointer.Int32 iCol);
+  public native void xPhraseNext(@NotNull Fts5Context cx,
+                                 @NotNull Fts5PhraseIter iter,
+                                 @NotNull OutputPointer.Int32 iCol,
+                                 @NotNull OutputPointer.Int32 iOff);
+  public native void xPhraseNextColumn(@NotNull Fts5Context cx,
+                                       @NotNull Fts5PhraseIter iter,
+                                       @NotNull OutputPointer.Int32 iCol);
+  public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
+
+  public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
+                                 @NotNull XQueryPhraseCallback callback);
+  public native int xRowCount(@NotNull Fts5Context fcx,
+                              @NotNull OutputPointer.Int64 nRow);
+
+  public native long xRowid(@NotNull Fts5Context cx);
+  /* Note that the JNI binding lacks the C version's xDelete()
+     callback argument. Instead, if pAux has an xDestroy() method, it
+     is called if the FTS5 API finalizes the aux state (including if
+     allocation of storage for the auxdata fails). Any reference to
+     pAux held by the JNI layer will be relinquished regardless of
+     whether pAux has an xDestroy() method. */
+
+  public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
+
+  public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
+                              @NotNull XTokenizeCallback callback);
+
+  public native Object xUserData(Fts5Context cx);
+  //^^^ returns the pointer passed as the 3rd arg to the C-level
+  // fts5_api::xCreateFunction().
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java
new file mode 100644
index 0000000000..5774eb5936
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java
@@ -0,0 +1,25 @@
+/*
+** 2023-08-04
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+
+/**
+   A wrapper for C-level Fts5PhraseIter. They are only modified and
+   inspected by native-level code.
+*/
+public final class Fts5PhraseIter extends NativePointerHolder<Fts5PhraseIter> {
+  //! Updated and used only by native code.
+  private long a;
+  private long b;
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java
new file mode 100644
index 0000000000..b72e5d0fc0
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java
@@ -0,0 +1,31 @@
+/*
+** 2023-08-05x
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+
+/**
+   INCOMPLETE AND COMPLETELY UNTESTED.
+
+   A wrapper for communicating C-level (Fts5Tokenizer*) instances with
+   Java. These wrappers do not own their associated pointer, they
+   simply provide a type-safe way to communicate it between Java and C
+   via JNI.
+
+   At the C level, the Fts5Tokenizer type is essentially a void
+   pointer used specifically for tokenizers.
+*/
+public final class Fts5Tokenizer extends NativePointerHolder<Fts5Tokenizer> {
+  //! Only called from JNI.
+  private Fts5Tokenizer(){}
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java
new file mode 100644
index 0000000000..c4264c5417
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java
@@ -0,0 +1,832 @@
+/*
+** 2023-08-04
+**
+** 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 a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.fts5;
+import static org.sqlite.jni.capi.CApi.*;
+import static org.sqlite.jni.capi.Tester1.*;
+import org.sqlite.jni.capi.*;
+import org.sqlite.jni.fts5.*;
+
+import java.util.*;
+
+public class TesterFts5 {
+
+  private static void test1(){
+    final Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance();
+    affirm( null != fea );
+    affirm( fea.getNativePointer() != 0 );
+    affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/;
+
+    sqlite3 db = createNewDb();
+    fts5_api fApi = fts5_api.getInstanceForDb(db);
+    affirm( fApi != null );
+    affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ );
+
+    execSql(db, new String[] {
+        "CREATE VIRTUAL TABLE ft USING fts5(a, b);",
+        "INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');",
+        "INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');"
+      });
+
+    final String pUserData = "This is pUserData";
+    final int outputs[] = {0, 0};
+    final fts5_extension_function func = new fts5_extension_function(){
+        @Override public void call(Fts5ExtensionApi ext, Fts5Context fCx,
+                                   sqlite3_context pCx, sqlite3_value argv[]){
+          final int nCols = ext.xColumnCount(fCx);
+          affirm( 2 == nCols );
+          affirm( nCols == argv.length );
+          affirm( ext.xUserData(fCx) == pUserData );
+          final OutputPointer.String op = new OutputPointer.String();
+          final OutputPointer.Int32 colsz = new OutputPointer.Int32();
+          final OutputPointer.Int64 colTotalSz = new OutputPointer.Int64();
+          for(int i = 0; i < nCols; ++i ){
+            int rc = ext.xColumnText(fCx, i, op);
+            affirm( 0 == rc );
+            final String val = op.value;
+            affirm( val.equals(sqlite3_value_text16(argv[i])) );
+            rc = ext.xColumnSize(fCx, i, colsz);
+            affirm( 0==rc );
+            affirm( 3==sqlite3_value_bytes(argv[i]) );
+            rc = ext.xColumnTotalSize(fCx, i, colTotalSz);
+            affirm( 0==rc );
+          }
+          ++outputs[0];
+        }
+        public void xDestroy(){
+          outputs[1] = 1;
+        }
+      };
+
+    int rc = fApi.xCreateFunction("myaux", pUserData, func);
+    affirm( 0==rc );
+
+    affirm( 0==outputs[0] );
+    execSql(db, "select myaux(ft,a,b) from ft;");
+    affirm( 2==outputs[0] );
+    affirm( 0==outputs[1] );
+    sqlite3_close_v2(db);
+    affirm( 1==outputs[1] );
+  }
+
+  /* 
+  ** Argument sql is a string containing one or more SQL statements
+  ** separated by ";" characters. This function executes each of these
+  ** statements against the database passed as the first argument. If
+  ** no error occurs, the results of the SQL script are returned as
+  ** an array of strings. If an error does occur, a RuntimeException is 
+  ** thrown.
+  */
+  private static String[] sqlite3_exec(sqlite3 db, String sql) {
+    List<String> aOut = new ArrayList<String>();
+
+    /* Iterate through the list of SQL statements. For each, step through
+    ** it and add any results to the aOut[] array.  */
+    int rc = sqlite3_prepare_multi(db, sql, new PrepareMultiCallback() {
+      @Override public int call(sqlite3_stmt pStmt){
+        while( SQLITE_ROW==sqlite3_step(pStmt) ){
+          int ii;
+          for(ii=0; ii<sqlite3_column_count(pStmt); ii++){
+            aOut.add( sqlite3_column_text16(pStmt, ii) );
+          }
+        }
+        return sqlite3_finalize(pStmt);
+      }
+    });
+    if( rc!=SQLITE_OK ){
+      throw new RuntimeException(sqlite3_errmsg(db));
+    }
+
+    /* Convert to array and return */
+    String[] arr = new String[aOut.size()];
+    return aOut.toArray(arr);
+  }
+
+  /*
+  ** Execute the SQL script passed as the second parameter via 
+  ** sqlite3_exec(). Then affirm() that the results, when converted to
+  ** a string, match the value of the 3rd parameter. Example:
+  **
+  **   do_execsql_test(db, "SELECT 'abc'", "[abc]");
+  **
+  */
+  private static void do_execsql_test(sqlite3 db, String sql, String expect) {
+    String res = Arrays.toString( sqlite3_exec(db, sql) );
+    affirm( res.equals(expect),
+      "got {" + res + "} expected {" + expect + "}"
+    );
+  }
+  private static void do_execsql_test(sqlite3 db, String sql){
+    do_execsql_test(db, sql, "[]");
+  }
+
+  /*
+  ** Create the following custom SQL functions:
+  **
+  **     fts5_rowid()
+  **     fts5_columncount()
+  */
+  private static void create_test_functions(sqlite3 db){
+    /* 
+    ** A user-defined-function fts5_rowid() that uses xRowid()
+    */
+    fts5_extension_function fts5_rowid = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        long rowid = ext.xRowid(fCx);
+        sqlite3_result_int64(pCx, rowid);
+      }
+      public void xDestroy(){ }
+    };
+
+    /* 
+    ** fts5_columncount() - xColumnCount() 
+    */
+    fts5_extension_function fts5_columncount = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        int nCol = ext.xColumnCount(fCx);
+        sqlite3_result_int(pCx, nCol);
+      }
+      public void xDestroy(){ }
+    };
+
+    /* 
+    ** fts5_columnsize() - xColumnSize() 
+    */
+    fts5_extension_function fts5_columnsize = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=1 ){
+          throw new RuntimeException("fts5_columncount: wrong number of args");
+        }
+        int iCol = sqlite3_value_int(argv[0]);
+
+        OutputPointer.Int32 piSz = new OutputPointer.Int32();
+        int rc = ext.xColumnSize(fCx, iCol, piSz);
+        if( rc!=SQLITE_OK ){
+          throw new RuntimeException( sqlite3_errstr(rc) );
+        }
+        sqlite3_result_int(pCx, piSz.get());
+      }
+      public void xDestroy(){ }
+    };
+
+    /* 
+    ** fts5_columntext() - xColumnText() 
+    */
+    fts5_extension_function fts5_columntext = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=1 ){
+          throw new RuntimeException("fts5_columntext: wrong number of args");
+        }
+        int iCol = sqlite3_value_int(argv[0]);
+
+        OutputPointer.String pzText = new OutputPointer.String();
+        int rc = ext.xColumnText(fCx, iCol, pzText);
+        if( rc!=SQLITE_OK ){
+          throw new RuntimeException( sqlite3_errstr(rc) );
+        }
+        sqlite3_result_text16(pCx, pzText.get());
+      }
+      public void xDestroy(){ }
+    };
+
+    /* 
+    ** fts5_columntotalsize() - xColumnTotalSize() 
+    */
+    fts5_extension_function fts5_columntsize = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=1 ){
+          throw new RuntimeException(
+              "fts5_columntotalsize: wrong number of args"
+          );
+        }
+        int iCol = sqlite3_value_int(argv[0]);
+
+        OutputPointer.Int64 piSz = new OutputPointer.Int64();
+        int rc = ext.xColumnTotalSize(fCx, iCol, piSz);
+        if( rc!=SQLITE_OK ){
+          throw new RuntimeException( sqlite3_errstr(rc) );
+        }
+        sqlite3_result_int64(pCx, piSz.get());
+      }
+      public void xDestroy(){ }
+    };
+
+    /*
+    ** fts5_aux(<fts>, <value>);
+    */
+    class fts5_aux implements fts5_extension_function {
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length>1 ){
+          throw new RuntimeException("fts5_aux: wrong number of args");
+        }
+
+        boolean bClear = (argv.length==1);
+        Object obj = ext.xGetAuxdata(fCx, bClear);
+        if( obj instanceof String ){
+          sqlite3_result_text16(pCx, (String)obj);
+        }
+
+        if( argv.length==1 ){
+          String val = sqlite3_value_text16(argv[0]);
+          if( !val.equals("") ){
+            ext.xSetAuxdata(fCx, val);
+          }
+        }
+      }
+      public void xDestroy(){ }
+    };
+
+    /*
+    ** fts5_inst(<fts>);
+    **
+    ** This is used to test the xInstCount() and xInst() APIs. It returns a
+    ** text value containing a Tcl list with xInstCount() elements. Each
+    ** element is itself a list of 3 integers - the phrase number, column
+    ** number and token offset returned by each call to xInst().
+    */
+    fts5_extension_function fts5_inst = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=0 ){
+          throw new RuntimeException("fts5_inst: wrong number of args");
+        }
+
+        OutputPointer.Int32 pnInst = new OutputPointer.Int32();
+        OutputPointer.Int32 piPhrase = new OutputPointer.Int32();
+        OutputPointer.Int32 piCol = new OutputPointer.Int32();
+        OutputPointer.Int32 piOff = new OutputPointer.Int32();
+        String ret = new String();
+
+        int rc = ext.xInstCount(fCx, pnInst);
+        int nInst = pnInst.get();
+        int ii;
+
+        for(ii=0; rc==SQLITE_OK && ii<nInst; ii++){
+          ext.xInst(fCx, ii, piPhrase, piCol, piOff);
+          if( ii>0 ) ret += " ";
+          ret += "{"+piPhrase.get()+" "+piCol.get()+" "+piOff.get()+"}";
+        }
+
+        sqlite3_result_text(pCx, ret);
+      }
+      public void xDestroy(){ }
+    };
+
+    /*
+    ** fts5_pinst(<fts>);
+    **
+    ** Like SQL function fts5_inst(), except using the following
+    **
+    **     xPhraseCount
+    **     xPhraseFirst
+    **     xPhraseNext
+    */
+    fts5_extension_function fts5_pinst = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=0 ){
+          throw new RuntimeException("fts5_pinst: wrong number of args");
+        }
+
+        OutputPointer.Int32 piCol = new OutputPointer.Int32();
+        OutputPointer.Int32 piOff = new OutputPointer.Int32();
+        String ret = new String();
+        int rc = SQLITE_OK;
+
+        int nPhrase = ext.xPhraseCount(fCx);
+        int ii;
+
+        for(ii=0; rc==SQLITE_OK && ii<nPhrase; ii++){
+          Fts5PhraseIter pIter = new Fts5PhraseIter();
+          for(rc = ext.xPhraseFirst(fCx, ii, pIter, piCol, piOff);
+              rc==SQLITE_OK && piCol.get()>=0;
+              ext.xPhraseNext(fCx, pIter, piCol, piOff)
+          ){
+            if( !ret.equals("") ) ret += " ";
+            ret += "{"+ii+" "+piCol.get()+" "+piOff.get()+"}";
+          }
+        }
+
+        if( rc!=SQLITE_OK ){
+          throw new RuntimeException("fts5_pinst: rc=" + rc);
+        }else{
+          sqlite3_result_text(pCx, ret);
+        }
+      }
+      public void xDestroy(){ }
+    };
+
+    /*
+    ** fts5_pcolinst(<fts>);
+    **
+    ** Like SQL function fts5_pinst(), except using the following
+    **
+    **     xPhraseFirstColumn
+    **     xPhraseNextColumn
+    */
+    fts5_extension_function fts5_pcolinst = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=0 ){
+          throw new RuntimeException("fts5_pcolinst: wrong number of args");
+        }
+
+        OutputPointer.Int32 piCol = new OutputPointer.Int32();
+        String ret = new String();
+        int rc = SQLITE_OK;
+
+        int nPhrase = ext.xPhraseCount(fCx);
+        int ii;
+
+        for(ii=0; rc==SQLITE_OK && ii<nPhrase; ii++){
+          Fts5PhraseIter pIter = new Fts5PhraseIter();
+          for(rc = ext.xPhraseFirstColumn(fCx, ii, pIter, piCol);
+              rc==SQLITE_OK && piCol.get()>=0;
+              ext.xPhraseNextColumn(fCx, pIter, piCol)
+          ){
+            if( !ret.equals("") ) ret += " ";
+            ret += "{"+ii+" "+piCol.get()+"}";
+          }
+        }
+
+        if( rc!=SQLITE_OK ){
+          throw new RuntimeException("fts5_pcolinst: rc=" + rc);
+        }else{
+          sqlite3_result_text(pCx, ret);
+        }
+      }
+      public void xDestroy(){ }
+    };
+
+    /*
+    ** fts5_rowcount(<fts>);
+    */
+    fts5_extension_function fts5_rowcount = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=0 ){
+          throw new RuntimeException("fts5_rowcount: wrong number of args");
+        }
+        OutputPointer.Int64 pnRow = new OutputPointer.Int64();
+
+        int rc = ext.xRowCount(fCx, pnRow);
+        if( rc==SQLITE_OK ){
+          sqlite3_result_int64(pCx, pnRow.get());
+        }else{
+          throw new RuntimeException("fts5_rowcount: rc=" + rc);
+        }
+      }
+      public void xDestroy(){ }
+    };
+
+    /*
+    ** fts5_phrasesize(<fts>);
+    */
+    fts5_extension_function fts5_phrasesize = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=1 ){
+          throw new RuntimeException("fts5_phrasesize: wrong number of args");
+        }
+        int iPhrase = sqlite3_value_int(argv[0]);
+
+        int sz = ext.xPhraseSize(fCx, iPhrase);
+        sqlite3_result_int(pCx, sz);
+      }
+      public void xDestroy(){ }
+    };
+
+    /*
+    ** fts5_phrasehits(<fts>, <phrase-number>);
+    **
+    ** Use the xQueryPhrase() API to determine how many hits, in total,
+    ** there are for phrase <phrase-number> in the database.
+    */
+    fts5_extension_function fts5_phrasehits = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=1 ){
+          throw new RuntimeException("fts5_phrasesize: wrong number of args");
+        }
+        int iPhrase = sqlite3_value_int(argv[0]);
+        int rc = SQLITE_OK;
+
+        class MyCallback implements Fts5ExtensionApi.XQueryPhraseCallback {
+          public int nRet = 0;
+          public int getRet() { return nRet; }
+
+          @Override
+          public int call(Fts5ExtensionApi fapi, Fts5Context cx){
+            OutputPointer.Int32 pnInst = new OutputPointer.Int32();
+            int rc = fapi.xInstCount(cx, pnInst);
+            nRet += pnInst.get();
+            return rc;
+          }
+        };
+
+        MyCallback xCall = new MyCallback();
+        rc = ext.xQueryPhrase(fCx, iPhrase, xCall);
+        if( rc!=SQLITE_OK ){
+          throw new RuntimeException("fts5_phrasehits: rc=" + rc);
+        }
+        sqlite3_result_int(pCx, xCall.getRet());
+      }
+      public void xDestroy(){ }
+    };
+
+    /*
+    ** fts5_tokenize(<fts>, <text>)
+    */
+    fts5_extension_function fts5_tokenize = new fts5_extension_function(){
+      @Override public void call(
+          Fts5ExtensionApi ext, 
+          Fts5Context fCx,
+          sqlite3_context pCx, 
+          sqlite3_value argv[]
+      ){
+        if( argv.length!=1 ){
+          throw new RuntimeException("fts5_tokenize: wrong number of args");
+        }
+        byte[] utf8 = sqlite3_value_text(argv[0]);
+        int rc = SQLITE_OK;
+
+        class MyCallback implements XTokenizeCallback {
+          private List<String> myList = new ArrayList<String>();
+
+          public String getval() {
+            return String.join("+", myList);
+          }
+
+          @Override
+          public int call(int tFlags, byte[] txt, int iStart, int iEnd){
+            try {
+              String str = new String(txt, "UTF-8");
+              myList.add(str);
+            } catch (Exception e) {
+            }
+            return SQLITE_OK;
+          }
+        };
+
+        MyCallback xCall = new MyCallback();
+        ext.xTokenize(fCx, utf8, xCall);
+        sqlite3_result_text16(pCx, xCall.getval());
+
+        if( rc!=SQLITE_OK ){
+          throw new RuntimeException("fts5_tokenize: rc=" + rc);
+        }
+      }
+      public void xDestroy(){ }
+    };
+
+    fts5_api api = fts5_api.getInstanceForDb(db);
+    api.xCreateFunction("fts5_rowid", fts5_rowid);
+    api.xCreateFunction("fts5_columncount", fts5_columncount);
+    api.xCreateFunction("fts5_columnsize", fts5_columnsize);
+    api.xCreateFunction("fts5_columntext", fts5_columntext);
+    api.xCreateFunction("fts5_columntotalsize", fts5_columntsize);
+
+    api.xCreateFunction("fts5_aux1", new fts5_aux());
+    api.xCreateFunction("fts5_aux2", new fts5_aux());
+
+    api.xCreateFunction("fts5_inst", fts5_inst);
+    api.xCreateFunction("fts5_pinst", fts5_pinst);
+    api.xCreateFunction("fts5_pcolinst", fts5_pcolinst);
+    api.xCreateFunction("fts5_rowcount", fts5_rowcount);
+    api.xCreateFunction("fts5_phrasesize", fts5_phrasesize);
+    api.xCreateFunction("fts5_phrasehits", fts5_phrasehits);
+    api.xCreateFunction("fts5_tokenize", fts5_tokenize);
+  }
+  /* 
+  ** Test of various Fts5ExtensionApi methods 
+  */
+  private static void test2(){
+
+    /* Open db and populate an fts5 table */
+    sqlite3 db = createNewDb();
+    do_execsql_test(db, 
+      "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+      "INSERT INTO ft(rowid, a, b) VALUES(-9223372036854775808, 'x', 'x');" +
+      "INSERT INTO ft(rowid, a, b) VALUES(0, 'x', 'x');" +
+      "INSERT INTO ft(rowid, a, b) VALUES(1, 'x y z', 'x y z');" +
+      "INSERT INTO ft(rowid, a, b) VALUES(2, 'x y z', 'x z');" +
+      "INSERT INTO ft(rowid, a, b) VALUES(3, 'x y z', 'x y z');" +
+      "INSERT INTO ft(rowid, a, b) VALUES(9223372036854775807, 'x', 'x');"
+    );
+
+    create_test_functions(db);
+
+    /* Test that fts5_rowid() seems to work */
+    do_execsql_test(db, 
+      "SELECT rowid==fts5_rowid(ft) FROM ft('x')",
+      "[1, 1, 1, 1, 1, 1]"
+    );
+
+    /* Test fts5_columncount() */
+    do_execsql_test(db, 
+      "SELECT fts5_columncount(ft) FROM ft('x')",
+      "[2, 2, 2, 2, 2, 2]"
+    );
+
+    /* Test fts5_columnsize() */
+    do_execsql_test(db, 
+      "SELECT fts5_columnsize(ft, 0) FROM ft('x') ORDER BY rowid",
+      "[1, 1, 3, 3, 3, 1]"
+    );
+    do_execsql_test(db, 
+      "SELECT fts5_columnsize(ft, 1) FROM ft('x') ORDER BY rowid",
+      "[1, 1, 3, 2, 3, 1]"
+    );
+    do_execsql_test(db, 
+      "SELECT fts5_columnsize(ft, -1) FROM ft('x') ORDER BY rowid",
+      "[2, 2, 6, 5, 6, 2]"
+    );
+
+    /* Test that xColumnSize() returns SQLITE_RANGE if the column number
+    ** is out-of range */
+    try {
+      do_execsql_test(db, 
+        "SELECT fts5_columnsize(ft, 2) FROM ft('x') ORDER BY rowid"
+      );
+    } catch( RuntimeException e ){
+      affirm( e.getMessage().matches(".*column index out of range") );
+    }
+
+    /* Test fts5_columntext() */
+    do_execsql_test(db, 
+      "SELECT fts5_columntext(ft, 0) FROM ft('x') ORDER BY rowid",
+      "[x, x, x y z, x y z, x y z, x]"
+    );
+    do_execsql_test(db, 
+      "SELECT fts5_columntext(ft, 1) FROM ft('x') ORDER BY rowid",
+      "[x, x, x y z, x z, x y z, x]"
+    );
+    do_execsql_test(db, 
+      "SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid",
+      "[null, null, null, null, null, null]"
+    );
+
+    /* Test fts5_columntotalsize() */
+    do_execsql_test(db, 
+      "SELECT fts5_columntotalsize(ft, 0) FROM ft('x') ORDER BY rowid",
+      "[12, 12, 12, 12, 12, 12]"
+    );
+    do_execsql_test(db, 
+      "SELECT fts5_columntotalsize(ft, 1) FROM ft('x') ORDER BY rowid",
+      "[11, 11, 11, 11, 11, 11]"
+    );
+    do_execsql_test(db, 
+      "SELECT fts5_columntotalsize(ft, -1) FROM ft('x') ORDER BY rowid",
+      "[23, 23, 23, 23, 23, 23]"
+    );
+
+    /* Test that xColumnTotalSize() returns SQLITE_RANGE if the column 
+    ** number is out-of range */
+    try {
+      do_execsql_test(db, 
+        "SELECT fts5_columntotalsize(ft, 2) FROM ft('x') ORDER BY rowid"
+      );
+    } catch( RuntimeException e ){
+      affirm( e.getMessage().matches(".*column index out of range") );
+    }
+
+    do_execsql_test(db, 
+      "SELECT rowid, fts5_rowcount(ft) FROM ft('z')",
+      "[1, 6, 2, 6, 3, 6]"
+    );
+
+    sqlite3_close_v2(db);
+  }
+
+  /* 
+  ** Test of various Fts5ExtensionApi methods 
+  */
+  private static void test3(){
+
+    /* Open db and populate an fts5 table */
+    sqlite3 db = createNewDb();
+    do_execsql_test(db, 
+      "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+      "INSERT INTO ft(a, b) VALUES('the one', 1);" +
+      "INSERT INTO ft(a, b) VALUES('the two', 2);" +
+      "INSERT INTO ft(a, b) VALUES('the three', 3);" +
+      "INSERT INTO ft(a, b) VALUES('the four', '');"
+    );
+    create_test_functions(db);
+
+    /* Test fts5_aux1() + fts5_aux2() - users of xGetAuxdata and xSetAuxdata */
+    do_execsql_test(db,
+      "SELECT fts5_aux1(ft, a) FROM ft('the')",
+      "[null, the one, the two, the three]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_aux2(ft, b) FROM ft('the')",
+      "[null, 1, 2, 3]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_aux1(ft, a), fts5_aux2(ft, b) FROM ft('the')",
+      "[null, null, the one, 1, the two, 2, the three, 3]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_aux1(ft, b), fts5_aux1(ft) FROM ft('the')",
+      "[null, 1, 1, 2, 2, 3, 3, null]"
+    );
+  }
+
+  /* 
+  ** Test of various Fts5ExtensionApi methods 
+  */
+  private static void test4(){
+
+    /* Open db and populate an fts5 table */
+    sqlite3 db = createNewDb();
+    create_test_functions(db);
+    do_execsql_test(db, 
+      "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+      "INSERT INTO ft(a, b) VALUES('one two three', 'two three four');" +
+      "INSERT INTO ft(a, b) VALUES('two three four', 'three four five');" +
+      "INSERT INTO ft(a, b) VALUES('three four five', 'four five six');" 
+    );
+
+
+    do_execsql_test(db,
+      "SELECT fts5_inst(ft) FROM ft('two')",
+      "[{0 0 1} {0 1 0}, {0 0 0}]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_inst(ft) FROM ft('four')",
+      "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
+    );
+
+    do_execsql_test(db,
+      "SELECT fts5_inst(ft) FROM ft('a OR b OR four')",
+      "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_inst(ft) FROM ft('two four')",
+      "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
+    );
+
+    do_execsql_test(db,
+      "SELECT fts5_pinst(ft) FROM ft('two')",
+      "[{0 0 1} {0 1 0}, {0 0 0}]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_pinst(ft) FROM ft('four')",
+      "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_pinst(ft) FROM ft('a OR b OR four')",
+      "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_pinst(ft) FROM ft('two four')",
+      "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
+    );
+
+    do_execsql_test(db,
+      "SELECT fts5_pcolinst(ft) FROM ft('two')",
+      "[{0 0} {0 1}, {0 0}]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_pcolinst(ft) FROM ft('four')",
+      "[{0 1}, {0 0} {0 1}, {0 0} {0 1}]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_pcolinst(ft) FROM ft('a OR b OR four')",
+      "[{2 1}, {2 0} {2 1}, {2 0} {2 1}]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_pcolinst(ft) FROM ft('two four')",
+      "[{0 0} {0 1} {1 1}, {0 0} {1 0} {1 1}]"
+    );
+
+    do_execsql_test(db,
+      "SELECT fts5_phrasesize(ft, 0) FROM ft('four five six') LIMIT 1;",
+      "[1]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_phrasesize(ft, 0) FROM ft('four + five + six') LIMIT 1;",
+      "[3]"
+    );
+
+
+    sqlite3_close_v2(db);
+  }
+
+  private static void test5(){
+    /* Open db and populate an fts5 table */
+    sqlite3 db = createNewDb();
+    create_test_functions(db);
+    do_execsql_test(db, 
+      "CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
+      "INSERT INTO ft(x) VALUES('one two three four five six seven eight');" +
+      "INSERT INTO ft(x) VALUES('one two one four one six one eight');" +
+      "INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
+    );
+
+    do_execsql_test(db,
+      "SELECT fts5_phrasehits(ft, 0) FROM ft('one') LIMIT 1",
+      "[6]"
+    );
+
+    sqlite3_close_v2(db);
+  }
+
+  private static void test6(){
+    sqlite3 db = createNewDb();
+    create_test_functions(db);
+    do_execsql_test(db, 
+      "CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
+      "INSERT INTO ft(x) VALUES('one two three four five six seven eight');" 
+    );
+
+    do_execsql_test(db,
+      "SELECT fts5_tokenize(ft, 'abc def ghi') FROM ft('one')",
+      "[abc+def+ghi]"
+    );
+    do_execsql_test(db,
+      "SELECT fts5_tokenize(ft, 'it''s BEEN a...') FROM ft('one')",
+      "[it+s+been+a]"
+    );
+
+    sqlite3_close_v2(db);
+  }
+
+  private static synchronized void runTests(){
+    test1();
+    test2();
+    test3();
+    test4();
+    test5();
+    test6();
+  }
+
+  public TesterFts5(){
+    runTests();
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java
new file mode 100644
index 0000000000..3aa514f314
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java
@@ -0,0 +1,22 @@
+/*
+** 2023-08-04
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+
+
+/**
+   Callback type for use with xTokenize() variants.
+*/
+public interface XTokenizeCallback {
+  int call(int tFlags, byte[] txt, int iStart, int iEnd);
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java
new file mode 100644
index 0000000000..d7d2da430d
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java
@@ -0,0 +1,76 @@
+/*
+** 2023-08-05
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.*;
+
+/**
+   A wrapper for communicating C-level (fts5_api*) instances with
+   Java. These wrappers do not own their associated pointer, they
+   simply provide a type-safe way to communicate it between Java and C
+   via JNI.
+*/
+public final class fts5_api extends NativePointerHolder<fts5_api> {
+  /* Only invoked from JNI */
+  private fts5_api(){}
+
+  public static final int iVersion = 2;
+
+  /**
+     Returns the fts5_api instance associated with the given db, or
+     null if something goes horribly wrong.
+  */
+  public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db);
+
+  public synchronized native int xCreateFunction(@NotNull String name,
+                                                 @Nullable Object userData,
+                                                 @NotNull fts5_extension_function xFunction);
+
+  /**
+     Convenience overload which passes null as the 2nd argument to the
+     3-parameter form.
+  */
+  public int xCreateFunction(@NotNull String name,
+                             @NotNull fts5_extension_function xFunction){
+    return xCreateFunction(name, null, xFunction);
+  }
+
+  // /* Create a new auxiliary function */
+  // int (*xCreateFunction)(
+  //   fts5_api *pApi,
+  //   const char *zName,
+  //   void *pContext,
+  //   fts5_extension_function xFunction,
+  //   void (*xDestroy)(void*)
+  // );
+
+  // Still potentially todo:
+
+  // int (*xCreateTokenizer)(
+  //   fts5_api *pApi,
+  //   const char *zName,
+  //   void *pContext,
+  //   fts5_tokenizer *pTokenizer,
+  //   void (*xDestroy)(void*)
+  // );
+
+  // /* Find an existing tokenizer */
+  // int (*xFindTokenizer)(
+  //   fts5_api *pApi,
+  //   const char *zName,
+  //   void **ppContext,
+  //   fts5_tokenizer *pTokenizer
+  // );
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java
new file mode 100644
index 0000000000..5e47633baa
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java
@@ -0,0 +1,47 @@
+/*
+** 2023-08-05
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+   JNI-level wrapper for C's fts5_extension_function type.
+*/
+public interface fts5_extension_function {
+  // typedef void (*fts5_extension_function)(
+  //   const Fts5ExtensionApi *pApi,   /* API offered by current FTS version */
+  //   Fts5Context *pFts,              /* First arg to pass to pApi functions */
+  //   sqlite3_context *pCtx,          /* Context for returning result/error */
+  //   int nVal,                       /* Number of values in apVal[] array */
+  //   sqlite3_value **apVal           /* Array of trailing arguments */
+  // );
+
+  /**
+     The callback implementation, corresponding to the xFunction
+     argument of C's fts5_api::xCreateFunction().
+  */
+  void call(Fts5ExtensionApi ext, Fts5Context fCx,
+            sqlite3_context pCx, sqlite3_value argv[]);
+  /**
+     Is called when this function is destroyed by sqlite3. Typically
+     this function will be empty.
+  */
+  void xDestroy();
+
+  public static abstract class Abstract implements fts5_extension_function {
+    @Override public abstract void call(Fts5ExtensionApi ext, Fts5Context fCx,
+                                        sqlite3_context pCx, sqlite3_value argv[]);
+    @Override public void xDestroy(){}
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java
new file mode 100644
index 0000000000..f4ada4dc30
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java
@@ -0,0 +1,49 @@
+/*
+** 2023-08-05
+**
+** 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 is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+   A wrapper for communicating C-level (fts5_tokenizer*) instances with
+   Java. These wrappers do not own their associated pointer, they
+   simply provide a type-safe way to communicate it between Java and C
+   via JNI.
+*/
+public final class fts5_tokenizer extends NativePointerHolder<fts5_tokenizer> {
+  /* Only invoked by JNI */
+  private fts5_tokenizer(){}
+
+  // int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
+  // void (*xDelete)(Fts5Tokenizer*);
+
+  public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags,
+                              @NotNull byte pText[],
+                              @NotNull XTokenizeCallback callback);
+
+
+  // int (*xTokenize)(Fts5Tokenizer*,
+  //     void *pCtx,
+  //     int flags,            /* Mask of FTS5_TOKENIZE_* flags */
+  //     const char *pText, int nText,
+  //     int (*xToken)(
+  //       void *pCtx,         /* Copy of 2nd argument to xTokenize() */
+  //       int tflags,         /* Mask of FTS5_TOKEN_* flags */
+  //       const char *pToken, /* Pointer to buffer containing token */
+  //       int nToken,         /* Size of token in bytes */
+  //       int iStart,         /* Byte offset of token within input text */
+  //       int iEnd            /* Byte offset of end of token within input text */
+  //     )
+  // );
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_api.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_api.java
index 43b3d62ded..d7d2da430d 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_api.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_api.java
@@ -11,11 +11,11 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.*;
 
 /**
-   INCOMPLETE AND COMPLETELY UNTESTED.
-
    A wrapper for communicating C-level (fts5_api*) instances with
    Java. These wrappers do not own their associated pointer, they
    simply provide a type-safe way to communicate it between Java and C
@@ -24,7 +24,8 @@ package org.sqlite.jni;
 public final class fts5_api extends NativePointerHolder<fts5_api> {
   /* Only invoked from JNI */
   private fts5_api(){}
-  public final int iVersion = 2;
+
+  public static final int iVersion = 2;
 
   /**
      Returns the fts5_api instance associated with the given db, or
@@ -32,6 +33,30 @@ public final class fts5_api extends NativePointerHolder<fts5_api> {
   */
   public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db);
 
+  public synchronized native int xCreateFunction(@NotNull String name,
+                                                 @Nullable Object userData,
+                                                 @NotNull fts5_extension_function xFunction);
+
+  /**
+     Convenience overload which passes null as the 2nd argument to the
+     3-parameter form.
+  */
+  public int xCreateFunction(@NotNull String name,
+                             @NotNull fts5_extension_function xFunction){
+    return xCreateFunction(name, null, xFunction);
+  }
+
+  // /* Create a new auxiliary function */
+  // int (*xCreateFunction)(
+  //   fts5_api *pApi,
+  //   const char *zName,
+  //   void *pContext,
+  //   fts5_extension_function xFunction,
+  //   void (*xDestroy)(void*)
+  // );
+
+  // Still potentially todo:
+
   // int (*xCreateTokenizer)(
   //   fts5_api *pApi,
   //   const char *zName,
@@ -48,22 +73,4 @@ public final class fts5_api extends NativePointerHolder<fts5_api> {
   //   fts5_tokenizer *pTokenizer
   // );
 
-  // /* Create a new auxiliary function */
-  // int (*xCreateFunction)(
-  //   fts5_api *pApi,
-  //   const char *zName,
-  //   void *pContext,
-  //   fts5_extension_function xFunction,
-  //   void (*xDestroy)(void*)
-  // );
-
-  public synchronized native int xCreateFunction(@NotNull String name,
-                                    @Nullable Object userData,
-                                    @NotNull fts5_extension_function xFunction);
-
-  public int xCreateFunction(@NotNull String name,
-                             @NotNull fts5_extension_function xFunction){
-    return xCreateFunction(name, null, xFunction);
-  }
-
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_extension_function.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_extension_function.java
index 0e273119f5..5e47633baa 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_extension_function.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_extension_function.java
@@ -11,13 +11,14 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
 
 /**
    JNI-level wrapper for C's fts5_extension_function type.
-
 */
-public abstract class fts5_extension_function {
+public interface fts5_extension_function {
   // typedef void (*fts5_extension_function)(
   //   const Fts5ExtensionApi *pApi,   /* API offered by current FTS version */
   //   Fts5Context *pFts,              /* First arg to pass to pApi functions */
@@ -30,8 +31,17 @@ public abstract class fts5_extension_function {
      The callback implementation, corresponding to the xFunction
      argument of C's fts5_api::xCreateFunction().
   */
-  public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx,
-                                 sqlite3_context pCx, sqlite3_value argv[]);
-  //! Optionally override
-  public void xDestroy(){}
+  void call(Fts5ExtensionApi ext, Fts5Context fCx,
+            sqlite3_context pCx, sqlite3_value argv[]);
+  /**
+     Is called when this function is destroyed by sqlite3. Typically
+     this function will be empty.
+  */
+  void xDestroy();
+
+  public static abstract class Abstract implements fts5_extension_function {
+    @Override public abstract void call(Fts5ExtensionApi ext, Fts5Context fCx,
+                                        sqlite3_context pCx, sqlite3_value argv[]);
+    @Override public void xDestroy(){}
+  }
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java
index 097a0cc055..f4ada4dc30 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java
@@ -11,11 +11,11 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+import org.sqlite.jni.annotation.NotNull;
 
 /**
-   INCOMPLETE AND COMPLETELY UNTESTED.
-
    A wrapper for communicating C-level (fts5_tokenizer*) instances with
    Java. These wrappers do not own their associated pointer, they
    simply provide a type-safe way to communicate it between Java and C
@@ -30,7 +30,7 @@ public final class fts5_tokenizer extends NativePointerHolder<fts5_tokenizer> {
 
   public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags,
                               @NotNull byte pText[],
-                              @NotNull Fts5.xTokenizeCallback callback);
+                              @NotNull XTokenizeCallback callback);
 
 
   // int (*xTokenize)(Fts5Tokenizer*,
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3.java
index cfc6c08d47..901317f0ef 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3.java
@@ -11,7 +11,7 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 /**
    A wrapper for communicating C-level (sqlite3*) instances with
@@ -19,19 +19,25 @@ package org.sqlite.jni;
    simply provide a type-safe way to communicate it between Java
    and C via JNI.
 */
-public final class sqlite3 extends NativePointerHolder<sqlite3> {
+public final class sqlite3 extends NativePointerHolder<sqlite3>
+ implements AutoCloseable {
+
   // Only invoked from JNI
   private sqlite3(){}
 
   public String toString(){
-    long ptr = getNativePointer();
+    final long ptr = getNativePointer();
     if( 0==ptr ){
       return sqlite3.class.getSimpleName()+"@null";
     }
-    String fn = SQLite3Jni.sqlite3_db_filename(this, "main");
+    final String fn = CApi.sqlite3_db_filename(this, "main");
     return sqlite3.class.getSimpleName()
       +"@"+String.format("0x%08x",ptr)
       +"["+((null == fn) ? "<unnamed>" : fn)+"]"
       ;
   }
+
+  @Override public void close(){
+    CApi.sqlite3_close_v2(this.clearNativePointer());
+  }
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java
index d672301378..3b8b71f8a5 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java
@@ -11,7 +11,7 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 /**
    A wrapper for communicating C-level (sqlite3_stmt*) instances with
@@ -19,7 +19,12 @@ package org.sqlite.jni;
    simply provide a type-safe way to communicate it between Java and C
    via JNI.
 */
-public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt> {
+public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt>
+  implements AutoCloseable {
   // Only invoked from JNI.
   private sqlite3_stmt(){}
+
+  @Override public void close(){
+    CApi.sqlite3_finalize(this.clearNativePointer());
+  }
 }
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3_value.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3_value.java
index 2cfb32ff1a..a4772f0f63 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3_value.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/sqlite3_value.java
@@ -11,7 +11,7 @@
 *************************************************************************
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
-package org.sqlite.jni;
+package org.sqlite.jni.capi;
 
 public final class sqlite3_value extends NativePointerHolder<sqlite3_value> {
   //! Invoked only from JNI.
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/test-script-interpreter.md b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/test-script-interpreter.md
new file mode 100644
index 0000000000..a51d64d107
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/test-script-interpreter.md
@@ -0,0 +1,269 @@
+# Specifications For A Rudimentary SQLite Test Script Interpreter
+
+## Overview
+
+The purpose of the Test Script Interpreter is to read and interpret
+script files that contain SQL commands and desired results.  The
+interpreter will check results and report an discrepencies found.
+
+The test script files are ASCII text files.  The filename always ends with
+".test".  Each script is evaluated independently; context does not carry
+forward from one script to the next.  So, for example, the --null command
+run in one test script does not cause any changes in the behavior of
+subsequent test scripts.  All open database connections are closed
+at the end of each test script.  All database files created by a test
+script are deleted when the script finishes.
+
+## Parsing Rules:
+
+  1.   The test script is read line by line, where a line is a sequence of
+       characters that runs up to the next '\\n' (0x0a) character or until
+       the end of the file.  There is never a need to read ahead past the
+       end of the current line.
+
+  2.   If any line contains the string " MODULE_NAME:" (with a space before
+       the initial "M") or "MIXED_MODULE_NAME:" then that test script is
+       incompatible with this spec.  Processing of the test script should
+       end immediately.  There is no need to read any more of the file.
+       In verbose mode, the interpreter might choose to emit an informational
+       messages saying that the test script was abandoned due to an
+       incompatible module type.
+
+  3.   If any line contains the string "SCRIPT_MODULE_NAME:" then the input
+       script is known to be of the correct type for this specification and
+       processing may continue.  The "MODULE_NAME" checking in steps 2 and 3
+       may optionally be discontinued after sighting a "SCRIPT_MODULE_NAME".
+
+  4.   If any line contains "REQUIRED_PROPERTIES:" and that substring is followed
+       by any non-whitespace text, then the script is not compatible with this
+       spec.  Processing should stop immediately.  In verbose mode, the
+       interpreter might choose to emit an information message saying that the
+       test script was abandoned due to unsupported requirement properties.
+
+  5.   If any line begins with the "\|" (0x7c) character, that indicates that
+       the input script is not compatible with this specification.  Processing
+       of the script should stop immediately.  In verbose mode, the interpreter
+       might choose to emit an informational message indicating that the
+       test script was abandoned because it contained "a dbtotxt format database
+       specification".
+
+  6.   Any line that begins with "#" is a C-preprocessor line.  The interpreter
+       described by this spec does not know how to deal with C-preprocessor lines.
+       Hence, processing should be abandoned.  In verbose mode, the interpreter
+       might emit an informational message similar to
+       "script NAME abandoned due to C-preprocessor line: ..."
+
+  7.   If a line begins with exactly two minus signs followed by a
+       lowercase letter, that is a command.  Process commands as described
+       below.
+
+  8.   All other lines should be accumulated into the "input buffer".
+       The various commands will have access to this input buffer.
+       Some commands will reset the buffer.
+
+## Initialization
+
+The initial state of the interpreter at the start of processing each script
+is as if the following command sequence had been run:
+
+> ~~~
+--close all
+--db 0
+--new test.db
+--null nil
+~~~
+
+In words, all database connections are closed except for connection 0 (the
+default) which is open on an empty database named "test.db".  The string
+"nil" is displayed for NULL column values.
+
+The only context carried forward after the evaluation of one test script
+into the evaluation of the next test script is the count of the number of
+tests run and the number of failures seen.
+
+## Commands:
+
+Each command looks like an SQL comment.  The command begins at the left
+margin (no leading space) and starts with exactly 2 minus signs ("-").
+The command name consists of lowercase letters and maybe a "-" or two.
+Some commands have arguments.
+The arguments are separated from the command name by one or more spaces.
+
+Commands have access to the input buffer and might reset the input buffer.
+The command can also optionally read (and consume) additional text from
+script that comes after the command.
+
+Unknown or unrecognized commands indicate that the script contains features
+that are not (yet) supported by this specification.  Processing of the
+script should terminate immediately.  When this happens and when the
+interpreter is in a "verbose" mode, the interpreter might choose to emit
+an informational message along the lines of "test script NAME abandoned
+due to unsupported command: --whatever".
+
+The initial implemention will only recognize a few commands.  Other
+commands may be added later.  The following is the initial set of
+commands:
+
+### The --testcase command
+
+Every test case starts with a --testcase command.  The --testcase
+command resets both the "input buffer" and the "result buffer".  The
+argument to the --testcase command is the name of the test case.  That
+test case name is used for logging and debugging and when printing
+errors. The input buffer is set to the body of the test case.
+
+### The --result command
+
+The --result command tries to execute the text in the input buffer as SQL.
+For each row of result coming out of this SQL, the text of that result is
+appended to the "result buffer".  If a result row contains multiple columns,
+the columns are processed from left to right.  For each column, text is
+appended to the result buffer according to the following rules:
+
+  *   If the result buffer already contains some text, append a space.
+      (In this way, all column values and all row values are separated from
+      each other by a single space.)
+
+  *   If sqlite3_column_text() returns NULL, then append "nil" - or
+      some other text that is specified by the --null command - and skip
+      all subsequent rules.
+
+  *   If sqlite3_column_text() is an empty string, append `{}` to the
+      result buffer and skip all subsequent rules.
+
+  *   If sqlite3_column_text() does not contain any special
+      characters, append it to the result buffer without any
+      formatting and skip all subsequent rules. Special characters are:
+      0x00 to 0x20 (inclusive), double-quote (0x22), backslash (0x5c),
+      curly braces (0x7b and 0x7d).
+
+  *   If sqlite3_column_text() does not contains curly braces, then put
+      the text inside of `{...}` and append it and skip all subsequent rules.
+
+  *   Append the text within double-quotes (`"..."`) and within the text
+      escape '"' and '\\' by prepending a single '\\' and escape any
+      control characters (characters less than 0x20) using octal notation:
+      '\\NNN'.
+
+If an error is encountered while running the SQL, then append the
+symbolic C-preprocessor name for the error
+code (ex: "SQLITE_CONSTRAINT") as if it were a column value.  Then append
+the error message text as if it where a column value.  Then stop processing.
+
+After the SQL text has been run, compare the content of the result buffer
+against the argument to the --result command and report a testing error if
+there are any differences.
+
+The --result command resets the input buffer, but it does not reset
+the result buffer.  This distinction does not matter for the --result
+command itself, but it is important for related commands like --glob
+and --notglob.  Sometimes test cases will contains a bunch of SQL
+followed by multiple --glob and/or --notglob statements.  All of the
+globs should be evaluted agains the result buffer correct, but the SQL
+should only be run once.  This is accomplished by resetting the input
+buffer but not the result buffer.
+
+### The --glob command
+
+The --glob command works just like --result except that the argument to
+--glob is interpreted as a TEST-GLOB pattern and the results are compared
+using that glob pattern rather than using strcmp().  Other than that,
+the two operate the same.
+
+The TEST-GLOB pattern is slightly different for a standard GLOB:
+
+   *    The '*' character matches zero or more characters.
+
+   *    The '?' character matches any single character
+
+   *    The '[...]' character sequence machines a single character
+        in between the brackets.
+
+   *    The '#' character matches one or more digits  (This is the main
+        difference between standard unix-glob and TEST-GLOB.  unix-glob
+        does not have this feature.  It was added to because it comes
+        up a lot during SQLite testing.)
+
+### The --notglob command
+
+The --notglob command works just like --glob except that it reports an
+error if the GLOB does match, rather than if the GLOB does not matches.
+
+### The --oom command
+
+This command is to be used for out-of-memory testing.  It means that
+OOM errors should be simulated to ensure that SQLite is able to deal with
+them.  This command can be silently ignored for now.  We might add support
+for this later.
+
+### The --tableresult command
+
+The --tableresult command works like --glob except that the GLOB pattern
+to be matched is taken from subsequent lines of the input script up to
+the next --end.  Every span of one or more whitespace characters in this
+pattern text is collapsed into a single space (0x20).
+Leading and trailing whitespace are removed from the pattern.
+The --end that ends the GLOB pattern is not part of the GLOB pattern, but
+the --end is consumed from the script input.
+
+### The --new and --open commands
+
+The --new and --open commands cause a database file to be opened.
+The name of the file is the argument to the command.  The --new command
+opens an initially empty database (it deletes the file before opening it)
+whereas the --open command opens an existing database if it already
+exists.
+
+### The --db command
+
+The script interpreter can have up to 7 different SQLite database
+connections open at a time.  The --db command is used to switch between
+them.  The argument to --db is an integer between 0 and 6 that selects
+which database connection to use moving forward.
+
+### The --close command
+
+The --close command causes an existing database connection to close.
+This command is a no-op if the database connection is not currently
+open.  There can be up to 7 different database connections, numbered 0
+through 6.  The number of the database connection to close is an
+argument to the --close command, which will fail if an out-of-range
+value is provided.  Or if the argument to --close is "all" then all
+open database connections are closed. If passed no argument, the
+currently-active database is assumed.
+
+### The --null command
+
+The NULL command changes the text that is used to represent SQL NULL
+values in the result buffer.
+
+### The --run command
+
+The --run command executes text in the input buffer as if it where SQL.
+However, nothing is added to the result buffer.  Any output from the SQL
+is silently ignored. Errors in the SQL are silently ignored.
+
+The --run command normally executes the SQL in the current database
+connection.  However, if --run has an argument that is an integer between
+0 and 6 then the SQL is run in the alternative database connection specified
+by that argument.
+
+### The --json and --json-block commands
+
+The --json and --json-block commands work like --result and --tableresult,
+respectively.  The difference is that column values are appended to the
+result buffer literally, without ever enclosing the values in `{...}` or
+`"..."` and without escaping any characters in the column value and comparison
+is always an exact strcmp() not a GLOB.
+
+### The --print command
+
+The --print command emits both its arguments and its body (if any) to
+stdout, indenting each line of output.
+
+### The --column-names command
+
+The --column-names command requires 0 or 1 as an argument, to disable
+resp.  enable it, and modifies SQL execution to include column names
+in output. When this option is on, each column value emitted gets
+prefixed by its column name, with a single space between them.
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/tester/SQLTester.java
index ffdb867d9b..81d6106be7 100644
--- a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/tester/SQLTester.java
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/tester/SQLTester.java
@@ -12,16 +12,13 @@
 ** This file contains the main application entry pointer for the
 ** SQLTester framework.
 */
-package org.sqlite.jni.tester;
+package org.sqlite.jni.capi;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.nio.charset.StandardCharsets;
 import java.util.regex.*;
-import org.sqlite.jni.*;
-import static org.sqlite.jni.SQLite3Jni.*;
-import org.sqlite.jni.sqlite3;
-
+import static org.sqlite.jni.capi.CApi.*;
 
 /**
    Modes for how to escape (or not) column values and names from
@@ -150,14 +147,17 @@ class Outer {
 }
 
 /**
-   This class provides an application which aims to implement the
+   <p>This class provides an application which aims to implement the
    rudimentary SQL-driven test tool described in the accompanying
-   test-script-interpreter.md.
+   {@code test-script-interpreter.md}.
 
-   This is a work in progress.
+   <p>This class is an internal testing tool, not part of the public
+   interface but is (A) in the same package as the library because
+   access permissions require it to be so and (B) the JDK8 javadoc
+   offers no way to filter individual classes out of the doc
+   generation process (it can only exclude packages, but see (A)).
 
-
-   An instance of this application provides a core set of services
+   <p>An instance of this application provides a core set of services
    which TestScript instances use for processing testing logic.
    TestScripts, in turn, delegate the concrete test work to Command
    objects, which the TestScript parses on their behalf.
@@ -181,7 +181,7 @@ public class SQLTester {
   private int nTestFile = 0;
   //! Number of scripts which were aborted.
   private int nAbortedScript = 0;
-  //! Per-script test counter.
+  //! Incremented by test case handlers
   private int nTest = 0;
   //! True to enable column name output from execSql()
   private boolean emitColNames;
@@ -250,14 +250,14 @@ public class SQLTester {
   }
 
   public void runTests() throws Exception {
-    final long tStart = System.nanoTime();
+    final long tStart = System.currentTimeMillis();
     for(String f : listInFiles){
       reset();
       ++nTestFile;
       final TestScript ts = new TestScript(f);
       outln(nextStartEmoji(), " starting [",f,"]");
       boolean threw = false;
-      final long timeStart = System.nanoTime();
+      final long timeStart = System.currentTimeMillis();
       try{
         ts.run(this);
       }catch(SQLTesterException e){
@@ -267,14 +267,13 @@ public class SQLTester {
         if( keepGoing ) outln("Continuing anyway becaure of the keep-going option.");
         else if( e.isFatal() ) throw e;
       }finally{
-        final long timeEnd = System.nanoTime();
+        final long timeEnd = System.currentTimeMillis();
         outln("🏁",(threw ? "❌" : "βœ…")," ",nTest," test(s) in ",
-              ((timeEnd-timeStart)/1000000.0),"ms.");
-        //ts.getFilename());
+              (timeEnd-timeStart),"ms.");
       }
     }
-    final long tEnd = System.nanoTime();
-    outln("Total run-time: ",((tEnd-tStart)/1000000.0),"ms");
+    final long tEnd = System.currentTimeMillis();
+    outln("Total run-time: ",(tEnd-tStart),"ms");
     Util.unlink(initialDbName);
   }
 
@@ -336,7 +335,9 @@ public class SQLTester {
   }
 
   sqlite3 setCurrentDb(int n) throws Exception{
-    return affirmDbId(n).aDb[n];
+    affirmDbId(n);
+    iCurrentDb = n;
+    return this.aDb[n];
   }
 
   sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; }
@@ -399,7 +400,7 @@ public class SQLTester {
     nullView = "nil";
     emitColNames = false;
     iCurrentDb = 0;
-    dbInitSql.append("SELECT 1;");
+    //dbInitSql.append("SELECT 1;");
   }
 
   void setNullValue(String v){nullView = v;}
@@ -456,7 +457,7 @@ public class SQLTester {
   }
 
   private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){
-    sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)).append(' ');
+    sb.append(org.sqlite.jni.capi.ResultCode.getEntryForInt(rc)).append(' ');
     final String msg = escapeSqlValue(sqlite3_errmsg(db));
     if( '{' == msg.charAt(0) ){
       sb.append(msg);
@@ -474,12 +475,12 @@ public class SQLTester {
      the db's result code.
 
      appendMode specifies how/whether to append results to the result
-     buffer. lineMode specifies whether to output all results in a
+     buffer. rowMode specifies whether to output all results in a
      single line or one line per row. If appendMode is
-     ResultBufferMode.NONE then lineMode is ignored and may be null.
+     ResultBufferMode.NONE then rowMode is ignored and may be null.
   */
   public int execSql(sqlite3 db, boolean throwOnError,
-                     ResultBufferMode appendMode, ResultRowMode lineMode,
+                     ResultBufferMode appendMode, ResultRowMode rowMode,
                      String sql) throws SQLTesterException {
     if( null==db && null==aDb[0] ){
       // Delay opening of the initial db to enable tests to change its
@@ -561,7 +562,7 @@ public class SQLTester {
                   throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
               }
             }
-            if( ResultRowMode.NEWLINE == lineMode ){
+            if( ResultRowMode.NEWLINE == rowMode ){
               spacing = 0;
               sb.append('\n');
             }
@@ -580,6 +581,10 @@ public class SQLTester {
         }
       }
     }finally{
+      sqlite3_reset(stmt
+        /* In order to trigger an exception in the
+           INSERT...RETURNING locking scenario:
+           https://sqlite.org/forum/forumpost/36f7a2e7494897df */);
       sqlite3_finalize(stmt);
     }
     if( 0!=rc && throwOnError ){
@@ -609,9 +614,9 @@ public class SQLTester {
       }
       t.addTestScript(a);
     }
-    final AutoExtension ax = new AutoExtension() {
+    final AutoExtensionCallback ax = new AutoExtensionCallback() {
         private final SQLTester tester = t;
-        public int xEntryPoint(sqlite3 db){
+        @Override public int call(sqlite3 db){
           final String init = tester.getDbInitSql();
           if( !init.isEmpty() ){
             tester.execSql(db, true, ResultBufferMode.NONE, null, init);
@@ -629,7 +634,7 @@ public class SQLTester {
         t.outln("Aborted ",t.nAbortedScript," script(s).");
       }
       if( dumpInternals ){
-        sqlite3_do_something_for_developer();
+        sqlite3_jni_internal_details();
       }
     }
   }
@@ -663,7 +668,7 @@ public class SQLTester {
   static {
     System.loadLibrary("sqlite3-jni")
       /* Interestingly, when SQLTester is the main app, we have to
-         load that lib from here. The same load from SQLite3Jni does
+         load that lib from here. The same load from CApi does
          not happen early enough. Without this,
          installCustomExtensions() is an unresolved symbol. */;
   }
@@ -924,8 +929,8 @@ class RunCommand extends Command {
     final sqlite3 db = (1==argv.length)
       ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) );
     final String sql = t.takeInputBuffer();
-    int rc = t.execSql(db, false, ResultBufferMode.NONE,
-                       ResultRowMode.ONELINE, sql);
+    final int rc = t.execSql(db, false, ResultBufferMode.NONE,
+                             ResultRowMode.ONELINE, sql);
     if( 0!=rc && t.isVerbose() ){
       String msg = sqlite3_errmsg(db);
       ts.verbose1(argv[0]," non-fatal command error #",rc,": ",
@@ -948,8 +953,7 @@ class TableResultCommand extends Command {
     if( !body.endsWith("\n--end") ){
       ts.toss(argv[0], " must be terminated with --end.");
     }else{
-      int n = body.length();
-      body = body.substring(0, n-6);
+      body = body.substring(0, body.length()-6);
     }
     final String[] globs = body.split("\\s*\\n\\s*");
     if( globs.length < 1 ){
@@ -1124,8 +1128,9 @@ class TestScript {
   }
 
   public String getOutputPrefix(){
-    String rc =  "["+(moduleName==null ? filename : moduleName)+"]";
+    String rc = "["+(moduleName==null ? "<unnamed>" : moduleName)+"]";
     if( null!=testCaseName ) rc += "["+testCaseName+"]";
+    if( null!=filename ) rc += "["+filename+"]";
     return rc + " line "+ cur.lineNo;
   }
 
@@ -1238,14 +1243,15 @@ class TestScript {
     final int oldPB = cur.putbackPos;
     final int oldPBL = cur.putbackLineNo;
     final int oldLine = cur.lineNo;
-    final String rc = getLine();
-    cur.peekedPos = cur.pos;
-    cur.peekedLineNo = cur.lineNo;
-    cur.pos = oldPos;
-    cur.lineNo = oldLine;
-    cur.putbackPos = oldPB;
-    cur.putbackLineNo = oldPBL;
-    return rc;
+    try{ return getLine(); }
+    finally{
+      cur.peekedPos = cur.pos;
+      cur.peekedLineNo = cur.lineNo;
+      cur.pos = oldPos;
+      cur.lineNo = oldLine;
+      cur.putbackPos = oldPB;
+      cur.putbackLineNo = oldPBL;
+    }
   }
 
   /**
@@ -1268,6 +1274,7 @@ class TestScript {
   }
 
   private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{
+    if( true ) return false;
     int nOk = 0;
     for(String rp : props){
       verbose1("REQUIRED_PROPERTIES: ",rp);
@@ -1288,6 +1295,12 @@ class TestScript {
           t.appendDbInitSql("pragma temp_store=0;");
           ++nOk;
           break;
+        case "AUTOVACUUM":
+          t.appendDbInitSql("pragma auto_vacuum=full;");
+          ++nOk;
+        case "INCRVACUUM":
+          t.appendDbInitSql("pragma auto_vacuum=incremental;");
+          ++nOk;
         default:
           break;
       }
@@ -1326,9 +1339,9 @@ class TestScript {
     m = patternRequiredProperties.matcher(line);
     if( m.find() ){
       final String rp = m.group(1);
-      //if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
-      throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
-      //}
+      if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
+        throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
+      }
     }
     m = patternMixedModuleName.matcher(line);
     if( m.find() ){
@@ -1372,11 +1385,10 @@ class TestScript {
     String line;
     while( (null != (line = peekLine())) ){
       checkForDirective(tester, line);
-      if( !isCommandLine(line, true) ){
+      if( isCommandLine(line, true) ) break;
+      else {
         sb.append(line).append("\n");
         consumePeeked();
-      }else{
-        break;
       }
     }
     line = sb.toString();
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
new file mode 100644
index 0000000000..173d775e62
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
@@ -0,0 +1,82 @@
+/*
+** 2023-10-16
+**
+** 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 is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+   EXPERIMENTAL/INCOMPLETE/UNTESTED
+
+   A SqlFunction implementation for aggregate functions. The T type
+   represents the type of data accumulated by this aggregate while it
+   works. e.g. a SUM()-like UDF might use Integer or Long and a
+   CONCAT()-like UDF might use a StringBuilder or a List<String>.
+*/
+public abstract class AggregateFunction<T> implements SqlFunction  {
+
+  /**
+     As for the xStep() argument of the C API's
+     sqlite3_create_function().  If this function throws, the
+     exception is reported via sqlite3_result_error().
+  */
+  public abstract void xStep(SqlFunction.Arguments args);
+
+  /**
+     As for the xFinal() argument of the C API's
+     sqlite3_create_function(). If this function throws, it is
+     translated into sqlite3_result_error().
+
+     Note that the passed-in object will not actually contain any
+     arguments for xFinal() but will contain the context object needed
+     for setting the call's result or error state.
+  */
+  public abstract void xFinal(SqlFunction.Arguments args);
+
+  /**
+     Optionally override to be notified when the UDF is finalized by
+     SQLite.
+  */
+  public void xDestroy() {}
+
+  /** Per-invocation state for the UDF. */
+  private final SqlFunction.PerContextState<T> map =
+    new SqlFunction.PerContextState<>();
+
+  /**
+     To be called from the implementation's xStep() method, as well
+     as the xValue() and xInverse() methods of the {@link WindowFunction}
+     subclass, to fetch the current per-call UDF state. On the
+     first call to this method for any given sqlite3_context
+     argument, the context is set to the given initial value. On all other
+     calls, the 2nd argument is ignored.
+
+     @see SQLFunction.PerContextState#getAggregateState
+  */
+  protected final ValueHolder<T> getAggregateState(SqlFunction.Arguments args, T initialValue){
+    return map.getAggregateState(args, initialValue);
+  }
+
+  /**
+     To be called from the implementation's xFinal() method to fetch
+     the final state of the UDF and remove its mapping.
+
+     see SQLFunction.PerContextState#takeAggregateState
+  */
+  protected final T takeAggregateState(SqlFunction.Arguments args){
+    return map.takeAggregateState(args);
+  }
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java
new file mode 100644
index 0000000000..067a6983eb
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java
@@ -0,0 +1,37 @@
+/*
+** 2023-10-16
+**
+** 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 is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+   The SqlFunction type for scalar SQL functions.
+*/
+public abstract class ScalarFunction implements SqlFunction  {
+  /**
+     As for the xFunc() argument of the C API's
+     sqlite3_create_function(). If this function throws, it is
+     translated into an sqlite3_result_error().
+  */
+  public abstract void xFunc(SqlFunction.Arguments args);
+
+  /**
+     Optionally override to be notified when the UDF is finalized by
+     SQLite. This default implementation does nothing.
+  */
+  public void xDestroy() {}
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
new file mode 100644
index 0000000000..d6acda5aa5
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
@@ -0,0 +1,301 @@
+/*
+** 2023-10-16
+**
+** 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 is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+   Base marker interface for SQLite's three types of User-Defined SQL
+   Functions (UDFs): Scalar, Aggregate, and Window functions.
+*/
+public interface SqlFunction  {
+
+  /**
+     The Arguments type is an abstraction on top of the lower-level
+     UDF function argument types. It provides _most_ of the functionality
+     of the lower-level interface, insofar as possible without "leaking"
+     those types into this API.
+  */
+  public final static class Arguments implements Iterable<SqlFunction.Arguments.Arg>{
+    private final sqlite3_context cx;
+    private final sqlite3_value args[];
+    public final int length;
+
+    /**
+       Must be passed the context and arguments for the UDF call this
+       object is wrapping. Intended to be used by internal proxy
+       classes which "convert" the lower-level interface into this
+       package's higher-level interface, e.g. ScalarAdapter and
+       AggregateAdapter.
+
+       Passing null for the args is equivalent to passing a length-0
+       array.
+    */
+    Arguments(sqlite3_context cx, sqlite3_value args[]){
+      this.cx = cx;
+      this.args = args==null ? new sqlite3_value[0] : args;;
+      this.length = this.args.length;
+    }
+
+    /**
+       Wrapper for a single SqlFunction argument. Primarily intended
+       for use with the Arguments class's Iterable interface.
+    */
+    public final static class Arg {
+      private final Arguments a;
+      private final int ndx;
+      /* Only for use by the Arguments class. */
+      private Arg(Arguments a, int ndx){
+        this.a = a;
+        this.ndx = ndx;
+      }
+      /** Returns this argument's index in its parent argument list. */
+      public int getIndex(){return ndx;}
+      public int getInt(){return a.getInt(ndx);}
+      public long getInt64(){return a.getInt64(ndx);}
+      public double getDouble(){return a.getDouble(ndx);}
+      public byte[] getBlob(){return a.getBlob(ndx);}
+      public byte[] getText(){return a.getText(ndx);}
+      public String getText16(){return a.getText16(ndx);}
+      public int getBytes(){return a.getBytes(ndx);}
+      public int getBytes16(){return a.getBytes16(ndx);}
+      public Object getObject(){return a.getObject(ndx);}
+      public <T> T getObjectCasted(Class<T> type){ return a.getObjectCasted(ndx, type); }
+      public int getType(){return a.getType(ndx);}
+      public Object getAuxData(){return a.getAuxData(ndx);}
+      public void setAuxData(Object o){a.setAuxData(ndx, o);}
+    }
+
+    @Override
+    public java.util.Iterator<SqlFunction.Arguments.Arg> iterator(){
+      final Arg[] proxies = new Arg[args.length];
+      for( int i = 0; i < args.length; ++i ){
+        proxies[i] = new Arg(this, i);
+      }
+      return java.util.Arrays.stream(proxies).iterator();
+    }
+
+    /**
+       Returns the sqlite3_value at the given argument index or throws
+       an IllegalArgumentException exception if ndx is out of range.
+    */
+    private sqlite3_value valueAt(int ndx){
+      if(ndx<0 || ndx>=args.length){
+        throw new IllegalArgumentException(
+          "SQL function argument index "+ndx+" is out of range."
+        );
+      }
+      return args[ndx];
+    }
+
+    sqlite3_context getContext(){return cx;}
+
+    public int getArgCount(){ return args.length; }
+
+    public int getInt(int arg){return CApi.sqlite3_value_int(valueAt(arg));}
+    public long getInt64(int arg){return CApi.sqlite3_value_int64(valueAt(arg));}
+    public double getDouble(int arg){return CApi.sqlite3_value_double(valueAt(arg));}
+    public byte[] getBlob(int arg){return CApi.sqlite3_value_blob(valueAt(arg));}
+    public byte[] getText(int arg){return CApi.sqlite3_value_text(valueAt(arg));}
+    public String getText16(int arg){return CApi.sqlite3_value_text16(valueAt(arg));}
+    public int getBytes(int arg){return CApi.sqlite3_value_bytes(valueAt(arg));}
+    public int getBytes16(int arg){return CApi.sqlite3_value_bytes16(valueAt(arg));}
+    public Object getObject(int arg){return CApi.sqlite3_value_java_object(valueAt(arg));}
+    public <T> T getObjectCasted(int arg, Class<T> type){
+      return CApi.sqlite3_value_java_casted(valueAt(arg), type);
+    }
+
+    public int getType(int arg){return CApi.sqlite3_value_type(valueAt(arg));}
+    public int getSubtype(int arg){return CApi.sqlite3_value_subtype(valueAt(arg));}
+    public int getNumericType(int arg){return CApi.sqlite3_value_numeric_type(valueAt(arg));}
+    public int getNoChange(int arg){return CApi.sqlite3_value_nochange(valueAt(arg));}
+    public boolean getFromBind(int arg){return CApi.sqlite3_value_frombind(valueAt(arg));}
+    public int getEncoding(int arg){return CApi.sqlite3_value_encoding(valueAt(arg));}
+
+    public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); }
+    public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); }
+    public void resultDouble(double v){ CApi.sqlite3_result_double(cx, v); }
+    public void resultError(String msg){CApi.sqlite3_result_error(cx, msg);}
+    public void resultError(Exception e){CApi.sqlite3_result_error(cx, e);}
+    public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);}
+    public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
+    public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);}
+    public void resultNull(){CApi.sqlite3_result_null(cx);}
+    public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
+    public void resultZeroBlob(long n){
+      // Throw on error? If n is too big,
+      // sqlite3_result_error_toobig() is automatically called.
+      CApi.sqlite3_result_zeroblob64(cx, n);
+    }
+
+    public void resultBlob(byte[] blob){CApi.sqlite3_result_blob(cx, blob);}
+    public void resultText(byte[] utf8){CApi.sqlite3_result_text(cx, utf8);}
+    public void resultText(String txt){CApi.sqlite3_result_text(cx, txt);}
+    public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);}
+    public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);}
+
+    public void setAuxData(int arg, Object o){
+      /* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html
+
+         The value of the N parameter to these interfaces should be
+         non-negative. Future enhancements may make use of negative N
+         values to define new kinds of function caching behavior.
+      */
+      valueAt(arg);
+      CApi.sqlite3_set_auxdata(cx, arg, o);
+    }
+
+    public Object getAuxData(int arg){
+      valueAt(arg);
+      return CApi.sqlite3_get_auxdata(cx, arg);
+    }
+  }
+
+  /**
+     PerContextState assists aggregate and window functions in
+     managing their accumulator state across calls to the UDF's
+     callbacks.
+
+     <p>T must be of a type which can be legally stored as a value in
+     java.util.HashMap<KeyType,T>.
+
+     <p>If a given aggregate or window function is called multiple times
+     in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
+     then the clients need some way of knowing which call is which so
+     that they can map their state between their various UDF callbacks
+     and reset it via xFinal(). This class takes care of such
+     mappings.
+
+     <p>This class works by mapping
+     sqlite3_context.getAggregateContext() to a single piece of
+     state, of a client-defined type (the T part of this class), which
+     persists across a "matching set" of the UDF's callbacks.
+
+     <p>This class is a helper providing commonly-needed functionality
+     - it is not required for use with aggregate or window functions.
+     Client UDFs are free to perform such mappings using custom
+     approaches. The provided {@link AggregateFunction} and {@link
+     WindowFunction} classes use this.
+  */
+  public static final class PerContextState<T> {
+    private final java.util.Map<Long,ValueHolder<T>> map
+      = new java.util.HashMap<>();
+
+    /**
+       Should be called from a UDF's xStep(), xValue(), and xInverse()
+       methods, passing it that method's first argument and an initial
+       value for the persistent state. If there is currently no
+       mapping for the given context within the map, one is created
+       using the given initial value, else the existing one is used
+       and the 2nd argument is ignored.  It returns a ValueHolder<T>
+       which can be used to modify that state directly without
+       requiring that the client update the underlying map's entry.
+
+       <p>The caller is obligated to eventually call
+       takeAggregateState() to clear the mapping.
+    */
+    public ValueHolder<T> getAggregateState(SqlFunction.Arguments args, T initialValue){
+      final Long key = args.getContext().getAggregateContext(true);
+      ValueHolder<T> rc = null==key ? null : map.get(key);
+      if( null==rc ){
+        map.put(key, rc = new ValueHolder<>(initialValue));
+      }
+      return rc;
+    }
+
+    /**
+       Should be called from a UDF's xFinal() method and passed that
+       method's first argument. This function removes the value
+       associated with with the arguments' aggregate context from the
+       map and returns it, returning null if no other UDF method has
+       been called to set up such a mapping. The latter condition will
+       be the case if a UDF is used in a statement which has no result
+       rows.
+    */
+    public T takeAggregateState(SqlFunction.Arguments args){
+      final ValueHolder<T> h = map.remove(args.getContext().getAggregateContext(false));
+      return null==h ? null : h.value;
+    }
+  }
+
+  /**
+     Internal-use adapter for wrapping this package's ScalarFunction
+     for use with the org.sqlite.jni.capi.ScalarFunction interface.
+  */
+  static final class ScalarAdapter extends org.sqlite.jni.capi.ScalarFunction {
+    final ScalarFunction impl;
+    ScalarAdapter(ScalarFunction impl){
+      this.impl = impl;
+    }
+    /**
+       Proxies this.impl.xFunc(), adapting the call arguments to that
+       function's signature. If the proxy throws, it's translated to
+       sqlite_result_error() with the exception's message.
+    */
+    public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+      try{
+        impl.xFunc( new SqlFunction.Arguments(cx, args) );
+      }catch(Exception e){
+        CApi.sqlite3_result_error(cx, e);
+      }
+    }
+
+    public void xDestroy(){
+      impl.xDestroy();
+    }
+  }
+
+  /**
+     Internal-use adapter for wrapping this package's AggregateFunction
+     for use with the org.sqlite.jni.capi.AggregateFunction interface.
+  */
+  static final class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction {
+    final AggregateFunction impl;
+    AggregateAdapter(AggregateFunction impl){
+      this.impl = impl;
+    }
+
+    /**
+       Proxies this.impl.xStep(), adapting the call arguments to that
+       function's signature. If the proxied function throws, it is
+       translated to sqlite_result_error() with the exception's
+       message.
+    */
+    public void xStep(sqlite3_context cx, sqlite3_value[] args){
+      try{
+        impl.xStep( new SqlFunction.Arguments(cx, args) );
+      }catch(Exception e){
+        CApi.sqlite3_result_error(cx, e);
+      }
+    }
+
+    /**
+       As for the xFinal() argument of the C API's sqlite3_create_function().
+       If the proxied function throws, it is translated into a sqlite3_result_error().
+    */
+    public void xFinal(sqlite3_context cx){
+      try{
+        impl.xFinal( new SqlFunction.Arguments(cx, null) );
+      }catch(Exception e){
+        CApi.sqlite3_result_error(cx, e);
+      }
+    }
+
+    public void xDestroy(){
+      impl.xDestroy();
+    }
+  }
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
new file mode 100644
index 0000000000..bcf97b2394
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
@@ -0,0 +1,218 @@
+/*
+** 2023-10-09
+**
+** 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 is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import java.nio.charset.StandardCharsets;
+import static org.sqlite.jni.capi.CApi.*;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.capi.sqlite3;
+import org.sqlite.jni.capi.sqlite3_stmt;
+import org.sqlite.jni.capi.OutputPointer;
+
+/**
+   This class represents a database connection, analog to the C-side
+   sqlite3 class but with added argument validation, exceptions, and
+   similar "smoothing of sharp edges" to make the API safe to use from
+   Java. It also acts as a namespace for other types for which
+   individual instances are tied to a specific database connection.
+*/
+public final class Sqlite implements AutoCloseable  {
+  private sqlite3 db;
+
+  //! Used only by the open() factory functions.
+  private Sqlite(sqlite3 db){
+    this.db = db;
+  }
+
+  /**
+     Returns a newly-opened db connection or throws SqliteException if
+     opening fails. All arguments are as documented for
+     sqlite3_open_v2().
+
+     Design question: do we want static factory functions or should
+     this be reformulated as a constructor?
+  */
+  public static Sqlite open(String filename, int flags, String vfsName){
+    final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+    final int rc = sqlite3_open_v2(filename, out, flags, vfsName);
+    final sqlite3 n = out.take();
+    if( 0!=rc ){
+      if( null==n ) throw new SqliteException(rc);
+      final SqliteException ex = new SqliteException(n);
+      n.close();
+      throw ex;
+    }
+    return new Sqlite(n);
+  }
+
+  public static Sqlite open(String filename, int flags){
+    return open(filename, flags, null);
+  }
+
+  public static Sqlite open(String filename){
+    return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null);
+  }
+
+  @Override public void close(){
+    if(null!=this.db){
+      this.db.close();
+      this.db = null;
+    }
+  }
+
+  /**
+     Returns this object's underlying native db handle, or null if
+     this instance has been closed. This is very specifically not
+     public.
+  */
+  sqlite3 nativeHandle(){ return this.db; }
+
+  private sqlite3 affirmOpen(){
+    if( null==db || 0==db.getNativePointer() ){
+      throw new IllegalArgumentException("This database instance is closed.");
+    }
+    return this.db;
+  }
+
+  // private byte[] stringToUtf8(String s){
+  //   return s==null ? null : s.getBytes(StandardCharsets.UTF_8);
+  // }
+
+  private void affirmRcOk(int rc){
+    if( 0!=rc ){
+      throw new SqliteException(db);
+    }
+  }
+
+  /**
+     Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to
+     create new instances.
+  */
+  public final class Stmt implements AutoCloseable {
+    private Sqlite _db = null;
+    private sqlite3_stmt stmt = null;
+    /** Only called by the prepare() factory functions. */
+    Stmt(Sqlite db, sqlite3_stmt stmt){
+      this._db = db;
+      this.stmt = stmt;
+    }
+
+    sqlite3_stmt nativeHandle(){
+      return stmt;
+    }
+
+    private sqlite3_stmt affirmOpen(){
+      if( null==stmt || 0==stmt.getNativePointer() ){
+        throw new IllegalArgumentException("This Stmt has been finalized.");
+      }
+      return stmt;
+    }
+
+    /**
+       Corresponds to sqlite3_finalize(), but we cannot override the
+       name finalize() here because this one requires a different
+       signature. It does not throw on error here because "destructors
+       do not throw." If it returns non-0, the object is still
+       finalized.
+    */
+    public int finalizeStmt(){
+      int rc = 0;
+      if( null!=stmt ){
+        sqlite3_finalize(stmt);
+        stmt = null;
+      }
+      return rc;
+    }
+
+    @Override public void close(){
+      finalizeStmt();
+    }
+
+    /**
+       Throws if rc is any value other than 0, SQLITE_ROW, or
+       SQLITE_DONE, else returns rc.
+    */
+    private int checkRc(int rc){
+      switch(rc){
+        case 0:
+        case SQLITE_ROW:
+        case SQLITE_DONE: return rc;
+        default:
+          throw new SqliteException(this);
+      }
+    }
+
+    /**
+       Works like sqlite3_step() but throws SqliteException for any
+       result other than 0, SQLITE_ROW, or SQLITE_DONE.
+    */
+    public int step(){
+      return checkRc(sqlite3_step(affirmOpen()));
+    }
+
+    public Sqlite db(){ return this._db; }
+
+    /**
+       Works like sqlite3_reset() but throws on error.
+    */
+    public void reset(){
+      checkRc(sqlite3_reset(affirmOpen()));
+    }
+
+    public void clearBindings(){
+      sqlite3_clear_bindings( affirmOpen() );
+    }
+  }
+
+
+  /**
+     prepare() TODOs include:
+
+     - overloads taking byte[] and ByteBuffer.
+
+     - multi-statement processing, like CApi.sqlite3_prepare_multi()
+     but using a callback specific to the higher-level Stmt class
+     rather than the sqlite3_stmt class.
+  */
+  public Stmt prepare(String sql, int prepFlags){
+    final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+    final int rc = sqlite3_prepare_v3(affirmOpen(), sql, prepFlags, out);
+    affirmRcOk(rc);
+    return new Stmt(this, out.take());
+  }
+
+  public Stmt prepare(String sql){
+    return prepare(sql, 0);
+  }
+
+  public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f ){
+    int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep,
+                                           new SqlFunction.ScalarAdapter(f));
+    if( 0!=rc ) throw new SqliteException(db);
+  }
+
+  public void createFunction(String name, int nArg, ScalarFunction f){
+    this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+  }
+
+  public void createFunction(String name, int nArg, int eTextRep, AggregateFunction f ){
+    int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep,
+                                           new SqlFunction.AggregateAdapter(f));
+    if( 0!=rc ) throw new SqliteException(db);
+  }
+
+  public void createFunction(String name, int nArg, AggregateFunction f){
+    this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+  }
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
new file mode 100644
index 0000000000..111f004db4
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
@@ -0,0 +1,82 @@
+/*
+** 2023-10-09
+**
+** 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 is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import static org.sqlite.jni.capi.CApi.*;
+import org.sqlite.jni.capi.sqlite3;
+
+/**
+   A wrapper for communicating C-level (sqlite3*) instances with
+   Java. These wrappers do not own their associated pointer, they
+   simply provide a type-safe way to communicate it between Java
+   and C via JNI.
+*/
+public final class SqliteException extends java.lang.RuntimeException {
+  int errCode = SQLITE_ERROR;
+  int xerrCode = SQLITE_ERROR;
+  int errOffset = -1;
+  int sysErrno = 0;
+
+  /**
+     Records the given error string and uses SQLITE_ERROR for both the
+     error code and extended error code.
+  */
+  public SqliteException(String msg){
+    super(msg);
+  }
+
+  /**
+     Uses sqlite3_errstr(sqlite3ResultCode) for the error string and
+     sets both the error code and extended error code to the given
+     value.
+  */
+  public SqliteException(int sqlite3ResultCode){
+    super(sqlite3_errstr(sqlite3ResultCode));
+    errCode = xerrCode = sqlite3ResultCode;
+  }
+
+  /**
+     Records the current error state of db (which must not be null and
+     must refer to an opened db object). Note that this does NOT close
+     the db.
+
+     Design note: closing the db on error is likely only useful during
+     a failed db-open operation, and the place(s) where that can
+     happen are inside this library, not client-level code.
+  */
+  SqliteException(sqlite3 db){
+    super(sqlite3_errmsg(db));
+    errCode = sqlite3_errcode(db);
+    xerrCode = sqlite3_extended_errcode(db);
+    errOffset = sqlite3_error_offset(db);
+    sysErrno = sqlite3_system_errno(db);
+  }
+
+  /**
+     Records the current error state of db (which must not be null and
+     must refer to an open database).
+  */
+  public SqliteException(Sqlite db){
+    this(db.nativeHandle());
+  }
+
+  public SqliteException(Sqlite.Stmt stmt){
+    this( stmt.db() );
+  }
+
+  public int errcode(){ return errCode; }
+  public int extendedErrcode(){ return xerrCode; }
+  public int errorOffset(){ return errOffset; }
+  public int systemErrno(){ return sysErrno; }
+
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
new file mode 100644
index 0000000000..f5fd5f84e6
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
@@ -0,0 +1,584 @@
+/*
+** 2023-10-09
+**
+** 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 a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.wrapper1;
+//import static org.sqlite.jni.capi.CApi.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.sqlite.jni.capi.*;
+
+/**
+   An annotation for Tester2 tests which we do not want to run in
+   reflection-driven test mode because either they are not suitable
+   for multi-threaded threaded mode or we have to control their execution
+   order.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface ManualTest{}
+/**
+   Annotation for Tester2 tests which mark those which must be skipped
+   in multi-threaded mode.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface SingleThreadOnly{}
+
+public class Tester2 implements Runnable {
+  //! True when running in multi-threaded mode.
+  private static boolean mtMode = false;
+  //! True to sleep briefly between tests.
+  private static boolean takeNaps = false;
+  //! True to shuffle the order of the tests.
+  private static boolean shuffle = false;
+  //! True to dump the list of to-run tests to stdout.
+  private static boolean listRunTests = false;
+  //! True to squelch all out() and outln() output.
+  private static boolean quietMode = false;
+  //! Total number of runTests() calls.
+  private static int nTestRuns = 0;
+  //! List of test*() methods to run.
+  private static List<java.lang.reflect.Method> testMethods = null;
+  //! List of exceptions collected by run()
+  private static List<Exception> listErrors = new ArrayList<>();
+  private static final class Metrics {
+    //! Number of times createNewDb() (or equivalent) is invoked.
+    volatile int dbOpen = 0;
+  }
+
+  //! Instance ID.
+  private Integer tId;
+
+  Tester2(Integer id){
+    tId = id;
+  }
+
+  static final Metrics metrics = new Metrics();
+
+  public static synchronized void outln(){
+    if( !quietMode ){
+      System.out.println("");
+    }
+  }
+
+  public static synchronized void outPrefix(){
+    if( !quietMode ){
+      System.out.print(Thread.currentThread().getName()+": ");
+    }
+  }
+
+  public static synchronized void outln(Object val){
+    if( !quietMode ){
+      outPrefix();
+      System.out.println(val);
+    }
+  }
+
+  public static synchronized void out(Object val){
+    if( !quietMode ){
+      System.out.print(val);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public static synchronized void out(Object... vals){
+    if( !quietMode ){
+      outPrefix();
+      for(Object v : vals) out(v);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public static synchronized void outln(Object... vals){
+    if( !quietMode ){
+      out(vals); out("\n");
+    }
+  }
+
+  static volatile int affirmCount = 0;
+  public static synchronized int affirm(Boolean v, String comment){
+    ++affirmCount;
+    if( false ) assert( v /* prefer assert over exception if it's enabled because
+                 the JNI layer sometimes has to suppress exceptions,
+                 so they might be squelched on their way back to the
+                 top. */);
+    if( !v ) throw new RuntimeException(comment);
+    return affirmCount;
+  }
+
+  public static void affirm(Boolean v){
+    affirm(v, "Affirmation failed.");
+  }
+
+
+  public static void execSql(Sqlite db, String[] sql){
+    execSql(db, String.join("", sql));
+  }
+
+  public static int execSql(Sqlite dbw, boolean throwOnError, String sql){
+    final sqlite3 db = dbw.nativeHandle();
+    OutputPointer.Int32 oTail = new OutputPointer.Int32();
+    final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+    int pos = 0, n = 1;
+    byte[] sqlChunk = sqlUtf8;
+    int rc = 0;
+    sqlite3_stmt stmt = null;
+    final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+    while(pos < sqlChunk.length){
+      if(pos > 0){
+        sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+                                      sqlChunk.length);
+      }
+      if( 0==sqlChunk.length ) break;
+      rc = CApi.sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+      if(throwOnError) affirm(0 == rc);
+      else if( 0!=rc ) break;
+      pos = oTail.value;
+      stmt = outStmt.take();
+      if( null == stmt ){
+        // empty statement was parsed.
+        continue;
+      }
+      affirm(0 != stmt.getNativePointer());
+      while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(stmt)) ){
+      }
+      CApi.sqlite3_finalize(stmt);
+      affirm(0 == stmt.getNativePointer());
+      if(0!=rc && CApi.SQLITE_ROW!=rc && CApi.SQLITE_DONE!=rc){
+        break;
+      }
+    }
+    CApi.sqlite3_finalize(stmt);
+    if(CApi.SQLITE_ROW==rc || CApi.SQLITE_DONE==rc) rc = 0;
+    if( 0!=rc && throwOnError){
+      throw new SqliteException(db);
+    }
+    return rc;
+  }
+
+  static void execSql(Sqlite db, String sql){
+    execSql(db, true, sql);
+  }
+
+  @SingleThreadOnly /* because it's thread-agnostic */
+  private void test1(){
+    affirm(CApi.sqlite3_libversion_number() == CApi.SQLITE_VERSION_NUMBER);
+  }
+
+  /* Copy/paste/rename this to add new tests. */
+  private void _testTemplate(){
+    //final sqlite3 db = createNewDb();
+    //sqlite3_stmt stmt = prepare(db,"SELECT 1");
+    //sqlite3_finalize(stmt);
+    //sqlite3_close_v2(db);
+  }
+
+  private void nap() throws InterruptedException {
+    if( takeNaps ){
+      Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
+    }
+  }
+
+  Sqlite openDb(String name){
+    final Sqlite db = Sqlite.open(name, CApi.SQLITE_OPEN_READWRITE|
+                                  CApi.SQLITE_OPEN_CREATE|
+                                  CApi.SQLITE_OPEN_EXRESCODE);
+    ++metrics.dbOpen;
+    return db;
+  }
+
+  Sqlite openDb(){ return openDb(":memory:"); }
+
+  void testOpenDb1(){
+    Sqlite db = openDb();
+    affirm( 0!=db.nativeHandle().getNativePointer() );
+    db.close();
+    affirm( null==db.nativeHandle() );
+
+    SqliteException ex = null;
+    try {
+      db = openDb("/no/such/dir/.../probably");
+    }catch(SqliteException e){
+      ex = e;
+    }
+    affirm( ex!=null );
+    affirm( ex.errcode() != 0 );
+    affirm( ex.extendedErrcode() != 0 );
+    affirm( ex.errorOffset() < 0 );
+    // there's no reliable way to predict what ex.systemErrno() might be
+  }
+
+  void testPrepare1(){
+    try (Sqlite db = openDb()) {
+      Sqlite.Stmt stmt = db.prepare("SELECT 1");
+      affirm( null!=stmt.nativeHandle() );
+      affirm( CApi.SQLITE_ROW == stmt.step() );
+      affirm( CApi.SQLITE_DONE == stmt.step() );
+      stmt.reset();
+      affirm( CApi.SQLITE_ROW == stmt.step() );
+      affirm( CApi.SQLITE_DONE == stmt.step() );
+      affirm( 0 == stmt.finalizeStmt() );
+      affirm( null==stmt.nativeHandle() );
+
+      stmt = db.prepare("SELECT 1");
+      affirm( CApi.SQLITE_ROW == stmt.step() );
+      affirm( 0 == stmt.finalizeStmt() )
+        /* getting a non-0 out of sqlite3_finalize() is tricky */;
+      affirm( null==stmt.nativeHandle() );
+    }
+  }
+
+  void testUdfScalar(){
+    final ValueHolder<Integer> xDestroyCalled = new ValueHolder<>(0);
+    try (Sqlite db = openDb()) {
+      execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
+      final ValueHolder<Integer> vh = new ValueHolder<>(0);
+      final ScalarFunction f = new ScalarFunction(){
+          public void xFunc(SqlFunction.Arguments args){
+            for( SqlFunction.Arguments.Arg arg : args ){
+              vh.value += arg.getInt();
+            }
+          }
+          public void xDestroy(){
+            ++xDestroyCalled.value;
+          }
+        };
+      db.createFunction("myfunc", -1, f);
+      execSql(db, "select myfunc(1,2,3)");
+      affirm( 6 == vh.value );
+      vh.value = 0;
+      execSql(db, "select myfunc(-1,-2,-3)");
+      affirm( -6 == vh.value );
+      affirm( 0 == xDestroyCalled.value );
+    }
+    affirm( 1 == xDestroyCalled.value );
+  }
+
+  void testUdfAggregate(){
+    final ValueHolder<Integer> xDestroyCalled = new ValueHolder<>(0);
+    final ValueHolder<Integer> vh = new ValueHolder<>(0);
+    try (Sqlite db = openDb()) {
+      execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
+      final AggregateFunction f = new AggregateFunction<Integer>(){
+          public void xStep(SqlFunction.Arguments args){
+            final ValueHolder<Integer> agg = this.getAggregateState(args, 0);
+            for( SqlFunction.Arguments.Arg arg : args ){
+              agg.value += arg.getInt();
+            }
+          }
+          public void xFinal(SqlFunction.Arguments args){
+            final Integer v = this.takeAggregateState(args);
+            if( null==v ) args.resultNull();
+            else args.resultInt(v);
+            vh.value = v;
+          }
+          public void xDestroy(){
+            ++xDestroyCalled.value;
+          }
+        };
+      db.createFunction("myagg", -1, f);
+      execSql(db, "select myagg(a) from t");
+      affirm( 6 == vh.value );
+      affirm( 0 == xDestroyCalled.value );
+    }
+    affirm( 1 == xDestroyCalled.value );
+  }
+
+  private void runTests(boolean fromThread) throws Exception {
+    List<java.lang.reflect.Method> mlist = testMethods;
+    affirm( null!=mlist );
+    if( shuffle ){
+      mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
+      java.util.Collections.shuffle(mlist);
+    }
+    if( listRunTests ){
+      synchronized(this.getClass()){
+        if( !fromThread ){
+          out("Initial test"," list: ");
+          for(java.lang.reflect.Method m : testMethods){
+            out(m.getName()+" ");
+          }
+          outln();
+          outln("(That list excludes some which are hard-coded to run.)");
+        }
+        out("Running"," tests: ");
+        for(java.lang.reflect.Method m : mlist){
+          out(m.getName()+" ");
+        }
+        outln();
+      }
+    }
+    for(java.lang.reflect.Method m : mlist){
+      nap();
+      try{
+        m.invoke(this);
+      }catch(java.lang.reflect.InvocationTargetException e){
+        outln("FAILURE: ",m.getName(),"(): ", e.getCause());
+        throw e;
+      }
+    }
+    synchronized( this.getClass() ){
+      ++nTestRuns;
+    }
+  }
+
+  public void run() {
+    try {
+      runTests(0!=this.tId);
+    }catch(Exception e){
+      synchronized( listErrors ){
+        listErrors.add(e);
+      }
+    }finally{
+      affirm( CApi.sqlite3_java_uncache_thread() );
+      affirm( !CApi.sqlite3_java_uncache_thread() );
+    }
+  }
+
+  /**
+     Runs the basic sqlite3 JNI binding sanity-check suite.
+
+     CLI flags:
+
+     -q|-quiet: disables most test output.
+
+     -t|-thread N: runs the tests in N threads
+      concurrently. Default=1.
+
+     -r|-repeat N: repeats the tests in a loop N times, each one
+      consisting of the -thread value's threads.
+
+     -shuffle: randomizes the order of most of the test functions.
+
+     -naps: sleep small random intervals between tests in order to add
+     some chaos for cross-thread contention.
+
+     -list-tests: outputs the list of tests being run, minus some
+      which are hard-coded. This is noisy in multi-threaded mode.
+
+     -fail: forces an exception to be thrown during the test run.  Use
+     with -shuffle to make its appearance unpredictable.
+
+     -v: emit some developer-mode info at the end.
+  */
+  public static void main(String[] args) throws Exception {
+    Integer nThread = 1;
+    boolean doSomethingForDev = false;
+    Integer nRepeat = 1;
+    boolean forceFail = false;
+    boolean sqlLog = false;
+    boolean configLog = false;
+    boolean squelchTestOutput = false;
+    for( int i = 0; i < args.length; ){
+      String arg = args[i++];
+      if(arg.startsWith("-")){
+        arg = arg.replaceFirst("-+","");
+        if(arg.equals("v")){
+          doSomethingForDev = true;
+          //listBoundMethods();
+        }else if(arg.equals("t") || arg.equals("thread")){
+          nThread = Integer.parseInt(args[i++]);
+        }else if(arg.equals("r") || arg.equals("repeat")){
+          nRepeat = Integer.parseInt(args[i++]);
+        }else if(arg.equals("shuffle")){
+          shuffle = true;
+        }else if(arg.equals("list-tests")){
+          listRunTests = true;
+        }else if(arg.equals("fail")){
+          forceFail = true;
+        }else if(arg.equals("sqllog")){
+          sqlLog = true;
+        }else if(arg.equals("configlog")){
+          configLog = true;
+        }else if(arg.equals("naps")){
+          takeNaps = true;
+        }else if(arg.equals("q") || arg.equals("quiet")){
+          squelchTestOutput = true;
+        }else{
+          throw new IllegalArgumentException("Unhandled flag:"+arg);
+        }
+      }
+    }
+
+    if( sqlLog ){
+      if( CApi.sqlite3_compileoption_used("ENABLE_SQLLOG") ){
+        final ConfigSqllogCallback log = new ConfigSqllogCallback() {
+            @Override public void call(sqlite3 db, String msg, int op){
+              switch(op){
+                case 0: outln("Opening db: ",db); break;
+                case 1: outln("SQL ",db,": ",msg); break;
+                case 2: outln("Closing db: ",db); break;
+              }
+            }
+          };
+        int rc = CApi.sqlite3_config( log );
+        affirm( 0==rc );
+        rc = CApi.sqlite3_config( (ConfigSqllogCallback)null );
+        affirm( 0==rc );
+        rc = CApi.sqlite3_config( log );
+        affirm( 0==rc );
+      }else{
+        outln("WARNING: -sqllog is not active because library was built ",
+              "without SQLITE_ENABLE_SQLLOG.");
+      }
+    }
+    if( configLog ){
+      final ConfigLogCallback log = new ConfigLogCallback() {
+          @Override public void call(int code, String msg){
+            outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg);
+          };
+        };
+      int rc = CApi.sqlite3_config( log );
+      affirm( 0==rc );
+      rc = CApi.sqlite3_config( (ConfigLogCallback)null );
+      affirm( 0==rc );
+      rc = CApi.sqlite3_config( log );
+      affirm( 0==rc );
+    }
+
+    quietMode = squelchTestOutput;
+    outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
+          "you are very likely seeing the side effects of a known openjdk8 ",
+          "bug. It is unsightly but does not affect the library.");
+
+    {
+      // Build list of tests to run from the methods named test*().
+      testMethods = new ArrayList<>();
+      int nSkipped = 0;
+      for(final java.lang.reflect.Method m : Tester2.class.getDeclaredMethods()){
+        final String name = m.getName();
+        if( name.equals("testFail") ){
+          if( forceFail ){
+            testMethods.add(m);
+          }
+        }else if( !m.isAnnotationPresent( ManualTest.class ) ){
+          if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
+            if( 0==nSkipped++ ){
+              out("Skipping tests in multi-thread mode:");
+            }
+            out(" "+name+"()");
+          }else if( name.startsWith("test") ){
+            testMethods.add(m);
+          }
+        }
+      }
+      if( nSkipped>0 ) out("\n");
+    }
+
+    final long timeStart = System.currentTimeMillis();
+    int nLoop = 0;
+    switch( CApi.sqlite3_threadsafe() ){ /* Sanity checking */
+      case 0:
+        affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ),
+                "Could not switch to single-thread mode." );
+        affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ),
+                "Could switch to multithread mode."  );
+        affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ),
+                "Could not switch to serialized threading mode."  );
+        outln("This is a single-threaded build. Not using threads.");
+        nThread = 1;
+        break;
+      case 1:
+      case 2:
+        affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ),
+                "Could not switch to single-thread mode." );
+        affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ),
+                "Could not switch to multithread mode."  );
+        affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ),
+                "Could not switch to serialized threading mode."  );
+        break;
+      default:
+        affirm( false, "Unhandled SQLITE_THREADSAFE value." );
+    }
+    outln("libversion_number: ",
+          CApi.sqlite3_libversion_number(),"\n",
+          CApi.sqlite3_libversion(),"\n",CApi.SQLITE_SOURCE_ID,"\n",
+          "SQLITE_THREADSAFE=",CApi.sqlite3_threadsafe());
+    final boolean showLoopCount = (nRepeat>1 && nThread>1);
+    if( showLoopCount ){
+      outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
+    }
+    if( takeNaps ) outln("Napping between tests is enabled.");
+    for( int n = 0; n < nRepeat; ++n ){
+      ++nLoop;
+      if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
+      if( nThread<=1 ){
+        new Tester2(0).runTests(false);
+        continue;
+      }
+      Tester2.mtMode = true;
+      final ExecutorService ex = Executors.newFixedThreadPool( nThread );
+      for( int i = 0; i < nThread; ++i ){
+        ex.submit( new Tester2(i), i );
+      }
+      ex.shutdown();
+      try{
+        ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS);
+        ex.shutdownNow();
+      }catch (InterruptedException ie){
+        ex.shutdownNow();
+        Thread.currentThread().interrupt();
+      }
+      if( !listErrors.isEmpty() ){
+        quietMode = false;
+        outln("TEST ERRORS:");
+        Exception err = null;
+        for( Exception e : listErrors ){
+          e.printStackTrace();
+          if( null==err ) err = e;
+        }
+        if( null!=err ) throw err;
+      }
+    }
+    if( showLoopCount ) outln();
+    quietMode = false;
+
+    final long timeEnd = System.currentTimeMillis();
+    outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
+    outln("\tAssertions checked: ",affirmCount);
+    outln("\tDatabases opened: ",metrics.dbOpen);
+    if( doSomethingForDev ){
+      CApi.sqlite3_jni_internal_details();
+    }
+    affirm( 0==CApi.sqlite3_release_memory(1) );
+    CApi.sqlite3_shutdown();
+    int nMethods = 0;
+    int nNatives = 0;
+    int nCanonical = 0;
+    final java.lang.reflect.Method[] declaredMethods =
+      CApi.class.getDeclaredMethods();
+    for(java.lang.reflect.Method m : declaredMethods){
+      final int mod = m.getModifiers();
+      if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
+        final String name = m.getName();
+        if(name.startsWith("sqlite3_")){
+          ++nMethods;
+          if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
+            ++nNatives;
+          }
+        }
+      }
+    }
+    outln("\tCApi.sqlite3_*() methods: "+
+          nMethods+" total, with "+
+          nNatives+" native, "+
+          (nMethods - nNatives)+" Java"
+    );
+    outln("\tTotal test time = "
+          +(timeEnd - timeStart)+"ms");
+  }
+}
diff --git a/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java
new file mode 100644
index 0000000000..009936a43e
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java
@@ -0,0 +1,25 @@
+/*
+** 2023-10-16
+**
+** 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 a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.wrapper1;
+
+/**
+   A helper class which simply holds a single value. Its primary use
+   is for communicating values out of anonymous classes, as doing so
+   requires a "final" reference.
+*/
+public class ValueHolder<T> {
+  public T value;
+  public ValueHolder(){}
+  public ValueHolder(T v){value = v;}
+}
diff --git a/libsql-sqlite3/ext/jni/src/tests/000-000-sanity.test b/libsql-sqlite3/ext/jni/src/tests/000-000-sanity.test
index 2bfacb1cec..4ccbece31c 100644
--- a/libsql-sqlite3/ext/jni/src/tests/000-000-sanity.test
+++ b/libsql-sqlite3/ext/jni/src/tests/000-000-sanity.test
@@ -6,7 +6,8 @@
 ** xMODULE_NAME:             module-name
 ** xREQUIRED_PROPERTIES:      small fast reliable
 ** xREQUIRED_PROPERTIES:      RECURSIVE_TRIGGERS
-** xREQUIRED_PROPERTIES:      TEMPSTORE_MEM  TEMPSTORE_FILE
+** xREQUIRED_PROPERTIES:      TEMPSTORE_FILE TEMPSTORE_MEM
+** xREQUIRED_PROPERTIES:      AUTOVACUUM INCRVACUUM
 **
 */
 --print starting up πŸ˜ƒ
diff --git a/libsql-sqlite3/ext/jni/src/tests/900-001-fts.test b/libsql-sqlite3/ext/jni/src/tests/900-001-fts.test
new file mode 100644
index 0000000000..65285e86b0
--- /dev/null
+++ b/libsql-sqlite3/ext/jni/src/tests/900-001-fts.test
@@ -0,0 +1,12 @@
+/*
+** SCRIPT_MODULE_NAME:      fts5-sanity-checks
+** xREQUIRED_PROPERTIES:     FTS5
+**
+*/
+
+--testcase 1.0
+CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
+insert into email values('fred','Help!','Dear Sir...');
+insert into email values('barney','Assistance','Dear Madam...');
+select * from email where email match 'assistance';
+--result barney Assistance {Dear Madam...}
diff --git a/libsql-sqlite3/ext/lsm1/lsm_vtab.c b/libsql-sqlite3/ext/lsm1/lsm_vtab.c
index bb1460297d..8c21923e1a 100644
--- a/libsql-sqlite3/ext/lsm1/lsm_vtab.c
+++ b/libsql-sqlite3/ext/lsm1/lsm_vtab.c
@@ -1061,6 +1061,11 @@ static sqlite3_module lsm1Module = {
   lsm1Rollback,            /* xRollback */
   0,                       /* xFindMethod */
   0,                       /* xRename */
+  0,                       /* xSavepoint */
+  0,                       /* xRelease */
+  0,                       /* xRollbackTo */
+  0,                       /* xShadowName */
+  0                        /* xIntegrity */
 };
 
 
diff --git a/libsql-sqlite3/ext/misc/amatch.c b/libsql-sqlite3/ext/misc/amatch.c
index bafa43283a..dd9bee53d7 100644
--- a/libsql-sqlite3/ext/misc/amatch.c
+++ b/libsql-sqlite3/ext/misc/amatch.c
@@ -1475,7 +1475,8 @@ static sqlite3_module amatchModule = {
   0,                      /* xSavepoint */
   0,                      /* xRelease */
   0,                      /* xRollbackTo */
-  0                       /* xShadowName */
+  0,                      /* xShadowName */
+  0                       /* xIntegrity */
 };
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/libsql-sqlite3/ext/misc/btreeinfo.c b/libsql-sqlite3/ext/misc/btreeinfo.c
index 22f8268139..02f8c0319c 100644
--- a/libsql-sqlite3/ext/misc/btreeinfo.c
+++ b/libsql-sqlite3/ext/misc/btreeinfo.c
@@ -411,7 +411,8 @@ int sqlite3BinfoRegister(sqlite3 *db){
     0,                           /* xSavepoint */
     0,                           /* xRelease */
     0,                           /* xRollbackTo */
-    0                            /* xShadowName */
+    0,                           /* xShadowName */
+    0                            /* xIntegrity */
   };
   return sqlite3_create_module(db, "sqlite_btreeinfo", &binfo_module, 0);
 }
diff --git a/libsql-sqlite3/ext/misc/carray.c b/libsql-sqlite3/ext/misc/carray.c
index 709c894f27..b1caa98c3f 100644
--- a/libsql-sqlite3/ext/misc/carray.c
+++ b/libsql-sqlite3/ext/misc/carray.c
@@ -409,6 +409,11 @@ static sqlite3_module carrayModule = {
   0,                         /* xRollback */
   0,                         /* xFindMethod */
   0,                         /* xRename */
+  0,                         /* xSavepoint */
+  0,                         /* xRelease */
+  0,                         /* xRollbackTo */
+  0,                         /* xShadow */
+  0                          /* xIntegrity */
 };
 
 /*
diff --git a/libsql-sqlite3/ext/misc/closure.c b/libsql-sqlite3/ext/misc/closure.c
index db9b2b7394..79a5a21d1e 100644
--- a/libsql-sqlite3/ext/misc/closure.c
+++ b/libsql-sqlite3/ext/misc/closure.c
@@ -939,7 +939,8 @@ static sqlite3_module closureModule = {
   0,                      /* xSavepoint */
   0,                      /* xRelease */
   0,                      /* xRollbackTo */
-  0                       /* xShadowName */
+  0,                      /* xShadowName */
+  0                       /* xIntegrity */
 };
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/libsql-sqlite3/ext/misc/completion.c b/libsql-sqlite3/ext/misc/completion.c
index d9e7b85972..987595a3d6 100644
--- a/libsql-sqlite3/ext/misc/completion.c
+++ b/libsql-sqlite3/ext/misc/completion.c
@@ -470,7 +470,8 @@ static sqlite3_module completionModule = {
   0,                         /* xSavepoint */
   0,                         /* xRelease */
   0,                         /* xRollbackTo */
-  0                          /* xShadowName */
+  0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/libsql-sqlite3/ext/misc/csv.c b/libsql-sqlite3/ext/misc/csv.c
index 870a0cf60e..b38500f4b9 100644
--- a/libsql-sqlite3/ext/misc/csv.c
+++ b/libsql-sqlite3/ext/misc/csv.c
@@ -897,6 +897,11 @@ static sqlite3_module CsvModule = {
   0,                       /* xRollback */
   0,                       /* xFindMethod */
   0,                       /* xRename */
+  0,                       /* xSavepoint */
+  0,                       /* xRelease */
+  0,                       /* xRollbackTo */
+  0,                       /* xShadowName */
+  0                        /* xIntegrity */
 };
 
 #ifdef SQLITE_TEST
@@ -929,6 +934,11 @@ static sqlite3_module CsvModuleFauxWrite = {
   0,                       /* xRollback */
   0,                       /* xFindMethod */
   0,                       /* xRename */
+  0,                       /* xSavepoint */
+  0,                       /* xRelease */
+  0,                       /* xRollbackTo */
+  0,                       /* xShadowName */
+  0                        /* xIntegrity */
 };
 #endif /* SQLITE_TEST */
 
diff --git a/libsql-sqlite3/ext/misc/explain.c b/libsql-sqlite3/ext/misc/explain.c
index 0095194570..726af76b96 100644
--- a/libsql-sqlite3/ext/misc/explain.c
+++ b/libsql-sqlite3/ext/misc/explain.c
@@ -293,6 +293,7 @@ static sqlite3_module explainModule = {
   0,                         /* xRelease */
   0,                         /* xRollbackTo */
   0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/libsql-sqlite3/ext/misc/fileio.c b/libsql-sqlite3/ext/misc/fileio.c
index 7cdbd5968f..70546adfca 100644
--- a/libsql-sqlite3/ext/misc/fileio.c
+++ b/libsql-sqlite3/ext/misc/fileio.c
@@ -983,6 +983,7 @@ static int fsdirRegister(sqlite3 *db){
     0,                         /* xRelease */
     0,                         /* xRollbackTo */
     0,                         /* xShadowName */
+    0                          /* xIntegrity */
   };
 
   int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0);
diff --git a/libsql-sqlite3/ext/misc/fossildelta.c b/libsql-sqlite3/ext/misc/fossildelta.c
index 6a597e0d7d..e638737d2b 100644
--- a/libsql-sqlite3/ext/misc/fossildelta.c
+++ b/libsql-sqlite3/ext/misc/fossildelta.c
@@ -1058,7 +1058,8 @@ static sqlite3_module deltaparsevtabModule = {
   /* xSavepoint  */ 0,
   /* xRelease    */ 0,
   /* xRollbackTo */ 0,
-  /* xShadowName */ 0
+  /* xShadowName */ 0,
+  /* xIntegrity  */ 0
 };
 
 
diff --git a/libsql-sqlite3/ext/misc/fuzzer.c b/libsql-sqlite3/ext/misc/fuzzer.c
index 65d9d8df69..92b7c0dae0 100644
--- a/libsql-sqlite3/ext/misc/fuzzer.c
+++ b/libsql-sqlite3/ext/misc/fuzzer.c
@@ -1165,6 +1165,11 @@ static sqlite3_module fuzzerModule = {
   0,                           /* xRollback */
   0,                           /* xFindMethod */
   0,                           /* xRename */
+  0,                           /* xSavepoint */
+  0,                           /* xRelease */
+  0,                           /* xRollbackTo */
+  0,                           /* xShadowName */
+  0                            /* xIntegrity */
 };
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/libsql-sqlite3/ext/misc/memstat.c b/libsql-sqlite3/ext/misc/memstat.c
index 800a86e7a4..c56af9f297 100644
--- a/libsql-sqlite3/ext/misc/memstat.c
+++ b/libsql-sqlite3/ext/misc/memstat.c
@@ -396,6 +396,7 @@ static sqlite3_module memstatModule = {
   0,                         /* xRelease */
   0,                         /* xRollbackTo */
   0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/libsql-sqlite3/ext/misc/mmapwarm.c b/libsql-sqlite3/ext/misc/mmapwarm.c
index 5afa47bf7a..851f8b0eb7 100644
--- a/libsql-sqlite3/ext/misc/mmapwarm.c
+++ b/libsql-sqlite3/ext/misc/mmapwarm.c
@@ -38,7 +38,7 @@ int sqlite3_mmap_warm(sqlite3 *db, const char *zDb){
   int rc = SQLITE_OK;
   char *zSql = 0;
   int pgsz = 0;
-  int nTotal = 0;
+  unsigned int nTotal = 0;
 
   if( 0==sqlite3_get_autocommit(db) ) return SQLITE_MISUSE;
 
@@ -86,8 +86,8 @@ int sqlite3_mmap_warm(sqlite3 *db, const char *zDb){
         rc = p->xFetch(pFd, pgsz*iPg, pgsz, (void**)&pMap);
         if( rc!=SQLITE_OK || pMap==0 ) break;
 
-        nTotal += pMap[0];
-        nTotal += pMap[pgsz-1];
+        nTotal += (unsigned int)pMap[0];
+        nTotal += (unsigned int)pMap[pgsz-1];
 
         rc = p->xUnfetch(pFd, pgsz*iPg, (void*)pMap);
         if( rc!=SQLITE_OK ) break;
@@ -103,5 +103,6 @@ int sqlite3_mmap_warm(sqlite3 *db, const char *zDb){
     if( rc==SQLITE_OK ) rc = rc2;
   }
 
+  (void)nTotal;
   return rc;
 }
diff --git a/libsql-sqlite3/ext/misc/prefixes.c b/libsql-sqlite3/ext/misc/prefixes.c
index 3f053b7f1c..e6517e7195 100644
--- a/libsql-sqlite3/ext/misc/prefixes.c
+++ b/libsql-sqlite3/ext/misc/prefixes.c
@@ -248,7 +248,8 @@ static sqlite3_module prefixesModule = {
   /* xSavepoint  */ 0,
   /* xRelease    */ 0,
   /* xRollbackTo */ 0,
-  /* xShadowName */ 0
+  /* xShadowName */ 0,
+  /* xIntegrity  */ 0
 };
 
 /*
diff --git a/libsql-sqlite3/ext/misc/qpvtab.c b/libsql-sqlite3/ext/misc/qpvtab.c
index fb0c155a27..b7c2a05126 100644
--- a/libsql-sqlite3/ext/misc/qpvtab.c
+++ b/libsql-sqlite3/ext/misc/qpvtab.c
@@ -439,7 +439,8 @@ static sqlite3_module qpvtabModule = {
   /* xSavepoint  */ 0,
   /* xRelease    */ 0,
   /* xRollbackTo */ 0,
-  /* xShadowName */ 0
+  /* xShadowName */ 0,
+  /* xIntegrity  */ 0
 };
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
 
diff --git a/libsql-sqlite3/ext/misc/series.c b/libsql-sqlite3/ext/misc/series.c
index 3bd567b2ef..abd6af7ad6 100644
--- a/libsql-sqlite3/ext/misc/series.c
+++ b/libsql-sqlite3/ext/misc/series.c
@@ -557,7 +557,8 @@ static sqlite3_module seriesModule = {
   0,                         /* xSavepoint */
   0,                         /* xRelease */
   0,                         /* xRollbackTo */
-  0                          /* xShadowName */
+  0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/libsql-sqlite3/ext/misc/spellfix.c b/libsql-sqlite3/ext/misc/spellfix.c
index 3b78d9f07d..a0c5aafd10 100644
--- a/libsql-sqlite3/ext/misc/spellfix.c
+++ b/libsql-sqlite3/ext/misc/spellfix.c
@@ -3009,6 +3009,11 @@ static sqlite3_module spellfix1Module = {
   0,                       /* xRollback */
   0,                       /* xFindMethod */
   spellfix1Rename,         /* xRename */
+  0,                       /* xSavepoint */
+  0,                       /* xRelease */
+  0,                       /* xRollbackTo */
+  0,                       /* xShadowName */
+  0                        /* xIntegrity */
 };
 
 /*
diff --git a/libsql-sqlite3/ext/misc/stmt.c b/libsql-sqlite3/ext/misc/stmt.c
index 4819cbe673..cc1aaa8493 100644
--- a/libsql-sqlite3/ext/misc/stmt.c
+++ b/libsql-sqlite3/ext/misc/stmt.c
@@ -314,6 +314,7 @@ static sqlite3_module stmtModule = {
   0,                         /* xRelease */
   0,                         /* xRollbackTo */
   0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/libsql-sqlite3/ext/misc/templatevtab.c b/libsql-sqlite3/ext/misc/templatevtab.c
index d7efa2b40e..5865f5214b 100644
--- a/libsql-sqlite3/ext/misc/templatevtab.c
+++ b/libsql-sqlite3/ext/misc/templatevtab.c
@@ -249,7 +249,8 @@ static sqlite3_module templatevtabModule = {
   /* xSavepoint  */ 0,
   /* xRelease    */ 0,
   /* xRollbackTo */ 0,
-  /* xShadowName */ 0
+  /* xShadowName */ 0,
+  /* xIntegrity  */ 0
 };
 
 
diff --git a/libsql-sqlite3/ext/misc/unionvtab.c b/libsql-sqlite3/ext/misc/unionvtab.c
index 6ac7ca6e95..506ad5ddba 100644
--- a/libsql-sqlite3/ext/misc/unionvtab.c
+++ b/libsql-sqlite3/ext/misc/unionvtab.c
@@ -1351,7 +1351,8 @@ static int createUnionVtab(sqlite3 *db){
     0,                            /* xSavepoint */
     0,                            /* xRelease */
     0,                            /* xRollbackTo */
-    0                             /* xShadowName */
+    0,                            /* xShadowName */
+    0                             /* xIntegrity */
   };
   int rc;
 
diff --git a/libsql-sqlite3/ext/misc/vfsstat.c b/libsql-sqlite3/ext/misc/vfsstat.c
index 83a7a3df75..ba22115ab8 100644
--- a/libsql-sqlite3/ext/misc/vfsstat.c
+++ b/libsql-sqlite3/ext/misc/vfsstat.c
@@ -775,6 +775,11 @@ static sqlite3_module VfsStatModule = {
   0,                         /* xRollback */
   0,                         /* xFindMethod */
   0,                         /* xRename */
+  0,                         /* xSavepoint */
+  0,                         /* xRelease */
+  0,                         /* xRollbackTo */
+  0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 /*
diff --git a/libsql-sqlite3/ext/misc/vtablog.c b/libsql-sqlite3/ext/misc/vtablog.c
index 424b3457ff..e414e7afa7 100644
--- a/libsql-sqlite3/ext/misc/vtablog.c
+++ b/libsql-sqlite3/ext/misc/vtablog.c
@@ -493,6 +493,7 @@ static sqlite3_module vtablogModule = {
   0,                         /* xRelease */
   0,                         /* xRollbackTo */
   0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 #ifdef _WIN32
diff --git a/libsql-sqlite3/ext/misc/wholenumber.c b/libsql-sqlite3/ext/misc/wholenumber.c
index 03d6e6902e..4c955925da 100644
--- a/libsql-sqlite3/ext/misc/wholenumber.c
+++ b/libsql-sqlite3/ext/misc/wholenumber.c
@@ -254,6 +254,11 @@ static sqlite3_module wholenumberModule = {
   0,                         /* xRollback */
   0,                         /* xFindMethod */
   0,                         /* xRename */
+  0,                         /* xSavepoint */
+  0,                         /* xRelease */
+  0,                         /* xRollbackTo */
+  0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/libsql-sqlite3/ext/misc/zipfile.c b/libsql-sqlite3/ext/misc/zipfile.c
index 4dbf80b197..db327809f8 100644
--- a/libsql-sqlite3/ext/misc/zipfile.c
+++ b/libsql-sqlite3/ext/misc/zipfile.c
@@ -29,6 +29,7 @@ SQLITE_EXTENSION_INIT1
 #include <stdio.h>
 #include <string.h>
 #include <assert.h>
+#include <stdint.h>
 
 #include <zlib.h>
 
@@ -2200,7 +2201,8 @@ static int zipfileRegister(sqlite3 *db){
     0,                         /* xSavepoint */
     0,                         /* xRelease */
     0,                         /* xRollback */
-    0                          /* xShadowName */
+    0,                         /* xShadowName */
+    0                          /* xIntegrity */
   };
 
   int rc = sqlite3_create_module(db, "zipfile"  , &zipfileModule, 0);
diff --git a/libsql-sqlite3/ext/recover/dbdata.c b/libsql-sqlite3/ext/recover/dbdata.c
index e3bec33d8d..25a6e9fd6a 100644
--- a/libsql-sqlite3/ext/recover/dbdata.c
+++ b/libsql-sqlite3/ext/recover/dbdata.c
@@ -933,7 +933,8 @@ static int sqlite3DbdataRegister(sqlite3 *db){
     0,                            /* xSavepoint */
     0,                            /* xRelease */
     0,                            /* xRollbackTo */
-    0                             /* xShadowName */
+    0,                            /* xShadowName */
+    0                             /* xIntegrity */
   };
 
   int rc = sqlite3_create_module(db, "sqlite_dbdata", &dbdata_module, 0);
diff --git a/libsql-sqlite3/ext/repair/checkindex.c b/libsql-sqlite3/ext/repair/checkindex.c
index 080a51530d..5f6e646e44 100644
--- a/libsql-sqlite3/ext/repair/checkindex.c
+++ b/libsql-sqlite3/ext/repair/checkindex.c
@@ -907,6 +907,8 @@ static int ciInit(sqlite3 *db){
     0,                            /* xSavepoint */
     0,                            /* xRelease */
     0,                            /* xRollbackTo */
+    0,                            /* xShadowName */
+    0                             /* xIntegrity */
   };
   return sqlite3_create_module(db, "incremental_index_check", &cidx_module, 0);
 }
diff --git a/libsql-sqlite3/ext/rtree/geopoly.c b/libsql-sqlite3/ext/rtree/geopoly.c
index 640cb86b20..3e9c2a2713 100644
--- a/libsql-sqlite3/ext/rtree/geopoly.c
+++ b/libsql-sqlite3/ext/rtree/geopoly.c
@@ -1252,24 +1252,28 @@ static int geopolyInit(
   (void)pAux;
 
   sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+  sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
 
   /* Allocate the sqlite3_vtab structure */
   nDb = strlen(argv[1]);
   nName = strlen(argv[2]);
-  pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
+  pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8);
   if( !pRtree ){
     return SQLITE_NOMEM;
   }
-  memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
+  memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8);
   pRtree->nBusy = 1;
   pRtree->base.pModule = &rtreeModule;
   pRtree->zDb = (char *)&pRtree[1];
   pRtree->zName = &pRtree->zDb[nDb+1];
+  pRtree->zNodeName = &pRtree->zName[nName+1];
   pRtree->eCoordType = RTREE_COORD_REAL32;
   pRtree->nDim = 2;
   pRtree->nDim2 = 4;
   memcpy(pRtree->zDb, argv[1], nDb);
   memcpy(pRtree->zName, argv[2], nName);
+  memcpy(pRtree->zNodeName, argv[2], nName);
+  memcpy(&pRtree->zNodeName[nName], "_node", 6);
 
 
   /* Create/Connect to the underlying relational database schema. If
@@ -1683,7 +1687,6 @@ static int geopolyUpdate(
     }
     if( rc==SQLITE_OK ){
       int rc2;
-      pRtree->iReinsertHeight = -1;
       rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
       rc2 = nodeRelease(pRtree, pLeaf);
       if( rc==SQLITE_OK ){
@@ -1780,7 +1783,8 @@ static sqlite3_module geopolyModule = {
   rtreeSavepoint,             /* xSavepoint */
   0,                          /* xRelease */
   0,                          /* xRollbackTo */
-  rtreeShadowName             /* xShadowName */
+  rtreeShadowName,            /* xShadowName */
+  rtreeIntegrity              /* xIntegrity */
 };
 
 static int sqlite3_geopoly_init(sqlite3 *db){
diff --git a/libsql-sqlite3/ext/rtree/rtree.c b/libsql-sqlite3/ext/rtree/rtree.c
index 4e85cc8aec..1705abd51c 100644
--- a/libsql-sqlite3/ext/rtree/rtree.c
+++ b/libsql-sqlite3/ext/rtree/rtree.c
@@ -166,6 +166,7 @@ struct Rtree {
   int iDepth;                 /* Current depth of the r-tree structure */
   char *zDb;                  /* Name of database containing r-tree table */
   char *zName;                /* Name of r-tree table */ 
+  char *zNodeName;            /* Name of the %_node table */
   u32 nBusy;                  /* Current number of users of this structure */
   i64 nRowEst;                /* Estimated number of rows in this table */
   u32 nCursor;                /* Number of open cursors */
@@ -178,7 +179,6 @@ struct Rtree {
   ** headed by the node (leaf nodes have RtreeNode.iNode==0).
   */
   RtreeNode *pDeleted;
-  int iReinsertHeight;        /* Height of sub-trees Reinsert() has run on */
 
   /* Blob I/O on xxx_node */
   sqlite3_blob *pNodeBlob;
@@ -200,7 +200,7 @@ struct Rtree {
   /* Statement for writing to the "aux:" fields, if there are any */
   sqlite3_stmt *pWriteAux;
 
-  RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */ 
+  RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
 };
 
 /* Possible values for Rtree.eCoordType: */
@@ -475,15 +475,20 @@ struct RtreeMatchArg {
 ** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined
 ** at run-time.
 */
-#ifndef SQLITE_BYTEORDER
-# if defined(i386)      || defined(__i386__)      || defined(_M_IX86) ||    \
+#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */
+# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__
+#   define SQLITE_BYTEORDER 4321
+# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
+#   define SQLITE_BYTEORDER 1234
+# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1
+#   define SQLITE_BYTEORDER 4321
+# elif defined(i386)    || defined(__i386__)      || defined(_M_IX86) ||    \
      defined(__x86_64)  || defined(__x86_64__)    || defined(_M_X64)  ||    \
      defined(_M_AMD64)  || defined(_M_ARM)        || defined(__x86)   ||    \
      defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64)
-#   define SQLITE_BYTEORDER    1234
-# elif defined(sparc)     || defined(__ppc__) || \
-       defined(__ARMEB__) || defined(__AARCH64EB__)
-#   define SQLITE_BYTEORDER    4321
+#   define SQLITE_BYTEORDER 1234
+# elif defined(sparc)   || defined(__ARMEB__)     || defined(__AARCH64EB__)
+#   define SQLITE_BYTEORDER 4321
 # else
 #   define SQLITE_BYTEORDER 0
 # endif
@@ -732,11 +737,9 @@ static int nodeAcquire(
     }
   }
   if( pRtree->pNodeBlob==0 ){
-    char *zTab = sqlite3_mprintf("%s_node", pRtree->zName);
-    if( zTab==0 ) return SQLITE_NOMEM;
-    rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0,
+    rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, pRtree->zNodeName,
+                           "data", iNode, 0,
                            &pRtree->pNodeBlob);
-    sqlite3_free(zTab);
   }
   if( rc ){
     nodeBlobReset(pRtree);
@@ -2077,8 +2080,12 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
 
   pIdxInfo->idxNum = 2;
   pIdxInfo->needToFreeIdxStr = 1;
-  if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){
-    return SQLITE_NOMEM;
+  if( iIdx>0 ){
+    pIdxInfo->idxStr = sqlite3_malloc( iIdx+1 );
+    if( pIdxInfo->idxStr==0 ){
+      return SQLITE_NOMEM;
+    }
+    memcpy(pIdxInfo->idxStr, zIdxStr, iIdx+1);
   }
 
   nRow = pRtree->nRowEst >> (iIdx/2);
@@ -2157,31 +2164,22 @@ static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
 */
 static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
   int ii;
-  int isInt = (pRtree->eCoordType==RTREE_COORD_INT32);
-  for(ii=0; ii<pRtree->nDim2; ii+=2){
-    RtreeCoord *a1 = &p1->aCoord[ii];
-    RtreeCoord *a2 = &p2->aCoord[ii];
-    if( (!isInt && (a2[0].f<a1[0].f || a2[1].f>a1[1].f)) 
-     || ( isInt && (a2[0].i<a1[0].i || a2[1].i>a1[1].i)) 
-    ){
-      return 0;
+  if( pRtree->eCoordType==RTREE_COORD_INT32 ){
+    for(ii=0; ii<pRtree->nDim2; ii+=2){
+      RtreeCoord *a1 = &p1->aCoord[ii];
+      RtreeCoord *a2 = &p2->aCoord[ii];
+      if( a2[0].i<a1[0].i || a2[1].i>a1[1].i ) return 0;
+    }
+  }else{
+    for(ii=0; ii<pRtree->nDim2; ii+=2){
+      RtreeCoord *a1 = &p1->aCoord[ii];
+      RtreeCoord *a2 = &p2->aCoord[ii];
+      if( a2[0].f<a1[0].f || a2[1].f>a1[1].f ) return 0;
     }
   }
   return 1;
 }
 
-/*
-** Return the amount cell p would grow by if it were unioned with pCell.
-*/
-static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){
-  RtreeDValue area;
-  RtreeCell cell;
-  memcpy(&cell, p, sizeof(RtreeCell));
-  area = cellArea(pRtree, &cell);
-  cellUnion(pRtree, &cell, pCell);
-  return (cellArea(pRtree, &cell)-area);
-}
-
 static RtreeDValue cellOverlap(
   Rtree *pRtree, 
   RtreeCell *p, 
@@ -2228,38 +2226,52 @@ static int ChooseLeaf(
   for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){
     int iCell;
     sqlite3_int64 iBest = 0;
-
+    int bFound = 0;
     RtreeDValue fMinGrowth = RTREE_ZERO;
     RtreeDValue fMinArea = RTREE_ZERO;
-
     int nCell = NCELL(pNode);
-    RtreeCell cell;
     RtreeNode *pChild = 0;
 
-    RtreeCell *aCell = 0;
-
-    /* Select the child node which will be enlarged the least if pCell
-    ** is inserted into it. Resolve ties by choosing the entry with
-    ** the smallest area.
+    /* First check to see if there is are any cells in pNode that completely
+    ** contains pCell.  If two or more cells in pNode completely contain pCell
+    ** then pick the smallest.
     */
     for(iCell=0; iCell<nCell; iCell++){
-      int bBest = 0;
-      RtreeDValue growth;
-      RtreeDValue area;
+      RtreeCell cell;
       nodeGetCell(pRtree, pNode, iCell, &cell);
-      growth = cellGrowth(pRtree, &cell, pCell);
-      area = cellArea(pRtree, &cell);
-      if( iCell==0||growth<fMinGrowth||(growth==fMinGrowth && area<fMinArea) ){
-        bBest = 1;
+      if( cellContains(pRtree, &cell, pCell) ){
+        RtreeDValue area = cellArea(pRtree, &cell);
+        if( bFound==0 || area<fMinArea ){
+          iBest = cell.iRowid;
+          fMinArea = area;
+          bFound = 1;
+        }
       }
-      if( bBest ){
-        fMinGrowth = growth;
-        fMinArea = area;
-        iBest = cell.iRowid;
+    }
+    if( !bFound ){
+      /* No cells of pNode will completely contain pCell.  So pick the
+      ** cell of pNode that grows by the least amount when pCell is added.
+      ** Break ties by selecting the smaller cell.
+      */
+      for(iCell=0; iCell<nCell; iCell++){
+        RtreeCell cell;
+        RtreeDValue growth;
+        RtreeDValue area;
+        nodeGetCell(pRtree, pNode, iCell, &cell);
+        area = cellArea(pRtree, &cell);
+        cellUnion(pRtree, &cell, pCell);
+        growth = cellArea(pRtree, &cell)-area;
+        if( iCell==0
+         || growth<fMinGrowth
+         || (growth==fMinGrowth && area<fMinArea)
+        ){
+          fMinGrowth = growth;
+          fMinArea = area;
+          iBest = cell.iRowid;
+        }
       }
     }
 
-    sqlite3_free(aCell);
     rc = nodeAcquire(pRtree, iBest, pNode, &pChild);
     nodeRelease(pRtree, pNode);
     pNode = pChild;
@@ -2332,77 +2344,6 @@ static int parentWrite(Rtree *pRtree, sqlite3_int64 iNode, sqlite3_int64 iPar){
 static int rtreeInsertCell(Rtree *, RtreeNode *, RtreeCell *, int);
 
 
-/*
-** Arguments aIdx, aDistance and aSpare all point to arrays of size
-** nIdx. The aIdx array contains the set of integers from 0 to 
-** (nIdx-1) in no particular order. This function sorts the values
-** in aIdx according to the indexed values in aDistance. For
-** example, assuming the inputs:
-**
-**   aIdx      = { 0,   1,   2,   3 }
-**   aDistance = { 5.0, 2.0, 7.0, 6.0 }
-**
-** this function sets the aIdx array to contain:
-**
-**   aIdx      = { 0,   1,   2,   3 }
-**
-** The aSpare array is used as temporary working space by the
-** sorting algorithm.
-*/
-static void SortByDistance(
-  int *aIdx, 
-  int nIdx, 
-  RtreeDValue *aDistance, 
-  int *aSpare
-){
-  if( nIdx>1 ){
-    int iLeft = 0;
-    int iRight = 0;
-
-    int nLeft = nIdx/2;
-    int nRight = nIdx-nLeft;
-    int *aLeft = aIdx;
-    int *aRight = &aIdx[nLeft];
-
-    SortByDistance(aLeft, nLeft, aDistance, aSpare);
-    SortByDistance(aRight, nRight, aDistance, aSpare);
-
-    memcpy(aSpare, aLeft, sizeof(int)*nLeft);
-    aLeft = aSpare;
-
-    while( iLeft<nLeft || iRight<nRight ){
-      if( iLeft==nLeft ){
-        aIdx[iLeft+iRight] = aRight[iRight];
-        iRight++;
-      }else if( iRight==nRight ){
-        aIdx[iLeft+iRight] = aLeft[iLeft];
-        iLeft++;
-      }else{
-        RtreeDValue fLeft = aDistance[aLeft[iLeft]];
-        RtreeDValue fRight = aDistance[aRight[iRight]];
-        if( fLeft<fRight ){
-          aIdx[iLeft+iRight] = aLeft[iLeft];
-          iLeft++;
-        }else{
-          aIdx[iLeft+iRight] = aRight[iRight];
-          iRight++;
-        }
-      }
-    }
-
-#if 0
-    /* Check that the sort worked */
-    {
-      int jj;
-      for(jj=1; jj<nIdx; jj++){
-        RtreeDValue left = aDistance[aIdx[jj-1]];
-        RtreeDValue right = aDistance[aIdx[jj]];
-        assert( left<=right );
-      }
-    }
-#endif
-  }
-}
 
 /*
 ** Arguments aIdx, aCell and aSpare all point to arrays of size
@@ -2886,108 +2827,7 @@ static int deleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell, int iHeight){
 
   return rc;
 }
-
-static int Reinsert(
-  Rtree *pRtree, 
-  RtreeNode *pNode, 
-  RtreeCell *pCell, 
-  int iHeight
-){
-  int *aOrder;
-  int *aSpare;
-  RtreeCell *aCell;
-  RtreeDValue *aDistance;
-  int nCell;
-  RtreeDValue aCenterCoord[RTREE_MAX_DIMENSIONS];
-  int iDim;
-  int ii;
-  int rc = SQLITE_OK;
-  int n;
-
-  memset(aCenterCoord, 0, sizeof(RtreeDValue)*RTREE_MAX_DIMENSIONS);
-
-  nCell = NCELL(pNode)+1;
-  n = (nCell+1)&(~1);
-
-  /* Allocate the buffers used by this operation. The allocation is
-  ** relinquished before this function returns.
-  */
-  aCell = (RtreeCell *)sqlite3_malloc64(n * (
-    sizeof(RtreeCell)     +         /* aCell array */
-    sizeof(int)           +         /* aOrder array */
-    sizeof(int)           +         /* aSpare array */
-    sizeof(RtreeDValue)             /* aDistance array */
-  ));
-  if( !aCell ){
-    return SQLITE_NOMEM;
-  }
-  aOrder    = (int *)&aCell[n];
-  aSpare    = (int *)&aOrder[n];
-  aDistance = (RtreeDValue *)&aSpare[n];
-
-  for(ii=0; ii<nCell; ii++){
-    if( ii==(nCell-1) ){
-      memcpy(&aCell[ii], pCell, sizeof(RtreeCell));
-    }else{
-      nodeGetCell(pRtree, pNode, ii, &aCell[ii]);
-    }
-    aOrder[ii] = ii;
-    for(iDim=0; iDim<pRtree->nDim; iDim++){
-      aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]);
-      aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]);
-    }
-  }
-  for(iDim=0; iDim<pRtree->nDim; iDim++){
-    aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2));
-  }
-
-  for(ii=0; ii<nCell; ii++){
-    aDistance[ii] = RTREE_ZERO;
-    for(iDim=0; iDim<pRtree->nDim; iDim++){
-      RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) - 
-                               DCOORD(aCell[ii].aCoord[iDim*2]));
-      aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]);
-    }
-  }
-
-  SortByDistance(aOrder, nCell, aDistance, aSpare);
-  nodeZero(pRtree, pNode);
-
-  for(ii=0; rc==SQLITE_OK && ii<(nCell-(RTREE_MINCELLS(pRtree)+1)); ii++){
-    RtreeCell *p = &aCell[aOrder[ii]];
-    nodeInsertCell(pRtree, pNode, p);
-    if( p->iRowid==pCell->iRowid ){
-      if( iHeight==0 ){
-        rc = rowidWrite(pRtree, p->iRowid, pNode->iNode);
-      }else{
-        rc = parentWrite(pRtree, p->iRowid, pNode->iNode);
-      }
-    }
-  }
-  if( rc==SQLITE_OK ){
-    rc = fixBoundingBox(pRtree, pNode);
-  }
-  for(; rc==SQLITE_OK && ii<nCell; ii++){
-    /* Find a node to store this cell in. pNode->iNode currently contains
-    ** the height of the sub-tree headed by the cell.
-    */
-    RtreeNode *pInsert;
-    RtreeCell *p = &aCell[aOrder[ii]];
-    rc = ChooseLeaf(pRtree, p, iHeight, &pInsert);
-    if( rc==SQLITE_OK ){
-      int rc2;
-      rc = rtreeInsertCell(pRtree, pInsert, p, iHeight);
-      rc2 = nodeRelease(pRtree, pInsert);
-      if( rc==SQLITE_OK ){
-        rc = rc2;
-      }
-    }
-  }
-
-  sqlite3_free(aCell);
-  return rc;
-}
-
+	
 /*
 ** Insert cell pCell into node pNode. Node pNode is the head of a 
 ** subtree iHeight high (leaf nodes have iHeight==0).
@@ -3008,12 +2848,7 @@ static int rtreeInsertCell(
     }
   }
   if( nodeInsertCell(pRtree, pNode, pCell) ){
-    if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){
-      rc = SplitNode(pRtree, pNode, pCell, iHeight);
-    }else{
-      pRtree->iReinsertHeight = iHeight;
-      rc = Reinsert(pRtree, pNode, pCell, iHeight);
-    }
+    rc = SplitNode(pRtree, pNode, pCell, iHeight);
   }else{
     rc = AdjustTree(pRtree, pNode, pCell);
     if( ALWAYS(rc==SQLITE_OK) ){
@@ -3356,7 +3191,6 @@ static int rtreeUpdate(
     }
     if( rc==SQLITE_OK ){
       int rc2;
-      pRtree->iReinsertHeight = -1;
       rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
       rc2 = nodeRelease(pRtree, pLeaf);
       if( rc==SQLITE_OK ){
@@ -3497,8 +3331,11 @@ static int rtreeShadowName(const char *zName){
   return 0;
 }
 
+/* Forward declaration */
+static int rtreeIntegrity(sqlite3_vtab*, const char*, const char*, int, char**);
+
 static sqlite3_module rtreeModule = {
-  3,                          /* iVersion */
+  4,                          /* iVersion */
   rtreeCreate,                /* xCreate - create a table */
   rtreeConnect,               /* xConnect - connect to an existing table */
   rtreeBestIndex,             /* xBestIndex - Determine search strategy */
@@ -3521,7 +3358,8 @@ static sqlite3_module rtreeModule = {
   rtreeSavepoint,             /* xSavepoint */
   0,                          /* xRelease */
   0,                          /* xRollbackTo */
-  rtreeShadowName             /* xShadowName */
+  rtreeShadowName,            /* xShadowName */
+  rtreeIntegrity              /* xIntegrity */
 };
 
 static int rtreeSqlInit(
@@ -3777,22 +3615,27 @@ static int rtreeInit(
   }
 
   sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+  sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+
 
   /* Allocate the sqlite3_vtab structure */
   nDb = (int)strlen(argv[1]);
   nName = (int)strlen(argv[2]);
-  pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
+  pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8);
   if( !pRtree ){
     return SQLITE_NOMEM;
   }
-  memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
+  memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8);
   pRtree->nBusy = 1;
   pRtree->base.pModule = &rtreeModule;
   pRtree->zDb = (char *)&pRtree[1];
   pRtree->zName = &pRtree->zDb[nDb+1];
+  pRtree->zNodeName = &pRtree->zName[nName+1];
   pRtree->eCoordType = (u8)eCoordType;
   memcpy(pRtree->zDb, argv[1], nDb);
   memcpy(pRtree->zName, argv[2], nName);
+  memcpy(pRtree->zNodeName, argv[2], nName);
+  memcpy(&pRtree->zNodeName[nName], "_node", 6);
 
 
   /* Create/Connect to the underlying relational database schema. If
@@ -4289,7 +4132,6 @@ static int rtreeCheckTable(
 ){
   RtreeCheck check;               /* Common context for various routines */
   sqlite3_stmt *pStmt = 0;        /* Used to find column count of rtree table */
-  int bEnd = 0;                   /* True if transaction should be closed */
   int nAux = 0;                   /* Number of extra columns. */
 
   /* Initialize the context object */
@@ -4298,14 +4140,6 @@ static int rtreeCheckTable(
   check.zDb = zDb;
   check.zTab = zTab;
 
-  /* If there is not already an open transaction, open one now. This is
-  ** to ensure that the queries run as part of this integrity-check operate
-  ** on a consistent snapshot.  */
-  if( sqlite3_get_autocommit(db) ){
-    check.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0);
-    bEnd = 1;
-  }
-
   /* Find the number of auxiliary columns */
   if( check.rc==SQLITE_OK ){
     pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab);
@@ -4346,15 +4180,34 @@ static int rtreeCheckTable(
   sqlite3_finalize(check.aCheckMapping[0]);
   sqlite3_finalize(check.aCheckMapping[1]);
 
-  /* If one was opened, close the transaction */
-  if( bEnd ){
-    int rc = sqlite3_exec(db, "END", 0, 0, 0);
-    if( check.rc==SQLITE_OK ) check.rc = rc;
-  }
   *pzReport = check.zReport;
   return check.rc;
 }
 
+/*
+** Implementation of the xIntegrity method for Rtree.
+*/
+static int rtreeIntegrity(
+  sqlite3_vtab *pVtab,   /* The virtual table to check */
+  const char *zSchema,   /* Schema in which the virtual table lives */
+  const char *zName,     /* Name of the virtual table */
+  int isQuick,           /* True for a quick_check */
+  char **pzErr           /* Write results here */
+){
+  Rtree *pRtree = (Rtree*)pVtab;
+  int rc;
+  assert( pzErr!=0 && *pzErr==0 );
+  UNUSED_PARAMETER(zSchema);
+  UNUSED_PARAMETER(zName);
+  UNUSED_PARAMETER(isQuick);
+  rc = rtreeCheckTable(pRtree->db, pRtree->zDb, pRtree->zName, pzErr);
+  if( rc==SQLITE_OK && *pzErr ){
+    *pzErr = sqlite3_mprintf("In RTree %s.%s:\n%z",
+                 pRtree->zDb, pRtree->zName, *pzErr);
+  }
+  return rc;
+}
+
 /*
 ** Usage:
 **
diff --git a/libsql-sqlite3/ext/rtree/rtree1.test b/libsql-sqlite3/ext/rtree/rtree1.test
index 633d0a5d5f..61664e1529 100644
--- a/libsql-sqlite3/ext/rtree/rtree1.test
+++ b/libsql-sqlite3/ext/rtree/rtree1.test
@@ -774,14 +774,27 @@ do_execsql_test 21.1 {
 # Round-off error associated with using large integer constraints on
 # a rtree search.
 #
+if {$tcl_platform(machine)!="i686" || $tcl_platform(os)!="Linux"} {
+  reset_db
+  do_execsql_test 22.0 {
+    CREATE VIRTUAL TABLE t1 USING rtree ( id, x0, x1 );  
+    INSERT INTO t1 VALUES (123, 9223372036854775799, 9223372036854775800);
+    SELECT id FROM t1 WHERE x0 > 9223372036854775807;
+  } {123}
+  do_execsql_test 22.1 {
+    SELECT id, x0 > 9223372036854775807 AS 'a0' FROM t1;
+  } {123 1}
+}
+
+# 2023-10-14 dbsqlfuzz --sql-fuzz find.  rtreecheck() should not call
+# BEGIN/COMMIT because that causes problems with statement transactions,
+# and it is unnecessary.
+#
 reset_db
-do_execsql_test 22.0 {
-  CREATE VIRTUAL TABLE t1 USING rtree ( id, x0, x1 );  
-  INSERT INTO t1 VALUES (123, 9223372036854775799, 9223372036854775800);
-  SELECT id FROM t1 WHERE x0 > 9223372036854775807;
-} {123}
-do_execsql_test 22.1 {
-  SELECT id, x0 > 9223372036854775807 AS 'a0' FROM t1;
-} {123 1}
+do_test 23.0 {
+  db eval {CREATE TABLE t1(a,b,c);}
+  catch {db eval {CREATE TABLE t2 AS SELECT rtreecheck('t1') AS y;}}
+  db eval {PRAGMA integrity_check;}
+} {ok}
 
 finish_test
diff --git a/libsql-sqlite3/ext/rtree/rtree8.test b/libsql-sqlite3/ext/rtree/rtree8.test
index 12e75a6850..51bd404164 100644
--- a/libsql-sqlite3/ext/rtree/rtree8.test
+++ b/libsql-sqlite3/ext/rtree/rtree8.test
@@ -75,7 +75,7 @@ do_test rtree8-1.2.2 { nested_select 1 } {51}
 #
 populate_t1 1500
 do_rtree_integrity_test rtree8-1.3.0 t1
-do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {164}
+do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {183}
 do_test rtree8-1.3.2 {
   set rowids [execsql {SELECT min(rowid) FROM t1_rowid GROUP BY nodeno}]
   set stmt_list [list]
diff --git a/libsql-sqlite3/ext/rtree/rtreeA.test b/libsql-sqlite3/ext/rtree/rtreeA.test
index 301cd4fc62..0b52070c42 100644
--- a/libsql-sqlite3/ext/rtree/rtreeA.test
+++ b/libsql-sqlite3/ext/rtree/rtreeA.test
@@ -113,7 +113,7 @@ do_execsql_test rtreeA-1.1.1 {
   SELECT rtreecheck('main', 't1')
 } {{Node 1 missing from database
 Wrong number of entries in %_rowid table - expected 0, actual 500
-Wrong number of entries in %_parent table - expected 0, actual 23}}
+Wrong number of entries in %_parent table - expected 0, actual 25}}
 
 do_execsql_test  rtreeA-1.2.0 { DROP TABLE t1_node } {}
 do_corruption_tests rtreeA-1.2 -error "database disk image is malformed" {
@@ -191,7 +191,7 @@ do_execsql_test rtreeA-3.3.3.4 {
   SELECT rtreecheck('main', 't1')
 } {{Rtree depth out of range (65535)
 Wrong number of entries in %_rowid table - expected 0, actual 499
-Wrong number of entries in %_parent table - expected 0, actual 23}}
+Wrong number of entries in %_parent table - expected 0, actual 25}}
 
 #-------------------------------------------------------------------------
 # Set the "number of entries" field on some nodes incorrectly.
diff --git a/libsql-sqlite3/ext/rtree/rtree_util.tcl b/libsql-sqlite3/ext/rtree/rtree_util.tcl
index afa588e4e9..5640baf4e0 100644
--- a/libsql-sqlite3/ext/rtree/rtree_util.tcl
+++ b/libsql-sqlite3/ext/rtree/rtree_util.tcl
@@ -192,6 +192,6 @@ proc rtree_treedump {db zTab} {
 }
 
 proc do_rtree_integrity_test {tn tbl} {
-  uplevel [list do_execsql_test $tn "SELECT rtreecheck('$tbl')" ok]
+  uplevel [list do_execsql_test $tn.1 "SELECT rtreecheck('$tbl')" ok]
+  uplevel [list do_execsql_test $tn.2 "PRAGMA integrity_check" ok]
 }
-
diff --git a/libsql-sqlite3/ext/rtree/rtreecheck.test b/libsql-sqlite3/ext/rtree/rtreecheck.test
index ff5397e1e1..7a98f9bf4e 100644
--- a/libsql-sqlite3/ext/rtree/rtreecheck.test
+++ b/libsql-sqlite3/ext/rtree/rtreecheck.test
@@ -78,6 +78,11 @@ do_execsql_test 2.3 {
   SELECT rtreecheck('r1') 
 } {{Dimension 0 of cell 0 on node 1 is corrupt
 Dimension 1 of cell 3 on node 1 is corrupt}}
+do_execsql_test 2.3b { 
+  PRAGMA integrity_check;
+} {{In RTree main.r1:
+Dimension 0 of cell 0 on node 1 is corrupt
+Dimension 1 of cell 3 on node 1 is corrupt}}
 
 setup_simple_db
 do_execsql_test 2.4 {
@@ -85,12 +90,21 @@ do_execsql_test 2.4 {
   SELECT rtreecheck('r1') 
 } {{Mapping (3 -> 1) missing from %_rowid table
 Wrong number of entries in %_rowid table - expected 5, actual 4}}
+do_execsql_test 2.4b {
+  PRAGMA integrity_check
+} {{In RTree main.r1:
+Mapping (3 -> 1) missing from %_rowid table
+Wrong number of entries in %_rowid table - expected 5, actual 4}}
 
 setup_simple_db
 do_execsql_test 2.5 {
   UPDATE r1_rowid SET nodeno=2 WHERE rowid=3;
   SELECT rtreecheck('r1') 
 } {{Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
+do_execsql_test 2.5b {
+  PRAGMA integrity_check
+} {{In RTree main.r1:
+Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
 
 reset_db
 do_execsql_test 3.0 { 
@@ -104,14 +118,16 @@ do_execsql_test 3.0 {
   INSERT INTO r1 VALUES(7, 5, 0x00000080);
   INSERT INTO r1 VALUES(8, 5, 0x40490fdb);
   INSERT INTO r1 VALUES(9, 0x7f800000, 0x7f900000);
-  SELECT rtreecheck('r1') 
-} {ok}
+  SELECT rtreecheck('r1');
+  PRAGMA integrity_check;
+} {ok ok}
 
 do_execsql_test 3.1 { 
   CREATE VIRTUAL TABLE r2 USING rtree_i32(id, x1, x2);
   INSERT INTO r2 VALUES(2, -1*(1<<31), -1*(1<<31)+5);
-  SELECT rtreecheck('r2') 
-} {ok}
+  SELECT rtreecheck('r2');
+  PRAGMA integrity_check;
+} {ok ok}
 
 sqlite3_db_config db DEFENSIVE 0
 do_execsql_test 3.2 {
@@ -125,6 +141,11 @@ do_execsql_test 3.3 {
   UPDATE r2_node SET data = X'00001234';
   SELECT rtreecheck('r2')!='ok';
 } {1}
+do_execsql_test 3.4 {
+  PRAGMA integrity_check;
+} {{In RTree main.r2:
+Node 1 is too small for cell count of 4660 (4 bytes)
+Wrong number of entries in %_rowid table - expected 0, actual 1}}
 
 do_execsql_test 4.0 {
   CREATE TABLE notanrtree(i);
@@ -181,4 +202,3 @@ if {[permutation]=="inmemory_journal"} {
 }
 
 finish_test
-
diff --git a/libsql-sqlite3/ext/rtree/rtreedoc.test b/libsql-sqlite3/ext/rtree/rtreedoc.test
index b64faa2e99..4e610db8a2 100644
--- a/libsql-sqlite3/ext/rtree/rtreedoc.test
+++ b/libsql-sqlite3/ext/rtree/rtreedoc.test
@@ -601,11 +601,21 @@ do_execsql_test 2.5.2 {
   SELECT A.id FROM demo_index AS A, demo_index AS B
     WHERE A.maxX>=B.minX AND A.minX<=B.maxX
     AND A.maxY>=B.minY AND A.minY<=B.maxY
-    AND B.id=28269;
+    AND B.id=28269 ORDER BY +A.id;
 } {
-  28293 28216 28322 28286 28269 
-  28215 28336 28262 28291 28320 
-  28313 28298 28287
+  28215
+  28216
+  28262
+  28269
+  28286
+  28287
+  28291
+  28293
+  28298
+  28313
+  28320
+  28322
+  28336
 }
 
 # EVIDENCE-OF: R-02723-34107 Note that it is not necessary for all
@@ -1575,7 +1585,7 @@ execsql BEGIN
 do_test 3.6 {
   execsql { INSERT INTO rt2_parent VALUES(1000, 1000) }
   execsql { SELECT rtreecheck('rt2') }
-} {{Wrong number of entries in %_parent table - expected 9, actual 10}}
+} {{Wrong number of entries in %_parent table - expected 10, actual 11}}
 execsql ROLLBACK
 
 
diff --git a/libsql-sqlite3/ext/rtree/rtreefuzz001.test b/libsql-sqlite3/ext/rtree/rtreefuzz001.test
index 58fd179ab9..c81c41da30 100644
--- a/libsql-sqlite3/ext/rtree/rtreefuzz001.test
+++ b/libsql-sqlite3/ext/rtree/rtreefuzz001.test
@@ -1043,7 +1043,7 @@ do_test rtreefuzz001-500 {
 | end crash-2e81f5dce5cbd4.db}]
   execsql { PRAGMA writable_schema = 1;}
   catchsql {UPDATE t1 SET ex= ex ISNULL}
-} {1 {database disk image is malformed}}
+} {0 {}}
 
 do_test rtreefuzz001-600 {
   sqlite3 db {}
diff --git a/libsql-sqlite3/ext/session/session3.test b/libsql-sqlite3/ext/session/session3.test
index ba316348ef..ee955f1376 100644
--- a/libsql-sqlite3/ext/session/session3.test
+++ b/libsql-sqlite3/ext/session/session3.test
@@ -135,8 +135,8 @@ do_test 2.2.2 {
     DROP TABLE t2;
     CREATE TABLE t2(a, b PRIMARY KEY, c, d);
   }
-  list [catch { S changeset } msg] $msg
-} {1 SQLITE_SCHEMA}
+  catch { S changeset } 
+} {0}
 do_test 2.2.3 {
   S delete
   sqlite3session S db main
@@ -167,8 +167,8 @@ do_test 2.2.4 {
     CREATE TABLE t2(a, b PRIMARY KEY, c, d);
     INSERT INTO t2 VALUES(4, 5, 6, 7);
   }
-  list [catch { S changeset } msg] $msg
-} {1 SQLITE_SCHEMA}
+  catch { S changeset }
+} {0}
 
 do_test 2.3 {
   S delete
diff --git a/libsql-sqlite3/ext/session/sessionalter.test b/libsql-sqlite3/ext/session/sessionalter.test
new file mode 100644
index 0000000000..34424cf275
--- /dev/null
+++ b/libsql-sqlite3/ext/session/sessionalter.test
@@ -0,0 +1,238 @@
+# 2023 October 02
+#
+# 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 implements that the sessions module interacts well with
+# the ALTER TABLE ADD COLUMN command.
+#
+
+if {![info exists testdir]} {
+  set testdir [file join [file dirname [info script]] .. .. test]
+} 
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+
+ifcapable !session {finish_test; return}
+set testprefix sessionalter
+
+
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+}
+
+do_execsql_test -db db2 1.1 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c DEFAULT 1234);
+}
+
+do_then_apply_sql {
+  INSERT INTO t1 VALUES(1, 'one');
+  INSERT INTO t1 VALUES(2, 'two');
+}
+
+do_execsql_test -db db2 1.2 {
+  SELECT * FROM t1
+} {
+  1 one 1234
+  2 two 1234
+}
+
+do_then_apply_sql {
+  UPDATE t1 SET b='four' WHERE a=2;
+}
+
+do_execsql_test -db db2 1.3 {
+  SELECT * FROM t1
+} {
+  1 one 1234
+  2 four 1234
+}
+
+do_then_apply_sql {
+  DELETE FROM t1 WHERE a=1;
+}
+
+do_execsql_test -db db2 1.4 {
+  SELECT * FROM t1
+} {
+  2 four 1234
+}
+
+db2 close
+
+#--------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 2.0 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+}
+
+do_test 2.1 {
+  sqlite3session S db main
+  S attach t1
+  set {} {}
+} {} 
+do_execsql_test 2.2 {
+  INSERT INTO t1 VALUES(1, 2);
+  ALTER TABLE t1 ADD COLUMN c DEFAULT 'abcd';
+  INSERT INTO t1 VALUES(2, 3, 4);
+}
+do_changeset_test 2.3 S {
+  {INSERT t1 0 X.. {} {i 1 i 2 t abcd}} 
+  {INSERT t1 0 X.. {} {i 2 i 3 i 4}}
+}
+
+do_iterator_test 2.4 {} {
+  DELETE FROM t1 WHERE a=2;
+  ALTER TABLE t1 ADD COLUMN d DEFAULT 'abcd';
+  ALTER TABLE t1 ADD COLUMN e DEFAULT 5;
+  ALTER TABLE t1 ADD COLUMN f DEFAULT 7.2;
+  -- INSERT INTO t1 VALUES(9, 9, 9, 9);
+} {
+  {DELETE t1 0 X..... {i 2 i 3 i 4 t abcd i 5 f 7.2} {}}
+}
+
+#-------------------------------------------------------------------------
+# Tests of the sqlite3changegroup_xxx() APIs.
+#
+reset_db
+do_execsql_test 3.0 {
+  CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
+  CREATE TABLE t2(x PRIMARY KEY, y);
+  CREATE TABLE t3(x, y);
+  CREATE TABLE t4(y PRIMARY KEY, x) WITHOUT ROWID;
+
+  INSERT INTO t1 VALUES(1, 2), (3, 4), (5, 6);
+  INSERT INTO t2 VALUES('one', 'two'), ('three', 'four'), ('five', 'six');
+  INSERT INTO t3 VALUES(1, 2), (3, 4), (5, 6);
+
+  INSERT INTO t4(x, y) VALUES(1, 2), (3, 4), (5, 6);
+}
+
+db_save_and_close
+foreach {tn sql1 at sql2} {
+  1 {
+    INSERT INTO t1(x, y) VALUES(7, 8);
+  } {
+    ALTER TABLE t1 ADD COLUMN z DEFAULT 10;
+  } {
+    UPDATE t1 SET y=11 WHERE x=7;
+  }
+
+  2 {
+    UPDATE t2 SET y='two.two' WHERE x='one';
+    DELETE FROM t2 WHERE x='five';
+    INSERT INTO t2(x, y) VALUES('seven', 'eight');
+  } {
+    ALTER TABLE t2 ADD COLUMN z;
+    ALTER TABLE t2 ADD COLUMN zz;
+  } {
+  }
+
+  3 {
+    DELETE FROM t2 WHERE x='five';
+  } {
+    ALTER TABLE t2 ADD COLUMN z DEFAULT 'xyz';
+  } {
+  }
+
+  4 {
+    UPDATE t2 SET y='two.two' WHERE x='three';
+  } {
+    ALTER TABLE t2 ADD COLUMN z;
+  } {
+    UPDATE t2 SET z='abc' WHERE x='one';
+  }
+
+  5* {
+    UPDATE t2 SET y='two.two' WHERE x='three';
+  } {
+    ALTER TABLE t2 ADD COLUMN z DEFAULT 'defu1';
+  } {
+  }
+
+  6* {
+    INSERT INTO t2(x, y) VALUES('nine', 'ten');
+  } {
+    ALTER TABLE t2 ADD COLUMN z;
+    ALTER TABLE t2 ADD COLUMN a DEFAULT 'eelve';
+    ALTER TABLE t2 ADD COLUMN b DEFAULT x'1234abcd';
+    ALTER TABLE t2 ADD COLUMN c DEFAULT 4.2;
+    ALTER TABLE t2 ADD COLUMN d DEFAULT NULL;
+  } {
+  }
+
+  7 {
+    INSERT INTO t3(x, y) VALUES(7, 8);
+    UPDATE t3 SET y='fourteen' WHERE x=1;
+    DELETE FROM t3 WHERE x=3;
+  } {
+    ALTER TABLE t3 ADD COLUMN c;
+  } {
+    INSERT INTO t3(x, y, c) VALUES(9, 10, 11);
+  }
+
+  8 {
+    INSERT INTO t4(x, y) VALUES(7, 8);
+    UPDATE t4 SET y='fourteen' WHERE x=1;
+    DELETE FROM t4 WHERE x=3;
+  } {
+    ALTER TABLE t4 ADD COLUMN c;
+  } {
+    INSERT INTO t4(x, y, c) VALUES(9, 10, 11);
+  }
+} {
+  foreach {tn2 cmd} {
+    1 changeset_from_sql
+    2 patchset_from_sql
+  } {
+    db_restore_and_reopen
+  
+    set C1 [$cmd $sql1]
+    execsql $at
+    set C2 [$cmd $sql2]
+  
+    sqlite3changegroup grp
+    grp schema db main
+    grp add $C1
+    grp add $C2
+    set T1 [grp output]
+    grp delete
+  
+    db_restore_and_reopen
+    execsql $at
+    set T2 [$cmd "$sql1 ; $sql2"]
+  
+    if {[string range $tn end end]!="*"} {
+      do_test 3.1.$tn.$tn2.1 { changeset_to_list $T1 } [changeset_to_list $T2]
+      set testname "$tn.$tn2"
+    } else {
+      set testname "[string range $tn 0 end-1].$tn2"
+    }
+  
+    db_restore_and_reopen
+    proc xConflict {args} { return "REPLACE" }
+    sqlite3changeset_apply_v2 db $T1 xConflict
+    set S1 [scksum db main]
+  
+    db_restore_and_reopen
+    sqlite3changeset_apply_v2 db $T2 xConflict
+    set S2 [scksum db main]
+  
+    # if { $tn==7 } { puts [changeset_to_list $T1] }
+  
+    do_test 3.1.$tn.2 { set S1 } $S2
+  }
+}
+
+
+finish_test
+
diff --git a/libsql-sqlite3/ext/session/sessionfault3.test b/libsql-sqlite3/ext/session/sessionfault3.test
new file mode 100644
index 0000000000..af5a4cdb43
--- /dev/null
+++ b/libsql-sqlite3/ext/session/sessionfault3.test
@@ -0,0 +1,59 @@
+# 2016 October 6
+#
+# 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the session module.
+#
+
+if {![info exists testdir]} {
+  set testdir [file join [file dirname [info script]] .. .. test]
+} 
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+set testprefix sessionfault3
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a, b, PRIMARY KEY(a));
+  INSERT INTO t1 VALUES(1, 2);
+  INSERT INTO t1 VALUES(3, 4);
+  INSERT INTO t1 VALUES('five', 'six');
+}
+
+set C1 [changeset_from_sql {
+  INSERT INTO t1 VALUES('seven', 'eight');
+  UPDATE t1 SET b=6 WHERE a='five';
+  DELETE FROM t1 WHERE a=1;
+}]
+
+do_execsql_test 1.1 {
+  ALTER TABLE t1 ADD COLUMN d DEFAULT 123;
+  ALTER TABLE t1 ADD COLUMN e DEFAULT 'string';
+}
+
+set C2 [changeset_from_sql {
+  UPDATE t1 SET e='new value' WHERE a='seven';
+  INSERT INTO t1 VALUES(0, 0, 0, 0);
+}]
+
+do_faultsim_test 1 -faults oom* -prep {
+  sqlite3changegroup G
+} -body {
+  G schema db main
+  G add $::C1
+  G add $::C2
+  G output
+  set {} {}
+} -test {
+  catch { G delete }
+  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+
+finish_test
diff --git a/libsql-sqlite3/ext/session/sessionnoact.test b/libsql-sqlite3/ext/session/sessionnoact.test
new file mode 100644
index 0000000000..1274ecb146
--- /dev/null
+++ b/libsql-sqlite3/ext/session/sessionnoact.test
@@ -0,0 +1,110 @@
+# 2023 October 20
+#
+# 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 implements regression tests for SQLite library.
+#
+
+if {![info exists testdir]} {
+  set testdir [file join [file dirname [info script]] .. .. test]
+} 
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionnoact
+
+do_execsql_test 1.0 {
+  CREATE TABLE p1(a INTEGER PRIMARY KEY, b, c UNIQUE);
+  INSERT INTO p1 VALUES(1, 1, 'one');
+  INSERT INTO p1 VALUES(2, 2, 'two');
+  INSERT INTO p1 VALUES(3, 3, 'three');
+  INSERT INTO p1 VALUES(4, 4, 'four');
+}
+
+db_save
+
+set C [changeset_from_sql {
+  DELETE FROM p1 WHERE a=2;
+  UPDATE p1 SET c='six' WHERE a=3;
+  INSERT INTO p1 VALUES(5, 5, 'two');
+  INSERT INTO p1 VALUES(6, 6, 'three');
+}]
+
+db_restore_and_reopen
+
+do_execsql_test 1.1 {
+  CREATE TABLE c1(x INTEGER PRIMARY KEY, y,
+    FOREIGN KEY(y) REFERENCES p1(c) ON DELETE CASCADE ON UPDATE SET NULL
+  );
+
+  INSERT INTO c1 VALUES(10, 'one');
+  INSERT INTO c1 VALUES(20, 'two');
+  INSERT INTO c1 VALUES(30, 'three');
+  INSERT INTO c1 VALUES(40, 'four');
+}
+
+db_save
+
+do_execsql_test 1.2 {
+  PRAGMA foreign_keys = 1;
+}
+
+set ::nConflict 0
+proc conflict {args} {
+  incr ::nConflict
+  return "OMIT"
+}
+
+sqlite3changeset_apply_v2 db $C conflict
+
+do_execsql_test 1.3 {
+  SELECT * FROM c1
+} {
+  10 one
+  30 {}
+  40 four
+}
+
+db_restore_and_reopen
+
+do_execsql_test 1.4 {
+  PRAGMA foreign_keys = 1;
+}
+
+do_execsql_test 1.5 {
+  UPDATE p1 SET c=12345 WHERE a = 45;
+}
+
+sqlite3changeset_apply_v2 -noaction db $C conflict
+do_execsql_test 1.6 {
+  SELECT * FROM c1
+} {
+  10 one
+  20 two
+  30 three
+  40 four
+}
+
+do_execsql_test 1.7 {
+  PRAGMA foreign_keys = 1;
+  UPDATE p1 SET c = 'ten' WHERE c='two';
+  SELECT * FROM c1;
+} {
+  10 one
+  20 {}
+  30 three
+  40 four
+}
+
+do_execsql_test 1.8 {
+  PRAGMA foreign_key_check
+}
+
+finish_test
diff --git a/libsql-sqlite3/ext/session/sqlite3session.c b/libsql-sqlite3/ext/session/sqlite3session.c
index 9f862f2465..4f682a13d3 100644
--- a/libsql-sqlite3/ext/session/sqlite3session.c
+++ b/libsql-sqlite3/ext/session/sqlite3session.c
@@ -119,6 +119,18 @@ struct sqlite3_changeset_iter {
 ** The data associated with each hash-table entry is a structure containing
 ** a subset of the initial values that the modified row contained at the
 ** start of the session. Or no initial values if the row was inserted.
+**
+** pDfltStmt:
+**   This is only used by the sqlite3changegroup_xxx() APIs, not by
+**   regular sqlite3_session objects. It is a SELECT statement that
+**   selects the default value for each table column. For example,
+**   if the table is 
+**
+**      CREATE TABLE xx(a DEFAULT 1, b, c DEFAULT 'abc')
+**
+**   then this variable is the compiled version of:
+**
+**      SELECT 1, NULL, 'abc'
 */
 struct SessionTable {
   SessionTable *pNext;
@@ -127,10 +139,12 @@ struct SessionTable {
   int bStat1;                     /* True if this is sqlite_stat1 */
   int bRowid;                     /* True if this table uses rowid for PK */
   const char **azCol;             /* Column names */
+  const char **azDflt;            /* Default value expressions */
   u8 *abPK;                       /* Array of primary key flags */
   int nEntry;                     /* Total number of entries in hash table */
   int nChange;                    /* Size of apChange[] array */
   SessionChange **apChange;       /* Hash table buckets */
+  sqlite3_stmt *pDfltStmt;
 };
 
 /* 
@@ -299,6 +313,7 @@ struct SessionTable {
 struct SessionChange {
   u8 op;                          /* One of UPDATE, DELETE, INSERT */
   u8 bIndirect;                   /* True if this change is "indirect" */
+  u16 nRecordField;               /* Number of fields in aRecord[] */
   int nMaxSize;                   /* Max size of eventual changeset record */
   int nRecord;                    /* Number of bytes in buffer aRecord[] */
   u8 *aRecord;                    /* Buffer containing old.* record */
@@ -324,7 +339,7 @@ static int sessionVarintLen(int iVal){
 ** Read a varint value from aBuf[] into *piVal. Return the number of 
 ** bytes read.
 */
-static int sessionVarintGet(u8 *aBuf, int *piVal){
+static int sessionVarintGet(const u8 *aBuf, int *piVal){
   return getVarint32(aBuf, *piVal);
 }
 
@@ -587,9 +602,11 @@ static int sessionPreupdateHash(
 ** Return the number of bytes of space occupied by the value (including
 ** the type byte).
 */
-static int sessionSerialLen(u8 *a){
-  int e = *a;
+static int sessionSerialLen(const u8 *a){
+  int e;
   int n;
+  assert( a!=0 );
+  e = *a;
   if( e==0 || e==0xFF ) return 1;
   if( e==SQLITE_NULL ) return 1;
   if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9;
@@ -994,13 +1011,14 @@ static int sessionGrowHash(
 **
 ** For example, if the table is declared as:
 **
-**     CREATE TABLE tbl1(w, x, y, z, PRIMARY KEY(w, z));
+**     CREATE TABLE tbl1(w, x DEFAULT 'abc', y, z, PRIMARY KEY(w, z));
 **
-** Then the four output variables are populated as follows:
+** Then the five output variables are populated as follows:
 **
 **     *pnCol  = 4
 **     *pzTab  = "tbl1"
 **     *pazCol = {"w", "x", "y", "z"}
+**     *pazDflt = {NULL, 'abc', NULL, NULL}
 **     *pabPK  = {1, 0, 0, 1}
 **
 ** All returned buffers are part of the same single allocation, which must
@@ -1014,6 +1032,7 @@ static int sessionTableInfo(
   int *pnCol,                     /* OUT: number of columns */
   const char **pzTab,             /* OUT: Copy of zThis */
   const char ***pazCol,           /* OUT: Array of column names for table */
+  const char ***pazDflt,          /* OUT: Array of default value expressions */
   u8 **pabPK,                     /* OUT: Array of booleans - true for PK col */
   int *pbRowid                    /* OUT: True if only PK is a rowid */
 ){
@@ -1026,11 +1045,18 @@ static int sessionTableInfo(
   int i;
   u8 *pAlloc = 0;
   char **azCol = 0;
+  char **azDflt = 0;
   u8 *abPK = 0;
   int bRowid = 0;                 /* Set to true to use rowid as PK */
 
   assert( pazCol && pabPK );
 
+  *pazCol = 0;
+  *pabPK = 0;
+  *pnCol = 0;
+  if( pzTab ) *pzTab = 0;
+  if( pazDflt ) *pazDflt = 0;
+
   nThis = sqlite3Strlen30(zThis);
   if( nThis==12 && 0==sqlite3_stricmp("sqlite_stat1", zThis) ){
     rc = sqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0);
@@ -1044,39 +1070,28 @@ static int sessionTableInfo(
     }else if( rc==SQLITE_ERROR ){
       zPragma = sqlite3_mprintf("");
     }else{
-      *pazCol = 0;
-      *pabPK = 0;
-      *pnCol = 0;
-      if( pzTab ) *pzTab = 0;
       return rc;
     }
   }else{
     zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
   }
   if( !zPragma ){
-    *pazCol = 0;
-    *pabPK = 0;
-    *pnCol = 0;
-    if( pzTab ) *pzTab = 0;
     return SQLITE_NOMEM;
   }
 
   rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
   sqlite3_free(zPragma);
   if( rc!=SQLITE_OK ){
-    *pazCol = 0;
-    *pabPK = 0;
-    *pnCol = 0;
-    if( pzTab ) *pzTab = 0;
     return rc;
   }
 
   nByte = nThis + 1;
   bRowid = (pbRowid!=0);
   while( SQLITE_ROW==sqlite3_step(pStmt) ){
-    nByte += sqlite3_column_bytes(pStmt, 1);
+    nByte += sqlite3_column_bytes(pStmt, 1);          /* name */
+    nByte += sqlite3_column_bytes(pStmt, 4);          /* dflt_value */
     nDbCol++;
-    if( sqlite3_column_int(pStmt, 5) ) bRowid = 0;
+    if( sqlite3_column_int(pStmt, 5) ) bRowid = 0;    /* pk */
   }
   if( nDbCol==0 ) bRowid = 0;
   nDbCol += bRowid;
@@ -1084,15 +1099,18 @@ static int sessionTableInfo(
   rc = sqlite3_reset(pStmt);
 
   if( rc==SQLITE_OK ){
-    nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
+    nByte += nDbCol * (sizeof(const char *)*2 + sizeof(u8) + 1 + 1);
     pAlloc = sessionMalloc64(pSession, nByte);
     if( pAlloc==0 ){
       rc = SQLITE_NOMEM;
+    }else{
+      memset(pAlloc, 0, nByte);
     }
   }
   if( rc==SQLITE_OK ){
     azCol = (char **)pAlloc;
-    pAlloc = (u8 *)&azCol[nDbCol];
+    azDflt = (char**)&azCol[nDbCol];
+    pAlloc = (u8 *)&azDflt[nDbCol];
     abPK = (u8 *)pAlloc;
     pAlloc = &abPK[nDbCol];
     if( pzTab ){
@@ -1112,11 +1130,21 @@ static int sessionTableInfo(
     }
     while( SQLITE_ROW==sqlite3_step(pStmt) ){
       int nName = sqlite3_column_bytes(pStmt, 1);
+      int nDflt = sqlite3_column_bytes(pStmt, 4);
       const unsigned char *zName = sqlite3_column_text(pStmt, 1);
+      const unsigned char *zDflt = sqlite3_column_text(pStmt, 4);
+
       if( zName==0 ) break;
       memcpy(pAlloc, zName, nName+1);
       azCol[i] = (char *)pAlloc;
       pAlloc += nName+1;
+      if( zDflt ){
+        memcpy(pAlloc, zDflt, nDflt+1);
+        azDflt[i] = (char *)pAlloc;
+        pAlloc += nDflt+1;
+      }else{
+        azDflt[i] = 0;
+      }
       abPK[i] = sqlite3_column_int(pStmt, 5);
       i++;
     }
@@ -1127,14 +1155,11 @@ static int sessionTableInfo(
   ** free any allocation made. An error code will be returned in this case.
   */
   if( rc==SQLITE_OK ){
-    *pazCol = (const char **)azCol;
+    *pazCol = (const char**)azCol;
+    if( pazDflt ) *pazDflt = (const char**)azDflt;
     *pabPK = abPK;
     *pnCol = nDbCol;
   }else{
-    *pazCol = 0;
-    *pabPK = 0;
-    *pnCol = 0;
-    if( pzTab ) *pzTab = 0;
     sessionFree(pSession, azCol);
   }
   if( pbRowid ) *pbRowid = bRowid;
@@ -1143,10 +1168,9 @@ static int sessionTableInfo(
 }
 
 /*
-** This function is only called from within a pre-update handler for a
-** write to table pTab, part of session pSession. If this is the first
-** write to this table, initalize the SessionTable.nCol, azCol[] and
-** abPK[] arrays accordingly.
+** This function is called to initialize the SessionTable.nCol, azCol[]
+** abPK[] and azDflt[] members of SessionTable object pTab. If these
+** fields are already initilialized, this function is a no-op.
 **
 ** If an error occurs, an error code is stored in sqlite3_session.rc and
 ** non-zero returned. Or, if no error occurs but the table has no primary
@@ -1154,15 +1178,22 @@ static int sessionTableInfo(
 ** indicate that updates on this table should be ignored. SessionTable.abPK 
 ** is set to NULL in this case.
 */
-static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
+static int sessionInitTable(
+  sqlite3_session *pSession,      /* Optional session handle */
+  SessionTable *pTab,             /* Table object to initialize */
+  sqlite3 *db,                    /* Database handle to read schema from */
+  const char *zDb                 /* Name of db - "main", "temp" etc. */
+){
+  int rc = SQLITE_OK;
+
   if( pTab->nCol==0 ){
     u8 *abPK;
     assert( pTab->azCol==0 || pTab->abPK==0 );
-    pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, 
-        pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK,
-        (pSession->bImplicitPK ? &pTab->bRowid : 0)
+    rc = sessionTableInfo(pSession, db, zDb, 
+        pTab->zName, &pTab->nCol, 0, &pTab->azCol, &pTab->azDflt, &abPK,
+        ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0)
     );
-    if( pSession->rc==SQLITE_OK ){
+    if( rc==SQLITE_OK ){
       int i;
       for(i=0; i<pTab->nCol; i++){
         if( abPK[i] ){
@@ -1174,14 +1205,321 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
         pTab->bStat1 = 1;
       }
 
-      if( pSession->bEnableSize ){
+      if( pSession && pSession->bEnableSize ){
         pSession->nMaxChangesetSize += (
           1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1
         );
       }
     }
   }
-  return (pSession->rc || pTab->abPK==0);
+
+  if( pSession ){
+    pSession->rc = rc;
+    return (rc || pTab->abPK==0);
+  }
+  return rc;
+}
+
+/*
+** Re-initialize table object pTab.
+*/
+static int sessionReinitTable(sqlite3_session *pSession, SessionTable *pTab){
+  int nCol = 0;
+  const char **azCol = 0;
+  const char **azDflt = 0;
+  u8 *abPK = 0; 
+  int bRowid = 0;
+
+  assert( pSession->rc==SQLITE_OK );
+
+  pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, 
+      pTab->zName, &nCol, 0, &azCol, &azDflt, &abPK,
+      (pSession->bImplicitPK ? &bRowid : 0)
+  );
+  if( pSession->rc==SQLITE_OK ){
+    if( pTab->nCol>nCol || pTab->bRowid!=bRowid ){
+      pSession->rc = SQLITE_SCHEMA;
+    }else{
+      int ii;
+      int nOldCol = pTab->nCol;
+      for(ii=0; ii<nCol; ii++){
+        if( ii<pTab->nCol ){
+          if( pTab->abPK[ii]!=abPK[ii] ){
+            pSession->rc = SQLITE_SCHEMA;
+          }
+        }else if( abPK[ii] ){
+          pSession->rc = SQLITE_SCHEMA;
+        }
+      }
+
+      if( pSession->rc==SQLITE_OK ){
+        const char **a = pTab->azCol;
+        pTab->azCol = azCol;
+        pTab->nCol = nCol;
+        pTab->azDflt = azDflt;
+        pTab->abPK = abPK;
+        azCol = a;
+      }
+      if( pSession->bEnableSize ){
+        pSession->nMaxChangesetSize += (nCol - nOldCol);
+        pSession->nMaxChangesetSize += sessionVarintLen(nCol);
+        pSession->nMaxChangesetSize -= sessionVarintLen(nOldCol);
+      }
+    }
+  }
+
+  sqlite3_free((char*)azCol);
+  return pSession->rc;
+}
+
+/*
+** Session-change object (*pp) contains an old.* record with fewer than
+** nCol fields. This function updates it with the default values for
+** the missing fields.
+*/
+static void sessionUpdateOneChange(
+  sqlite3_session *pSession,      /* For memory accounting */
+  int *pRc,                       /* IN/OUT: Error code */
+  SessionChange **pp,             /* IN/OUT: Change object to update */
+  int nCol,                       /* Number of columns now in table */
+  sqlite3_stmt *pDflt             /* SELECT <default-values...> */
+){
+  SessionChange *pOld = *pp;
+
+  while( pOld->nRecordField<nCol ){
+    SessionChange *pNew = 0;
+    int nByte = 0;
+    int nIncr = 0;
+    int iField = pOld->nRecordField;
+    int eType = sqlite3_column_type(pDflt, iField);
+    switch( eType ){
+      case SQLITE_NULL:
+        nIncr = 1;
+        break;
+      case SQLITE_INTEGER:
+      case SQLITE_FLOAT:
+        nIncr = 9;
+        break;
+      default: {
+        int n = sqlite3_column_bytes(pDflt, iField);
+        nIncr = 1 + sessionVarintLen(n) + n;
+        assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
+        break;
+      }
+    }
+
+    nByte = nIncr + (sizeof(SessionChange) + pOld->nRecord);
+    pNew = sessionMalloc64(pSession, nByte);
+    if( pNew==0 ){
+      *pRc = SQLITE_NOMEM;
+      return;
+    }else{
+      memcpy(pNew, pOld, sizeof(SessionChange));
+      pNew->aRecord = (u8*)&pNew[1];
+      memcpy(pNew->aRecord, pOld->aRecord, pOld->nRecord);
+      pNew->aRecord[pNew->nRecord++] = (u8)eType;
+      switch( eType ){
+        case SQLITE_INTEGER: {
+          i64 iVal = sqlite3_column_int64(pDflt, iField);
+          sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal); 
+          pNew->nRecord += 8;
+          break;
+        }
+          
+        case SQLITE_FLOAT: {
+          double rVal = sqlite3_column_double(pDflt, iField);
+          i64 iVal = 0;
+          memcpy(&iVal, &rVal, sizeof(rVal));
+          sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal); 
+          pNew->nRecord += 8;
+          break;
+        }
+
+        case SQLITE_TEXT: {
+          int n = sqlite3_column_bytes(pDflt, iField);
+          const char *z = (const char*)sqlite3_column_text(pDflt, iField);
+          pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); 
+          memcpy(&pNew->aRecord[pNew->nRecord], z, n);
+          pNew->nRecord += n;
+          break;
+        }
+
+        case SQLITE_BLOB: {
+          int n = sqlite3_column_bytes(pDflt, iField);
+          const u8 *z = (const u8*)sqlite3_column_blob(pDflt, iField);
+          pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); 
+          memcpy(&pNew->aRecord[pNew->nRecord], z, n);
+          pNew->nRecord += n;
+          break;
+        }
+
+        default:
+          assert( eType==SQLITE_NULL );
+          break;
+      }
+
+      sessionFree(pSession, pOld);
+      *pp = pOld = pNew;
+      pNew->nRecordField++;
+      pNew->nMaxSize += nIncr;
+      if( pSession ){
+        pSession->nMaxChangesetSize += nIncr;
+      }
+    }
+  }
+}
+
+/*
+** Ensure that there is room in the buffer to append nByte bytes of data.
+** If not, use sqlite3_realloc() to grow the buffer so that there is.
+**
+** If successful, return zero. Otherwise, if an OOM condition is encountered,
+** set *pRc to SQLITE_NOMEM and return non-zero.
+*/
+static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){
+#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1) 
+  i64 nReq = p->nBuf + nByte;
+  if( *pRc==SQLITE_OK && nReq>p->nAlloc ){
+    u8 *aNew;
+    i64 nNew = p->nAlloc ? p->nAlloc : 128;
+
+    do {
+      nNew = nNew*2;
+    }while( nNew<nReq );
+
+    /* The value of SESSION_MAX_BUFFER_SZ is copied from the implementation
+    ** of sqlite3_realloc64(). Allocations greater than this size in bytes
+    ** always fail. It is used here to ensure that this routine can always
+    ** allocate up to this limit - instead of up to the largest power of
+    ** two smaller than the limit.  */
+    if( nNew>SESSION_MAX_BUFFER_SZ ){
+      nNew = SESSION_MAX_BUFFER_SZ;
+      if( nNew<nReq ){
+        *pRc = SQLITE_NOMEM;
+        return 1;
+      }
+    }
+
+    aNew = (u8 *)sqlite3_realloc64(p->aBuf, nNew);
+    if( 0==aNew ){
+      *pRc = SQLITE_NOMEM;
+    }else{
+      p->aBuf = aNew;
+      p->nAlloc = nNew;
+    }
+  }
+  return (*pRc!=SQLITE_OK);
+}
+
+
+/*
+** This function is a no-op if *pRc is other than SQLITE_OK when it is 
+** called. Otherwise, append a string to the buffer. All bytes in the string
+** up to (but not including) the nul-terminator are written to the buffer.
+**
+** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before
+** returning.
+*/
+static void sessionAppendStr(
+  SessionBuffer *p, 
+  const char *zStr, 
+  int *pRc
+){
+  int nStr = sqlite3Strlen30(zStr);
+  if( 0==sessionBufferGrow(p, nStr+1, pRc) ){
+    memcpy(&p->aBuf[p->nBuf], zStr, nStr);
+    p->nBuf += nStr;
+    p->aBuf[p->nBuf] = 0x00;
+  }
+}
+
+/*
+** Format a string using printf() style formatting and then append it to the
+** buffer using sessionAppendString().
+*/
+static void sessionAppendPrintf(
+  SessionBuffer *p,               /* Buffer to append to */
+  int *pRc, 
+  const char *zFmt,
+  ...
+){
+  if( *pRc==SQLITE_OK ){
+    char *zApp = 0;
+    va_list ap;
+    va_start(ap, zFmt);
+    zApp = sqlite3_vmprintf(zFmt, ap);
+    if( zApp==0 ){
+      *pRc = SQLITE_NOMEM;
+    }else{
+      sessionAppendStr(p, zApp, pRc);
+    }
+    va_end(ap);
+    sqlite3_free(zApp);
+  }
+}
+
+/*
+** Prepare a statement against database handle db that SELECTs a single
+** row containing the default values for each column in table pTab. For
+** example, if pTab is declared as:
+**
+**   CREATE TABLE pTab(a PRIMARY KEY, b DEFAULT 123, c DEFAULT 'abcd');
+**
+** Then this function prepares and returns the SQL statement:
+**
+**   SELECT NULL, 123, 'abcd';
+*/
+static int sessionPrepareDfltStmt(
+  sqlite3 *db,                    /* Database handle */
+  SessionTable *pTab,             /* Table to prepare statement for */
+  sqlite3_stmt **ppStmt           /* OUT: Statement handle */
+){
+  SessionBuffer sql = {0,0,0};
+  int rc = SQLITE_OK;
+  const char *zSep = " ";
+  int ii = 0;
+
+  *ppStmt = 0;
+  sessionAppendPrintf(&sql, &rc, "SELECT");
+  for(ii=0; ii<pTab->nCol; ii++){
+    const char *zDflt = pTab->azDflt[ii] ? pTab->azDflt[ii] : "NULL";
+    sessionAppendPrintf(&sql, &rc, "%s%s", zSep, zDflt);
+    zSep = ", ";
+  }
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_prepare_v2(db, (const char*)sql.aBuf, -1, ppStmt, 0);
+  }
+  sqlite3_free(sql.aBuf);
+
+  return rc;
+}
+
+/*
+** Table pTab has one or more existing change-records with old.* records
+** with fewer than pTab->nCol columns. This function updates all such 
+** change-records with the default values for the missing columns.
+*/
+static int sessionUpdateChanges(sqlite3_session *pSession, SessionTable *pTab){
+  sqlite3_stmt *pStmt = 0;
+  int rc = pSession->rc;
+
+  rc = sessionPrepareDfltStmt(pSession->db, pTab, &pStmt);
+  if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+    int ii = 0;
+    SessionChange **pp = 0;
+    for(ii=0; ii<pTab->nChange; ii++){
+      for(pp=&pTab->apChange[ii]; *pp; pp=&((*pp)->pNext)){
+        if( (*pp)->nRecordField!=pTab->nCol ){
+          sessionUpdateOneChange(pSession, &rc, pp, pTab->nCol, pStmt);
+        }
+      }
+    }
+  }
+
+  pSession->rc = rc;
+  rc = sqlite3_finalize(pStmt);
+  if( pSession->rc==SQLITE_OK ) pSession->rc = rc;
+  return pSession->rc;
 }
 
 /*
@@ -1344,16 +1682,22 @@ static void sessionPreupdateOneChange(
   int iHash; 
   int bNull = 0; 
   int rc = SQLITE_OK;
+  int nExpect = 0;
   SessionStat1Ctx stat1 = {{0,0,0,0,0},0};
 
   if( pSession->rc ) return;
 
   /* Load table details if required */
-  if( sessionInitTable(pSession, pTab) ) return;
+  if( sessionInitTable(pSession, pTab, pSession->db, pSession->zDb) ) return;
 
   /* Check the number of columns in this xPreUpdate call matches the 
   ** number of columns in the table.  */
-  if( (pTab->nCol-pTab->bRowid)!=pSession->hook.xCount(pSession->hook.pCtx) ){
+  nExpect = pSession->hook.xCount(pSession->hook.pCtx);
+  if( (pTab->nCol-pTab->bRowid)<nExpect ){
+    if( sessionReinitTable(pSession, pTab) ) return;
+    if( sessionUpdateChanges(pSession, pTab) ) return;
+  }
+  if( (pTab->nCol-pTab->bRowid)!=nExpect ){
     pSession->rc = SQLITE_SCHEMA;
     return;
   }
@@ -1430,7 +1774,7 @@ static void sessionPreupdateOneChange(
       }
   
       /* Allocate the change object */
-      pC = (SessionChange *)sessionMalloc64(pSession, nByte);
+      pC = (SessionChange*)sessionMalloc64(pSession, nByte);
       if( !pC ){
         rc = SQLITE_NOMEM;
         goto error_out;
@@ -1463,6 +1807,7 @@ static void sessionPreupdateOneChange(
       if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){
         pC->bIndirect = 1;
       }
+      pC->nRecordField = pTab->nCol;
       pC->nRecord = nByte;
       pC->op = op;
       pC->pNext = pTab->apChange[iHash];
@@ -1842,7 +2187,7 @@ int sqlite3session_diff(
     /* Locate and if necessary initialize the target table object */
     rc = sessionFindTable(pSession, zTbl, &pTo);
     if( pTo==0 ) goto diff_out;
-    if( sessionInitTable(pSession, pTo) ){
+    if( sessionInitTable(pSession, pTo, pSession->db, pSession->zDb) ){
       rc = pSession->rc;
       goto diff_out;
     }
@@ -1855,7 +2200,7 @@ int sqlite3session_diff(
       int bRowid = 0;
       u8 *abPK;
       const char **azCol = 0;
-      rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK, 
+      rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, 0, &abPK, 
           pSession->bImplicitPK ? &bRowid : 0
       );
       if( rc==SQLITE_OK ){
@@ -1970,6 +2315,7 @@ static void sessionDeleteTable(sqlite3_session *pSession, SessionTable *pList){
         sessionFree(pSession, p);
       }
     }
+    sqlite3_finalize(pTab->pDfltStmt);
     sessionFree(pSession, (char*)pTab->azCol);  /* cast works around VC++ bug */
     sessionFree(pSession, pTab->apChange);
     sessionFree(pSession, pTab);
@@ -2004,7 +2350,7 @@ void sqlite3session_delete(sqlite3_session *pSession){
 
   /* Assert that all allocations have been freed and then free the 
   ** session object itself. */
-  assert( pSession->nMalloc==0 );
+  // assert( pSession->nMalloc==0 );
   sqlite3_free(pSession);
 }
 
@@ -2075,48 +2421,6 @@ int sqlite3session_attach(
   return rc;
 }
 
-/*
-** Ensure that there is room in the buffer to append nByte bytes of data.
-** If not, use sqlite3_realloc() to grow the buffer so that there is.
-**
-** If successful, return zero. Otherwise, if an OOM condition is encountered,
-** set *pRc to SQLITE_NOMEM and return non-zero.
-*/
-static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){
-#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1) 
-  i64 nReq = p->nBuf + nByte;
-  if( *pRc==SQLITE_OK && nReq>p->nAlloc ){
-    u8 *aNew;
-    i64 nNew = p->nAlloc ? p->nAlloc : 128;
-
-    do {
-      nNew = nNew*2;
-    }while( nNew<nReq );
-
-    /* The value of SESSION_MAX_BUFFER_SZ is copied from the implementation
-    ** of sqlite3_realloc64(). Allocations greater than this size in bytes
-    ** always fail. It is used here to ensure that this routine can always
-    ** allocate up to this limit - instead of up to the largest power of
-    ** two smaller than the limit.  */
-    if( nNew>SESSION_MAX_BUFFER_SZ ){
-      nNew = SESSION_MAX_BUFFER_SZ;
-      if( nNew<nReq ){
-        *pRc = SQLITE_NOMEM;
-        return 1;
-      }
-    }
-
-    aNew = (u8 *)sqlite3_realloc64(p->aBuf, nNew);
-    if( 0==aNew ){
-      *pRc = SQLITE_NOMEM;
-    }else{
-      p->aBuf = aNew;
-      p->nAlloc = nNew;
-    }
-  }
-  return (*pRc!=SQLITE_OK);
-}
-
 /*
 ** Append the value passed as the second argument to the buffer passed
 ** as the first.
@@ -2185,27 +2489,6 @@ static void sessionAppendBlob(
   }
 }
 
-/*
-** This function is a no-op if *pRc is other than SQLITE_OK when it is 
-** called. Otherwise, append a string to the buffer. All bytes in the string
-** up to (but not including) the nul-terminator are written to the buffer.
-**
-** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before
-** returning.
-*/
-static void sessionAppendStr(
-  SessionBuffer *p, 
-  const char *zStr, 
-  int *pRc
-){
-  int nStr = sqlite3Strlen30(zStr);
-  if( 0==sessionBufferGrow(p, nStr+1, pRc) ){
-    memcpy(&p->aBuf[p->nBuf], zStr, nStr);
-    p->nBuf += nStr;
-    p->aBuf[p->nBuf] = 0x00;
-  }
-}
-
 /*
 ** This function is a no-op if *pRc is other than SQLITE_OK when it is 
 ** called. Otherwise, append the string representation of integer iVal
@@ -2224,27 +2507,6 @@ static void sessionAppendInteger(
   sessionAppendStr(p, aBuf, pRc);
 }
 
-static void sessionAppendPrintf(
-  SessionBuffer *p,               /* Buffer to append to */
-  int *pRc, 
-  const char *zFmt,
-  ...
-){
-  if( *pRc==SQLITE_OK ){
-    char *zApp = 0;
-    va_list ap;
-    va_start(ap, zFmt);
-    zApp = sqlite3_vmprintf(zFmt, ap);
-    if( zApp==0 ){
-      *pRc = SQLITE_NOMEM;
-    }else{
-      sessionAppendStr(p, zApp, pRc);
-    }
-    va_end(ap);
-    sqlite3_free(zApp);
-  }
-}
-
 /*
 ** This function is a no-op if *pRc is other than SQLITE_OK when it is 
 ** called. Otherwise, append the string zStr enclosed in quotes (") and
@@ -2735,26 +2997,16 @@ static int sessionGenerateChangeset(
   for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
     if( pTab->nEntry ){
       const char *zName = pTab->zName;
-      int nCol = 0;               /* Number of columns in table */
-      u8 *abPK = 0;               /* Primary key array */
-      const char **azCol = 0;     /* Table columns */
       int i;                      /* Used to iterate through hash buckets */
       sqlite3_stmt *pSel = 0;     /* SELECT statement to query table pTab */
       int nRewind = buf.nBuf;     /* Initial size of write buffer */
       int nNoop;                  /* Size of buffer after writing tbl header */
-      int bRowid = 0;
+      int nOldCol = pTab->nCol;
 
       /* Check the table schema is still Ok. */
-      rc = sessionTableInfo(
-          0, db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK, 
-          (pSession->bImplicitPK ? &bRowid : 0)
-      );
-      if( rc==SQLITE_OK && (
-          pTab->nCol!=nCol 
-       || pTab->bRowid!=bRowid 
-       || memcmp(abPK, pTab->abPK, nCol)
-      )){
-        rc = SQLITE_SCHEMA;
+      rc = sessionReinitTable(pSession, pTab);
+      if( rc==SQLITE_OK && pTab->nCol!=nOldCol ){
+        rc = sessionUpdateChanges(pSession, pTab);
       }
 
       /* Write a table header */
@@ -2762,8 +3014,8 @@ static int sessionGenerateChangeset(
 
       /* Build and compile a statement to execute: */
       if( rc==SQLITE_OK ){
-        rc = sessionSelectStmt(
-            db, 0, pSession->zDb, zName, bRowid, nCol, azCol, abPK, &pSel
+        rc = sessionSelectStmt(db, 0, pSession->zDb, 
+            zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel
         );
       }
 
@@ -2772,22 +3024,22 @@ static int sessionGenerateChangeset(
         SessionChange *p;         /* Used to iterate through changes */
 
         for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
-          rc = sessionSelectBind(pSel, nCol, abPK, p);
+          rc = sessionSelectBind(pSel, pTab->nCol, pTab->abPK, p);
           if( rc!=SQLITE_OK ) continue;
           if( sqlite3_step(pSel)==SQLITE_ROW ){
             if( p->op==SQLITE_INSERT ){
               int iCol;
               sessionAppendByte(&buf, SQLITE_INSERT, &rc);
               sessionAppendByte(&buf, p->bIndirect, &rc);
-              for(iCol=0; iCol<nCol; iCol++){
+              for(iCol=0; iCol<pTab->nCol; iCol++){
                 sessionAppendCol(&buf, pSel, iCol, &rc);
               }
             }else{
-              assert( abPK!=0 );  /* Because sessionSelectStmt() returned ok */
-              rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK);
+              assert( pTab->abPK!=0 );
+              rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, pTab->abPK);
             }
           }else if( p->op!=SQLITE_INSERT ){
-            rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
+            rc = sessionAppendDelete(&buf, bPatchset, p, pTab->nCol,pTab->abPK);
           }
           if( rc==SQLITE_OK ){
             rc = sqlite3_reset(pSel);
@@ -2812,7 +3064,6 @@ static int sessionGenerateChangeset(
       if( buf.nBuf==nNoop ){
         buf.nBuf = nRewind;
       }
-      sqlite3_free((char*)azCol);  /* cast works around VC++ bug */
     }
   }
 
@@ -3236,15 +3487,19 @@ static int sessionReadRecord(
         }
       }
       if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
-        sqlite3_int64 v = sessionGetI64(aVal);
-        if( eType==SQLITE_INTEGER ){
-          sqlite3VdbeMemSetInt64(apOut[i], v);
+        if( (pIn->nData-pIn->iNext)<8 ){
+          rc = SQLITE_CORRUPT_BKPT;
         }else{
-          double d;
-          memcpy(&d, &v, 8);
-          sqlite3VdbeMemSetDouble(apOut[i], d);
+          sqlite3_int64 v = sessionGetI64(aVal);
+          if( eType==SQLITE_INTEGER ){
+            sqlite3VdbeMemSetInt64(apOut[i], v);
+          }else{
+            double d;
+            memcpy(&d, &v, 8);
+            sqlite3VdbeMemSetDouble(apOut[i], d);
+          }
+          pIn->iNext += 8;
         }
-        pIn->iNext += 8;
       }
     }
   }
@@ -4937,7 +5192,7 @@ static int sessionChangesetApply(
 
         sqlite3changeset_pk(pIter, &abPK, 0);
         rc = sessionTableInfo(0, db, "main", zNew, 
-            &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK, &sApply.bRowid
+            &sApply.nCol, &zTab, &sApply.azCol, 0, &sApply.abPK, &sApply.bRowid
         );
         if( rc!=SQLITE_OK ) break;
         for(i=0; i<sApply.nCol; i++){
@@ -5069,11 +5324,24 @@ int sqlite3changeset_apply_v2(
   sqlite3_changeset_iter *pIter;  /* Iterator to skip through changeset */  
   int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
   int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1);
+  u64 savedFlag = db->flags & SQLITE_FkNoAction;
+
+  if( flags & SQLITE_CHANGESETAPPLY_FKNOACTION ){
+    db->flags |= ((u64)SQLITE_FkNoAction);
+    db->aDb[0].pSchema->schema_cookie -= 32;
+  }
+
   if( rc==SQLITE_OK ){
     rc = sessionChangesetApply(
         db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
     );
   }
+
+  if( (flags & SQLITE_CHANGESETAPPLY_FKNOACTION) && savedFlag==0 ){
+    assert( db->flags & SQLITE_FkNoAction );
+    db->flags &= ~((u64)SQLITE_FkNoAction);
+    db->aDb[0].pSchema->schema_cookie -= 32;
+  }
   return rc;
 }
 
@@ -5161,6 +5429,9 @@ struct sqlite3_changegroup {
   int rc;                         /* Error code */
   int bPatch;                     /* True to accumulate patchsets */
   SessionTable *pList;            /* List of tables in current patch */
+
+  sqlite3 *db;                    /* Configured by changegroup_schema() */
+  char *zDb;                      /* Configured by changegroup_schema() */
 };
 
 /*
@@ -5181,6 +5452,7 @@ static int sessionChangeMerge(
 ){
   SessionChange *pNew = 0;
   int rc = SQLITE_OK;
+  assert( aRec!=0 );
 
   if( !pExist ){
     pNew = (SessionChange *)sqlite3_malloc64(sizeof(SessionChange) + nRec);
@@ -5346,6 +5618,114 @@ static int sessionChangeMerge(
   return rc;
 }
 
+/*
+** Check if a changeset entry with nCol columns and the PK array passed
+** as the final argument to this function is compatible with SessionTable
+** pTab. If so, return 1. Otherwise, if they are incompatible in some way,
+** return 0.
+*/
+static int sessionChangesetCheckCompat(
+  SessionTable *pTab,
+  int nCol,
+  u8 *abPK
+){
+  if( pTab->azCol && nCol<pTab->nCol ){
+    int ii;
+    for(ii=0; ii<pTab->nCol; ii++){
+      u8 bPK = (ii < nCol) ? abPK[ii] : 0;
+      if( pTab->abPK[ii]!=bPK ) return 0;
+    }
+    return 1;
+  }
+  return (pTab->nCol==nCol && 0==memcmp(abPK, pTab->abPK, nCol));
+}
+
+static int sessionChangesetExtendRecord(
+  sqlite3_changegroup *pGrp,
+  SessionTable *pTab, 
+  int nCol, 
+  int op, 
+  const u8 *aRec, 
+  int nRec, 
+  SessionBuffer *pOut
+){
+  int rc = SQLITE_OK;
+  int ii = 0;
+
+  assert( pTab->azCol );
+  assert( nCol<pTab->nCol );
+
+  pOut->nBuf = 0;
+  if( op==SQLITE_INSERT || (op==SQLITE_DELETE && pGrp->bPatch==0) ){
+    /* Append the missing default column values to the record. */
+    sessionAppendBlob(pOut, aRec, nRec, &rc);
+    if( rc==SQLITE_OK && pTab->pDfltStmt==0 ){
+      rc = sessionPrepareDfltStmt(pGrp->db, pTab, &pTab->pDfltStmt);
+    }
+    for(ii=nCol; rc==SQLITE_OK && ii<pTab->nCol; ii++){
+      int eType = sqlite3_column_type(pTab->pDfltStmt, ii);
+      sessionAppendByte(pOut, eType, &rc);
+      switch( eType ){
+        case SQLITE_FLOAT:
+        case SQLITE_INTEGER: {
+          i64 iVal;
+          if( eType==SQLITE_INTEGER ){
+            iVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
+          }else{
+            double rVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
+            memcpy(&iVal, &rVal, sizeof(i64));
+          }
+          if( SQLITE_OK==sessionBufferGrow(pOut, 8, &rc) ){
+            sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal);
+          }
+          break;
+        }
+
+        case SQLITE_BLOB:
+        case SQLITE_TEXT: {
+          int n = sqlite3_column_bytes(pTab->pDfltStmt, ii);
+          sessionAppendVarint(pOut, n, &rc);
+          if( eType==SQLITE_TEXT ){
+            const u8 *z = (const u8*)sqlite3_column_text(pTab->pDfltStmt, ii);
+            sessionAppendBlob(pOut, z, n, &rc);
+          }else{
+            const u8 *z = (const u8*)sqlite3_column_blob(pTab->pDfltStmt, ii);
+            sessionAppendBlob(pOut, z, n, &rc);
+          }
+          break;
+        }
+
+        default:
+          assert( eType==SQLITE_NULL );
+          break;
+      }
+    }
+  }else if( op==SQLITE_UPDATE ){
+    /* Append missing "undefined" entries to the old.* record. And, if this
+    ** is an UPDATE, to the new.* record as well.  */
+    int iOff = 0;
+    if( pGrp->bPatch==0 ){
+      for(ii=0; ii<nCol; ii++){
+        iOff += sessionSerialLen(&aRec[iOff]);
+      }
+      sessionAppendBlob(pOut, aRec, iOff, &rc);
+      for(ii=0; ii<(pTab->nCol-nCol); ii++){
+        sessionAppendByte(pOut, 0x00, &rc);
+      }
+    }
+
+    sessionAppendBlob(pOut, &aRec[iOff], nRec-iOff, &rc);
+    for(ii=0; ii<(pTab->nCol-nCol); ii++){
+      sessionAppendByte(pOut, 0x00, &rc);
+    }
+  }else{
+    assert( op==SQLITE_DELETE && pGrp->bPatch );
+    sessionAppendBlob(pOut, aRec, nRec, &rc);
+  }
+
+  return rc;
+}
+
 /*
 ** Add all changes in the changeset traversed by the iterator passed as
 ** the first argument to the changegroup hash tables.
@@ -5359,6 +5739,7 @@ static int sessionChangesetToHash(
   int nRec;
   int rc = SQLITE_OK;
   SessionTable *pTab = 0;
+  SessionBuffer rec = {0, 0, 0};
 
   while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){
     const char *zNew;
@@ -5370,6 +5751,9 @@ static int sessionChangesetToHash(
     SessionChange *pExist = 0;
     SessionChange **pp;
 
+    /* Ensure that only changesets, or only patchsets, but not a mixture
+    ** of both, are being combined. It is an error to try to combine a
+    ** changeset and a patchset.  */
     if( pGrp->pList==0 ){
       pGrp->bPatch = pIter->bPatchset;
     }else if( pIter->bPatchset!=pGrp->bPatch ){
@@ -5402,18 +5786,38 @@ static int sessionChangesetToHash(
         pTab->zName = (char*)&pTab->abPK[nCol];
         memcpy(pTab->zName, zNew, nNew+1);
 
+        if( pGrp->db ){
+          pTab->nCol = 0;
+          rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb);
+          if( rc ){
+            assert( pTab->azCol==0 );
+            sqlite3_free(pTab);
+            break;
+          }
+        }
+
         /* The new object must be linked on to the end of the list, not
         ** simply added to the start of it. This is to ensure that the
         ** tables within the output of sqlite3changegroup_output() are in 
         ** the right order.  */
         for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext);
         *ppTab = pTab;
-      }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){
+      }
+
+      if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
         rc = SQLITE_SCHEMA;
         break;
       }
     }
 
+    if( nCol<pTab->nCol ){
+      assert( pGrp->db );
+      rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, &rec);
+      if( rc ) break;
+      aRec = rec.aBuf;
+      nRec = rec.nBuf;
+    }
+
     if( sessionGrowHash(0, pIter->bPatchset, pTab) ){
       rc = SQLITE_NOMEM;
       break;
@@ -5451,6 +5855,7 @@ static int sessionChangesetToHash(
     }
   }
 
+  sqlite3_free(rec.aBuf);
   if( rc==SQLITE_OK ) rc = pIter->rc;
   return rc;
 }
@@ -5537,6 +5942,31 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp){
   return rc;
 }
 
+/*
+** Provide a database schema to the changegroup object.
+*/
+int sqlite3changegroup_schema(
+  sqlite3_changegroup *pGrp, 
+  sqlite3 *db, 
+  const char *zDb
+){
+  int rc = SQLITE_OK;
+
+  if( pGrp->pList || pGrp->db ){
+    /* Cannot add a schema after one or more calls to sqlite3changegroup_add(),
+    ** or after sqlite3changegroup_schema() has already been called. */
+    rc = SQLITE_MISUSE;
+  }else{
+    pGrp->zDb = sqlite3_mprintf("%s", zDb);
+    if( pGrp->zDb==0 ){
+      rc = SQLITE_NOMEM;
+    }else{
+      pGrp->db = db;
+    }
+  }
+  return rc;
+}
+
 /*
 ** Add the changeset currently stored in buffer pData, size nData bytes,
 ** to changeset-group p.
@@ -5600,6 +6030,7 @@ int sqlite3changegroup_output_strm(
 */
 void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
   if( pGrp ){
+    sqlite3_free(pGrp->zDb);
     sessionDeleteTable(0, pGrp->pList);
     sqlite3_free(pGrp);
   }
diff --git a/libsql-sqlite3/ext/session/sqlite3session.h b/libsql-sqlite3/ext/session/sqlite3session.h
index 1ea90dce47..160ea8786b 100644
--- a/libsql-sqlite3/ext/session/sqlite3session.h
+++ b/libsql-sqlite3/ext/session/sqlite3session.h
@@ -884,6 +884,18 @@ int sqlite3changeset_concat(
 );
 
 
+/*
+** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
+*/
+int sqlite3changeset_upgrade(
+  sqlite3 *db,
+  const char *zDb,
+  int nIn, const void *pIn,       /* Input changeset */
+  int *pnOut, void **ppOut        /* OUT: Inverse of input */
+);
+
+
+
 /*
 ** CAPI3REF: Changegroup Handle
 **
@@ -930,6 +942,38 @@ typedef struct sqlite3_changegroup sqlite3_changegroup;
 */
 int sqlite3changegroup_new(sqlite3_changegroup **pp);
 
+/*
+** CAPI3REF: Add a Schema to a Changegroup
+** METHOD: sqlite3_changegroup_schema
+**
+** This method may be used to optionally enforce the rule that the changesets
+** added to the changegroup handle must match the schema of database zDb
+** ("main", "temp", or the name of an attached database). If
+** sqlite3changegroup_add() is called to add a changeset that is not compatible
+** with the configured schema, SQLITE_SCHEMA is returned and the changegroup
+** object is left in an undefined state.
+**
+** A changeset schema is considered compatible with the database schema in
+** the same way as for sqlite3changeset_apply(). Specifically, for each
+** table in the changeset, there exists a database table with:
+**
+** <ul>
+**   <li> The name identified by the changeset, and
+**   <li> at least as many columns as recorded in the changeset, and
+**   <li> the primary key columns in the same position as recorded in 
+**        the changeset.
+** </ul>
+**
+** The output of the changegroup object always has the same schema as the
+** database nominated using this function. In cases where changesets passed
+** to sqlite3changegroup_add() have fewer columns than the corresponding table
+** in the database schema, these are filled in using the default column
+** values from the database schema. This makes it possible to combined 
+** changesets that have different numbers of columns for a single table
+** within a changegroup, provided that they are otherwise compatible.
+*/
+int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const char *zDb);
+
 /*
 ** CAPI3REF: Add A Changeset To A Changegroup
 ** METHOD: sqlite3_changegroup
@@ -998,13 +1042,18 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp);
 ** If the new changeset contains changes to a table that is already present
 ** in the changegroup, then the number of columns and the position of the
 ** primary key columns for the table must be consistent. If this is not the
-** case, this function fails with SQLITE_SCHEMA. If the input changeset
-** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is
-** returned. Or, if an out-of-memory condition occurs during processing, this
-** function returns SQLITE_NOMEM. In all cases, if an error occurs the state
-** of the final contents of the changegroup is undefined.
+** case, this function fails with SQLITE_SCHEMA. Except, if the changegroup
+** object has been configured with a database schema using the
+** sqlite3changegroup_schema() API, then it is possible to combine changesets
+** with different numbers of columns for a single table, provided that
+** they are otherwise compatible.
 **
-** If no error occurs, SQLITE_OK is returned.
+** If the input changeset appears to be corrupt and the corruption is
+** detected, SQLITE_CORRUPT is returned. Or, if an out-of-memory condition
+** occurs during processing, this function returns SQLITE_NOMEM. 
+**
+** In all cases, if an error occurs the state of the final contents of the
+** changegroup is undefined. If no error occurs, SQLITE_OK is returned.
 */
 int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
 
@@ -1269,10 +1318,17 @@ int sqlite3changeset_apply_v2(
 **    <li>an insert change if all fields of the conflicting row match
 **        the row being inserted.
 **    </ul>
+**
+** <dt>SQLITE_CHANGESETAPPLY_FKNOACTION <dd>
+**   If this flag it set, then all foreign key constraints in the target
+**   database behave as if they were declared with "ON UPDATE NO ACTION ON
+**   DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL
+**   or SET DEFAULT.
 */
 #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT   0x0001
 #define SQLITE_CHANGESETAPPLY_INVERT        0x0002
 #define SQLITE_CHANGESETAPPLY_IGNORENOOP    0x0004
+#define SQLITE_CHANGESETAPPLY_FKNOACTION    0x0008
 
 /* 
 ** CAPI3REF: Constants Passed To The Conflict Handler
diff --git a/libsql-sqlite3/ext/session/test_session.c b/libsql-sqlite3/ext/session/test_session.c
index 0836238b5d..af42351ba7 100644
--- a/libsql-sqlite3/ext/session/test_session.c
+++ b/libsql-sqlite3/ext/session/test_session.c
@@ -392,7 +392,6 @@ static int SQLITE_TCLAPI test_session_cmd(
       };
       size_t sz = sizeof(aOpt[0]);
 
-      int rc;
       int iArg;
       int iOpt;
       if( Tcl_GetIndexFromObjStruct(interp,objv[2],aOpt,sz,"option",0,&iOpt) ){
@@ -812,9 +811,12 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
     while( objc>1 ){
       const char *z1 = Tcl_GetString(objv[1]);
       int n = strlen(z1);
-      if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
+      if( n>3 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
         flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
       }
+      else if( n>3 && n<=9 && 0==sqlite3_strnicmp("-noaction", z1, n) ){
+        flags |= SQLITE_CHANGESETAPPLY_FKNOACTION;
+      }
       else if( n>2 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){
         flags |= SQLITE_CHANGESETAPPLY_INVERT;
       }
@@ -1452,12 +1454,144 @@ static int SQLITE_TCLAPI test_sqlite3session_config(
   return TCL_OK;
 }
 
+typedef struct TestChangegroup TestChangegroup;
+struct TestChangegroup {
+  sqlite3_changegroup *pGrp;
+};
+
+/*
+** Destructor for Tcl changegroup command object.
+*/
+static void test_changegroup_del(void *clientData){
+  TestChangegroup *pGrp = (TestChangegroup*)clientData;
+  sqlite3changegroup_delete(pGrp->pGrp);
+  ckfree(pGrp);
+}
+
+/*
+** Tclcmd:  $changegroup schema DB DBNAME
+** Tclcmd:  $changegroup add CHANGESET
+** Tclcmd:  $changegroup output
+** Tclcmd:  $changegroup delete
+*/
+static int SQLITE_TCLAPI test_changegroup_cmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  TestChangegroup *p = (TestChangegroup*)clientData;
+  static struct ChangegroupCmd {
+    const char *zSub;
+    int nArg;
+    const char *zMsg;
+    int iSub;
+  } aSub[] = {
+    { "schema",       2, "DB DBNAME",  }, /* 0 */
+    { "add",          1, "CHANGESET",  }, /* 1 */
+    { "output",       0, "",           }, /* 2 */
+    { "delete",       0, "",           }, /* 3 */
+    { 0 }
+  };
+  int rc = TCL_OK;
+  int iSub = 0;
+
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
+    return TCL_ERROR;
+  }
+  rc = Tcl_GetIndexFromObjStruct(interp, 
+      objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
+  );
+  if( rc!=TCL_OK ) return rc;
+  if( objc!=2+aSub[iSub].nArg ){
+    Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
+    return TCL_ERROR;
+  }
+
+  switch( iSub ){
+    case 0: {      /* schema */
+      sqlite3 *db = 0;
+      const char *zDb = Tcl_GetString(objv[3]);
+      if( dbHandleFromObj(interp, objv[2], &db) ){
+        return TCL_ERROR;
+      }
+      rc = sqlite3changegroup_schema(p->pGrp, db, zDb);
+      if( rc!=SQLITE_OK ) rc = test_session_error(interp, rc, 0);
+      break;
+    };
+
+    case 1: {      /* add */
+      int nByte = 0;
+      const u8 *aByte = Tcl_GetByteArrayFromObj(objv[2], &nByte);
+      rc = sqlite3changegroup_add(p->pGrp, nByte, (void*)aByte);
+      if( rc!=SQLITE_OK ) rc = test_session_error(interp, rc, 0);
+      break;
+    };
+
+    case 2: {      /* output */
+      int nByte = 0;
+      u8 *aByte = 0;
+      rc = sqlite3changegroup_output(p->pGrp, &nByte, (void**)&aByte);
+      if( rc!=SQLITE_OK ){
+        rc = test_session_error(interp, rc, 0);
+      }else{
+        Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(aByte, nByte));
+      }
+      sqlite3_free(aByte);
+      break;
+    };
+
+    default: {     /* delete */
+      assert( iSub==3 );
+      Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+      break;
+    }
+  }
+
+  return rc;
+}
+
+/*
+** Tclcmd:  sqlite3changegroup CMD
+*/
+static int SQLITE_TCLAPI test_sqlite3changegroup(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;                         /* sqlite3changegroup_new() return code */
+  TestChangegroup *p;             /* New wrapper object */
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "CMD");
+    return TCL_ERROR;
+  }
+
+  p = (TestChangegroup*)ckalloc(sizeof(TestChangegroup));
+  memset(p, 0, sizeof(TestChangegroup));
+  rc = sqlite3changegroup_new(&p->pGrp);
+  if( rc!=SQLITE_OK ){
+    ckfree((char*)p);
+    return test_session_error(interp, rc, 0);
+  }
+
+  Tcl_CreateObjCommand(
+      interp, Tcl_GetString(objv[1]), test_changegroup_cmd, (ClientData)p,
+      test_changegroup_del
+  );
+  Tcl_SetObjResult(interp, objv[1]);
+  return TCL_OK;
+}
+
 int TestSession_Init(Tcl_Interp *interp){
   struct Cmd {
     const char *zCmd;
     Tcl_ObjCmdProc *xProc;
   } aCmd[] = {
     { "sqlite3session", test_sqlite3session },
+    { "sqlite3changegroup", test_sqlite3changegroup },
     { "sqlite3session_foreach", test_sqlite3session_foreach },
     { "sqlite3changeset_invert", test_sqlite3changeset_invert },
     { "sqlite3changeset_concat", test_sqlite3changeset_concat },
diff --git a/libsql-sqlite3/ext/wasm/GNUmakefile b/libsql-sqlite3/ext/wasm/GNUmakefile
index 8507157efb..e14818bc56 100644
--- a/libsql-sqlite3/ext/wasm/GNUmakefile
+++ b/libsql-sqlite3/ext/wasm/GNUmakefile
@@ -463,13 +463,6 @@ emcc.exportedRuntimeMethods := \
 emcc.jsflags += $(emcc.exportedRuntimeMethods)
 emcc.jsflags += -sUSE_CLOSURE_COMPILER=0
 emcc.jsflags += -sIMPORTED_MEMORY
-#emcc.jsflags += -sASYNCIFY=2
-# ^^^ ASYNCIFY=2 is for experimental JSPI support
-# (https://v8.dev/blog/jspi), but enabling it causes the lib-level
-# init code to throw inexplicable complaints about C-level function
-# signatures not matching what we expect them to be. JSPI requires, as of
-# this writing, requires an experimental Chrome flag:
-# chrome://flags/#enable-experimental-webassembly-stack-switching
 emcc.jsflags += -sSTRICT_JS=0
 # STRICT_JS disabled due to:
 #   https://github.com/emscripten-core/emscripten/issues/18610
@@ -855,11 +848,11 @@ sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-frien
 sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js
 $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js)))
 $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\
-    $(c-pp.D.bundler-friendly)))
+    $(c-pp.D.sqlite3-bundler-friendly)))
 $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js)))
 $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\
     $(sqlite3-worker1-promiser-bundler-friendly.js),\
-    $(c-pp.D.bundler-friendly)))
+    $(c-pp.D.sqlite3-bundler-friendly)))
 $(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \
     $(sqlite3-worker1-promiser-bundler-friendly.js)
 $(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js)
diff --git a/libsql-sqlite3/ext/wasm/api/sqlite3-api-glue.js b/libsql-sqlite3/ext/wasm/api/sqlite3-api-glue.js
index 60050461c7..f23a02366b 100644
--- a/libsql-sqlite3/ext/wasm/api/sqlite3-api-glue.js
+++ b/libsql-sqlite3/ext/wasm/api/sqlite3-api-glue.js
@@ -888,9 +888,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
      consistency with non-special-case wrappings.
   */
   const __dbArgcMismatch = (pDb,f,n)=>{
-    return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE,
-                                              f+"() requires "+n+" argument"+
-                                              (1===n?"":'s')+".");
+    return util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE,
+                                      f+"() requires "+n+" argument"+
+                                      (1===n?"":'s')+".");
   };
 
   /** Code duplication reducer for functions which take an encoding
diff --git a/libsql-sqlite3/ext/wasm/api/sqlite3-api-prologue.js b/libsql-sqlite3/ext/wasm/api/sqlite3-api-prologue.js
index ca5d1c44ff..ef1154f6b6 100644
--- a/libsql-sqlite3/ext/wasm/api/sqlite3-api-prologue.js
+++ b/libsql-sqlite3/ext/wasm/api/sqlite3-api-prologue.js
@@ -772,8 +772,43 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
     isSharedTypedArray,
     toss: function(...args){throw new Error(args.join(' '))},
     toss3,
-    typedArrayPart
-  };
+    typedArrayPart,
+    /**
+       Given a byte array or ArrayBuffer, this function throws if the
+       lead bytes of that buffer do not hold a SQLite3 database header,
+       else it returns without side effects.
+
+       Added in 3.44.
+    */
+    affirmDbHeader: function(bytes){
+      if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+      const header = "SQLite format 3";
+      if( header.length > bytes.byteLength ){
+        toss3("Input does not contain an SQLite3 database header.");
+      }
+      for(let i = 0; i < header.length; ++i){
+        if( header.charCodeAt(i) !== bytes[i] ){
+          toss3("Input does not contain an SQLite3 database header.");
+        }
+      }
+    },
+    /**
+       Given a byte array or ArrayBuffer, this function throws if the
+       database does not, at a cursory glance, appear to be an SQLite3
+       database. It only examines the size and header, but further
+       checks may be added in the future.
+
+       Added in 3.44.
+    */
+    affirmIsDb: function(bytes){
+      if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+      const n = bytes.byteLength;
+      if(n<512 || n%512!==0) {
+        toss3("Byte array size",n,"is invalid for an SQLite3 db.");
+      }
+      util.affirmDbHeader(bytes);
+    }
+  }/*util*/;
 
   Object.assign(wasm, {
     /**
@@ -1100,7 +1135,23 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
       return 1===n
         ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof)
         : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof);
+    },
+
+    /**
+       Records the current pstack position, calls the given function,
+       passing it the sqlite3 object, then restores the pstack
+       regardless of whether the function throws. Returns the result
+       of the call or propagates an exception on error.
+
+       Added in 3.44.
+    */
+    call: function(f){
+      const stackPos = wasm.pstack.pointer;
+      try{ return f(sqlite3) } finally{
+        wasm.pstack.restore(stackPos);
+      }
     }
+
   })/*wasm.pstack*/;
   Object.defineProperties(wasm.pstack, {
     /**
@@ -1508,6 +1559,26 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
     }
   };
 
+  /**
+     Converts SQL input from a variety of convenient formats
+     to plain strings.
+
+     If v is a string, it is returned as-is. If it is-a Array, its
+     join("") result is returned.  If is is a Uint8Array, Int8Array,
+     or ArrayBuffer, it is assumed to hold UTF-8-encoded text and is
+     decoded to a string. If it looks like a WASM pointer,
+     wasm.cstrToJs(sql) is returned. Else undefined is returned.
+
+     Added in 3.44
+  */
+  capi.sqlite3_js_sql_to_string = (sql)=>{
+    if('string' === typeof sql){
+      return sql;
+    }
+    const x = flexibleString(v);
+    return x===v ? undefined : x;
+  }
+
   if( util.isUIThread() ){
     /* Features specific to the main window thread... */
 
diff --git a/libsql-sqlite3/ext/wasm/api/sqlite3-api-worker1.js b/libsql-sqlite3/ext/wasm/api/sqlite3-api-worker1.js
index 9a386c13e7..29f7d2be63 100644
--- a/libsql-sqlite3/ext/wasm/api/sqlite3-api-worker1.js
+++ b/libsql-sqlite3/ext/wasm/api/sqlite3-api-worker1.js
@@ -333,7 +333,6 @@ sqlite3.initWorker1API = function(){
   if(!(globalThis.WorkerGlobalScope instanceof Function)){
     toss("initWorker1API() must be run from a Worker thread.");
   }
-  const self = this.self;
   const sqlite3 = this.sqlite3 || toss("Missing this.sqlite3 object.");
   const DB = sqlite3.oo1.DB;
 
@@ -657,5 +656,5 @@ sqlite3.initWorker1API = function(){
     }, wState.xfer);
   };
   globalThis.postMessage({type:'sqlite3-api',result:'worker1-ready'});
-}.bind({self, sqlite3});
+}.bind({sqlite3});
 });
diff --git a/libsql-sqlite3/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/libsql-sqlite3/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
index 709d3414c3..870073cc09 100644
--- a/libsql-sqlite3/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
+++ b/libsql-sqlite3/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
@@ -59,6 +59,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   const toss3 = sqlite3.util.toss3;
   const initPromises = Object.create(null);
   const capi = sqlite3.capi;
+  const util = sqlite3.util;
   const wasm = sqlite3.wasm;
   // Config opts for the VFS...
   const SECTOR_SIZE = 4096;
@@ -154,8 +155,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
             pool.deletePath(file.path);
           }
         }catch(e){
-          pool.storeErr(e);
-          return capi.SQLITE_IOERR;
+          return pool.storeErr(e, capi.SQLITE_IOERR);
         }
       }
       return 0;
@@ -199,8 +199,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         }
         return 0;
       }catch(e){
-        pool.storeErr(e);
-        return capi.SQLITE_IOERR;
+        return pool.storeErr(e, capi.SQLITE_IOERR);
       }
     },
     xSectorSize: function(pFile){
@@ -216,8 +215,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         file.sah.flush();
         return 0;
       }catch(e){
-        pool.storeErr(e);
-        return capi.SQLITE_IOERR;
+        return pool.storeErr(e, capi.SQLITE_IOERR);
       }
     },
     xTruncate: function(pFile,sz64){
@@ -230,8 +228,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         file.sah.truncate(HEADER_OFFSET_DATA + Number(sz64));
         return 0;
       }catch(e){
-        pool.storeErr(e);
-        return capi.SQLITE_IOERR;
+        return pool.storeErr(e, capi.SQLITE_IOERR);
       }
     },
     xUnlock: function(pFile,lockType){
@@ -251,10 +248,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
           wasm.heap8u().subarray(pSrc, pSrc+n),
           { at: HEADER_OFFSET_DATA + Number(offset64) }
         );
-        return nBytes === n ? 0 : capi.SQLITE_IOERR;
+        return n===nBytes ? 0 : toss("Unknown write() failure.");
       }catch(e){
-        pool.storeErr(e);
-        return capi.SQLITE_IOERR;
+        return pool.storeErr(e, capi.SQLITE_IOERR);
       }
     }
   }/*ioMethods*/;
@@ -313,8 +309,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     },
     xGetLastError: function(pVfs,nOut,pOut){
       const pool = getPoolForVfs(pVfs);
-      pool.log(`xGetLastError ${nOut}`);
       const e = pool.popErr();
+      pool.log(`xGetLastError ${nOut} e =`,e);
       if(e){
         const scope = wasm.scopedAllocPush();
         try{
@@ -327,7 +323,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
           wasm.scopedAllocPop(scope);
         }
       }
-      return 0;
+      return e ? (e.sqlite3Rc || capi.SQLITE_IOERR) : 0;
     },
     //xSleep is optionally defined below
     xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
@@ -761,12 +757,20 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     }
 
     /**
-       Sets e as this object's current error. Pass a falsy
-       (or no) value to clear it.
+       Sets e (an Error object) as this object's current error. Pass a
+       falsy (or no) value to clear it. If code is truthy it is
+       assumed to be an SQLITE_xxx result code, defaulting to
+       SQLITE_IOERR if code is falsy.
+
+       Returns the 2nd argument.
     */
-    storeErr(e){
-      if(e) this.error(e);
-      return this.$error = e;
+    storeErr(e,code){
+      if(e){
+        e.sqlite3Rc = code || capi.SQLITE_IOERR;
+        this.error(e);
+      }
+      this.$error = e;
+      return code;
     }
     /**
        Pops this object's Error object and returns
@@ -869,9 +873,49 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       return b;
     }
 
+    //! Impl for importDb() when its 2nd arg is a function.
+    async importDbChunked(name, callback){
+      const sah = this.#mapFilenameToSAH.get(name)
+            || this.nextAvailableSAH()
+            || toss("No available handles to import to.");
+      sah.truncate(0);
+      let nWrote = 0, chunk, checkedHeader = false, err = false;
+      try{
+        while( undefined !== (chunk = await callback()) ){
+          if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk);
+          if( 0===nWrote && chunk.byteLength>=15 ){
+            util.affirmDbHeader(chunk);
+            checkedHeader = true;
+          }
+          sah.write(chunk, {at:  HEADER_OFFSET_DATA + nWrote});
+          nWrote += chunk.byteLength;
+        }
+        if( nWrote < 512 || 0!==nWrote % 512 ){
+          toss("Input size",nWrote,"is not correct for an SQLite database.");
+        }
+        if( !checkedHeader ){
+          const header = new Uint8Array(20);
+          sah.read( header, {at: 0} );
+          util.affirmDbHeader( header );
+        }
+        sah.write(new Uint8Array([1,1]), {
+          at: HEADER_OFFSET_DATA + 18
+        }/*force db out of WAL mode*/);
+      }catch(e){
+        this.setAssociatedPath(sah, '', 0);
+        throw e;
+      }
+      this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB);
+      return nWrote;
+    }
+
     //! Documented elsewhere in this file.
     importDb(name, bytes){
-      if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+      if( bytes instanceof ArrayBuffer ) bytes = new Uint8Array(bytes);
+      else if( bytes instanceof Function ) return this.importDbChunked(name, bytes);
+      const sah = this.#mapFilenameToSAH.get(name)
+            || this.nextAvailableSAH()
+            || toss("No available handles to import to.");
       const n = bytes.byteLength;
       if(n<512 || n%512!=0){
         toss("Byte array size is invalid for an SQLite db.");
@@ -882,16 +926,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
           toss("Input does not contain an SQLite database header.");
         }
       }
-      const sah = this.#mapFilenameToSAH.get(name)
-            || this.nextAvailableSAH()
-            || toss("No available handles to import to.");
       const nWrote = sah.write(bytes, {at: HEADER_OFFSET_DATA});
       if(nWrote != n){
         this.setAssociatedPath(sah, '', 0);
         toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
       }else{
+        sah.write(new Uint8Array([1,1]), {at: HEADER_OFFSET_DATA+18}
+                   /* force db out of WAL mode */);
         this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB);
       }
+      return nWrote;
     }
 
   }/*class OpfsSAHPool*/;
@@ -1098,6 +1142,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
      automatically clean up any non-database files so importing them
      is pointless.
 
+     If passed a function for its second argument, its behavior
+     changes to asynchronous and it imports its data in chunks fed to
+     it by the given callback function. It calls the callback (which
+     may be async) repeatedly, expecting either a Uint8Array or
+     ArrayBuffer (to denote new input) or undefined (to denote
+     EOF). For so long as the callback continues to return
+     non-undefined, it will append incoming data to the given
+     VFS-hosted database file. The result of the resolved Promise when
+     called this way is the size of the resulting database.
+
+     On succes this routine rewrites the database header bytes in the
+     output file (not the input array) to force disabling of WAL mode.
+
      On a write error, the handle is removed from the pool and made
      available for re-use.
 
diff --git a/libsql-sqlite3/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/libsql-sqlite3/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
index 93482505ac..af89f216f7 100644
--- a/libsql-sqlite3/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
+++ b/libsql-sqlite3/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
@@ -136,6 +136,7 @@ const installOpfsVfs = function callee(options){
     const error =  (...args)=>logImpl(0, ...args);
     const toss = sqlite3.util.toss;
     const capi = sqlite3.capi;
+    const util = sqlite3.util;
     const wasm = sqlite3.wasm;
     const sqlite3_vfs = capi.sqlite3_vfs;
     const sqlite3_file = capi.sqlite3_file;
@@ -1168,40 +1169,101 @@ const installOpfsVfs = function callee(options){
       doDir(opt.directory, 0);
     };
 
+    /**
+       impl of importDb() when it's given a function as its second
+       argument.
+    */
+    const importDbChunked = async function(filename, callback){
+      const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true);
+      const hFile = await hDir.getFileHandle(fnamePart, {create:true});
+      let sah = await hFile.createSyncAccessHandle();
+      let nWrote = 0, chunk, checkedHeader = false, err = false;
+      try{
+        sah.truncate(0);
+        while( undefined !== (chunk = await callback()) ){
+          if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk);
+          if( 0===nWrote && chunk.byteLength>=15 ){
+            util.affirmDbHeader(chunk);
+            checkedHeader = true;
+          }
+          sah.write(chunk, {at: nWrote});
+          nWrote += chunk.byteLength;
+        }
+        if( nWrote < 512 || 0!==nWrote % 512 ){
+          toss("Input size",nWrote,"is not correct for an SQLite database.");
+        }
+        if( !checkedHeader ){
+          const header = new Uint8Array(20);
+          sah.read( header, {at: 0} );
+          util.affirmDbHeader( header );
+        }
+        sah.write(new Uint8Array([1,1]), {at: 18}/*force db out of WAL mode*/);
+        return nWrote;
+      }catch(e){
+        await sah.close();
+        sah = undefined;
+        await hDir.removeEntry( fnamePart ).catch(()=>{});
+        throw e;
+      }finally {
+        if( sah ) await sah.close();
+      }
+    };
+
     /**
        Asynchronously imports the given bytes (a byte array or
        ArrayBuffer) into the given database file.
 
+       If passed a function for its second argument, its behaviour
+       changes to async and it imports its data in chunks fed to it by
+       the given callback function. It calls the callback (which may
+       be async) repeatedly, expecting either a Uint8Array or
+       ArrayBuffer (to denote new input) or undefined (to denote
+       EOF). For so long as the callback continues to return
+       non-undefined, it will append incoming data to the given
+       VFS-hosted database file. When called this way, the resolved
+       value of the returned Promise is the number of bytes written to
+       the target file.
+
        It very specifically requires the input to be an SQLite3
        database and throws if that's not the case.  It does so in
        order to prevent this function from taking on a larger scope
        than it is specifically intended to. i.e. we do not want it to
        become a convenience for importing arbitrary files into OPFS.
 
-       Throws on error. Resolves to the number of bytes written.
+       This routine rewrites the database header bytes in the output
+       file (not the input array) to force disabling of WAL mode.
+
+       On error this throws and the state of the input file is
+       undefined (it depends on where the exception was triggered).
+
+       On success, resolves to the number of bytes written.
     */
     opfsUtil.importDb = async function(filename, bytes){
+      if( bytes instanceof Function ){
+        return importDbChunked(filename, bytes);
+      }
       if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+      util.affirmIsDb(bytes);
       const n = bytes.byteLength;
-      if(n<512 || n%512!=0){
-        toss("Byte array size is invalid for an SQLite db.");
-      }
-      const header = "SQLite format 3";
-      for(let i = 0; i < header.length; ++i){
-        if( header.charCodeAt(i) !== bytes[i] ){
-          toss("Input does not contain an SQLite database header.");
-        }
-      }
       const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true);
-      const hFile = await hDir.getFileHandle(fnamePart, {create:true});
-      const sah = await hFile.createSyncAccessHandle();
-      sah.truncate(0);
-      const nWrote = sah.write(bytes, {at: 0});
-      sah.close();
-      if(nWrote != n){
-        toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
+      let sah, err, nWrote = 0;
+      try {
+        const hFile = await hDir.getFileHandle(fnamePart, {create:true});
+        sah = await hFile.createSyncAccessHandle();
+        sah.truncate(0);
+        nWrote = sah.write(bytes, {at: 0});
+        if(nWrote != n){
+          toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
+        }
+        sah.write(new Uint8Array([1,1]), {at: 18}) /* force db out of WAL mode */;
+        return nWrote;
+      }catch(e){
+        if( sah ){ await sah.close(); sah = undefined; }
+        await hDir.removeEntry( fnamePart ).catch(()=>{});
+        throw e;
+      }finally{
+        if( sah ) await sah.close();
       }
-      return nWrote;
     };
 
     if(sqlite3.oo1){
diff --git a/libsql-sqlite3/ext/wasm/api/sqlite3-wasm.c b/libsql-sqlite3/ext/wasm/api/sqlite3-wasm.c
index fcfbc06929..18d27bdf0c 100644
--- a/libsql-sqlite3/ext/wasm/api/sqlite3-wasm.c
+++ b/libsql-sqlite3/ext/wasm/api/sqlite3-wasm.c
@@ -84,6 +84,14 @@
 
 /**********************************************************************/
 /* SQLITE_ENABLE_... */
+/*
+** Unconditionally enable API_ARMOR in the WASM build. It ensures that
+** public APIs behave predictable in the face of passing illegal NULLs
+** or ranges which might otherwise invoke undefined behavior.
+*/
+#undef SQLITE_ENABLE_API_ARMOR
+#define SQLITE_ENABLE_API_ARMOR 1
+
 #ifndef SQLITE_ENABLE_BYTECODE_VTAB
 #  define SQLITE_ENABLE_BYTECODE_VTAB 1
 #endif
@@ -121,12 +129,6 @@
 #  define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
 #endif
 
-/**********************************************************************/
-/* SQLITE_M... */
-#ifndef SQLITE_MAX_ALLOCATION_SIZE
-# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff
-#endif
-
 /**********************************************************************/
 /* SQLITE_O... */
 #ifndef SQLITE_OMIT_DEPRECATED
@@ -352,7 +354,9 @@ int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){
   if( db!=0 ){
     if( 0!=zMsg ){
       const int nMsg = sqlite3Strlen30(zMsg);
+      sqlite3_mutex_enter(sqlite3_db_mutex(db));
       sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
+      sqlite3_mutex_leave(sqlite3_db_mutex(db));
     }else{
       sqlite3ErrorWithMsg(db, err_code, NULL);
     }
@@ -1799,6 +1803,118 @@ char * sqlite3_wasm_test_str_hello(int fail){
   }
   return s;
 }
+
+/*
+** For testing using SQLTester scripts.
+**
+** Return non-zero if string z matches glob pattern zGlob and zero if the
+** pattern does not match.
+**
+** To repeat:
+**
+**         zero == no match
+**     non-zero == match
+**
+** Globbing rules:
+**
+**      '*'       Matches any sequence of zero or more characters.
+**
+**      '?'       Matches exactly one character.
+**
+**     [...]      Matches one character from the enclosed list of
+**                characters.
+**
+**     [^...]     Matches one character not in the enclosed list.
+**
+**      '#'       Matches any sequence of one or more digits with an
+**                optional + or - sign in front, or a hexadecimal
+**                literal of the form 0x...
+*/
+static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
+  int c, c2;
+  int invert;
+  int seen;
+  typedef int (*recurse_f)(const char *,const char *);
+  static const recurse_f recurse = sqlite3_wasm_SQLTester_strnotglob;
+
+  while( (c = (*(zGlob++)))!=0 ){
+    if( c=='*' ){
+      while( (c=(*(zGlob++))) == '*' || c=='?' ){
+        if( c=='?' && (*(z++))==0 ) return 0;
+      }
+      if( c==0 ){
+        return 1;
+      }else if( c=='[' ){
+        while( *z && recurse(zGlob-1,z)==0 ){
+          z++;
+        }
+        return (*z)!=0;
+      }
+      while( (c2 = (*(z++)))!=0 ){
+        while( c2!=c ){
+          c2 = *(z++);
+          if( c2==0 ) return 0;
+        }
+        if( recurse(zGlob,z) ) return 1;
+      }
+      return 0;
+    }else if( c=='?' ){
+      if( (*(z++))==0 ) return 0;
+    }else if( c=='[' ){
+      int prior_c = 0;
+      seen = 0;
+      invert = 0;
+      c = *(z++);
+      if( c==0 ) return 0;
+      c2 = *(zGlob++);
+      if( c2=='^' ){
+        invert = 1;
+        c2 = *(zGlob++);
+      }
+      if( c2==']' ){
+        if( c==']' ) seen = 1;
+        c2 = *(zGlob++);
+      }
+      while( c2 && c2!=']' ){
+        if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
+          c2 = *(zGlob++);
+          if( c>=prior_c && c<=c2 ) seen = 1;
+          prior_c = 0;
+        }else{
+          if( c==c2 ){
+            seen = 1;
+          }
+          prior_c = c2;
+        }
+        c2 = *(zGlob++);
+      }
+      if( c2==0 || (seen ^ invert)==0 ) return 0;
+    }else if( c=='#' ){
+      if( z[0]=='0'
+       && (z[1]=='x' || z[1]=='X')
+       && sqlite3Isxdigit(z[2])
+      ){
+        z += 3;
+        while( sqlite3Isxdigit(z[0]) ){ z++; }
+      }else{
+        if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++;
+        if( !sqlite3Isdigit(z[0]) ) return 0;
+        z++;
+        while( sqlite3Isdigit(z[0]) ){ z++; }
+      }
+    }else{
+      if( c!=(*(z++)) ) return 0;
+    }
+  }
+  return *z==0;
+}
+
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_SQLTester_strglob(const char *zGlob, const char *z){
+ return !sqlite3_wasm_SQLTester_strnotglob(zGlob, z);
+}
+
+
 #endif /* SQLITE_WASM_TESTS */
 
 #undef SQLITE_WASM_EXPORT
diff --git a/libsql-sqlite3/ext/wasm/api/sqlite3-worker1.c-pp.js b/libsql-sqlite3/ext/wasm/api/sqlite3-worker1.c-pp.js
index f260422309..220722ffe1 100644
--- a/libsql-sqlite3/ext/wasm/api/sqlite3-worker1.c-pp.js
+++ b/libsql-sqlite3/ext/wasm/api/sqlite3-worker1.c-pp.js
@@ -37,7 +37,7 @@ import {default as sqlite3InitModule} from './sqlite3-bundler-friendly.mjs';
 "use strict";
 {
   const urlParams = globalThis.location
-        ? new URL(self.location.href).searchParams
+        ? new URL(globalThis.location.href).searchParams
         : new URLSearchParams();
   let theJs = 'sqlite3.js';
   if(urlParams.has('sqlite3.dir')){
diff --git a/libsql-sqlite3/ext/wasm/tester1.c-pp.js b/libsql-sqlite3/ext/wasm/tester1.c-pp.js
index bd945dcab7..92d763f1ba 100644
--- a/libsql-sqlite3/ext/wasm/tester1.c-pp.js
+++ b/libsql-sqlite3/ext/wasm/tester1.c-pp.js
@@ -2939,8 +2939,27 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
         let db;
         try {
           const exp = this.opfsDbExport;
+          const filename = this.opfsDbFile;
           delete this.opfsDbExport;
-          this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(this.opfsDbFile, exp);
+          this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp);
+          db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
+          T.assert(6 === db.selectValue('select count(*) from p')).
+            assert( this.opfsImportSize == exp.byteLength );
+          db.close();
+          this.opfsUnlink(filename);
+          T.assert(!(await sqlite3.opfs.entryExists(filename)));
+          // Try again with a function as an input source:
+          let cursor = 0;
+          const blockSize = 512, end = exp.byteLength;
+          const reader = async function(){
+            if(cursor >= exp.byteLength){
+              return undefined;
+            }
+            const rv = exp.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
+            cursor += blockSize;
+            return rv;
+          };
+          this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, reader);
           db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
           T.assert(6 === db.selectValue('select count(*) from p')).
             assert( this.opfsImportSize == exp.byteLength );
@@ -3059,8 +3078,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
           const dbytes = u1.exportFile(dbName);
           T.assert(dbytes.length >= 4096);
           const dbName2 = '/exported.db';
-          u1.importDb(dbName2, dbytes);
-          T.assert( 2 == u1.getFileCount() );
+          let nWrote = u1.importDb(dbName2, dbytes);
+          T.assert( 2 == u1.getFileCount() )
+            .assert( dbytes.byteLength == nWrote );
           let db2 = new u1.OpfsSAHPoolDb(dbName2);
           T.assert(db2 instanceof sqlite3.oo1.DB)
             .assert(3 === db2.selectValue('select count(*) from t'));
@@ -3069,6 +3089,25 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
             .assert(false === u1.unlink(dbName2))
             .assert(1 === u1.getFileCount())
             .assert(1 === u1.getFileNames().length);
+          // Try again with a function as an input source:
+          let cursor = 0;
+          const blockSize = 1024, end = dbytes.byteLength;
+          const reader = async function(){
+            if(cursor >= dbytes.byteLength){
+              return undefined;
+            }
+            const rv = dbytes.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
+            cursor += blockSize;
+            return rv;
+          };
+          nWrote = await u1.importDb(dbName2, reader);
+          T.assert( 2 == u1.getFileCount() );
+          db2 = new u1.OpfsSAHPoolDb(dbName2);
+          T.assert(db2 instanceof sqlite3.oo1.DB)
+            .assert(3 === db2.selectValue('select count(*) from t'));
+          db2.close();
+          T.assert(true === u1.unlink(dbName2))
+            .assert(dbytes.byteLength == nWrote);
         }
 
         T.assert(true === u1.unlink(dbName))
@@ -3214,4 +3253,3 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
     TestUtil.runTests(sqlite3);
   });
 })(self);
-
diff --git a/libsql-sqlite3/main.mk b/libsql-sqlite3/main.mk
index a1cc4cc702..90483bda53 100644
--- a/libsql-sqlite3/main.mk
+++ b/libsql-sqlite3/main.mk
@@ -599,7 +599,17 @@ dbfuzz2$(EXE):	$(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h
 	  $(DBFUZZ2_OPTS) $(TOP)/test/dbfuzz2.c sqlite3.c  $(TLIBS) $(THREADLIB)
 
 fuzzcheck$(EXE):	$(FUZZSRC) sqlite3.c sqlite3.h $(FUZZDEP)
-	$(TCCX) -o fuzzcheck$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
+	$(TCCX) -o $@ -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
+		-DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \
+		$(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB)
+
+fuzzcheck-asan$(EXE):	$(FUZZSRC) sqlite3.c sqlite3.h $(FUZZDEP)
+	$(TCCX) -fsanitize=address -o $W -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
+		-DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \
+		$(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB)
+
+fuzzcheck-ubsan$(EXE):	$(FUZZSRC) sqlite3.c sqlite3.h $(FUZZDEP)
+	$(TCCX) -fsanitize=undefined -o $@ -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
 		-DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \
 		$(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB)
 
@@ -919,11 +929,11 @@ fulltestonly:	$(TESTPROGS) fuzztest
 queryplantest:	testfixture$(EXE) sqlite3$(EXE)
 	./testfixture$(EXE) $(TOP)/test/permutations.test queryplanner $(TESTOPTS)
 
-fuzztest:	fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db
+fuzztest:	fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE)
 	./fuzzcheck$(EXE) $(FUZZDATA)
 	./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db
 
-valgrindfuzz:	fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db
+valgrindfuzz:	fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE)
 	valgrind ./fuzzcheck$(EXE) --cell-size-check --limit-mem 10M $(FUZZDATA)
 	valgrind ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db
 
diff --git a/libsql-sqlite3/manifest b/libsql-sqlite3/manifest
index c84c400b74..603321be0f 100644
--- a/libsql-sqlite3/manifest
+++ b/libsql-sqlite3/manifest
@@ -1,13 +1,13 @@
-C Version\s3.43.0
-D 2023-08-24T12:36:59.632
+C Version\s3.44.0
+D 2023-11-01T11:23:50.173
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in 1e9105ffed727b1557ce59f0941c5d271e276ec00bc9823f03d77a89e131b918
+F Makefile.in 8b59912fc1538f96a08555605c5886cdcc733696ae7f22e374b2a4752196ca20
 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6
-F Makefile.msc 26c2d196391a285c279adb10fd6001774d9b243af94b700b681e4a49cd476684
+F Makefile.msc f0cf219350d9af4fba411b4f6306dce2adc897484e8f446de1fb4f40de674d00
 F README.md 963d30019abf0cc06b263cd2824bce022893f3f93a531758f6f04ff2194a16a8
-F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa
+F VERSION 4c09b629c03b8ae32317cb336a32f3aa3252841d6dcd51184cecc4278d08f21e
 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90
@@ -15,14 +15,14 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2
 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903
 F autoconf/Makefile.am adedc1324b6a87fdd1265ddd336d2fb7d4f36a0e77b86ea553ae7cc4ea239347
 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac
-F autoconf/Makefile.msc 3248809e70cf439a13e9faf82a4e12cbdb7b042006300ac67175fc5125b5c031
+F autoconf/Makefile.msc 34f8c222846db1b354720c8a78a4fa7dec9176ea447c0fffdf442b7f74e8b1df
 F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7
 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56
 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277
 F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196
 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873
 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43
-F autoconf/tea/configure.ac fce5eca183cf47853f7d5b81c1e7bfa41c021fb460939ec67ef83653ffc6a531
+F autoconf/tea/configure.ac 1ee185f2f8c2e9bed69d0113222724202f962d04eafd57197e65c662d2d9b180
 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb
 F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523
 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce
@@ -33,14 +33,15 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034
 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63
 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6
 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559
-F configure 9dc3300339f4d6b3c3b108de60cc6ae6b3c547e25c7e6df280b4775db4de3a1b x
-F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8
+F configure 1d9cbcb416cb5387b3f750ae6db63cfedb703a2e46bf8ca61daecff31d208252 x
+F configure.ac de31fea7d975bb7ebafbe0e2190a855cc80d48558bf0c9a6578a1836daf1cd3a
 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
 F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd
-F doc/compile-for-windows.md c52f2903f1cb11b2308798feecca2e44701b037b78f467a538ac5c46c28ee250
+F doc/compile-for-windows.md 922ba580d210a1f1bd3ef9d0413121556f9b5714fb5c01e664d980f85fa4ac8c
 F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f
 F doc/lemon.html 44a53a1d2b42d7751f7b2f478efb23c978e258d794bfd172442307a755b9fa44
 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710
+F doc/testrunner.md 2434864be2219d4f0b6ffc99d0a2172d531c4ca4345340776f67ad4edd90dc90
 F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a
 F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56
 F doc/vfs-shm.txt e101f27ea02a8387ce46a05be2b1a902a021d37a
@@ -51,33 +52,33 @@ F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad
 F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a
 F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3
 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4
-F ext/expert/expert1.test 95b00567ce0775126a1b788af2d055255014714ecfddc97913864d2f9266e583
-F ext/expert/sqlite3expert.c a912efbad597eafdb0ce934ebc11039f3190b2d479685d89184e107f65d856e1
+F ext/expert/expert1.test 0dd5cb096d66bed593e33053a3b364f6ef52ed72064bf5cf298364636dbf3cd6
+F ext/expert/sqlite3expert.c 90446bb1183429308c68ceb23e00cb8252f43b8886cf5efa3fc7e833b5fa24c8
 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
 F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
 F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c7cc3bf59ee
 F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
 F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d
 F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
-F ext/fts3/fts3.c 66d2ced306ac88c39c00d81184f2c60f338696af6ae8cc26ed7c361b157b09f7
+F ext/fts3/fts3.c d01dfb641fc04efeeadcb94d7a8342eb07d71c1a3a3852ec8ab5e64c1fcdfff9
 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
-F ext/fts3/fts3Int.h e573c6d881f7238d77cc3fd2396cbb9b2fe13efef7d2ad295a155151c4e7efbd
-F ext/fts3/fts3_aux.c f0dc9bd98582615b7750218899bd0c729879b6bbf94d1be57ca1833ff49afc6f
+F ext/fts3/fts3Int.h be688580701d41340de73384e3acc8c55be12a438583207444bd5e20f9ef426c
+F ext/fts3/fts3_aux.c 7eab82a9cf0830f6551ba3abfdbe73ed39e322a4d3940ee82fbf723674ecd9f3
 F ext/fts3/fts3_expr.c 903bfb9433109fffb10e910d7066c49cbf8eeae316adc93f0499c4da7dfc932a
 F ext/fts3/fts3_hash.c 8b6e31bfb0844c27dc6092c2620bdb1fca17ed613072db057d96952c6bdb48b7
 F ext/fts3/fts3_hash.h 39cf6874dc239d6b4e30479b1975fe5b22a3caaf
 F ext/fts3/fts3_icu.c 305ce7fb6036484085b5556a9c8e62acdc7763f0f4cdf5fd538212a9f3720116
 F ext/fts3/fts3_porter.c e19807ce0ae31c1c6e9898e89ecc93183d7ec224ea101af039722a4f49e5f2b8
 F ext/fts3/fts3_snippet.c 4d6523e3eddeb7b46e7a82b3476a0a86a0c04821e0e2b8dd40f45ee28057cb13
-F ext/fts3/fts3_term.c f45a1e7c6ef464abb1231245d123dae12266b69e05cc56e14045b76591ae92d1
+F ext/fts3/fts3_term.c 845f0e2456b1be42f7f1bec1da1dfc05bc347531eff90775ffc6698902c281de
 F ext/fts3/fts3_test.c d8d7b2734f894e8a489987447658e374cdd3a3bc8575c401decf1911cb7c6454
-F ext/fts3/fts3_tokenize_vtab.c a95feda3590f3c3e17672fe35b67ea6112471aeea4c07ef7744a6606b66549aa
+F ext/fts3/fts3_tokenize_vtab.c 7fd9ef364f257b97218b9c331f2378e307375c592f70fd541f714e747d944962
 F ext/fts3/fts3_tokenizer.c 6d8fc150c48238955d5182bf661498db0dd473c8a2a80e00c16994a646fa96e7
 F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3
 F ext/fts3/fts3_tokenizer1.c c1de4ae28356ad98ccb8b2e3388a7fdcce7607b5523738c9afb6275dab765154
 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226
 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f
-F ext/fts3/fts3_write.c d28d9ef383ef848a7b77df4b9964abcc90d67a2b584120c0ad465972dce416e6
+F ext/fts3/fts3_write.c 5bb4721330ca589f906e72bb824dd4080b313c6d4c4231fa541e9db32dc67982
 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73
 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674
@@ -88,25 +89,25 @@ F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a0
 F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0
 F ext/fts5/fts5.h 05501612cc655504c5dce8ba765ab621d50fc478490089beaa0d75e00b23e520
 F ext/fts5/fts5Int.h 78a63cc0795186cde5384816a9403a68c65774b35d952e05b81a1b4b158e07c8
-F ext/fts5/fts5_aux.c 572d5ec92ba7301df2fea3258576332f2f4d2dfd66d8263afd157d9deceac480
+F ext/fts5/fts5_aux.c 35c4101613eff86902877a4dedd9400b07922e412cbdd637b45041dce2fd5388
 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5
 F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081
 F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6
 F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d
-F ext/fts5/fts5_index.c b484322421cbb421d22bb2cd304001b80596d671cd626367c8c806b889de4b42
-F ext/fts5/fts5_main.c 7070031993ba5b5d89b13206ec4ef624895f2f7c0ec72725913d301e4d382445
-F ext/fts5/fts5_storage.c 3c9b41fce41b6410f2e8f82eb035c6a29b2560483f773e6dc98cf3cb2e4ddbb5
+F ext/fts5/fts5_index.c 730c9c32ada18ce1eb7ff847b36507f4b005d88d47af7b47db521e695a8ea4c7
+F ext/fts5/fts5_main.c a07ed863b8bd9e6fefb62db2fd40a3518eb30a5f7dcfda5be915dd2db45efa2f
+F ext/fts5/fts5_storage.c 5d10b9bdcce5b90656cad13c7d12ad4148677d4b9e3fca0481fca56d6601426d
 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae
 F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee
-F ext/fts5/fts5_test_tok.c a2bed8edb25f6432e8cdb62aad5916935c19dba8dac2b8324950cfff397e25ff
+F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b
 F ext/fts5/fts5_tokenize.c 5e251efb0f1af99a25ed50010ba6b1ad1250aca5921af1988fdcabe5ebc3cb43
 F ext/fts5/fts5_unicode2.c eca63dbc797f8ff0572e97caf4631389c0ab900d6364861b915bdd4735973f00
 F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80
-F ext/fts5/fts5_vocab.c 12138e84616b56218532e3e8feb1d3e0e7ae845e33408dbe911df520424dc9d6
+F ext/fts5/fts5_vocab.c aed56169ae5c1aa9b8189c779ffeef04ed516d3c712c06914e6d91a6759f4e4a
 F ext/fts5/fts5parse.y eb526940f892ade5693f22ffd6c4f2702543a9059942772526eac1fde256bb05
 F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba
 F ext/fts5/test/fts5_common.tcl a9de9c2209cc4e7ae3c753e783504e67206c6c1467d08f209cd0c5923d3e8d8b
-F ext/fts5/test/fts5aa.test 5bd43427b7d08ce2e19c488a26534be450538b9232d4d5305049e8de236e9aa9
+F ext/fts5/test/fts5aa.test ba5158eba7d61359becdfca895ef471072c7bf7b20e5e60dcb4d024c8419c926
 F ext/fts5/test/fts5ab.test bd932720c748383277456b81f91bc00453de2174f9762cd05f95d0495dc50390
 F ext/fts5/test/fts5ac.test a7aa7e1fefc6e1918aa4d3111d5c44a09177168e962c5fd2cca9620de8a7ed6d
 F ext/fts5/test/fts5ad.test e8cf959dfcd57c8e46d6f5f25665686f3b6627130a9a981371dafdf6482790de
@@ -120,7 +121,7 @@ F ext/fts5/test/fts5ak.test f459a64c9d38698af72a7c657ab6349bca96150241dd69fcce75
 F ext/fts5/test/fts5al.test 00c4c1c6a1366b73aa48ce2068c634520867c3cf7f5d1676ebbb775ee1f35734
 F ext/fts5/test/fts5alter.test 5565f7e4605512b69171ac18ca84398603f9f6456dbe377beeca97e83cc242cd
 F ext/fts5/test/fts5auto.test 78989e6527ce69c9eddbef7392fea5c10b0010cd2b2ae68eec7bc869c471e691
-F ext/fts5/test/fts5aux.test ebf6f2ff7cb556e83f66991b7f12bff016d3c83d4eab36704b649dd6b1437318
+F ext/fts5/test/fts5aux.test 3f194345fcd581f49f7fbb2e5495400efcc7d2835b77816328d8283c942f41b8
 F ext/fts5/test/fts5auxdata.test eacc97ff04892f1a5f3d4df5a73f8bcbc3955ea1d12c9f24137eb1fc079e7611
 F ext/fts5/test/fts5bigid.test 2860854c2561a57594192b00c33a29f91cb85e25f3d6c03b5c2b8f62708f39dd
 F ext/fts5/test/fts5bigpl.test 6466c89b38439f0aba26ac09e232a6b963f29b1cbe1304f6a664fe1e7a8f5fd3
@@ -130,16 +131,16 @@ F ext/fts5/test/fts5circref.test f880dfd0d99f6fb73b88ccacb0927d18e833672fd906cc4
 F ext/fts5/test/fts5colset.test 7031ce84fb4d312df5a99fc4e7b324e660ccb513c97eccdef469bfd52d3d0f8f
 F ext/fts5/test/fts5columnsize.test 45459ce4dd9fd853b6044cdc9674921bff89e3d840f348ca8c1630f9edbf5482
 F ext/fts5/test/fts5config.test 60094712debc59286c59aef0e6cf511c37d866802776a825ce437d26afe0817f
-F ext/fts5/test/fts5conflict.test 655925678e630d3cdf145d18725a558971806416f453ac8410ca8c04d934238d
+F ext/fts5/test/fts5conflict.test bf6030a77dbb1bedfcc42e589ed7980846c995765d77460551e448b56d741244
 F ext/fts5/test/fts5connect.test 08030168fc96fc278fa81f28654fb7e90566f33aff269c073e19b3ae9126b2f4
-F ext/fts5/test/fts5content.test 213506436fb2c87567b8e31f6d43ab30aab99354cec74ed679f22aad0cdbf283
-F ext/fts5/test/fts5contentless.test eb31159d62811b3a32fb1cfb36be20f9d9db75637c7fe6aa7f5c533db2d00576
-F ext/fts5/test/fts5contentless2.test 12c778d134a121b8bad000fbf3ae900d53226fee840ce36fe941b92737f1fda7
-F ext/fts5/test/fts5contentless3.test 487dce16b6677f68b44d7cbd158b9b7275d25e2c14d713f9188d9645bb699286
-F ext/fts5/test/fts5contentless4.test 0f43ededc2874f65d7da99b641a82239854d98d3fa43db729f284b723f23b69f
-F ext/fts5/test/fts5contentless5.test 216962d51376b62e4ff45e8db00aa3e1ab548cb36b7fd450633e73b2d678f88e
-F ext/fts5/test/fts5corrupt.test 77ae6f41a7eba10620efb921cf7dbe218b0ef232b04519deb43581cb17a57ebe
-F ext/fts5/test/fts5corrupt2.test 7453752ba12ce91690c469a6449d412561cc604b1dec994e16ab132952e7805f
+F ext/fts5/test/fts5content.test 219a4e49386b9b197b9b7cadca97ea10ddff858ecd8b763a1cb8bb07575afc2a
+F ext/fts5/test/fts5contentless.test 1cd1237894eeff11feb1ff8180044eac0b17dde22c181f7a722f2dcbfdb3377c
+F ext/fts5/test/fts5contentless2.test 14c83bdacf8230f5f7ca74ecf2926b87d8a7cb788a69ce9937020428ac4fe192
+F ext/fts5/test/fts5contentless3.test 353d871c5ea08992aed3e2ebda0b1bdc35116cd24fe330fe7cf05be1e2b49fd7
+F ext/fts5/test/fts5contentless4.test dd33ead36b048c9447b81ec358bd4a27166c49ffaac65a54e95eabf59f338947
+F ext/fts5/test/fts5contentless5.test 96041cbf5ef781a68a5d0f0d18a88030c47a52b156b17876ed6ce36e80e27a7e
+F ext/fts5/test/fts5corrupt.test b6d4034b682bb3387bc44c510c71b3c67d4349e4df139490fc0b69e6a972b99f
+F ext/fts5/test/fts5corrupt2.test 99e7e23a58b4d89eb7167c6de1669cbc595cd3c79ab333e0eb56405473319e77
 F ext/fts5/test/fts5corrupt3.test 7da9895dafa404efd20728f66ff4b94399788bdc042c36fe2689801bba2ccd78
 F ext/fts5/test/fts5corrupt4.test f4c08e2182a48d8b70975fd869ee5391855c06d8a0ff87b6a2529e7c5a88a1d3
 F ext/fts5/test/fts5corrupt5.test eb6ba5ca28ef7c4c6b01e850d388cdb3dacc8c4c2f383f79d0a98128257742b4
@@ -166,11 +167,12 @@ F ext/fts5/test/fts5faultB.test d606bdb8e81aaeb6f41de3fc9fc7ae315733f0903fbff05c
 F ext/fts5/test/fts5faultD.test e7ed7895abfe6bc98a5e853826f6b74956e7ba7f594f1860bbf9e504b9647996
 F ext/fts5/test/fts5faultE.test 844586ce71dab4be85bb86880e87b624d089f851654cd22e4710c77eb8ce7075
 F ext/fts5/test/fts5faultF.test 4abef99f86e99d9f0c6460dd68c586a766b6b9f1f660ada55bf2e8266bd1bbc1
+F ext/fts5/test/fts5faultG.test 340e59d2c2c1c7c379224f3968ee8d09b0f64bf56c5194217d1ded887b9d47c4
 F ext/fts5/test/fts5first.test 3fcf2365c00a15fc9704233674789a3b95131d12de18a9b996159f6909dc8079
 F ext/fts5/test/fts5full.test e1701a112354e0ff9a1fdffb0c940c576530c33732ee20ac5e8361777070d717
 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e
 F ext/fts5/test/fts5hash.test dc7bc7e0cdeb42cfce31294ad2f8fcf43192bfd0145bb7f3ecc5465d8c72696f
-F ext/fts5/test/fts5integrity.test 62147a1e85405b986691177e0312be5a64ec9e67b17994e83892d9afa6247600
+F ext/fts5/test/fts5integrity.test 0d249d351163e17e2227aa9850e68193f88a7813d16cc7d51287e554bf915b3d
 F ext/fts5/test/fts5interrupt.test 09613247b273a99889808ef852898177e671406fe71fdde7ea00e78ea283d227
 F ext/fts5/test/fts5lastrowid.test be98fe3e03235296585b72daad7aed5717ba0062bae5e5c18dd6e04e194c6b28
 F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc2782680740513c4d1fc114b43d4ad
@@ -178,13 +180,14 @@ F ext/fts5/test/fts5limits.test 8ab67cf5d311c124b6ceb0062d0297767176df4572d955fc
 F ext/fts5/test/fts5matchinfo.test 10c9a6f7fe61fb132299c4183c012770b10c4d5c2f2edb6df0b6607f683d737a
 F ext/fts5/test/fts5merge.test e92a8db28b45931e7a9c7b1bbd36101692759d00274df74d83fd29d25d53b3a6
 F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2
-F ext/fts5/test/fts5misc.test 416ec0ffbc79320a0760ec32d6684866e3ccd3fbce09f9bcd62d9aee4c666b43
+F ext/fts5/test/fts5misc.test 5ca82f2a5ee016b0842043155d1382f98a34d0d86b2791165a44d7807f6e0f54
 F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581
 F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45
 F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd
 F ext/fts5/test/fts5onepass.test f9b7d9b2c334900c6542a869760290e2ab5382af8fbd618834bf1fcc3e7b84da
 F ext/fts5/test/fts5optimize.test 36a752d24c818792032e4ff502936fc9cc5ef938721696396fdc79214b2717f1
-F ext/fts5/test/fts5optimize2.test c7c97693abe8a2cb572acfb1f252d78f03d3984094cfc5eb2285a76d8a702a92
+F ext/fts5/test/fts5optimize2.test 93e742c36b487d8874621360af5b1ce4d39b04fb9e71ce9bc34015c5fc811785
+F ext/fts5/test/fts5optimize3.test bf9c91bb927d0fb2b9a06318a217a0419183ac5913842e062c7e0b98ea5d0fca
 F ext/fts5/test/fts5phrase.test 13e5d8e9083077b3d9c74315b3c92ec723cc6eb37c8155e0bfe1bba00559f07b
 F ext/fts5/test/fts5plan.test b65cfcca9ddd6fdaa118c61e17aeec8e8433bc5b6bb307abd116514f79c49c5a
 F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15
@@ -192,17 +195,18 @@ F ext/fts5/test/fts5porter2.test 0d251a673f02fa13ca7f011654873b3add20745f7402f10
 F ext/fts5/test/fts5prefix.test a0fa67b06650f2deaa7bf27745899d94e0fb547ad9ecbd08bfad98c04912c056
 F ext/fts5/test/fts5prefix2.test 3847ce46f70b82d61c6095103a9d7c53f2952c40a4704157bc079c04d9c8b18b
 F ext/fts5/test/fts5query.test ac363b17a442620bb0780e93c24f16a5f963dfe2f23dc85647b869efcfada728
-F ext/fts5/test/fts5rank.test c9fd4a1e36b4fa92d572ec13d846469b97da249d1c2f7fd3ee7e017ce46f2416
+F ext/fts5/test/fts5rank.test 30f29e278cd7fb8831ba4f082feb74d8eb90c463bf07113ae200afc2b467ef32
 F ext/fts5/test/fts5rebuild.test 55d6f17715cddbf825680dd6551efbc72ed916d8cf1cde40a46fc5d785b451e7
 F ext/fts5/test/fts5restart.test 835ecc8f449e3919f72509ab58056d0cedca40d1fe04108ccf8ac4c2ba41f415
 F ext/fts5/test/fts5rowid.test b8790ec170a8dc1942a15aef3db926a5f3061b1ff171013003d8297203a20ad6
-F ext/fts5/test/fts5savepoint.test fc02929f238d02a22df4172625704e029f7c1e0e92e332d654375690f8e6e43f
-F ext/fts5/test/fts5secure.test 214a561519d1b1817f146efd1057e2a97cc896e75c2accc77157d874154bda64
+F ext/fts5/test/fts5savepoint.test 050796b24929325cdbbb2fbfe2794816ae95d298e940ae15032200c2f4a73725
+F ext/fts5/test/fts5secure.test a02f771742fb2b1b9bdcb4bf523bcf2d0aa1ff597831d40fe3e72aaa6d0ec40f
 F ext/fts5/test/fts5secure2.test 2e961d7eef939f294c56b5d895cac7f1c3a60b934ee2cfd5e5e620bdf1ba6bbc
 F ext/fts5/test/fts5secure3.test c7e1080a6912f2a3ac68f2e05b88b72a99de38543509b2bbf427cac5c9c1c610
 F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b9846cf7fc8337b67c97
 F ext/fts5/test/fts5secure5.test c07a68ced5951567ac116c22f2d2aafae497e47fe9fcb6a335c22f9c7a4f2c3a
-F ext/fts5/test/fts5secure6.test a0a28cfb9bf9721408b65b5d7c7ce369af3d688e273da24d101c25d60cdce05c
+F ext/fts5/test/fts5secure6.test 74bf04733cc523bccca519bb03d3b4e2ed6f6e3db7c59bf6be82c88a0ac857fd
+F ext/fts5/test/fts5secure7.test fd03d0868d64340a1db8615b02e5508fea409de13910114e4f19eaefc120777a
 F ext/fts5/test/fts5securefault.test dbca2b6a1c16700017f5051138991b705410889933f2a37c57ae8a23b296b10b
 F ext/fts5/test/fts5simple.test a298670508c1458b88ce6030440f26a30673931884eb5f4094ac1773b3ba217b
 F ext/fts5/test/fts5simple2.test 258a1b0c590409bfa5271e872c79572b319d2a56554d0585f68f146a0da603f0
@@ -212,7 +216,7 @@ F ext/fts5/test/fts5synonym2.test 8f891fc49cc1e8daed727051e77e1f42849c784a6a54be
 F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef
 F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2
 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43
-F ext/fts5/test/fts5trigram.test c76acc1913a06182e791a0dfdae285b9cdd67327a1a35b34cabf0a6aa09cf05e
+F ext/fts5/test/fts5trigram.test 6c4e37864f3e7d90673db5563d9736d7e40080ab94d10ebdffa94c1b77941da0
 F ext/fts5/test/fts5ubsan.test 783d5a8d13ebfa169e634940228db54540780e3ba7a87ad1e4510e61440bf64b
 F ext/fts5/test/fts5umlaut.test a42fe2fe6387c40c49ab27ccbd070e1ae38e07f38d05926482cc0bccac9ad602
 F ext/fts5/test/fts5unicode.test 17056f4efe6b0a5d4f41fdf7a7dc9af2873004562eaa899d40633b93dc95f5a9
@@ -232,46 +236,71 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
 F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
-F ext/jni/GNUmakefile 3deba6bc0bf37c1ee5f15d1ff3c3512ae2f3cf44a2b8ae7b4af92690514b0cb4
-F ext/jni/README.md 5c60e4580aa5c94ff74d7bef1fb6231e578f7764e831a07b5981b6ab62b35560
-F ext/jni/jar-dist.make 93da95f8fe01ef22fccacc27f2e805938058e91e8c72c0532558d3a812a42e74
-F ext/jni/src/c/sqlite3-jni.c bea6b8691a5fa3a8626a771757bb261208d3c5fc6598266d3b0ee23d88e35632
-F ext/jni/src/c/sqlite3-jni.h 28565de9efc971195c684095ba0d184b90401290698c987f7ea3f54e47ff4f2f
-F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892
-F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093
-F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c
-F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1
-F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8
-F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a
-F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901
-F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890
-F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 01f890105c6b7edbbad1c0f5635f783cea62c4b2ae694a71e76514a936ee03ec
-F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc
-F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9
-F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060
-F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee
-F ext/jni/src/org/sqlite/jni/OutputPointer.java d81f8bd43d2296ae373692370cfad16ddde76f5c14cd2760f7b4e1113ef56d4c
-F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc
-F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86
-F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
-F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46
-F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 4b6fd22e04e63eb65d8e4e38fda39ecf15ce244d034607517627ce2e766e7e65
-F ext/jni/src/org/sqlite/jni/Tester1.java 4253dc7bcff64500a9388f1a17d3d39dbe4eb9d7db9fc035ce6e2380d45ad5fc
-F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1
-F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d
-F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d
-F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee
-F ext/jni/src/org/sqlite/jni/fts5_api.java 5198be71c162e3e0cb1f4962a7cdf0d7596e8af53f70c4af6db24aab8d53d9ba
-F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c
-F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b
-F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc
-F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810
-F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc
-F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a
-F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1f1286428fab38dfefe328e72b5735f533b19af8dd17712dd3df7e044d21c8b8
-F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e
-F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d
+F ext/jni/GNUmakefile 5c3ac326bf3853486ebe0d70819abc790cc65c412182ce4ebd5012b008d9b059
+F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4
+F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
+F ext/jni/src/c/sqlite3-jni.c 6f6df9657989e9ca2cfdcc2fe9a71c279de56d5c941adfd09a0f24256de35c8f
+F ext/jni/src/c/sqlite3-jni.h b4c413a0d0c734683da1049cfcf89e35ae2719759d0656ec0f8c57188f18cab8
+F ext/jni/src/org/sqlite/jni/annotation/NotNull.java a99341e88154e70447596b1af6a27c586317df41a7e0f246fd41370cd7b723b2
+F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba
+F ext/jni/src/org/sqlite/jni/annotation/package-info.java 977b374aed9d5853cbf3438ba3b0940abfa2ea4574f702a2448ee143b98ac3ca
+F ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 1afa90d3f236f79cc7fcd2497e111992644f7596fbc8e8bcf7f1908ae00acd6c
+F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java bc29e986c866c2ddbbb9f935f5b7264c1c1026864e50a4a735192864f75e37c0
+F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 7ed409d5449684616cc924534e22ff6b07d361f12ad904b69ecb10e0568a8013
+F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759
+F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca
+F ext/jni/src/org/sqlite/jni/capi/CApi.java bccb442ca81cd4decb1adae99006a60b7a9f54e5153842e738c01104e97d1de0
+F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 0bfd6e56e8265c2f05c9207665707285534d78f8466ef0e0430c65677f00943d
+F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a
+F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java f81cf10b79c52f9b2e9247d523d29ae48863935f60420eae35f257c38c80ce95
+F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 29c002f3c638cc80f7db1594564a262d1beb32637824c3dca2d60a224d1f71d7
+F ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b995ca412f59b631803b93aa5b3684fce62e335d1e123207084c054abfd488d4
+F ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 701f2e4d8bdeb27cfbeeb56315d15b13d8752b0fdbca705f31bd4366c58d8a33
+F ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b7036dcb1ef1b39f1f36ac605dde0ff1a24a9a01ade6aa1a605039443e089a61
+F ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 68f60aec7aeb5cd4e5fb83449037f668c63cb99f682ee1036cc226d0cbd909b9
+F ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java aca8f9fa72e3b6602bc9a7dd3ae9f5b2808103fbbee9b2749dc96c19cdc261a1
+F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 819d938e26208adde17ca4b7ddde1d8cd6915b6ab7b708249a9787beca6bd6b6
+F ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 01bc0c238eed2d5f93c73522cb7849a445cc9098c2ed1e78248fa20ed1cfde5b
+F ext/jni/src/org/sqlite/jni/capi/ResultCode.java 8141171f1bcf9f46eef303b9d3c5dc2537a25ad1628f3638398d8a60cacefa7f
+F ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 105e324d09c207100485e7667ad172e64322c62426bb49b547e9b0dc9c33f5f0
+F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java fef556adbc3624292423083a648bdf97fa8a4f6b3b6577c9660dd7bd6a6d3c4a
+F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1
+F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615
+F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f
+F ext/jni/src/org/sqlite/jni/capi/Tester1.java ca195521b6bda3e0cd00e76bb71ec8060d1fab76a2f13b1af9feea40789f44bb
+F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723
+F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2766b8526bbffc4f1045f70e79f1bc1b1efe1c3e95ca06cdb8a7391032dda3b4
+F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 9f9e151f1da017b706c0ee5f40f4c86b54e773d6ae4339723e0cc85a456251ab
+F ext/jni/src/org/sqlite/jni/capi/WindowFunction.java caf4396f91b2567904cf94bc538a069fd62260d975bd037d15a02a890ed1ef9e
+F ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java f3abb8dd7381f53ebba909437090caf68200f06717b8a7d6aa96fa3e8133117d
+F ext/jni/src/org/sqlite/jni/capi/package-info.java 08ff986a65d2be9162442c82d28a65ce431d826f188520717c2ecb1484d0a50e
+F ext/jni/src/org/sqlite/jni/capi/sqlite3.java 4010bbebc5bf44e2044e610786088cdee7dc155da2b333c0551492ff1cedf33b
+F ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java 6742b431cd4d77e8000c1f92ec66265a58414c86bf3b0b5fbcb1164e08477227
+F ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java f204ab6ab1263e119fe43730141a00662d80972129a5351dfb11aae5d282df36
+F ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java f0ef982009c335c4393ffcb68051809ca1711e4f47bcb8d1d46952f22c01bc22
+F ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java ff579621e9bd5ffbc6b2ef9f996c12db4df6e0c8cc5697c91273e5fca279fcf8
+F ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java e1d62a257c13504b46d39d5c21c49cf157ad73fda00cc5f34c931aa008c37049
+F ext/jni/src/org/sqlite/jni/fts5/Fts5.java e94681023785f1eff5399f0ddc82f46b035977d350f14838db659236ebdf6b41
+F ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java 338637e6e5a2cc385d962b220f3c1f475cc371d12ae43d18ef27327b6e6225f7
+F ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java 7da0fbb5728f7c056a43e6407f13dd0c7c9c445221267786a109b987f5fc8a9d
+F ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java 28045042d593a1f1b9b80d54ec77cbf1d8a1bc95e442eceefa9a3a6f56600b0e
+F ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java 3c8f677ffb85b8782f865d6fcbc16200b3375d0e3c29ed541a494fde3011bf49
+F ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java eaee4d641229a098eb704b96a45c9a23c6514dc39009d3611e265bab33834deb
+F ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java 1efd1220ea328a32f2d2a1b16c735864159e929480f71daad4de9d5944839167
+F ext/jni/src/org/sqlite/jni/fts5/fts5_api.java a8e88c3783d21cec51b0748568a96653fead88f8f4953376178d9c7385b197ea
+F ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 9e2b954d210d572552b28aca523b272fae14bd41e318921b22f65b728d5bf978
+F ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 92bdaa3893bd684533004d64ade23d329843f809cd0d0f4f1a2856da6e6b4d90
+F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e
+F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java 5ad99bd74c85f56bbef324d9ec29b4048f4620547c9a80093d8586c3557f9f9a
+F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03
+F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 004394eeb944baa56e36cd7ae69ba6d4a52b52db3c49439db16e98270b861421
+F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java a9ddc6a9e8c113168cc67592ae24c0e56d30dd06226eeab012f2761a0889d7bb
+F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 1386f7b753134fc12253ce2fbbc448ba8c970567fac01a3356cb672e14408d73
+F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java c24b510ebe801c30533cc62efdf69a4a5e2da9ec4b49f8d403f2060693f060a0
+F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af
+F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
+F ext/jni/src/tests/900-001-fts.test bf0ce17a8d082773450e91f2388f5bbb2dfa316d0b676c313c637a91198090f0
 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013
 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86
@@ -312,70 +341,70 @@ F ext/lsm1/lsm_str.c 65e361b488c87b10bf3e5c0070b14ffc602cf84f094880bece77bbf6678
 F ext/lsm1/lsm_tree.c 682679d7ef2b8b6f2fe77aeb532c8d29695bca671c220b0abac77069de5fb9fb
 F ext/lsm1/lsm_unix.c 11e0a5c19d754a4e1d93dfad06de8cc201f10f886b8e61a4c599ed34e334fc24
 F ext/lsm1/lsm_varint.c fe134ad7b2db1ecd99b6a155d2f3625cfd497730e227ae18892452e457b73327
-F ext/lsm1/lsm_vtab.c e57aa3eb456bf2b98064014027e097c9402d6dec7b59564ddbfa1c0ead8f96c5
+F ext/lsm1/lsm_vtab.c 0bc7d2702150e9d5513118f23fdb5d7f3642884e6c0dde332da08b016857887a
 F ext/lsm1/lsm_win32.c 0a4acbd7e8d136dd3a5753f0a9e7a9802263a9d96cef3278cf120bcaa724db7c
 F ext/lsm1/test/lsm1_common.tcl 5ed4bab07c93be2e4f300ebe46007ecf4b3e20bc5fbe1dedaf04a8774a6d8d82
 F ext/lsm1/test/lsm1_simple.test a04d08e8661ae6fc53786c67f0bd102c6692f003e859dde03ed9ac3f12e066e5
 F ext/lsm1/tool/mklsm1c.tcl f31561bbee5349f0a554d1ad7236ac1991fc09176626f529f6078e07335398b0
 F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f238c240
-F ext/misc/amatch.c e3ad5532799cee9a97647f483f67f43b38796b84b5a8c60594fe782a4338f358
+F ext/misc/amatch.c 5001711cbecdd57b288cb613386789f3034e5beb58fbe0c79f2b3d643ffd4e03
 F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
 F ext/misc/appendvfs.c 9642c7a194a2a25dca7ad3e36af24a0a46d7702168c4ad7e59c9f9b0e16a3824
 F ext/misc/base64.c a71b131e50300c654a66c469a25b62874481f3d1cb3beb56aca9a68edd812e0d
 F ext/misc/base85.c 073054111988db593ef5fdb87ab8c459df1ea0c3aaaddf0f5bfa3d72b7e6280a
 F ext/misc/basexx.c 89ad6b76558efbceb627afd5e2ef1d84b2e96d9aaf9b7ecb20e3d00b51be6fcf
 F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a
-F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9
-F ext/misc/carray.c 0ba03f1e6647785d4e05b51be567f5652f06941314ff9d3d3763900aa353b6b5
+F ext/misc/btreeinfo.c cb952620eedf5c0b7625b678f0f08e54d2ec0011d4e50efda5ebdc97f3df7d04
+F ext/misc/carray.c 34fac63770971611c5285de0a9f0ac67d504eaf66be891f637add9290f1c76a5
 F ext/misc/carray.h 503209952ccf2431c7fd899ebb92bf46bf7635b38aace42ec8aa1b8d7b6e98a5
 F ext/misc/cksumvfs.c 9224e33cc0cb6aa61ff1d7d7b8fd6fe56beca9f9c47954fa4ae0a69bef608f69
-F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c85c243
-F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8beb2f22b9
+F ext/misc/closure.c 0e04f52d93e678dd6f950f195f365992edf3c380df246f3d80425cba4c13891e
+F ext/misc/completion.c ef78835483b43ac18c96be312b90b615d8368189909be03513ab7a9338131298
 F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9
-F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73
+F ext/misc/csv.c 575c2c05fba0a451586a4d42c2c81e711780c41e797126f198d8d9e0a308dcdb
 F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01
 F ext/misc/decimal.c 172cf81a8634e6a0f0bedaf71a8372fee63348cf5a3c4e1b78bb233c35889fdc
 F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1
-F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe
-F ext/misc/fileio.c 4e7f7cd30de8df4820c552f14af3c9ca451c5ffe1f2e7bef34d598a12ebfb720
-F ext/misc/fossildelta.c 1240b2d3e52eab1d50c160c7fe1902a9bd210e052dc209200a750bbf885402d5
-F ext/misc/fuzzer.c eae560134f66333e9e1ca4c8ffea75df42056e2ce8456734565dbe1c2a92bf3d
+F ext/misc/explain.c 606100185fb90d6a1eade1ed0414d53503c86820d8956a06e3b0a56291894f2b
+F ext/misc/fileio.c d88e60f63557d76d4e38acffda5556b2ab42e98f5d830897f22aba65930d975c
+F ext/misc/fossildelta.c 8c026e086e406e2b69947f1856fa3b848fff5379962276430d10085b8756b05a
+F ext/misc/fuzzer.c 8b28acf1a7e95d50e332bdd47e792ff27054ad99d3f9bc2e91273814d4b31a5a
 F ext/misc/ieee754.c 62a90978204d2c956d5036eb89e548e736ca5fac0e965912867ddd7bb833256d
-F ext/misc/memstat.c 3017a0832c645c0f8c773435620d663855f04690172316bd127270d1a7523d4d
+F ext/misc/memstat.c 5b284b78be431c1f5fa154b18eade2407e42c65ed32ec9e9fbf195d114778d7d
 F ext/misc/memtrace.c 7c0d115d2ef716ad0ba632c91e05bd119cb16c1aedf3bec9f06196ead2d5537b
 F ext/misc/memvfs.c 7dffa8cc89c7f2d73da4bd4ccea1bcbd2bd283e3bb4cea398df7c372a197291b
-F ext/misc/mmapwarm.c 347caa99915fb254e8949ec131667b7fae99e2a9ce91bd468efb6dc372d9b7a9
+F ext/misc/mmapwarm.c a81af4aaec00f24f308e2f4c19bf1d88f3ac3ce848c36daa7a4cd38145c4080d
 F ext/misc/nextchar.c 7877914c2a80c2f181dd04c3dbef550dfb54c93495dc03da2403b5dd58f34edd
 F ext/misc/noop.c 81efe4cad9ec740e64388b14281cb983e6e2c223fed43eb77ab3e34946e0c1ab
 F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d09ea61f
 F ext/misc/pcachetrace.c f4227ce03fb16aa8d6f321b72dd051097419d7a028a9853af048bee7645cb405
 F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691
-F ext/misc/prefixes.c 0f4f8cff5aebc00a7e3ac4021fd59cfe1a8e17c800ceaf592859ecb9cbc38196
-F ext/misc/qpvtab.c 09738419e25f603a35c0ac8bd0a04daab794f48d08a9bc07a6085b9057b99009
+F ext/misc/prefixes.c 82645f79229877afab08c8b08ca1e7fa31921280906b90a61c294e4f540cd2a6
+F ext/misc/qpvtab.c fc189e127f68f791af90a487f4460ec91539a716daf45a0c357e963fd47cc06c
 F ext/misc/randomjson.c 7dd13664155319d47b9facc0d8dbf45e13062966a47168e54e3f26d48240d7ea
 F ext/misc/regexp.c 4bdd0045912f81c84908bd535ec5ad3b1c8540b4287c70ab84070963624047db
 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c
 F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c
 F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946
-F ext/misc/series.c dde5ba69cb9053ff32b5afd64e8d202472325bc052301e31e4d9c0d87e4fff50
+F ext/misc/series.c 80692f675de989629ee20796f75010ce87ca826cb383131040e84f7e1bea5d7a
 F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d
 F ext/misc/shathree.c 543af7ce71d391cd3a9ab6924a6a1124efc63211fd0f2e240dc4b56077ba88ac
 F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
-F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f
+F ext/misc/spellfix.c c0aa7b80d6df45f7da59d912b38752bcac1af53a5766966160e6c5cdd397dbea
 F ext/misc/sqlar.c 53e7d48f68d699a24f1a92e68e71eca8b3a9ff991fe9588c2a05bde103c6e7b7
-F ext/misc/stmt.c bc30d60d55e70d0133f10ac6103fe9336543f673740b73946f98758a2bb16dd7
-F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4
+F ext/misc/stmt.c b090086cd6bd6281c21271d38d576eeffe662f0e6b67536352ce32bbaa438321
+F ext/misc/templatevtab.c 10f15b165b95423ddef593bc5dcb915ec4eb5e0f1066d585e5435a368b8bc22b
 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b
 F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b
-F ext/misc/unionvtab.c 36237f0607ca954ac13a4a0e2d2ac40c33bc6e032a5f55f431713061ef1625f9
+F ext/misc/unionvtab.c 716d385256d5fb4beea31b0efede640807e423e85c9784d21d22f0cce010a785
 F ext/misc/urifuncs.c f71360d14fa9e7626b563f1f781c6148109462741c5235ac63ae0f8917b9c751
 F ext/misc/uuid.c 5bb2264c1b64d163efa46509544fd7500cb8769cb7c16dd52052da8d961505cf
 F ext/misc/vfslog.c 3932ab932eeb2601dbc4447cb14d445aaa9fbe43b863ef5f014401c3420afd20
-F ext/misc/vfsstat.c 474d08efc697b8eba300082cb1eb74a5f0f3df31ed257db1cb07e72ab0e53dfb
-F ext/misc/vtablog.c 5538acd0c8ddaae372331bee11608d76973436b77d6a91e8635cfc9432fba5ae
+F ext/misc/vfsstat.c a85df08654743922a19410d7b1e3111de41bb7cd07d20dd16eda4e2b808d269d
+F ext/misc/vtablog.c f2c9d41afe00b51b2c8307b79f508eb0c2dcd57bae074807944e73376fcf15b7
 F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd
-F ext/misc/wholenumber.c a838d1bea913c514ff316c69695efbb49ea3b8cb37d22afc57f73b6b010b4546
-F ext/misc/zipfile.c b1f36004c19fb5f949fb166fc4ab88e96a86f66629e9ddb4736a45b63fc3d553
+F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f65b4fc0668
+F ext/misc/zipfile.c 64cb3d98b6316586e6056d182051aa9d28fdedfbf4b908e6b7a7d70209b1db11
 F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64
 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
@@ -424,7 +453,7 @@ F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69
 F ext/rbu/sqlite3rbu.c d4ddf8f0e93772556e452a6c2814063cf47efb760a0834391a9d0cd9859fa4b9
 F ext/rbu/sqlite3rbu.h 9d923eb135c5d04aa6afd7c39ca47b0d1d0707c100e02f19fdde6a494e414304
 F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055
-F ext/recover/dbdata.c 81661e3a98cabb70be8f2760a67a8d6d5bf7aaa7a4055a53ff915ac884221a64
+F ext/recover/dbdata.c fc7147a68422cbbbaa481ee92ae1752cc25f5a24302bece1c70dcb76345bd736
 F ext/recover/recover1.test c484d01502239f11b61f23c1cee9f5dd19fa17617f8974e42e74d64639c524cf
 F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a
 F ext/recover/recoverbuild.test c74170e0f7b02456af41838afeb5353fdb985a48cc2331d661bbabbca7c6b8e3
@@ -443,7 +472,7 @@ F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0
 F ext/recover/test_recover.c 1a34e2d04533d919a30ae4d5caeb1643f6684e9ccd7597ca27721d8af81f4ade
 F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
 F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996
-F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890
+F ext/repair/checkindex.c af5c66463f51462d8a6f796b2c44ef8cfa1116bbdc35a15da07c67a705388bfd
 F ext/repair/sqlite3_checker.c.in 445118c5f7fea958b36fba1b2c464283e60ed4842039ddee3265f1698115ebf7
 F ext/repair/sqlite3_checker.tcl a9a2caa9660567257c177a91124d8c0dccdfa341e25c51e6da7f1fd9e601eafa
 F ext/repair/test/README.md 34b2f542cf5be7bffe479242b33ee3492cea30711e447cc4a1a86cb5915f419e
@@ -451,19 +480,19 @@ F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc782
 F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c335096108c12c01bddbadcec
 F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c
 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
-F ext/rtree/geopoly.c 971e0b5bd9adaf0811feb8c0842a310811159da10319eb0e74fdb42bf26b99ca
-F ext/rtree/rtree.c 6954f4a3ca51c2e3db35c52e0513f3520999eb7a967f3d53b71db7ebddd8b3a5
+F ext/rtree/geopoly.c 0dd4775e896cee6067979d67aff7c998e75c2c9d9cd8d62a1a790c09cde7adca
+F ext/rtree/rtree.c 2e1452a9338fe4db057fa677277bed86b65c667ed48b9b59144adae99f85a7cb
 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412
-F ext/rtree/rtree1.test 877d40b8b61b1f88cec9d4dc0ff8334f5b05299fac12a35141532e2881860e9d
+F ext/rtree/rtree1.test 2b5b8c719c6a4abe377f57766f428a49af36a93061cb146cccfdc3b30000c0a4
 F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d
 F ext/rtree/rtree3.test 272594f88c344e973864008bbe4c71fd3a41a264c097d568593ee7886d83d409
 F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b
 F ext/rtree/rtree5.test 49c9041d713d54560b315c2c7ef7207ee287eba1b20f8266968a06f2e55d3142
 F ext/rtree/rtree6.test 2f5ffc69670395c1a84fad7924e2d49e82a25460c5293fb1e54e1aa906f04945
 F ext/rtree/rtree7.test c8fb2e555b128dd0f0bdb520c61380014f497f8a23c40f2e820acc9f9e4fdce5
-F ext/rtree/rtree8.test 2d99006a1386663978c9e1df167554671e4f711c419175b39f332719deb1ce0e
+F ext/rtree/rtree8.test 4da84c7f328bbdca15052fa13da6e8b8d426433347bf75fc85574c2f5a411a02
 F ext/rtree/rtree9.test fd3c9384ef8aabbc127b3878764070398f136eebc551cd20484b570f2cc1956a
-F ext/rtree/rtreeA.test a7fd235d8194115fa2e14d300337931eb2e960fe8a46cdfb66add2206412ea41
+F ext/rtree/rtreeA.test 14e67fccc5b41efbad7ea99d21d11aaa66d2067da7d5b296ee86e4de64391d82
 F ext/rtree/rtreeB.test 4cec297f8e5c588654bbf3c6ed0903f10612be8a2878055dd25faf8c71758bc9
 F ext/rtree/rtreeC.test 2978b194d09b13e106bdb0e1c5b408b9d42eb338c1082bf43c87ef43bd626147
 F ext/rtree/rtreeD.test fe46aa7f012e137bd58294409b16c0d43976c3bb92c8f710481e577c4a1100dc
@@ -473,14 +502,14 @@ F ext/rtree/rtreeG.test 1b9ca6e3effb48f4161edaa463ddeaa8fca4b2526d084f9cbf5dbe4e
 F ext/rtree/rtreeH.test 0885151ee8429242625600ae47142cca935332c70a06737f35af53a7bd7aaf90
 F ext/rtree/rtreeI.test 608e77f7fde9be5a12eae316baef640fffaafcfa90a3d67443e78123e19c4ca4
 F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195
-F ext/rtree/rtree_util.tcl db734b4c5e75fed6acc56d9701f2235345acfdec750b5fc7b587936f5f6bceed
-F ext/rtree/rtreecheck.test 4e859a9cd49d2353ff10c122f72183ec37b400e35d2b0349b2e9696649b6a00e
+F ext/rtree/rtree_util.tcl 202ca70df1f0645ef9d5a2170e62d378a28098d9407f0569e85c9c1cf1bd020a
+F ext/rtree/rtreecheck.test 934546ad9b563e090ee0c5cbdc69ad014189ad76e5df7320526797a9a345661f
 F ext/rtree/rtreecirc.test aec664eb21ae943aeb344191407afff5d392d3ae9d12b9a112ced0d9c5de298e
 F ext/rtree/rtreeconnect.test 225ad3fcb483d36cbee423a25052a6bbae762c9576ae9268332360c68c170d3d
-F ext/rtree/rtreedoc.test 27a5703cb1200f6f69051de68da546cef3dfdcf59be73afadfc50b9f9c9960d9
+F ext/rtree/rtreedoc.test d633982d61542f3bc0a0a2df0382a02cc699ac56cbda01130cde6da44a228490
 F ext/rtree/rtreedoc2.test 194ebb7d561452dcdc10bf03f44e30c082c2f0c14efeb07f5e02c7daf8284d93
 F ext/rtree/rtreedoc3.test 555a878c4d79c4e37fa439a1c3b02ee65d3ebaf75d9e8d96a9c55d66db3efbf8
-F ext/rtree/rtreefuzz001.test 0fc793f67897c250c5fde96cefee455a5e2fb92f4feeabde5b85ea02040790ee
+F ext/rtree/rtreefuzz001.test 44f680a23dbe00d1061dbde381d711119099846d166580c4381e402b9d62cb74
 F ext/rtree/sqlite3rtree.h 03c8db3261e435fbddcfc961471795cbf12b24e03001d0015b2636b0f3881373
 F ext/rtree/test_rtreedoc.c de76b3472bc74b788d079342fdede22ff598796dd3d97acffe46e09228af83a3
 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
@@ -492,7 +521,7 @@ F ext/session/changesetfuzz.c 227076ab0ae4447d742c01ee88a564da6478bbf26b65108bf8
 F ext/session/changesetfuzz1.test 2e1b90d888fbf0eea5e1bd2f1e527a48cc85f8e0ff75df1ec4e320b21f580b3a
 F ext/session/session1.test e94f764fbfb672147c0ef7026b195988133b371dc8cf9e52423eba6cad69717e
 F ext/session/session2.test ee83bb973b9ce17ccce4db931cdcdae65eb40bbb22089b2fe6aa4f6be3b9303f
-F ext/session/session3.test ce9ce3dfa489473987f899e9f6a0f2db9bde3479
+F ext/session/session3.test 2cc1629cfb880243aec1a7251145e07b78411d851b39b2aa1390704550db8e6a
 F ext/session/session4.test 6778997065b44d99c51ff9cece047ff9244a32856b328735ae27ddef68979c40
 F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169
 F ext/session/session6.test 35279f2ec45448cd2e24a61688219dc6cf7871757716063acf4a8b5455e1e926
@@ -508,13 +537,16 @@ F ext/session/sessionG.test 3efe388282d641b65485b5462e67851002cd91a282dc95b685d0
 F ext/session/sessionH.test 71bbff6b1abb2c4ac62b84dee53273c37e0b21e5fde3aed80929403e091ef859
 F ext/session/session_common.tcl e5598096425486b363718e2cda48ee85d660c96b4f8ea9d9d7a4c3ef514769da
 F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3
+F ext/session/sessionalter.test 460bdac2832a550519f6bc32e5db2c0cee94f335870aaf25a3a403a81ab20e17
 F ext/session/sessionat.test 00c8badb35e43a2f12a716d2734a44d614ff62361979b6b85419035bc04b45ee
 F ext/session/sessionbig.test 47c381e7acfabeef17d98519a3080d69151723354d220afa2053852182ca7adf
 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
 F ext/session/sessionfault.test 573bf027fb870d57bd4e7cf50822a3e4b17b2b923407438747aaa918dec57a09
 F ext/session/sessionfault2.test b0d6a7c1d7398a7e800d84657404909c7d385965ea8576dc79ed344c46fbf41c
+F ext/session/sessionfault3.test 7c7547202775de268f3fe6f074c4d0d165151829710b4e64f90d4a01645ba9e7
 F ext/session/sessioninvert.test 04075517a9497a80d39c495ba6b44f3982c7371129b89e2c52219819bc105a25
 F ext/session/sessionmem.test f2a735db84a3e9e19f571033b725b0b2daf847f3f28b1da55a0c1a4e74f1de09
+F ext/session/sessionnoact.test 506526a5fe29421ecc50d371774ef1bb04cbd9d906a8a468f0556cdbde184c22
 F ext/session/sessionnoop.test a9366a36a95ef85f8a3687856ebef46983df399541174cb1ede2ee53b8011bc7
 F ext/session/sessionnoop2.test de4672dce88464396ec9f30ed08c6c01643a69c53ae540fadbbf6d30642d64e8
 F ext/session/sessionrebase.test 702378bdcb5062f1106e74457beca8797d09c113a81768734a58b197b5b334e2
@@ -522,16 +554,21 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a
 F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795
 F ext/session/sessionstat1.test b039e38e2ba83767b464baf39b297cc0b1cc6f3292255cb467ea7e12d0d0280c
 F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc
-F ext/session/sqlite3session.c 1971b61ca45babf0d9e4bb669a65b0903135e9828af2fcd4f0c8f1b7acf36b6f
-F ext/session/sqlite3session.h 653e9d49c4edae231df8a4c8d69c2145195aedb32462d4b44229dbee7d2680fb
-F ext/session/test_session.c 5285482f83cd92b4c1fe12fcf88210566a18312f4f2aa110f6399dae46aeccbb
+F ext/session/sqlite3session.c 7b0f9bf6479aebd49e5f34cdaeb1479cde843cf5189bca6f32db4f36c130b414
+F ext/session/sqlite3session.h 4cf19a51975746d7cff2fdd74db8b769c570958e1c3639ac150d824ac1553b3e
+F ext/session/test_session.c 7b94ad945cd4afe6c73ee935aeb3d44b4446186e1729362af616c7695a5283d9
 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.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
-F ext/wasm/GNUmakefile d02f3c8798b754f68b1f6b422ccff894a10bf352fc9c4eb8945baeace1acac28
+F ext/wasm/GNUmakefile 0e362f3fc04eab6628cbe4f1e35f4ab4a200881f6b5f753b27fb45eabeddd9d2
 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576
 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193
+F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff
+F ext/wasm/SQLTester/SQLTester.mjs ec2f6ba63a0f2f0562941a0fb8e46b7dc55589711513f1952349785966edfe50
+F ext/wasm/SQLTester/SQLTester.run.mjs c72b7fe2072d05992f7a3d8c6a1d34e95712513ceabe40849784e24e41c84638
+F ext/wasm/SQLTester/index.html 3f8a016df0776be76605abf20e815ecaafbe055abac0e1fe5ea080e7846b760d
+F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536
 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab
 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b
 F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
@@ -542,18 +579,18 @@ F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08
 F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62
 F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219
 F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e
-F ext/wasm/api/sqlite3-api-glue.js b65e546568f1dfb35205b9792feb5146a6323d71b55cda58e2ed30def6dd52f3
+F ext/wasm/api/sqlite3-api-glue.js 26aedfb27915f4f316f6eac84078443e4f0d2dfe5f012310014923ed4b77b2b6
 F ext/wasm/api/sqlite3-api-oo1.js 9678dc4d9a5d39632b6ffe6ea94a023119260815bf32f265bf5f6c36c9516db8
-F ext/wasm/api/sqlite3-api-prologue.js 5f283b096b98bfb1ee2f2201e7ff0489dff00e29e1030c30953bdb4f5b87f4bd
-F ext/wasm/api/sqlite3-api-worker1.js 9f32af64df1a031071912eea7a201557fe39b1738645c0134562bb84e88e2fec
+F ext/wasm/api/sqlite3-api-prologue.js 9aeba7b45cf41b3a26d34d7fb2525633cd1adfc544888c1ea8dbb077496f4ce9
+F ext/wasm/api/sqlite3-api-worker1.js f941382f21006b4a817754184e2661b0a63ce650201f3419cd60f4758b6fd60e
 F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
 F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379
 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25
-F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js abb69b5e008961026bf5ff433d7116cb046359af92a5daf73208af2e7ac80ae7
-F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js e04fc2fda6a0200ef80efdbb4ddfa0254453558adb17ec3a230f93d2bf1d711c
-F ext/wasm/api/sqlite3-wasm.c d4d4c2b349b43b7b861e6d2994299630fb79e07573ea6b61e28e8071b7d16b61
+F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 595953994aa3ae2287c889c4da39ab3d6f17b6461ecf4bec334b7a3faafddb02
+F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 46c4afa6c50d7369252c104f274ad977a97e91ccfafc38b400fe36e90bdda88e
+F ext/wasm/api/sqlite3-wasm.c 038de1b6d40b2cc0f41a143a0451db60b2a6f1b5bc06de67da255c54ea1661b7
 F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f
-F ext/wasm/api/sqlite3-worker1.c-pp.js da509469755035e919c015deea41b4514b5e84c12a1332e6cc8d42cb2cc1fb75
+F ext/wasm/api/sqlite3-worker1.c-pp.js a541112aa51e16705f13a99bb943c64efe178aa28c86704a955f8fd9afe4ba37
 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
 F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e
 F ext/wasm/c-pp.c 6d80d8569d85713effe8b0818a3cf51dc779e3f0bf8dc88771b8998552ee25b4
@@ -596,7 +633,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
 F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b
 F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c
 F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2
-F ext/wasm/tester1.c-pp.js 64eb0ee6e695d5638d0f758f31a0ca2231e627ca5d768de3d8b44f9f494de8d4
+F ext/wasm/tester1.c-pp.js fb20d9e1c308ea34a29d8afdda1a6c5edc406241b5560fa23c19c14747ee41bc
 F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1
 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@@ -604,7 +641,7 @@ F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
-F main.mk e18c03071dbb9da424212ba2a1ea331ae0e8f6d7d51283b7ad610c52ea11229c
+F main.mk 51fd5fc8565007266e6bfd9e299199c752b171707a667b8914a204b38258e1b1
 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421
@@ -616,39 +653,39 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
 F sqlite3.1 acdff36db796e2d00225b911d3047d580cd136547298435426ce9d40347973cc
 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
 F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2
-F src/alter.c 3ff8c2fca0c0636d43459154bb40d79c882df1b34df77f89c4ec47ab2e2389f5
+F src/alter.c 30c2333b8bb3af71e4eb9adeadee8aa20edb15917ed44b8422e5cd15f3dfcddc
 F src/analyze.c d4cc28738c29e009640ec20ebb6936ba6fcefff0d11aa93398d9bb9a5ead6c1f
 F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39
 F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4
 F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523
 F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645
 F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522
-F src/btree.c 7a37bdf09f338561880860681cb03499a60c3bb0869e539c58bc1d2cdd705ff2
+F src/btree.c f3b09c5414de3a11db73e11e1d66f4c5e53c9e89876ff3b531a887ab656ca303 x
 F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240
-F src/btreeInt.h 91a9e0c41a0e71fa91a742ec285c63dd8dcb38b73d14fae0ed7209174ff0fdc1
-F src/build.c a8ae3b32d9aa9bbd2c0e97d7c0dd80def9fbca408425de1608f57ee6f47f45f4
+F src/btreeInt.h ef12a72b708677e48d6bc8dcd66fed25434740568b89e2cfa368093cfc5b9d15
+F src/build.c 189e4517d67f09f0a3e0d8e1faa6e2ef0c2e95f6ac82e33c912cb7efa2a359cc
 F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490
 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
-F src/ctime.c cff2b493de894383832347c3a3f936a05f4e089a92b42366838da1735f3848b5
-F src/date.c f73f203b3877cef866c60ab402aec2bf89597219b60635cf50cbe3c5e4533e94
-F src/dbpage.c f3eea5f7ec47e09ee7da40f42b25092ecbe961fc59566b8e5f705f34335b2387
-F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef
+F src/ctime.c 23331529e654be40ca97d171cbbffe9b3d4c71cc53b78fe5501230675952da8b
+F src/date.c eebc54a00e888d3c56147779e9f361b77d62fd69ff2008c5373946aa1ba1d574
+F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782
+F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43
 F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500
-F src/expr.c 1affe0cc049683ef0ef3545d9b6901508556b0ef7e2892a344c3df6d7288d79d
+F src/expr.c 433f12e1237524482b0b2681c07da3cd54ddada2a625237cecde419f3e3a2553
 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
-F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36
-F src/func.c dd1ecd1be6aaa67c9fa723f841e05e1536314c6aaa0509e25289b310f64dbb9c
+F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00
+F src/func.c 472f6dcfa39cf54f89a6aec76c79c225fb880a6c14469c15d361331662b9bf43
 F src/global.c 29f56a330ed9d1b5cd9b79ac0ca36f97ac3afc730ff8bfa987b0db9e559d684d
 F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220
 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6
 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276
-F src/json.c ae840f87b418f039f5d336b488933d09396bd31e6b31e855b93055ccaee4e255
+F src/json.c d69c6e28ff7b602877bda68cd20583b8487c059759aa4d154dd21b3fd99c6238
 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
-F src/loadext.c 98cfba10989b3da6f1807ad42444017742db7f100a54f1032af7a8b1295912c0
-F src/main.c fde8f13c876a658b4e8b74b77d875ca887915c174ea6a2f3122d80966f93d865
-F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23
+F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36
+F src/main.c e1bc8864834697503d370d94613be945d05ca1c5ebdda43e7d5c8ee8c48d433c
+F src/malloc.c f016922435dc7d1f1f5083a03338a3e91f8c67ce2c5bdcfa4cdef62e612f5fcc
 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2
 F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75
@@ -657,66 +694,66 @@ F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff
 F src/memdb.c 559c42e61eb70cd6d4bc692b042497133c6d96c09a3d514d92f3dac72268e223
 F src/memjournal.c c283c6c95d940eb9dc70f1863eef3ee40382dbd35e5a1108026e7817c206e8a0
 F src/msvc.h 80b35f95d93bf996ccb3e498535255f2ef1118c78764719a7cd15ab4106ccac9
-F src/mutex.c 5e3409715552348732e97b9194abe92fdfcd934cfb681df4ba0ab87ac6c18d25
+F src/mutex.c 1b4c7e5e3621b510e0c18397210be27cd54c8084141144fbbafd003fde948e88
 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a
 F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4
-F src/mutex_unix.c bd52ec50e44a41fe1e3deb5a6e3fe98edb6f2059da3e46d196363d0fa3192cda
+F src/mutex_unix.c f7ee5a2061a4c11815a2bf4fc0e2bfa6fb8d9dc89390eb613ca0cec32fc9a3d1
 F src/mutex_w32.c 38b56d0bc8d54c17c20cbaaad3719b0c36b92fd07a7e34360d0c6a18d5589912
-F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6
+F src/notify.c 57c2d1a2805d6dee32acd5d250d928ab94e02d76369ae057dee7d445fd64e878
 F src/os.c 509452169d5ea739723e213b8e2481cf0e587f0e88579a912d200db5269f5f6d
 F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63
 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06
 F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a
 F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107
-F src/os_unix.c 2e8b12107f75d1bd16412f312b4c5d5103191807a37836d3b81beb26436ad81b
+F src/os_unix.c cb116fde9e3ca3c1bbfdf89d6928f776a2a34da168e2667426523a4db353b271
 F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8
 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
-F src/pager.c 993445a19b611d473ca007542ab3149840661a4c7e9f2d9e1ec008b7cc2abe78
+F src/pager.c 699aab8dfc88056d796b03b40c0ab979040d58dfc3ae9db207f1be91e4880bbf
 F src/pager.h f4d33fec8052603758792045493423b8871a996da2d0973927b7d36cd6070473
-F src/parse.y aeb7760d41cfa86465e3adba506500c021597049fd55f82a30e5b7045862c28c
+F src/parse.y 020d80386eb216ec9520549106353c517d2bbc89be28752ffdca649a9eaf56ec
 F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75
 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5
 F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00
-F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a
+F src/pragma.c b3b4ad9c0298d63098a067acca613c21a5f56b4d176d5842922bcd0b07b7164e
 F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7
-F src/prepare.c 80548297dc0e1fb3139cdebffb5a1bcac3dfac66d791012dd74838e70445072d
-F src/printf.c e3ba080e2f409f9bfcc8d34724e6fc160e9c718dc92d0548f6b71b8b6f860ce2
+F src/prepare.c bde74add20fc0e8ce0c4e937a1f70a36d17413afe4f71d3e103f5cb74b17c8d9
+F src/printf.c 9da63b9ae1c14789bcae12840f5d800fd9302500cd2d62733fac77f0041b4750
 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
-F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9
+F src/resolve.c 31229276a8eb5b5de1428cd2d80f6f1cf8ffc5248be25e47cf575df12f1b8f23
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
-F src/select.c 5f545a2c8702d4d3430bbb188cfec47d6c122d899061ef00cbe56af14591c574
-F src/shell.c.in 2f9be25294b68b07e7e81f0adcec4475aba6011b64f160e414efe226910c4d7b
-F src/sqlite.h.in 73a366c1c45d5ac9888cfe81c458826a44498531d106cfb4f328193ab5f6f17d
+F src/select.c a19daa26e95f7245106a31f288b2f50c72d1f2cc156703f04c8c91450e111515
+F src/shell.c.in 3e9371ca6a93294931a8ed8b098bc3cb15d57567aa9a1f2ade72db7f5c572795
+F src/sqlite.h.in ef0e41e83ad1ac0dcc9ec9939bf541a44b1c5de821bee2d6c61754c3252f3276
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
-F src/sqlite3ext.h 2f30b2671f4c03cd27a43f039e11251391066c97d11385f5f963bb40b03038ac
-F src/sqliteInt.h 025ed58a41968ef80d64cdc194caa8dd207b0256b147253d762fdac7a62408f9
+F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54
+F src/sqliteInt.h 567e317f8631883897b7d3da43fce778b7c30dd0dd7f714558c9725fc1c1196c
 F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6
 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
 F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd
-F src/test1.c ebba2473874a23add4a10881b90bd445cfa7f59f90749434938aec14239c6486
+F src/test1.c f9620e8f0d0fa4edb239201a732c4dd1562f0cdd9741955c89332d49e14a5edd
 F src/test2.c 54520d0565ef2b9bf0f8f1dcac43dc4d06baf4ffe13d10905f8d8c3ad3e4b9ab
 F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e
 F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664
 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d
 F src/test6.c e53bc69dc3cb3815fb74df74f38159ec05ba6dd5273216062e26bc797f925530
-F src/test8.c ccc5d3e2a2bf7248f7da185e2afc4c08b4c6840447f5eb4dd106db165fddbdbc
+F src/test8.c 303c2e3bcf7795e888810a7ef03809602b851f0ebec8d6e06a429ed85cafd9a2
 F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5
 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a
 F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871
 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0
-F src/test_bestindex.c 68c62586d2ae9f032903fe53be743657d0c2aac0a850b880938b668e1161d516
+F src/test_bestindex.c f6af1e41cb7901edafb065a8198e4a0192dd42432b642d038965be5e628dec12
 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce
 F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274
 F src/test_config.c f0cc1f517deaa96dd384822ae2bb91534fa56aa458528b439830d709941d3932
 F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f
 F src/test_demovfs.c 38a459d1c78fd9afa770445b224c485e079018d6ac07332ff9bd07b54d2b8ce9
 F src/test_devsym.c 649434ed34d0b03fbd5a6b42df80f0f9a7e53f94dd1710aad5dd8831e91c4e86
-F src/test_fs.c ba1e1dc18fd3159fdba0b9c4256f14032159785320dfbd6776eb9973cb75d480
+F src/test_fs.c 56cc17e4fdc57efa61695026e2ba96e910b17060d7ee01d775ec048791522e2f
 F src/test_func.c 24df3a346c012b1fc9e1001d346db6054deb426db0a7437e92490630e71c9b0a
 F src/test_hexio.c 9478e56a0f08e07841a014a93b20e4ba2709ab56d039d1ca8020e26846aa19bd
 F src/test_init.c f2cc4774b7c9140f76e45ecbb2ae219f68e3acbbe248c0179db666a70eae9f08
-F src/test_intarray.c 39b4181662a0f33a427748d87218e7578d913e683dc27eab7098bb41617cac71
+F src/test_intarray.c 26ffba666beb658d73cd925d9b4fb56913a3ca9aaeac122b3691436abb192b92
 F src/test_intarray.h 6c3534641108cd1bea517a8e117dcba237081310a29a4c35bd2190caa8972293
 F src/test_journal.c a0b9709b2f12b1ec819eea8a1176f283bca6d688a6d4a502bd6fd79786f4e287
 F src/test_loadext.c 337056bae59f80b9eb00ba82088b39d0f4fe6dfd
@@ -726,17 +763,17 @@ F src/test_multiplex.c 70479161239d65af2a231550b270e9d11ece717ad7bf0e13ef4220658
 F src/test_multiplex.h f0ff5b6f4462bfd46dac165d6375b9530d08089b7bcbe75e88e0926110db5363
 F src/test_mutex.c cd5bac43f2fd168f43c4326b1febe0966439217fac52afb270a6b8215f94cb40
 F src/test_onefile.c f31e52e891c5fef6709b9fcef54ce660648a34172423a9cbdf4cbce3ba0049f4
-F src/test_osinst.c d341f9d7613e007c8c3f7eba6cd307230047506aa8f97858c1fd21f5069616bd
+F src/test_osinst.c 8e11faf10f5d4df10d3450ecee0b8f4cfa2b62e0f341fafbeb480a08cefeaec4
 F src/test_pcache.c 3960cd2c1350adc992c4bf7adcfb0d1ac0574733012bd1a5f94e195928577599
 F src/test_quota.c ea44c05f29b995bdb71c55eb0c602604884e55681d59b7736e604bbcc68b0464
 F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d
 F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b
-F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b
+F src/test_schema.c cbfd7a9a9b6b40d4377d0c76a6c5b2a58387385977f26edab4e77eb5f90a14ce
 F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
 F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
 F src/test_syscall.c 9fdb13b1df05e639808d44fcb8f6064aaded32b6565c00b215cfd05a060d1aca
 F src/test_tclsh.c 3ff5d188a72f00807425954ea3b493dfd3a4b890ecc6700ea83bad2fd1332ecf
-F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb268dfc
+F src/test_tclvar.c 3273f9d59395b336e381b53cfc68ec6ebdaada4e93106a2e976ffb0550504e1c
 F src/test_thread.c 7ddcf0c8b79fa3c1d172f82f322302c963d923cdb503c6171f3c8081586d0b01
 F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43
 F src/test_vfs.c 193c18da3dbf62a0e33ae7a240bbef938a50846672ee947664512b77d853fe81
@@ -747,48 +784,49 @@ F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394
 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
 F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5
-F src/treeview.c 1d52fbc4e97161e65858d36e3424ea6e3fc045dd8a679c82b4b9593dc30de3bd
-F src/trigger.c ad6ab9452715fa9a8075442e15196022275b414b9141b566af8cdb7a1605f2b0
+F src/treeview.c 62fafcd31eea60b718f8daf448116b7b19f90134ebc6c20777ddbb07f56a3d28
+F src/trigger.c 0905b96b04bb6658509f711a8207287f1315cdbc3df1a1b13ba6483c8e341c81
 F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92
 F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242
 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
-F src/util.c 278b81c3b33db1b5a5f3859adf8905c165b910080043061d44d3c5a25b4b406d
+F src/util.c b22cc9f203a8c0b9ee5338a67f8860347d14845864c10248bebe84518a781677
 F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104
-F src/vdbe.c 346d848a0bf8128e3e3722c5406f4bde6c32d7093b93402c6f8e0718d19305c3
+F src/vdbe.c 14479441337135eed8e290fb1d4abb7db657d93217a3b1ea8a2f031d3895536a
 F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0
 F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c
-F src/vdbeapi.c 37341acd781fda162e8cf4d9fc2eaea2febad3b365877a9d7233b8c6d0960d85
-F src/vdbeaux.c e3aa5c46827cd95e0fc4d0f302fa3e901ab5f07258fdbb42709eeef40f63018d
-F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce
-F src/vdbemem.c 317b9f48708139db6239ade40c7980b4bc8233168383690d588dad6d8437f722
-F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015
+F src/vdbeapi.c fe654b1f54e1feebcaed6c2ae3ed035cc65bfeb9a1169bed866abc42bfc63ff6
+F src/vdbeaux.c dffcf79e7e415fcd6e4c8ac1ec7124cae5257018443adf09551c807655b04993
+F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5
+F src/vdbemem.c c936e9002af4993b84c4eb7133d6b1190efe46d391cc86117ecd67ba17b1a04b
+F src/vdbesort.c 237840ca1947511fa59bd4e18b9eeae93f2af2468c34d2427b059f896230a547
 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823
-F src/vdbevtab.c 57fa8f56478e5b5cb558cb425e7878515e0a105c54f96f1d1bbf4b9433529254
-F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64
+F src/vdbevtab.c 2143db7db0ceed69b21422581f434baffc507a08d831565193a7a02882a1b6a7
+F src/vtab.c 154725ebecd3bc02f7fbd7ad3974334f73fff76e02a964e828e48a7c5fb7efff
 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
 F src/wal.c 01e051a1e713d9eabdb25df38602837cec8f4c2cae448ce2cf6accc87af903e9
 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452
 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2
-F src/where.c b8917792f1e0dbfa28fb29e6cd3d560060d69667be0ba4c491cbc772363264f5
-F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a
+F src/where.c 313ce81270d2a414672370e1ee74e65949ad620519193d4cac2986d073cbc8a0
+F src/whereInt.h 4b38c5889514e3aead3f27d0ee9a26e47c3f150efc59e2a8b4e3bc8835e4d7a1
 F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1
 F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00
-F src/window.c b7ad9cff3ce8ae6f8cc25e18e1a258426cb6bd2999aace6f5248d781b2a74098
+F src/window.c ad21e2b73ec75acc79dde2576c573f54a338b0c49e9de847ce984f9b9595b5e2
 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627
 F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9
 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
 F test/aggfault.test 777f269d0da5b0c2524c7ff6d99ae9a93db4f1b1839a914dd2a12e3035c29829
-F test/aggnested.test 7269d07ac879fce161cb26c8fabe65cba5715742fac8a1fccac570dcdaf28f00
+F test/aggnested.test 2e738bfe2980df301a782f6e7bbf9459266f64f7e72f58f3b5c843bf897c568c
+F test/aggorderby.test e6b98dbbf3ababa96892435d387de2dcf602ef02c2b848d2d817473066f154ba
 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87
 F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13
-F test/alter.test 313073774ab5c3f2ef1d3f0d03757c9d3a81284ae7e1b4a6ca34db088f886896
+F test/alter.test 403a7f8842457044a994d0ffb42963d6e84fcfbf5e8f54556063b25d966cd454
 F test/alter2.test a966ccfcddf9ce0a4e0e6ff1aca9e6e7948e0e242cd7e43fc091948521807687
 F test/alter3.test ffc4ab29ce78a3517a66afd69b2730667e3471622509c283b2bd4c46f680fba3
 F test/alter4.test 716caa071dd8a3c6d57225778d15d3c3cbf5e34b2e84ae44199aeb2bbf50a707
 F test/alterauth.test 63442ba61ceb0c1eeb63aac1f4f5cebfa509d352276059d27106ae256bafc959
 F test/alterauth2.test 48967abae0494d9a300d1c92473d99fcb66edfcc23579c89322f033f49410adc
-F test/altercol.test 8465ca659c2c55a359cf16cc261df4fcb5c45a5f104a50827c337ae66c09dc15
+F test/altercol.test 29fed774747777fbbaacdd865b4413ed2d0844a4c824f8af531b5c7d4a832087
 F test/altercorrupt.test 2e1d705342cf9d7de884518ddbb053fd52d7e60d2b8869b7b63b2fda68435c12
 F test/alterdropcol.test a653a3945f964d26845ec0cd0a8e74189f46de3119a984c5bc45457da392612e
 F test/alterdropcol2.test 527fce683b200d620f560f666c44ae33e22728e990a10a48a543280dfd4b4d41
@@ -796,12 +834,12 @@ F test/alterfault.test 289067108947bedca27534edd4ff251bcd298cf84402d7b24eaa37493
 F test/alterlegacy.test f38c6d06cda39e1f7b955bbce57f2e3ef5b7cb566d3d1234502093e228c15811
 F test/altermalloc.test 167a47de41b5c638f5f5c6efb59784002b196fff70f98d9b4ed3cd74a3fb80c9
 F test/altermalloc2.test 17fb3724c4b004c469c27dc4ef181608aa644555fbd3f3236767584f73747c81
-F test/altermalloc3.test 8531b3086f0a7889f43971a579a8c81832d5123f4703d8c86b89ba1136c63b9a
-F test/alterqf.test ff6c6f881485c29ed699b8ef4774864ca1b0c01a6c08f5cdd624a008e4b40fca
+F test/altermalloc3.test 8040e486368403f2fdd6fc3998258b499bd4cc2f3ddbb5f8f874cd436f076e81
+F test/alterqf.test 8ec03d776de9c391daa0078ea8f838903bdcfb11dfae4ba3576b48436834ccba
 F test/altertab.test 8a2712f9076da5012a002d0b5cc0a421398a5bf61c25bab41b77c427586a7a27
 F test/altertab2.test 62597b6fd08feaba1b6bfe7d31dac6117c67e06dc9ce9c478a3abe75b5926de0
 F test/altertab3.test 6c432fbb9963e0bd6549bf1422f6861d744ee5a80cb3298564e81e556481df16
-F test/altertrig.test fb5951d21a2c954be3b8a8cf8e10b5c0fa20687c53fd67d63cea88d08dd058d5
+F test/altertrig.test aacc980b657354fe2d3d4d3a004f07d04ccc1a93e5ef82d68a79088c274ddc6b
 F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f
 F test/analyze.test 2fb21d7d64748636384e6cb8998dbf83968caf644c07fcb4f76c18f2e7ede94b
 F test/analyze3.test 03f4b3d794760cf15da2d85a52df9bae300e51c8fefe9c36cfae1f86dc10d23f
@@ -865,8 +903,9 @@ F test/bestindex5.test a0c90b2dad7836e80a01379e200e5f8ec9476d49b349af02c0dbff2fb
 F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a6f99dd4
 F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e
 F test/bestindex8.test 333ad8c6a554b885a49b68c019166eda92b05f493a92b36b0acdf7f766d04dad
-F test/bestindex9.test bf2eb8556e8d5c00ef3ee18c521751cd03c1b55454b6e7683b4c6742e3131b23
-F test/bestindexA.test dd7b7439a46169b45d0305c4cbbb14fc20c7044acc2055c767d2f838b3479c3f
+F test/bestindex9.test 1a4b93db117fd8abe74ae9be982f86aa72f01e60cd4ac541e6ede39673a451a0
+F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572eef44c7f
+F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce
 F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263
 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59
 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc
@@ -904,8 +943,8 @@ F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe
 F test/carray01.test 23ed7074307c4a829ba5ff2970993a9d87db7c5cdbbe1a2cbef672d0df6d6e31
 F test/cast.test af2286fdd28f3470b7dcad23977282b8cc117747ad55acff74a770dad3b19398
 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef
-F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b
-F test/changes2.test d222c0cbf5ab0ac4d7c180594e486c1bf20b2098d33e56ce33b8e12eba6823b9
+F test/changes.test 4377d202a487f66fc2822c1bf57c46798c8b2caf7446f4f701723b1dbb6b86f6
+F test/changes2.test 07949edcc732af28cb54276bfb7d99723bccc1e905a423648bf57ac5cb0dc792
 F test/check.test 56e4ed457e9f8683b9fc56f5b964f461f6e8a8dd5a13f3d495408215d66419ed
 F test/checkfault.test da6cb3d50247169efcb20bdf57863a3ccfa1d27d9e55cd324f0680096970f014
 F test/chunksize.test 427d87791743486cbf0c3b8c625002f3255cb3a89c6eba655a98923b1387b760
@@ -974,16 +1013,17 @@ F test/ctime.test 340f362f41f92972bbd71f44e10569a5cc694062b692231bd08aa6fe6c1c47
 F test/cursorhint.test 05cf0febe5c5f8a31f199401fd1c9322249e753950d55f26f9d5aca61408a270
 F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee9ae9c42f
 F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8
-F test/date.test 1d44557f668298b10d3335b22ab8feb133267b67ec4d85538908fe4dfebd2611
+F test/date.test c0d17cdfd89395bc78087b131e3538d96f864b5029c335318011accc7c0d0934
 F test/date2.test 7e12ec14aaf4d5e6294b4ba140445b0eca06ea50062a9c3a69c4ee13d0b6f8b1
 F test/date3.test a1b77abf05c6772fe5ca2337cac1398892f2a41e62bce7e6be0f4a08a0e64ae5
+F test/date4.test 8aeb3de5b5e9fda968baa9357e4c0fae573724b7904943410195a19e96e31b6a
 F test/dbdata.test 042f49acff3438f940eeba5868d3af080ae64ddf26ae78f80c92bec3ca7d8603
 F test/dbfuzz.c 73047c920d6210e5912c87cdffd9a1c281d4252e
-F test/dbfuzz001.test 55e1a3504f8dea84155e09912fe3b1c3ad77e0b1a938ec42ca03b8e51b321e30
+F test/dbfuzz001.test 6c9a4622029d69dc38926f115864b055cb2f39badd25ec22cbfb130c8ba8e9c3
 F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee
 F test/dbfuzz2.c 4b3c12de4d98b1b2d908ab03d217d4619e47c8b23d5e67f8a6f2b1bdee7cae23
 F test/dbpage.test fce29035c7566fd7835ec0f19422cb4b9c6944ce0e1b936ff8452443f92e887d
-F test/dbpagefault.test d9111a62f3601d3efc6841ace3940181937342d245f92a1cca6cba8206d4f58a
+F test/dbpagefault.test 35f06cfb2ef100a9b19d25754e8141b9cba9b7daabd4c60fa5af93fcce884435
 F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759
 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef
 F test/decimal.test ef731887b43ee32ef86e1c8fddb61a40789f988332c029c601dcf2c319277e9e
@@ -999,7 +1039,7 @@ F test/descidx3.test 953c831df7ea219c73826dfbf2f6ee02d95040725aa88ccb4fa43d1a199
 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e
 F test/distinct.test 691c9e850b0d0b56b66e7e235453198cb4cf0760e324b7403d3c5abbeab0a014
 F test/distinct2.test bb71cc7b5e58e895787f9910a788c254f679928d324732d063fe9bc202ecbe71
-F test/distinctagg.test 14ec5026e684eddd414c61c08692b43773e224ac92efbed6ec08c6994bc39723
+F test/distinctagg.test ad2b4cf1483cd4cf24867dfafbfa0abb61184d92085fcc9784cea0592b278d64
 F test/e_blobbytes.test 4c01dfe4f12087b92b20705a3fdfded45dc4ed16d5a211fed4e1d2786ba68a52
 F test/e_blobclose.test 692fc02a058476c2222a63d97e3f3b2b809c1842e5525ded7f854d540ac2e075
 F test/e_blobopen.test 29f6055ee453b8e679fe9570c4d3acfedbef821622c5dad16875148c5952ef50
@@ -1009,13 +1049,13 @@ F test/e_createtable.test 31b9bcb6ac8876bc7ec342d86d9c231a84c62b442093a6651dfd0f
 F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e
 F test/e_droptrigger.test 235c610f8bf8ec44513e222b9085c7e49fad65ad0c1975ac2577109dd06fd8fa
 F test/e_dropview.test 74e405df7fa0f762e0c9445b166fe03955856532e2bb234c372f7c51228d75e7
-F test/e_expr.test 27e905ed17266c745bffe65f56b809c13ae6e225e56aeda1aaec926b32439286
+F test/e_expr.test b950818a48269506d75a41c819003bd77a0893bc4a4f2fdee191bc74109c1a87
 F test/e_fkey.test feeba6238aeff9d809fb6236b351da8df4ae9bda89e088e54526b31a0cbfeec5
 F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07
 F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e
 F test/e_reindex.test 2b0e29344497d9a8a999453a003cb476b6b1d2eef2d6c120f83c2d3a429f3164
 F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8
-F test/e_select.test 89fa483f68d868f1be3d6f56180ed42d979979c266e051bf8b5e66a251e6b44a
+F test/e_select.test 327a15f14068bbd6f647cedc67210f8680fcb2f05e481a0a855fccd2abfa1292
 F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f
 F test/e_totalchanges.test c927f7499dc3aa28b9b556b7d6d115a2f0fe41f012b128d16bf1f3b30e9b41e4
 F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528
@@ -1048,7 +1088,7 @@ F test/filectrl.test 6e871c2d35dead1d9a88e176e8d2ca094fec6bb3
 F test/filefmt.test f393e80c4b8d493b7a7f8f3809a8425bbf4292af1f5140f01cb1427798a2bbd4
 F test/filter1.test 590f8ba9a0cd0823b80d89ac75c5ce72276189cef9225d2436adaf1ee87f3727
 F test/filter2.tcl 44e525497ce07382915f01bd29ffd0fa49dab3adb87253b5e5103ba8f93393e8
-F test/filter2.test 485cf95d1f6d6ceee5632201ca52a71868599836f430cdee42e5f7f14666e30a
+F test/filter2.test 3cc20eaea2ea1ab245197cc4a62468deb460b78f5aa9bd7d5d3353c2fe569bae
 F test/filterfault.test c08fb491d698e8df6c122c98f7db1c65ffcfcad2c1ab0e07fa8a5be1b34eaa8b
 F test/fkey1.test e563bcb4cb108ce3f40363cda4f84009dc89a39e2973076e5057ba99fca35378
 F test/fkey2.test 1063d65e5923c054cfb8f0555a92a3ae0fa8c067275a33ee1715bd856cdb304c
@@ -1088,11 +1128,11 @@ F test/fts3aux2.test 2459e7fa3e22734aed237d1e2ae192f5541c4d8b218956ad2d90754977b
 F test/fts3b.test c15c4a9d04e210d0be67e54ce6a87b927168fbf9c1e3faec8c1a732c366fd491
 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958
 F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c
-F test/fts3conf.test c84bbaec81281c1788aa545ac6e78a6bd6cde2bdbbce2da261690e3659f5a76b
+F test/fts3conf.test c9cd45433b6787d48a43e84949aa2eb8b3b3d242bac7276731c1476290d31f29
 F test/fts3corrupt.test 6732477c5ace050c5758a40a8b5706c8c0cccd416b9c558e0e15224805a40e57
 F test/fts3corrupt2.test e318f0676e5e78d5a4b702637e2bb25265954c08a1b1e4aaf93c7880bb0c67d0
 F test/fts3corrupt3.test 0d5b69a0998b4adf868cc301fc78f3d0707745f1d984ce044c205cdb764b491f
-F test/fts3corrupt4.test 589e043d1114ea02c83530e459ef5c2d6adce63094e72826ed3bceb02e795116
+F test/fts3corrupt4.test 48bd57baed9654e511709a02dbef2d22ee54c012ad466e8648f0f825233faa08
 F test/fts3corrupt5.test 0549f85ec4bd22e992f645f13c59b99d652f2f5e643dac75568bfd23a6db7ed5
 F test/fts3corrupt6.test f417c910254f32c0bc9ead7affa991a1d5aec35b3b32a183ffb05eea78289525
 F test/fts3cov.test 7eacdbefd756cfa4dc2241974e3db2834e9b372ca215880e00032222f32194cf
@@ -1111,8 +1151,9 @@ F test/fts3expr5.test a5b9a053becbdb8e973fbf4d6d3abaabeb42d511d1848bd57931f3e0a1
 F test/fts3f.test 8c438d5e1cab526b0021988fb1dc70cf3597b006a33ffd6c955ee89929077fe3
 F test/fts3fault.test f4e1342acfe6d216a001490e8cd52afac1f9ffe4a11bbcdcb296129a45c5df45
 F test/fts3fault2.test 7b2741e5095367238380b0fcdb837f36c24484c7a5f353659b387df63cf039ec
+F test/fts3fault3.test 4a39a1618546776255dc1de306213b600aef87eca589ca8428a70c00fd11961b
 F test/fts3first.test dbdedd20914c8d539aa3206c9b34a23775644641
-F test/fts3fuzz001.test e3c7b0ce9b04cc02281dcc96812a277f02df03cd7dc082055d87e11eb18aaf56
+F test/fts3fuzz001.test c78afcd8ad712ea0b8d2ed50851a8aab3bc9dc52c64a536291e07112f519357c
 F test/fts3join.test 1a4d786539b2b79a41c28ef2ac22cacd92a8ee830249b68a7dee4a020848e3bb
 F test/fts3malloc.test b0e4c133b8d61d4f6d112d8110f8320e9e453ef6
 F test/fts3matchinfo.test aa66cc50615578b30f6df9984819ae5b702511cf8a94251ec7c594096a703a4a
@@ -1133,15 +1174,16 @@ F test/fts3tok1.test a663f4cac22a9505400bc22aacb818d7055240409c28729669ea7d4cc21
 F test/fts3tok_err.test 52273cd193b9036282f7bacb43da78c6be87418d
 F test/fts3varint.test 0b84a3fd4eba8a39f3687523804d18f3b322e6d4539a55bf342079c3614f2ada
 F test/fts4aa.test 0e6bfd6a81695a39b23e448dda25d864e63dda75bde6949c45ddc95426c6c3f5
-F test/fts4check.test 6259f856604445d7b684c9b306b2efb6346834c3f50e8fc4a59a2ca6d5319ad0
+F test/fts4check.test f0ea5e5581951d8ef7a341eea14486daf6c5f516a2f3273b0d5e8cb8a6cd3bd2
 F test/fts4content.test 73bbb123420d2c46ef2fb3b24761e9acdb78b0877179d3a5d7d57aada08066f6
 F test/fts4docid.test e33c383cfbdff0284685604d256f347a18fdbf01
 F test/fts4growth.test 289833c34ad45a5e6e6133b53b6a71647231fb89d36ddcb8d9c87211b6721d7f
 F test/fts4growth2.test 13ad4e76451af6e6906c95cdc725d01b00044269
 F test/fts4incr.test 4e353a0bd886ea984e56fce9e77724fc923b8d0d
-F test/fts4langid.test 89e623218935507bca69d076ca254a7a8969dfc681c282b6374feaea8c7de784
+F test/fts4intck1.test 43774c641fdf6607c6ee90c3db8af065a37434d55d6eaf13bafe515e8b0c5729
+F test/fts4langid.test 4be912f42454998e239a2e877600263e0394afbaba03e06cedcc5a08693a345a
 F test/fts4lastrowid.test 185835895948d5325c7710649824042373b2203149abe8024a9319d25234dfd7
-F test/fts4merge.test e2b2ec21e287d54ec09824ccfb41e66896eeca568fc818ba0e0eb2efd94c35d2
+F test/fts4merge.test 57d093660a5093ae6e9fbd2d17592a88b45bbd66db2703c4b640b28828dbe38b
 F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891
 F test/fts4merge3.test 8d9ccb4a3d41c4c617a149d6c4b13ad02de797d0
 F test/fts4merge4.test 66fce89934cd9508cbdc67de486558c34912ffb2e8ffe5c9a1bbb9b8a4408ba7
@@ -1156,7 +1198,7 @@ F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f75
 F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401bf1a0f4c9
 F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d
 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d
-F test/func.test 2acd982669b2c15ceccc046385b458e985f90c9ca58b14d0ef75eec79c32f45b
+F test/func.test 3a29323b640c0552f6e9f1577407ced3a68e7d8c0bc04b61dd6040fa593a3a02
 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
 F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a
 F test/func4.test 2285fb5792d593fef442358763f0fd9de806eda47dbc7a5934df57ffdc484c31
@@ -1164,6 +1206,7 @@ F test/func5.test 863e6d1bd0013d09c17236f8a13ea34008dd857d87d85a13a673960e4c25d8
 F test/func6.test 9cc9b1f43b435af34fe1416eb1e318c8920448ea7a6962f2121972f5215cb9b0
 F test/func7.test adbfc910385a6ffd15dc47be3c619ef070c542fcb7488964badb17b2d9a4d080
 F test/func8.test c4e2ecacf9f16e47a245e7a25fbabcc7e78f9c7c41a80f158527cdfdc6dd299d
+F test/func9.test b32d313f679aa9698d52f39519d301c3941823cb72b4e23406c210eadd82c824
 F test/fuzz-oss1.test 514dcabb24687818ea949fa6760229eaacad74ca70157743ef36d35bbe01ffb0
 F test/fuzz.test 4608c1310cff4c3014a84bcced6278139743e080046e5f6784b0de7b069371d8
 F test/fuzz2.test 76dc35b32b6d6f965259508508abce75a6c4d7e1
@@ -1179,13 +1222,13 @@ F test/fuzzdata4.db b502c7d5498261715812dd8b3c2005bad08b3a26e6489414bd13926cd3e4
 F test/fuzzdata5.db e35f64af17ec48926481cfaf3b3855e436bd40d1cfe2d59a9474cb4b748a52a5
 F test/fuzzdata6.db b8725a5f5cf7a3b7241a9038e57ca7e7cc8c3f4d86b44bd770617bda245ab2b0
 F test/fuzzdata7.db 0166b56fd7a6b9636a1d60ef0a060f86ddaecf99400a666bb6e5bbd7199ad1f2
-F test/fuzzdata8.db 40c85daae47da64387c3dab7bbd99c21e425c0bfdb4b149cb685b1ab474a2cb4
+F test/fuzzdata8.db 4a53b6d077c6a5c23b609d8d3ac66996fa55ba3f8d02f9b6efdd0214a767a35a
 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8
 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14
 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc
 F test/fuzzinvariants.c b34530e8431f2cf3591eff588fc7684d6fdef466916fb46141c8c5374a3d8099
-F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c
-F test/gencol1.test aef8b0670abd4b1ae4cae786b15a43758d86f6cd9f12b381d45d96bb51e597c9
+F test/gcfault.test 4ea410ac161e685f17b19e1f606f58514a2850e806c65b846d05f60d436c5b0d
+F test/gencol1.test e169bdfa11c7ed5e9f322a98a7db3afe9e66235750b68c923efee8e1876b46ec
 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98
 F test/having.test a89236dd8d55aa50c4805f82ac9daf64d477a44d712d8209c118978d0ca21ec9
 F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751
@@ -1221,6 +1264,7 @@ F test/index6.test b376a648e85aa71c50074382784e6cb0c126ec46e43d1ad15af9a4d234c52
 F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6eb3
 F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3
 F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997
+F test/indexA.test 11d84f6995e6e5b9d8315953fb1b6d29772ee7c7803ee9112715e7e4dd3e4974
 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0
 F test/indexexpr1.test 62558b1cfd7ccbe7bc015849cc6d1a13ef124e80cbd5b3a98dc66c3c9cce0cf4
 F test/indexexpr2.test 1c382e81ef996d8ae8b834a74f2a9013dddf59214c32201d7c8a656d739f999a
@@ -1262,7 +1306,7 @@ F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f2
 F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be28
 F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b
 F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127
-F test/joinH.test c9550bb6a0257cf99668a28485bb309bac542081702e89261b95542ab5f676b1
+F test/joinH.test 84198ea42bf78b79fe399c0567218cd6df36c50c6dd27d9c4aab221acaad929e
 F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497
 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4
 F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e
@@ -1273,8 +1317,8 @@ F test/json/README.md 63e3e589e1df8fd3cc1588ba1faaff659214003f8b77a15af5c6452b35
 F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd28656fb261bddc8a3f
 F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307
 F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x
-F test/json101.test dc9d5a2a5b1fd1b54dbd71c538b17933cc98d84b4c1f821ead754933663dca55
-F test/json102.test 24f6f204f9cde45b971016691d0b92a9b4c58040d699e36d6b12cb165f9083ff
+F test/json101.test bc05d2476fd6f7ead31ec05b43d1b24b2b193ae112fd8f0d2ed56d9a904f9fa5
+F test/json102.test 4c69694773a470f1fda34e5f4ba24920b35184fb66050b450fc2ef9ab5ad310b
 F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe
 F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1
 F test/json105.test 11670a4387f4308ae0318cadcbd6a918ea7edcd19fbafde020720a073952675d
@@ -1331,9 +1375,9 @@ F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e
 F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f
 F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7
 F test/memdb1.test 2c4e9cc10d21c6bf4e217d72b7f6b8ba9b2605971bb2c5e6df76018e189f98f5
-F test/memdb2.test 7789975b96b0726032a22c1afc026592c7ff175bf05be11f1d640791541a77ea
+F test/memdb2.test 4ba1fc09e2f51df80d148a540e4a3fa66d0462e91167b27497084de4d1f6b5b4
 F test/memjournal.test 70f3a00c7f84ee2978ad14e831231caa1e7f23915a2c54b4f775a021d5740c6c
-F test/memjournal2.test 6b9083cfaab9a3281ec545c3da2487999e8025fb7501bbae10f713f80c56454c
+F test/memjournal2.test dbc2c5cb5f7b38950f4f6dc3e73fcecf0fcbed3fc32c7ce913bba164d288da1e
 F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2
 F test/memsubsys1.test 86b8158752af9188ed5b32a30674a1ef71183e6bc4e6808e815cd658ca9058a6
 F test/memsubsys2.test 774b93cb09ca50d1b759bb7c645baa2a9ce172edc3a3da67d5150a26a9fc2a08
@@ -1362,7 +1406,7 @@ F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4d
 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a
 F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7
 F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4
-F test/mutex1.test 4d7ecb155ae5ba9ca6f1817c1c74d51b5bb284d7f599de1859a9f2c9a1ca0d38
+F test/mutex1.test 42cb5e244c3a77bb0ef2b967e06fa5e7ba7d32d90a9b20bed98f6f5ede185a25
 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
 F test/nan.test 437d40e6d0778b050d7750726c0cbd2c9936b81962926e8f8c48ca698f00f4d1
 F test/nockpt.test 8c43b25af63b0bd620cf1b003529e37b6f1dc53bd22690e96a1bd73f78dde53a
@@ -1409,7 +1453,7 @@ F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305
 F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035ce4b3
 F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
 F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
-F test/pendingrace.test cbdf0f74bc939fb43cebad64dda7a0b5a3941a10b7e9cc2b596ff3e423a18156
+F test/pendingrace.test 6aa33756b950c4529f79c4f3817a9a1e4025bd0d9961571a05c0279bd183d9c6
 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
 F test/permutations.test f7caf8dd5c7b1da74842a48df116f7f193399c656d4ffc805cd0d9658568c675
 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f
@@ -1427,7 +1471,7 @@ F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
 F test/pushdown.test 1495a09837a1cedfc0adf07ba42dc6b83be05a2c15de331b67c39a0e22078238
 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
 F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
-F test/quickcheck.test f86b25b33455af0189b4d3fe7bd6e553115e80b2d7ec9bbe9a6b37fce0881bfe
+F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a
 F test/quota-glob.test 32901e9eed6705d68ca3faee2a06b73b57cb3c26
 F test/quota.test bfb269ce81ea52f593f9648316cd5013d766dd2a
 F test/quota2.test 7dc12e08b11cbc4c16c9ba2aa2e040ea8d8ab4b8
@@ -1460,8 +1504,8 @@ F test/rowvalue5.test 00740304ea6a53a8704640c7405690f0045d5d2a6b4b04dde7bccc14c3
 F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087
 F test/rowvalue7.test c1cbdbf407029db01f87764097c6ac02a1c5a37efd2776eff32a9cdfdf6f2dba
 F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0
-F test/rowvalue9.test 138252b53b835208a5712e01595403a0ae32b4bc58284d9fe6bea10e58203fe4
-F test/rowvalueA.test 51f79b6098c193f838168752c9640f4eae6c63346bf64b5bed4f4e22fe2c71d0
+F test/rowvalue9.test 7499a8fd7ca3a3f0e19d94e135355439aa2b596f86b775ca8de79672da2ca378
+F test/rowvalueA.test be8d6ad8b476eb24c151bb20bfd487e0d50c5e99618b7b0e656035069d2fc2cf
 F test/rowvaluefault.test 963ae9cdaed30a85a29668dd514e639f3556cae903ee9f172ea972d511c54fff
 F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f
 F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798
@@ -1474,7 +1518,7 @@ F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7
 F test/savepoint7.test cde525ea3075283eb950cdcdefe23ead4f700daa
 F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2
 F test/scanstatus.test b249328caf4d317e71058006872b8012598a5fa045b30bf24a81eeff650ab49e
-F test/scanstatus2.test d0434bc3b356fb9d948f3417846b2ed5bbc4bd4cc49bddb38ac86469f754bfb0
+F test/scanstatus2.test 317670daf7f3eef48a9598cb7800ba8eccab51949cf52bca3f7da3b83a0c1c8c
 F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431
 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5
 F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5c9ce
@@ -1487,7 +1531,7 @@ F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5
 F test/seekscan1.test 31af16e3bb3203d153aea320939c5da97ec44705c2710d153c06a01397d45b09
 F test/select1.test 692e84cfa29c405854c69e8a4027183d64c22952866a123fabbce741a379e889
 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56
-F test/select3.test 8d04b66df7475275a65f7e4a786d6a724c30bd9929f8ae5bd59c8d3d6e75e6cd
+F test/select3.test 180223af31e1ca5537dd395ef9708ae18e651a233777fd366fd0d75469fc19c6
 F test/select4.test f0684d3da3bccacbe2a1ebadf6fb49d9df6f53acb4c6ebc228a88d0d6054cc7b
 F test/select5.test 8afc5e5dcdebc2be54472e73ebd9cd1adef1225fd15d37a1c62f969159f390ae
 F test/select6.test 9b2fb4ffedf52e1b5703cfcae1212e7a4a063f014c0458d78d29aca3db766d1f
@@ -1501,10 +1545,10 @@ F test/selectD.test 6d1909b49970bf92f45ce657505befcef5fc7cbc13544e18103a316d3218
 F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf
 F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3
 F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae8840
-F test/selectH.test 88237ded5925adfb3f27fdafb5428c2ffc4d61e313bceb854e225ffc3ef0d206
+F test/selectH.test 0b54599f1917d99568c9b929df22ec6261ed7b6d2f02a46b5945ef81b7871aac
 F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be
 F test/sessionfuzz-data1.db 1f8d5def831f19b1c74571037f0d53a588ea49a6c4ca2a028fc0c27ef896dbcb
-F test/sessionfuzz.c 5eef09af01eeff6f20250ae4c0112c2e576e4d2f2026cc9a49dc5be6886fa6ee
+F test/sessionfuzz.c 666b47e177c7b25f01ba645d41fb9131d2d54ae673f0d81c08f5af2b3e6ecbda
 F test/shared.test f022874d9d299fe913529dc10f52ad5a386e4e7ff709270b9b1111b3a0f3420a
 F test/shared2.test 03eb4a8d372e290107d34b6ce1809919a698e879
 F test/shared3.test f8cd07c1a2b7cdb315c01671a0b2f8e3830b11ef31da6baa9a9cd8da88965403
@@ -1517,7 +1561,7 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21
 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707
 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
-F test/shell1.test 300b77328aaafb9f3e7a53a26e4162fbf92181d92251d259ff105a2275ff998d
+F test/shell1.test 2191c892d8256b1b6cccb4fca894cfc2e748450cf422e6ec80a320a0d538b6df
 F test/shell2.test 35226c070a8c7f64fd016dfac2a0db2a40f709b3131f61daacd9dad61536c9cb
 F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a
 F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f
@@ -1568,13 +1612,13 @@ F test/sqllimits1.test b28e5cc8d337aaf290614d96a47e8fbfb720bb7ad35620c9d5432996f
 F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a
 F test/startup.c 1beb5ca66fcc0fce95c3444db9d1674f90fc605499a574ae2434dcfc10d22805
 F test/stat.test 123212a20ceb496893d5254a5f6c76442ce549fdc08d1702d8288a2bbaac8408
-F test/statfault.test 55f86055f9cd7b2d962a621b8a04215c1cebd4eaaecde92d279442327fe648a0
+F test/statfault.test 064f43379e4992b5221b7d9ac887c313b3191f85cce605d78e416fc4045da64e
 F test/stmt.test 54ed2cc0764bf3e48a058331813c3dbd19fc1d0827c3d8369914a5d8f564ec75
 F test/stmtvtab1.test 6873dfb24f8e79cbb5b799b95c2e4349060eb7a3b811982749a84b359468e2d5
 F test/strict1.test 4d2b492152b984fd7e8196d23eb88e2ccb0ef9e46ca2f96c2ce7147ceef9d168
 F test/strict2.test b22c7a98b5000aef937f1990776497f0e979b1a23bc4f63e2d53b00e59b20070
 F test/subjournal.test 8d4e2572c0ee9a15549f0d8e40863161295107e52f07a3e8012a2e1fdd093c49
-F test/subquery.test 3a1a5b600b8d4f504d2a2c61f33db820983dba94a0ef3e4aedca8f0165eaecb8
+F test/subquery.test 312c5d26304b0e93a56ba2cb9d4480b8a1c8217e3b2b8f8be2bfb0b2458ac3a7
 F test/subquery2.test 90cf944b9de8204569cf656028391e4af1ccc8c0cc02d4ef38ee3be8de1ffb12
 F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303
 F test/substr.test a673e3763e247e9b5e497a6cacbaf3da2bd8ec8921c0677145c109f2e633f36b
@@ -1591,7 +1635,7 @@ F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef20
 F test/syscall.test a39d9a36f852ae6e4800f861bc2f2e83f68bbc2112d9399931ecfadeabd2d69d
 F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04
 F test/tabfunc01.test 54f27eacd054aa528a8b6e3331192c484104f30aaee351ad035f2b39a00f87c4
-F test/table.test eb3463b7add9f16a5bb836badf118cf391b809d09fdccd1f79684600d07ec132
+F test/table.test 7862a00b58b5541511a26757ea9c5c7c3f8298766e98aa099deec703d9c0a8e0
 F test/tableapi.test ecbcc29c4ab62c1912c3717c48ea5c5e59f7d64e4a91034e6148bd2b82f177f4
 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
 F test/tclsqlite.test ad0bbd92edabe64cc91d990a0748142fe5ab962d74ac71fa3bfa94d50d2f4c87
@@ -1603,8 +1647,8 @@ F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d163
 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
 F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede
-F test/testrunner.tcl a9fee4df57276bc9e446961b160068c269da5902cc8ffc3e8852d77626b7594c
-F test/testrunner_data.tcl c448693eb6fdbadb78cb26f6253d4f335666f9836f988afa575de960b666b19f
+F test/testrunner.tcl 14c8b8ece841b1dd17516a0dc9c7ad9b5f4d4db7987974d3fdf66ae56b2a71fa
+F test/testrunner_data.tcl 7f73f93634d32dafc857ed491b840f371113d09fde6a8bfb9e47b938d47b8c85
 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502
 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
@@ -1612,7 +1656,7 @@ F test/thread004.test f51dfc3936184aaf73ee85f315224baad272a87f
 F test/thread005.test 50d10b5684399676174bd96c94ad4250b1a2c8b6
 F test/thread1.test df115faa10a4ba1d456e9d4d9ec165016903eae4
 F test/thread2.test f35d2106452b77523b3a2b7d1dcde2e5ee8f9e46
-F test/thread3.test 5f53b6a8e7391d8653116fd0bee4f9774efee4410e039990821de39c6b4375a9
+F test/thread3.test a12656a56cdf67acb6a2ff7638826c6d6a645f79909d86df521045ad31cf547d
 F test/thread_common.tcl 334639cadcb9f912bf82aa73f49efd5282e6cadd
 F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b
 F test/threadtest2.c a70a8e94bef23339d34226eb9521015ef99f4df8
@@ -1670,7 +1714,7 @@ F test/tkt-bd484a090c.test 60460bf946f79a79712b71f202eda501ca99b898
 F test/tkt-bdc6bbbb38.test fc38bb09bdd440e3513a1f5f98fc60a075182d7d
 F test/tkt-c48d99d690.test ba61977d62ab612fc515b3c488a6fbd6464a2447
 F test/tkt-c694113d5.test 82c461924ada5c14866c47e85535b0b0923ba16a2e907e370061a5ca77f65d77
-F test/tkt-cbd054fa6b.test 708475ef4d730a6853512c8ce363bcbd3becf0e26826e1f4cd46e2f52ff38edf
+F test/tkt-cbd054fa6b.test 6ec9f1a5721fba74a83397683c50f472df68a0a749d193a537264eda3ad6d113
 F test/tkt-d11f09d36e.test d999b548fef885d1d1afa49a0e8544ecf436869d
 F test/tkt-d635236375.test 9d37e988b47d87505bc9445be0ca447002df5d09
 F test/tkt-d82e3f3721.test bcc0dfba658d15bab30fd4a9320c9e35d214ce30
@@ -1775,7 +1819,7 @@ F test/trans2.test 62bd045bfc7a1c14c5ba83ba64d21ade31583f76
 F test/trans3.test 91a100e5412b488e22a655fe423a14c26403ab94
 F test/transitive1.test f8ee983600b33d167da1885657f064aec404e1c0d0bc8765fdf163f4c749237a
 F test/trigger1.test 02cc64dc98278816c1c1ed8e472e18db8edbad88f37018bf46223e9614831963
-F test/trigger2.test 6e35bd7321c49e63d540aee980eb95dec63e1d1caca175224101045bcc80871f
+F test/trigger2.test 30fcb3a6aa6782020d47968735ee6086ed795f73a7affa9406c8d5a36e7b5265
 F test/trigger3.test aa640bb2bbb03edd5ff69c055117ea088f121945
 F test/trigger4.test 74700b76ebf3947b2f7a92405141eb2cf2a5d359
 F test/trigger5.test 619391a3e9fc194081d22cefd830d811e7badf83
@@ -1801,7 +1845,7 @@ F test/tt3_vacuum.c 71b254cde1fc49d6c8c44efd54f4668f3e57d7b3a8f4601ade069f75a999
 F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff
 F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac
 F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a
-F test/unhex.test 47b547f4b35e4f6525ecac7c7839bd3ae4eb4613d4e8932592eff55da83308f1
+F test/unhex.test b7f1b806207cb77fa31c3e434fe92fba524464e3e9356809bfcc28f15af1a8b7
 F test/unionall.test eb9afa030897af75fd2f0dd28354ef63c8a5897b6c76aa1f15acae61a12eabcf
 F test/unionall2.test 71e8fa08d5699d50dc9f9dc0c9799c2e7a6bb7931a330d369307a4df7f157fa1
 F test/unionallfault.test 652bfbb630e6c43135965dc1e8f0a9a791da83aec885d626a632fe1909c56f73
@@ -1829,7 +1873,7 @@ F test/uri.test c1abaaaa28e9422d61e5f3f9cbc8ef993ec49fe802f581520731708561d49384
 F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7
 F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9
 F test/utf16align.test 9fde0bb5d3a821594aa68c6829ab9c5453a084384137ebb9f6153e2d678039da
-F test/vacuum-into.test e0e3406845be4cf1b44db354179e5d9437e38bc267e4ac8e8dc617f9c3c903ab
+F test/vacuum-into.test 35dc6f79b563f91c61822f61797363e97fed1bf28f1f722688b98d43f1980d76
 F test/vacuum.test ce91c39f7f91a4273bf620efad21086b5aa6ef1d
 F test/vacuum2.test 9fd45ce6ce29f5614c249e03938d3567c06a9e772d4f155949f8eafe2d8af520
 F test/vacuum3.test d9d9a04ee58c485b94694fd4f68cffaba49c32234fdefe1ac1a622c5e17d4ce3
@@ -1842,7 +1886,7 @@ F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661
 F test/view.test d4c4281e1679245829db35597817282f60dc513fc39cc5439078f009bd118487
 F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679
 F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456
-F test/vt02.c f4a357c8180d71120ca2b466a8df48d9c40fc50873694840327d9647450485f3
+F test/vt02.c 5b44ac67b1a283fedecf2d6e2ceda61e7a157f01d44dcb4490dcb1e87d057060
 F test/vtab1.test 09a72330d0f31eda2ffaa828b06a6b917fb86250ee72de0301570af725774c07
 F test/vtab2.test 14d4ab26cee13ba6cf5c5601b158e4f57552d3b055cdd9406cf7f711e9c84082
 F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e
@@ -1899,7 +1943,7 @@ F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db8
 F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20
 F test/walro2.test 33955a6fd874dd9724005e17f77fef89d334b3171454a1256fe4941a96766cdc
 F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68
-F test/walseh1.test 82da37763b0d87942dccd191e58321532ce3d44b87ef36e04ff9ce13f382bbae
+F test/walseh1.test bae700eb99519b6d5cd3f893c04759accc5a59c391d4189fe4dd6995a533442b
 F test/walsetlk.test 34c901443b31ab720afc463f5b236c86ca5c4134402573dce91aa0761de8db5a
 F test/walshared.test 42e3808582504878af237ea02c42ca793e8a0efaa19df7df26ac573370dbc7a3
 F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f
@@ -1938,11 +1982,11 @@ F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2
 F test/win32lock.test e0924eb8daac02bf80e9da88930747bd44dd9b230b7759fed927b1655b467c9c
 F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976
 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc
-F test/window1.test 1e7e13d36235b9a08fcb9790f2b05383f2f8c9538532b027f455766686926114
+F test/window1.test ccfeaf116afc6a8f748a8122a4f1ee6b69e6bbc5acee61197d3c17167338b100
 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476
 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c
 F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03
-F test/window3.test e9959a993c8a71e96433be8daaa1827d78b8921e4f12debd7bdbeb3c856ef3cb
+F test/window3.test 330733bcca73aba4ddae7a1011f2a2120ef7a0c68d8155854e08677417b8dbd0
 F test/window4.tcl 6f85307eb67242b654d051f7da32a996a66aee039a09c5ae358541aa61720742
 F test/window4.test fbead87f681400ac07ef3555e0488b544a47d35491f8bf09a7474b6f76ce9b4e
 F test/window5.test d328dd18221217c49c144181975eea17339eaeaf0e9aa558cee3afb84652821e
@@ -1963,7 +2007,7 @@ F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c
 F test/windowpushd.test d8895d08870b7226f7693665bd292eb177e62ca06799184957b3ca7dc03067df
 F test/with1.test b93833890e5d2a368e78747f124503a0159aa029b98e9ed4795ebf630b2efd3d
 F test/with2.test a1df41b987198383b9b70bf5e5fda390582e46398653858dbc6ceb24253b28df
-F test/with3.test fe15975c0b53c9098a757902a908e3f8d6d80ce47c5363ac600f28a79ef8c0ca
+F test/with3.test e30369ea27aa27eb1bda4c5e510c8a9f782c8afd2ab99d1a02b8a7f25a5d3e65
 F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205
 F test/with5.test 6248213c41fab36290b5b73aa3f937309dfba337004d9d8434c3fabc8c7d4be8
 F test/with6.test e097a03e5c898a8cd8f3a2d6a994ec510ea4376b5d484c2b669a41001e7758c8
@@ -1990,6 +2034,7 @@ F tool/build-all-msvc.bat c817b716e0edeecaf265a6775b63e5f45c34a6544f1d4114a22270
 F tool/build-shell.sh f193b5e3eb4afcb4abbf96bf1475be6cfb74763ee2e50c82bc7ca105e8a136c5
 F tool/cg_anno.tcl c1f875f5a4c9caca3d59937b16aff716f8b1883935f1b4c9ae23124705bc8099 x
 F tool/checkSpacing.c 810e51703529a204fc4e1eb060e9ab663e3c06d2
+F tool/cktclsh.sh 6075eef9c6b9ba4b38fef2ca2a66d25f2311bd3c610498d18a9b01f861629cca
 F tool/custom.txt 6cdf298f43e1db4bb91406d14777669b8fb1df790837823fa6754c4308decc27
 F tool/dbhash.c 5da0c61032d23d74f2ab84ffc5740f0e8abec94f2c45c0b4306be7eb3ae96df0
 F tool/dbtotxt.c ca48d34eaca6d6b6e4bd6a7be2b72caf34475869054240244c60fa7e69a518d6
@@ -1999,7 +2044,7 @@ F tool/extract-sqlite3h.tcl 069ceab0cee26cba99952bfa08c0b23e35941c837acabe143f0c
 F tool/extract.c 054069d81b095fbdc189a6f5d4466e40380505e2
 F tool/fast_vacuum.c c129ae2924a48310c7b766810391da9e8fda532b9f6bd3f9a9e3a799a1b42af9
 F tool/fragck.tcl 5265a95126abcf6ab357f7efa544787e5963f439
-F tool/fuzzershell.c e1d90a03ca790d7c331c2aae08ca46ff435f1ae1faa6cb9cc48f4687c18fdc6e
+F tool/fuzzershell.c 41480c8a1e4749351f381431ecfdfceba645396c5d836f8d26b51a33c4a21b33
 F tool/genfkey.README cf68fddd4643bbe3ff8e31b8b6d8b0a1b85e20f4
 F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a5a4f
 F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce
@@ -2012,9 +2057,9 @@ F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862
 F tool/logest.c c34e5944318415de513d29a6098df247a9618c96d83c38d4abd88641fe46e669
 F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439
 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176
-F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8
+F tool/mkautoconfamal.sh cbdcf993fa83dccbef7fb77b39cdeb31ef9f77d9d88c9e343b58d35ca3898a6a
 F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x
-F tool/mkctimec.tcl aca4b83e49aecf10368cd5d11bc4847061041ade026db5bd8da17ef201f1403b x
+F tool/mkctimec.tcl 372452e24267dfe1b496eec3992d10c6e5e7d7870a152560cdcfe5404bc8cc04 x
 F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd
 F tool/mkmsvcmin.tcl 8897d515ef7f94772322db95a3b6fce6c614d84fe0bdd06ba5a1c786351d5a1d
 F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef
@@ -2025,9 +2070,10 @@ F tool/mkshellc.tcl b7adf08b82de60811d2cb6af05ff59fc17e5cd6f3e98743c14eaaa3f8971
 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
-F tool/mksqlite3c.tcl 49e39b1e616abc92fd8c24445f2b0a38881825f764541e0026f72371f0d84b65
+F tool/mksqlite3c.tcl 2c760ab786cb509b47f00c96fea82994866cb99f5e046df81c768288f57897b4
 F tool/mksqlite3h.tcl d391cff7cad0a372ee1406faee9ccc7dad9cb80a0c95cae0f73d10dd26e06762
 F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b
+F tool/mktoolzip.tcl c7a9b685f5131d755e7d941cec50cee7f34178b9e34c9a89811eeb08617f8423
 F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5
 F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c6115285845
 F tool/omittest-msvc.tcl d6b8f501ac1d7798c4126065030f89812379012cad98a1735d6d7221492abc08
@@ -2038,14 +2084,14 @@ F tool/replace.tcl 937c931ad560688e85bdd6258bdc754371bb1e2732e1fb28ef441e44c9228
 F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a
 F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5
 F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076
-F tool/showdb.c 495a43b759ae37a0c4561a557a70090cb79b8c1601204e5a77e8b5360e65a954
+F tool/showdb.c 0f74b54cc67076c76cba9b2b7f54d3e05b78d130c70ffc394eb84c5b41bab017
 F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818
 F tool/showlocks.c 9cc5e66d4ebbf2d194f39db2527ece92077e86ae627ddd233ee48e16e8142564
 F tool/showshm.c a0ab6ec32dd1f11218ca2a4018f8fb875b59414801ab8ceed8b2e69b7b45a809
 F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c2a1
-F tool/showwal.c 65ecabae3a2dcff4116301d5a8dbb8c4964814da1b2aff6d85c806a88b71fa4e
+F tool/showwal.c 11eca547980a066b081f512636151233350ac679f29ecf4ebfce7f4530230b3d
 F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe
-F tool/spaceanal.tcl 1b5be34c6223cb1af06da2a10fb77863eb869b1962d055820b0a11cf2336ab45
+F tool/spaceanal.tcl 70c87c04cfd2e77b3e6f21c33ca768296aa8f67d4ab4874786ac8fbb28433477
 F tool/speed-check.sh 72dc85b2c0484af971ee3e7d10775f72b4e771e27e162c2099b3bf25517c25fb
 F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355
 F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e
@@ -2054,12 +2100,13 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd
 F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d40618 x
 F tool/split-sqlite3c.tcl 5aa60643afca558bc732b1444ae81a522326f91e1dc5665b369c54f09e20de60
-F tool/sqldiff.c efe73c6706b0ead9b4e415c684e4abf439e5f405793ae6ba50796e0e4a030349
+F tool/sqldiff.c fcccbc07da942b4534d0c769e9fcc21c67cbd7086ddc1c8f13372c40a83d4634
 F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c
 F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898
 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848
 F tool/src-verify.c 41c586dee84d0b190ad13e0282ed83d4a65ec9fefde9adf4943efdf6558eea7f
 F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f
+F tool/srctree-check.tcl cef630bc4ff21a460d72479c43a42bf1c1ed61897659305c35c8d72e91bcb176
 F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43
 F tool/stripccomments.c 20b8aabc4694d0d4af5566e42da1f1a03aff057689370326e9269a9ddcffdc37
 F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d
@@ -2092,11 +2139,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 3f5bec9a684f6b3d88383617831862fc5595a51ad99ec12430ac6650b704ccbb
-R 0c01ee5f49ac8bbf7d5b3e02bf525f93
+P d1895dd8f5757a339f619f22b29c8a739398ded673bb9c93f1b8eb8a4b38f510
+R 06db090922d30265c1e1381238ea6702
 T +sym-major-release *
 T +sym-release *
-T +sym-version-3.43.0 *
+T +sym-version-3.44.0 *
 U drh
-Z 2bd48930e1a5ff814b90ece6e1f4c0d0
+Z 14f36413d8197ea0c35026cb3842c9f7
 # Remove this line to create a well-formed Fossil manifest.
diff --git a/libsql-sqlite3/manifest.uuid b/libsql-sqlite3/manifest.uuid
index cca48d5b41..1cefea455e 100644
--- a/libsql-sqlite3/manifest.uuid
+++ b/libsql-sqlite3/manifest.uuid
@@ -1 +1 @@
-0f80b798b3f4b81a7bb4233c58294edd0f1156f36b6ecf5ab8e83631d468778c
\ No newline at end of file
+17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301
\ No newline at end of file
diff --git a/libsql-sqlite3/src/alter.c b/libsql-sqlite3/src/alter.c
index dd5cbe585f..74fad7c998 100644
--- a/libsql-sqlite3/src/alter.c
+++ b/libsql-sqlite3/src/alter.c
@@ -446,14 +446,19 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){
     /* Verify that constraints are still satisfied */
     if( pNew->pCheck!=0
      || (pCol->notNull && (pCol->colFlags & COLFLAG_GENERATED)!=0)
+     || (pTab->tabFlags & TF_Strict)!=0
     ){
       sqlite3NestedParse(pParse,
         "SELECT CASE WHEN quick_check GLOB 'CHECK*'"
         " THEN raise(ABORT,'CHECK constraint failed')"
+        " WHEN quick_check GLOB 'non-* value in*'"
+        " THEN raise(ABORT,'type mismatch on DEFAULT')"
         " ELSE raise(ABORT,'NOT NULL constraint failed')"
         " END"
         "  FROM pragma_quick_check(%Q,%Q)"
-        " WHERE quick_check GLOB 'CHECK*' OR quick_check GLOB 'NULL*'",
+        " WHERE quick_check GLOB 'CHECK*'"
+        " OR quick_check GLOB 'NULL*'"
+        " OR quick_check GLOB 'non-* value in*'",
         zTab, zDb
       );
     }
diff --git a/libsql-sqlite3/src/btree.c b/libsql-sqlite3/src/btree.c
old mode 100644
new mode 100755
index b7ee5225b3..1fa833aafc
--- a/libsql-sqlite3/src/btree.c
+++ b/libsql-sqlite3/src/btree.c
@@ -1,4 +1,3 @@
-
 /*
 ** 2004 April 6
 **
@@ -7496,9 +7495,10 @@ static int rebuildPage(
   int k;                          /* Current slot in pCArray->apEnd[] */
   u8 *pSrcEnd;                    /* Current pCArray->apEnd[k] value */
 
+  assert( nCell>0 );
   assert( i<iEnd );
   j = get2byte(&aData[hdr+5]);
-  if( NEVER(j>(u32)usableSize) ){ j = 0; }
+  if( j>(u32)usableSize ){ j = 0; }
   memcpy(&pTmp[j], &aData[j], usableSize - j);
 
   for(k=0; ALWAYS(k<NB*2) && pCArray->ixNx[k]<=i; k++){}
@@ -7802,6 +7802,7 @@ static int editPage(
   return SQLITE_OK;
  editpage_fail:
   /* Unable to edit this page. Rebuild it from scratch instead. */
+  if( nNew<1 ) return SQLITE_CORRUPT_BKPT;
   populateCellCache(pCArray, iNew, nNew);
   return rebuildPage(pCArray, iNew, nNew, pPg);
 }
@@ -10464,7 +10465,8 @@ static void checkAppendMsg(
 ** corresponds to page iPg is already set.
 */
 static int getPageReferenced(IntegrityCk *pCheck, Pgno iPg){
-  assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 );
+  assert( pCheck->aPgRef!=0 );
+  assert( iPg<=pCheck->nCkPage && sizeof(pCheck->aPgRef[0])==1 );
   return (pCheck->aPgRef[iPg/8] & (1 << (iPg & 0x07)));
 }
 
@@ -10472,7 +10474,8 @@ static int getPageReferenced(IntegrityCk *pCheck, Pgno iPg){
 ** Set the bit in the IntegrityCk.aPgRef[] array that corresponds to page iPg.
 */
 static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){
-  assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 );
+  assert( pCheck->aPgRef!=0 );
+  assert( iPg<=pCheck->nCkPage && sizeof(pCheck->aPgRef[0])==1 );
   pCheck->aPgRef[iPg/8] |= (1 << (iPg & 0x07));
 }
 
@@ -10486,7 +10489,7 @@ static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){
 ** Also check that the page number is in bounds.
 */
 static int checkRef(IntegrityCk *pCheck, Pgno iPage){
-  if( iPage>pCheck->nPage || iPage==0 ){
+  if( iPage>pCheck->nCkPage || iPage==0 ){
     checkAppendMsg(pCheck, "invalid page number %u", iPage);
     return 1;
   }
@@ -10713,6 +10716,7 @@ static int checkTreePage(
   if( (rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0 ){
     checkAppendMsg(pCheck,
        "unable to get the page. error code=%d", rc);
+    if( rc==SQLITE_IOERR_NOMEM ) pCheck->rc = SQLITE_NOMEM;
     goto end_of_check;
   }
 
@@ -10983,15 +10987,15 @@ int sqlite3BtreeIntegrityCheck(
   sCheck.db = db;
   sCheck.pBt = pBt;
   sCheck.pPager = pBt->pPager;
-  sCheck.nPage = btreePagecount(sCheck.pBt);
+  sCheck.nCkPage = btreePagecount(sCheck.pBt);
   sCheck.mxErr = mxErr;
   sqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH);
   sCheck.errMsg.printfFlags = SQLITE_PRINTF_INTERNAL;
-  if( sCheck.nPage==0 ){
+  if( sCheck.nCkPage==0 ){
     goto integrity_ck_cleanup;
   }
 
-  sCheck.aPgRef = sqlite3MallocZero((sCheck.nPage / 8)+ 1);
+  sCheck.aPgRef = sqlite3MallocZero((sCheck.nCkPage / 8)+ 1);
   if( !sCheck.aPgRef ){
     checkOom(&sCheck);
     goto integrity_ck_cleanup;
@@ -11003,7 +11007,7 @@ int sqlite3BtreeIntegrityCheck(
   }
 
   i = PENDING_BYTE_PAGE(pBt);
-  if( i<=sCheck.nPage ) setPageReferenced(&sCheck, i);
+  if( i<=sCheck.nCkPage ) setPageReferenced(&sCheck, i);
 
   /* Check the integrity of the freelist
   */
@@ -11054,7 +11058,7 @@ int sqlite3BtreeIntegrityCheck(
   /* Make sure every page in the file is referenced
   */
   if( !bPartial ){
-    for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){
+    for(i=1; i<=sCheck.nCkPage && sCheck.mxErr; i++){
 #ifdef SQLITE_OMIT_AUTOVACUUM
       if( getPageReferenced(&sCheck, i)==0 ){
         checkAppendMsg(&sCheck, "Page %u: never used", i);
diff --git a/libsql-sqlite3/src/btreeInt.h b/libsql-sqlite3/src/btreeInt.h
index 26a0bc6869..563e15f8ac 100644
--- a/libsql-sqlite3/src/btreeInt.h
+++ b/libsql-sqlite3/src/btreeInt.h
@@ -695,7 +695,7 @@ struct IntegrityCk {
   BtShared *pBt;    /* The tree being checked out */
   Pager *pPager;    /* The associated pager.  Also accessible by pBt->pPager */
   u8 *aPgRef;       /* 1 bit per page in the db (see above) */
-  Pgno nPage;       /* Number of pages in the database */
+  Pgno nCkPage;     /* Pages in the database.  0 for partial check */
   int mxErr;        /* Stop accumulating errors when this reaches zero */
   int nErr;         /* Number of messages written to zErrMsg so far */
   int rc;           /* SQLITE_OK, SQLITE_NOMEM, or SQLITE_INTERRUPT */
diff --git a/libsql-sqlite3/src/build.c b/libsql-sqlite3/src/build.c
index 5733ae01f0..654ec2e259 100644
--- a/libsql-sqlite3/src/build.c
+++ b/libsql-sqlite3/src/build.c
@@ -246,19 +246,14 @@ void sqlite3FinishCoding(Parse *pParse){
     */
     if( pParse->pAinc ) sqlite3AutoincrementBegin(pParse);
 
-    /* Code constant expressions that where factored out of inner loops.
-    **
-    ** The pConstExpr list might also contain expressions that we simply
-    ** want to keep around until the Parse object is deleted.  Such
-    ** expressions have iConstExprReg==0.  Do not generate code for
-    ** those expressions, of course.
+    /* Code constant expressions that were factored out of inner loops. 
     */
     if( pParse->pConstExpr ){
       ExprList *pEL = pParse->pConstExpr;
       pParse->okConstFactor = 0;
       for(i=0; i<pEL->nExpr; i++){
-        int iReg = pEL->a[i].u.iConstExprReg;
-        sqlite3ExprCode(pParse, pEL->a[i].pExpr, iReg);
+        assert( pEL->a[i].u.iConstExprReg>0 );
+        sqlite3ExprCode(pParse, pEL->a[i].pExpr, pEL->a[i].u.iConstExprReg);
       }
     }
 
@@ -1412,20 +1407,13 @@ void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){
 }
 #endif
 
-/*
-** Name of the special TEMP trigger used to implement RETURNING.  The
-** name begins with "sqlite_" so that it is guaranteed not to collide
-** with any application-generated triggers.
-*/
-#define RETURNING_TRIGGER_NAME  "sqlite_returning"
-
 /*
 ** Clean up the data structures associated with the RETURNING clause.
 */
 static void sqlite3DeleteReturning(sqlite3 *db, Returning *pRet){
   Hash *pHash;
   pHash = &(db->aDb[1].pSchema->trigHash);
-  sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, 0);
+  sqlite3HashInsert(pHash, pRet->zName, 0);
   sqlite3ExprListDelete(db, pRet->pReturnEL);
   sqlite3DbFree(db, pRet);
 }
@@ -1468,7 +1456,9 @@ void sqlite3AddReturning(Parse *pParse, ExprList *pList){
      (void(*)(sqlite3*,void*))sqlite3DeleteReturning, pRet);
   testcase( pParse->earlyCleanup );
   if( db->mallocFailed ) return;
-  pRet->retTrig.zName = RETURNING_TRIGGER_NAME;
+  sqlite3_snprintf(sizeof(pRet->zName), pRet->zName,
+                   "sqlite_returning_%p", pParse);
+  pRet->retTrig.zName = pRet->zName;
   pRet->retTrig.op = TK_RETURNING;
   pRet->retTrig.tr_tm = TRIGGER_AFTER;
   pRet->retTrig.bReturning = 1;
@@ -1479,9 +1469,9 @@ void sqlite3AddReturning(Parse *pParse, ExprList *pList){
   pRet->retTStep.pTrig = &pRet->retTrig;
   pRet->retTStep.pExprList = pList;
   pHash = &(db->aDb[1].pSchema->trigHash);
-  assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0
+  assert( sqlite3HashFind(pHash, pRet->zName)==0
           || pParse->nErr  || pParse->ifNotExists );
-  if( sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, &pRet->retTrig)
+  if( sqlite3HashInsert(pHash, pRet->zName, &pRet->retTrig)
           ==&pRet->retTrig ){
     sqlite3OomFault(db);
   }
@@ -2934,6 +2924,17 @@ void sqlite3EndTable(
     /* Reparse everything to update our internal data structures */
     sqlite3VdbeAddParseSchemaOp(v, iDb,
            sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName),0);
+
+    /* Test for cycles in generated columns and illegal expressions
+    ** in CHECK constraints and in DEFAULT clauses. */
+    if( p->tabFlags & TF_HasGenerated ){
+      sqlite3VdbeAddOp4(v, OP_SqlExec, 1, 0, 0,
+             sqlite3MPrintf(db, "SELECT*FROM\"%w\".\"%w\"",
+                   db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC);
+    }
+    sqlite3VdbeAddOp4(v, OP_SqlExec, 1, 0, 0,
+           sqlite3MPrintf(db, "PRAGMA \"%w\".integrity_check(%Q)",
+                 db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC);
   }
 
   /* Add the table to the in-memory representation of the database.
diff --git a/libsql-sqlite3/src/ctime.c b/libsql-sqlite3/src/ctime.c
index 3687241680..cf761299fe 100644
--- a/libsql-sqlite3/src/ctime.c
+++ b/libsql-sqlite3/src/ctime.c
@@ -355,6 +355,9 @@ static const char * const sqlite3azCompileOpt[] = {
 #ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS
   "EXPLAIN_ESTIMATED_ROWS",
 #endif
+#ifdef SQLITE_EXTRA_AUTOEXT
+  "EXTRA_AUTOEXT=" CTIMEOPT_VAL(SQLITE_EXTRA_AUTOEXT),
+#endif
 #ifdef SQLITE_EXTRA_IFNULLROW
   "EXTRA_IFNULLROW",
 #endif
@@ -636,6 +639,9 @@ static const char * const sqlite3azCompileOpt[] = {
 #ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
   "OMIT_SCHEMA_VERSION_PRAGMAS",
 #endif
+#ifdef SQLITE_OMIT_SEH
+  "OMIT_SEH",
+#endif
 #ifdef SQLITE_OMIT_SHARED_CACHE
   "OMIT_SHARED_CACHE",
 #endif
diff --git a/libsql-sqlite3/src/date.c b/libsql-sqlite3/src/date.c
index 648cdeb882..f163085445 100644
--- a/libsql-sqlite3/src/date.c
+++ b/libsql-sqlite3/src/date.c
@@ -1270,13 +1270,16 @@ static void strftimeFunc(
   computeJD(&x);
   computeYMD_HMS(&x);
   for(i=j=0; zFmt[i]; i++){
+    char cf;
     if( zFmt[i]!='%' ) continue;
     if( j<i ) sqlite3_str_append(&sRes, zFmt+j, (int)(i-j));
     i++;
     j = i + 1;
-    switch( zFmt[i] ){
-      case 'd': {
-        sqlite3_str_appendf(&sRes, "%02d", x.D);
+    cf = zFmt[i];
+    switch( cf ){
+      case 'd':  /* Fall thru */
+      case 'e': {
+        sqlite3_str_appendf(&sRes, cf=='d' ? "%02d" : "%2d", x.D);
         break;
       }
       case 'f': {
@@ -1285,8 +1288,21 @@ static void strftimeFunc(
         sqlite3_str_appendf(&sRes, "%06.3f", s);
         break;
       }
-      case 'H': {
-        sqlite3_str_appendf(&sRes, "%02d", x.h);
+      case 'F': {
+        sqlite3_str_appendf(&sRes, "%04d-%02d-%02d", x.Y, x.M, x.D);
+        break;
+      }
+      case 'H':
+      case 'k': {
+        sqlite3_str_appendf(&sRes, cf=='H' ? "%02d" : "%2d", x.h);
+        break;
+      }
+      case 'I': /* Fall thru */
+      case 'l': {
+        int h = x.h;
+        if( h>12 ) h -= 12;
+        if( h==0 ) h = 12;
+        sqlite3_str_appendf(&sRes, cf=='I' ? "%02d" : "%2d", h);
         break;
       }
       case 'W': /* Fall thru */
@@ -1298,7 +1314,7 @@ static void strftimeFunc(
         y.D = 1;
         computeJD(&y);
         nDay = (int)((x.iJD-y.iJD+43200000)/86400000);
-        if( zFmt[i]=='W' ){
+        if( cf=='W' ){
           int wd;   /* 0=Monday, 1=Tuesday, ... 6=Sunday */
           wd = (int)(((x.iJD+43200000)/86400000)%7);
           sqlite3_str_appendf(&sRes,"%02d",(nDay+7-wd)/7);
@@ -1319,6 +1335,19 @@ static void strftimeFunc(
         sqlite3_str_appendf(&sRes,"%02d",x.m);
         break;
       }
+      case 'p': /* Fall thru */
+      case 'P': {
+        if( x.h>=12 ){
+          sqlite3_str_append(&sRes, cf=='p' ? "PM" : "pm", 2);
+        }else{
+          sqlite3_str_append(&sRes, cf=='p' ? "AM" : "am", 2);
+        }
+        break;
+      }
+      case 'R': {
+        sqlite3_str_appendf(&sRes, "%02d:%02d", x.h, x.m);
+        break;
+      }
       case 's': {
         if( x.useSubsec ){
           sqlite3_str_appendf(&sRes,"%.3f",
@@ -1333,9 +1362,15 @@ static void strftimeFunc(
         sqlite3_str_appendf(&sRes,"%02d",(int)x.s);
         break;
       }
+      case 'T': {
+        sqlite3_str_appendf(&sRes,"%02d:%02d:%02d", x.h, x.m, (int)x.s);
+        break;
+      }
+      case 'u': /* Fall thru */
       case 'w': {
-        sqlite3_str_appendchar(&sRes, 1,
-                       (char)(((x.iJD+129600000)/86400000) % 7) + '0');
+        char c = (char)(((x.iJD+129600000)/86400000) % 7) + '0';
+        if( c=='0' && cf=='u' ) c = '7';
+        sqlite3_str_appendchar(&sRes, 1, c);
         break;
       }
       case 'Y': {
diff --git a/libsql-sqlite3/src/dbpage.c b/libsql-sqlite3/src/dbpage.c
index 32a9ce55bf..73c31f0dab 100644
--- a/libsql-sqlite3/src/dbpage.c
+++ b/libsql-sqlite3/src/dbpage.c
@@ -425,7 +425,8 @@ int sqlite3DbpageRegister(sqlite3 *db){
     0,                            /* xSavepoint */
     0,                            /* xRelease */
     0,                            /* xRollbackTo */
-    0                             /* xShadowName */
+    0,                            /* xShadowName */
+    0                             /* xIntegrity */
   };
   return sqlite3_create_module(db, "sqlite_dbpage", &dbpage_module, 0);
 }
diff --git a/libsql-sqlite3/src/dbstat.c b/libsql-sqlite3/src/dbstat.c
index 0a89d05249..c70d806370 100644
--- a/libsql-sqlite3/src/dbstat.c
+++ b/libsql-sqlite3/src/dbstat.c
@@ -895,7 +895,8 @@ int sqlite3DbstatRegister(sqlite3 *db){
     0,                            /* xSavepoint */
     0,                            /* xRelease */
     0,                            /* xRollbackTo */
-    0                             /* xShadowName */
+    0,                            /* xShadowName */
+    0                             /* xIntegrity */
   };
   return sqlite3_create_module(db, "dbstat", &dbstat_module, 0);
 }
diff --git a/libsql-sqlite3/src/expr.c b/libsql-sqlite3/src/expr.c
index d96f362856..0a975ae2e5 100644
--- a/libsql-sqlite3/src/expr.c
+++ b/libsql-sqlite3/src/expr.c
@@ -591,6 +591,7 @@ Expr *sqlite3ExprForVectorField(
     */
     pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0);
     if( pRet ){
+      ExprSetProperty(pRet, EP_FullSize);
       pRet->iTable = nField;
       pRet->iColumn = iField;
       pRet->pLeft = pVector;
@@ -1181,6 +1182,69 @@ Expr *sqlite3ExprFunction(
   return pNew;
 }
 
+/*
+** Report an error when attempting to use an ORDER BY clause within
+** the arguments of a non-aggregate function.
+*/
+void sqlite3ExprOrderByAggregateError(Parse *pParse, Expr *p){
+  sqlite3ErrorMsg(pParse,
+     "ORDER BY may not be used with non-aggregate %#T()", p
+  );
+}
+
+/*
+** Attach an ORDER BY clause to a function call.
+**
+**     functionname( arguments ORDER BY sortlist )
+**     \_____________________/          \______/
+**             pExpr                    pOrderBy
+**
+** The ORDER BY clause is inserted into a new Expr node of type TK_ORDER
+** and added to the Expr.pLeft field of the parent TK_FUNCTION node.
+*/
+void sqlite3ExprAddFunctionOrderBy(
+  Parse *pParse,        /* Parsing context */
+  Expr *pExpr,          /* The function call to which ORDER BY is to be added */
+  ExprList *pOrderBy    /* The ORDER BY clause to add */
+){
+  Expr *pOB;
+  sqlite3 *db = pParse->db;
+  if( NEVER(pOrderBy==0) ){
+    assert( db->mallocFailed );
+    return;
+  }
+  if( pExpr==0 ){
+    assert( db->mallocFailed );
+    sqlite3ExprListDelete(db, pOrderBy);
+    return;
+  }
+  assert( pExpr->op==TK_FUNCTION );
+  assert( pExpr->pLeft==0 );
+  assert( ExprUseXList(pExpr) );
+  if( pExpr->x.pList==0 || NEVER(pExpr->x.pList->nExpr==0) ){
+    /* Ignore ORDER BY on zero-argument aggregates */
+    sqlite3ParserAddCleanup(pParse,
+        (void(*)(sqlite3*,void*))sqlite3ExprListDelete,
+        pOrderBy);
+    return;
+  }
+  if( IsWindowFunc(pExpr) ){
+    sqlite3ExprOrderByAggregateError(pParse, pExpr);
+    sqlite3ExprListDelete(db, pOrderBy);
+    return;
+  }
+
+  pOB = sqlite3ExprAlloc(db, TK_ORDER, 0, 0);
+  if( pOB==0 ){
+    sqlite3ExprListDelete(db, pOrderBy);
+    return;
+  }
+  pOB->x.pList = pOrderBy;
+  assert( ExprUseXList(pOB) );
+  pExpr->pLeft = pOB;
+  ExprSetProperty(pOB, EP_FullSize);
+}
+
 /*
 ** Check to see if a function is usable according to current access
 ** rules:
@@ -1434,11 +1498,7 @@ static int dupedExprStructSize(const Expr *p, int flags){
   assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */
   assert( EXPR_FULLSIZE<=0xfff );
   assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 );
-  if( 0==flags || p->op==TK_SELECT_COLUMN
-#ifndef SQLITE_OMIT_WINDOWFUNC
-   || ExprHasProperty(p, EP_WinFunc)
-#endif
-  ){
+  if( 0==flags || ExprHasProperty(p, EP_FullSize) ){
     nSize = EXPR_FULLSIZE;
   }else{
     assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
@@ -1469,56 +1529,93 @@ static int dupedExprNodeSize(const Expr *p, int flags){
 
 /*
 ** Return the number of bytes required to create a duplicate of the
-** expression passed as the first argument. The second argument is a
-** mask containing EXPRDUP_XXX flags.
+** expression passed as the first argument.
 **
 ** The value returned includes space to create a copy of the Expr struct
 ** itself and the buffer referred to by Expr.u.zToken, if any.
 **
-** If the EXPRDUP_REDUCE flag is set, then the return value includes
-** space to duplicate all Expr nodes in the tree formed by Expr.pLeft
-** and Expr.pRight variables (but not for any structures pointed to or
-** descended from the Expr.x.pList or Expr.x.pSelect variables).
+** The return value includes space to duplicate all Expr nodes in the
+** tree formed by Expr.pLeft and Expr.pRight, but not any other
+** substructure such as Expr.x.pList, Expr.x.pSelect, and Expr.y.pWin.
 */
-static int dupedExprSize(const Expr *p, int flags){
-  int nByte = 0;
-  if( p ){
-    nByte = dupedExprNodeSize(p, flags);
-    if( flags&EXPRDUP_REDUCE ){
-      nByte += dupedExprSize(p->pLeft, flags) + dupedExprSize(p->pRight, flags);
-    }
-  }
+static int dupedExprSize(const Expr *p){
+  int nByte;
+  assert( p!=0 );
+  nByte = dupedExprNodeSize(p, EXPRDUP_REDUCE);
+  if( p->pLeft ) nByte += dupedExprSize(p->pLeft);
+  if( p->pRight ) nByte += dupedExprSize(p->pRight);
+  assert( nByte==ROUND8(nByte) );
   return nByte;
 }
 
 /*
-** This function is similar to sqlite3ExprDup(), except that if pzBuffer
-** is not NULL then *pzBuffer is assumed to point to a buffer large enough
-** to store the copy of expression p, the copies of p->u.zToken
-** (if applicable), and the copies of the p->pLeft and p->pRight expressions,
-** if any. Before returning, *pzBuffer is set to the first byte past the
-** portion of the buffer copied into by this function.
+** An EdupBuf is a memory allocation used to stored multiple Expr objects
+** together with their Expr.zToken content.  This is used to help implement
+** compression while doing sqlite3ExprDup().  The top-level Expr does the
+** allocation for itself and many of its decendents, then passes an instance
+** of the structure down into exprDup() so that they decendents can have
+** access to that memory.
 */
-static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){
+typedef struct EdupBuf EdupBuf;
+struct EdupBuf {
+  u8 *zAlloc;          /* Memory space available for storage */
+#ifdef SQLITE_DEBUG
+  u8 *zEnd;            /* First byte past the end of memory */
+#endif
+};
+
+/*
+** This function is similar to sqlite3ExprDup(), except that if pEdupBuf
+** is not NULL then it points to memory that can be used to store a copy
+** of the input Expr p together with its p->u.zToken (if any).  pEdupBuf
+** is updated with the new buffer tail prior to returning.
+*/
+static Expr *exprDup(
+  sqlite3 *db,          /* Database connection (for memory allocation) */
+  const Expr *p,        /* Expr tree to be duplicated */
+  int dupFlags,         /* EXPRDUP_REDUCE for compression.  0 if not */
+  EdupBuf *pEdupBuf     /* Preallocated storage space, or NULL */
+){
   Expr *pNew;           /* Value to return */
-  u8 *zAlloc;           /* Memory space from which to build Expr object */
+  EdupBuf sEdupBuf;     /* Memory space from which to build Expr object */
   u32 staticFlag;       /* EP_Static if space not obtained from malloc */
+  int nToken = -1;       /* Space needed for p->u.zToken.  -1 means unknown */
 
   assert( db!=0 );
   assert( p );
   assert( dupFlags==0 || dupFlags==EXPRDUP_REDUCE );
-  assert( pzBuffer==0 || dupFlags==EXPRDUP_REDUCE );
+  assert( pEdupBuf==0 || dupFlags==EXPRDUP_REDUCE );
 
   /* Figure out where to write the new Expr structure. */
-  if( pzBuffer ){
-    zAlloc = *pzBuffer;
+  if( pEdupBuf ){
+    sEdupBuf.zAlloc = pEdupBuf->zAlloc;
+#ifdef SQLITE_DEBUG
+    sEdupBuf.zEnd = pEdupBuf->zEnd;
+#endif
     staticFlag = EP_Static;
-    assert( zAlloc!=0 );
+    assert( sEdupBuf.zAlloc!=0 );
+    assert( dupFlags==EXPRDUP_REDUCE );
   }else{
-    zAlloc = sqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags));
+    int nAlloc;
+    if( dupFlags ){
+      nAlloc = dupedExprSize(p);
+    }else if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){
+      nToken = sqlite3Strlen30NN(p->u.zToken)+1;
+      nAlloc = ROUND8(EXPR_FULLSIZE + nToken);
+    }else{
+      nToken = 0;
+      nAlloc = ROUND8(EXPR_FULLSIZE);
+    }
+    assert( nAlloc==ROUND8(nAlloc) );
+    sEdupBuf.zAlloc = sqlite3DbMallocRawNN(db, nAlloc);
+#ifdef SQLITE_DEBUG
+    sEdupBuf.zEnd = sEdupBuf.zAlloc ? sEdupBuf.zAlloc+nAlloc : 0;
+#endif
+    
     staticFlag = 0;
   }
-  pNew = (Expr *)zAlloc;
+  pNew = (Expr *)sEdupBuf.zAlloc;
+  assert( EIGHT_BYTE_ALIGNMENT(pNew) );
 
   if( pNew ){
     /* Set nNewSize to the size allocated for the structure pointed to
@@ -1527,22 +1624,27 @@ static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){
     ** by the copy of the p->u.zToken string (if any).
     */
     const unsigned nStructSize = dupedExprStructSize(p, dupFlags);
-    const int nNewSize = nStructSize & 0xfff;
-    int nToken;
-    if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){
-      nToken = sqlite3Strlen30(p->u.zToken) + 1;
-    }else{
-      nToken = 0;
+    int nNewSize = nStructSize & 0xfff;
+    if( nToken<0 ){
+      if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){
+        nToken = sqlite3Strlen30(p->u.zToken) + 1;
+      }else{
+        nToken = 0;
+      }
     }
     if( dupFlags ){
+      assert( (int)(sEdupBuf.zEnd - sEdupBuf.zAlloc) >= nNewSize+nToken );
       assert( ExprHasProperty(p, EP_Reduced)==0 );
-      memcpy(zAlloc, p, nNewSize);
+      memcpy(sEdupBuf.zAlloc, p, nNewSize);
     }else{
       u32 nSize = (u32)exprStructSize(p);
-      memcpy(zAlloc, p, nSize);
+      assert( (int)(sEdupBuf.zEnd - sEdupBuf.zAlloc) >=
+                                                   (int)EXPR_FULLSIZE+nToken );
+      memcpy(sEdupBuf.zAlloc, p, nSize);
       if( nSize<EXPR_FULLSIZE ){
-        memset(&zAlloc[nSize], 0, EXPR_FULLSIZE-nSize);
+        memset(&sEdupBuf.zAlloc[nSize], 0, EXPR_FULLSIZE-nSize);
       }
+      nNewSize = EXPR_FULLSIZE;
     }
 
     /* Set the EP_Reduced, EP_TokenOnly, and EP_Static flags appropriately. */
@@ -1555,44 +1657,50 @@ static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){
     }
 
     /* Copy the p->u.zToken string, if any. */
-    if( nToken ){
-      char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize];
+    assert( nToken>=0 );
+    if( nToken>0 ){
+      char *zToken = pNew->u.zToken = (char*)&sEdupBuf.zAlloc[nNewSize];
       memcpy(zToken, p->u.zToken, nToken);
+      nNewSize += nToken;
     }
+    sEdupBuf.zAlloc += ROUND8(nNewSize);
+
+    if( ((p->flags|pNew->flags)&(EP_TokenOnly|EP_Leaf))==0 ){
 
-    if( 0==((p->flags|pNew->flags) & (EP_TokenOnly|EP_Leaf)) ){
       /* Fill in the pNew->x.pSelect or pNew->x.pList member. */
       if( ExprUseXSelect(p) ){
         pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, dupFlags);
       }else{
-        pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, dupFlags);
+        pNew->x.pList = sqlite3ExprListDup(db, p->x.pList,
+                           p->op!=TK_ORDER ? dupFlags : 0);
       }
-    }
 
-    /* Fill in pNew->pLeft and pNew->pRight. */
-    if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly|EP_WinFunc) ){
-      zAlloc += dupedExprNodeSize(p, dupFlags);
-      if( !ExprHasProperty(pNew, EP_TokenOnly|EP_Leaf) ){
-        pNew->pLeft = p->pLeft ?
-                      exprDup(db, p->pLeft, EXPRDUP_REDUCE, &zAlloc) : 0;
-        pNew->pRight = p->pRight ?
-                       exprDup(db, p->pRight, EXPRDUP_REDUCE, &zAlloc) : 0;
-      }
 #ifndef SQLITE_OMIT_WINDOWFUNC
       if( ExprHasProperty(p, EP_WinFunc) ){
         pNew->y.pWin = sqlite3WindowDup(db, pNew, p->y.pWin);
         assert( ExprHasProperty(pNew, EP_WinFunc) );
       }
 #endif /* SQLITE_OMIT_WINDOWFUNC */
-      if( pzBuffer ){
-        *pzBuffer = zAlloc;
-      }
-    }else{
-      if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){
-        if( pNew->op==TK_SELECT_COLUMN ){
+
+      /* Fill in pNew->pLeft and pNew->pRight. */
+      if( dupFlags ){
+        if( p->op==TK_SELECT_COLUMN ){
           pNew->pLeft = p->pLeft;
-          assert( p->pRight==0  || p->pRight==p->pLeft
-                                || ExprHasProperty(p->pLeft, EP_Subquery) );
+          assert( p->pRight==0 
+               || p->pRight==p->pLeft
+               || ExprHasProperty(p->pLeft, EP_Subquery) );
+        }else{
+          pNew->pLeft = p->pLeft ?
+                      exprDup(db, p->pLeft, EXPRDUP_REDUCE, &sEdupBuf) : 0;
+        }
+        pNew->pRight = p->pRight ?
+                       exprDup(db, p->pRight, EXPRDUP_REDUCE, &sEdupBuf) : 0;
+      }else{
+        if( p->op==TK_SELECT_COLUMN ){
+          pNew->pLeft = p->pLeft;
+          assert( p->pRight==0 
+               || p->pRight==p->pLeft
+               || ExprHasProperty(p->pLeft, EP_Subquery) );
         }else{
           pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0);
         }
@@ -1600,6 +1708,8 @@ static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){
       }
     }
   }
+  if( pEdupBuf ) memcpy(pEdupBuf, &sEdupBuf, sizeof(sEdupBuf));
+  assert( sEdupBuf.zAlloc <= sEdupBuf.zEnd );
   return pNew;
 }
 
@@ -1864,11 +1974,7 @@ Select *sqlite3SelectDup(sqlite3 *db, const Select *p, int flags){
 ** initially NULL, then create a new expression list.
 **
 ** The pList argument must be either NULL or a pointer to an ExprList
-** obtained from a prior call to sqlite3ExprListAppend().  This routine
-** may not be used with an ExprList obtained from sqlite3ExprListDup().
-** Reason:  This routine assumes that the number of slots in pList->a[]
-** is a power of two.  That is true for sqlite3ExprListAppend() returns
-** but is not necessarily true from the return value of sqlite3ExprListDup().
+** obtained from a prior call to sqlite3ExprListAppend().
 **
 ** If a memory allocation error occurs, the entire list is freed and
 ** NULL is returned.  If non-NULL is returned, then it is guaranteed
@@ -2694,6 +2800,27 @@ int sqlite3IsRowid(const char *z){
   return 0;
 }
 
+/*
+** Return a pointer to a buffer containing a usable rowid alias for table
+** pTab. An alias is usable if there is not an explicit user-defined column 
+** of the same name.
+*/
+const char *sqlite3RowidAlias(Table *pTab){
+  const char *azOpt[] = {"_ROWID_", "ROWID", "OID"};
+  int ii;
+  assert( VisibleRowid(pTab) );
+  for(ii=0; ii<ArraySize(azOpt); ii++){
+    int iCol;
+    for(iCol=0; iCol<pTab->nCol; iCol++){
+      if( sqlite3_stricmp(azOpt[ii], pTab->aCol[iCol].zCnName)==0 ) break;
+    }
+    if( iCol==pTab->nCol ){
+      return azOpt[ii];
+    }
+  }
+  return 0;
+}
+
 /*
 ** pX is the RHS of an IN operator.  If pX is a SELECT statement
 ** that can be simplified to a direct table access, then return
@@ -4231,6 +4358,41 @@ static SQLITE_NOINLINE int sqlite3IndexedExprLookup(
 }
 
 
+/*
+** Expresion pExpr is guaranteed to be a TK_COLUMN or equivalent. This
+** function checks the Parse.pIdxPartExpr list to see if this column
+** can be replaced with a constant value. If so, it generates code to
+** put the constant value in a register (ideally, but not necessarily, 
+** register iTarget) and returns the register number.
+**
+** Or, if the TK_COLUMN cannot be replaced by a constant, zero is 
+** returned.
+*/
+static int exprPartidxExprLookup(Parse *pParse, Expr *pExpr, int iTarget){
+  IndexedExpr *p;
+  for(p=pParse->pIdxPartExpr; p; p=p->pIENext){
+    if( pExpr->iColumn==p->iIdxCol && pExpr->iTable==p->iDataCur ){
+      Vdbe *v = pParse->pVdbe;
+      int addr = 0;
+      int ret;
+
+      if( p->bMaybeNullRow ){
+        addr = sqlite3VdbeAddOp1(v, OP_IfNullRow, p->iIdxCur);
+      }
+      ret = sqlite3ExprCodeTarget(pParse, p->pExpr, iTarget);
+      sqlite3VdbeAddOp4(pParse->pVdbe, OP_Affinity, ret, 1, 0,
+                        (const char*)&p->aff, 1);
+      if( addr ){
+        sqlite3VdbeJumpHere(v, addr);
+        sqlite3VdbeChangeP3(v, addr, ret);
+      }
+      return ret;
+    }
+  }
+  return 0;
+}
+
+
 /*
 ** Generate code into the current Vdbe to evaluate the given
 ** expression.  Attempt to store the results in register "target".
@@ -4267,6 +4429,7 @@ expr_code_doover:
     assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
     op = pExpr->op;
   }
+  assert( op!=TK_ORDER );
   switch( op ){
     case TK_AGG_COLUMN: {
       AggInfo *pAggInfo = pExpr->pAggInfo;
@@ -4280,7 +4443,7 @@ expr_code_doover:
 #ifdef SQLITE_VDBE_COVERAGE
         /* Verify that the OP_Null above is exercised by tests
         ** tag-20230325-2 */
-        sqlite3VdbeAddOp2(v, OP_NotNull, target, 1);
+        sqlite3VdbeAddOp3(v, OP_NotNull, target, 1, 20230325);
         VdbeCoverageNeverTaken(v);
 #endif
         break;
@@ -4388,6 +4551,11 @@ expr_code_doover:
           iTab = pParse->iSelfTab - 1;
         }
       }
+      else if( pParse->pIdxPartExpr 
+       && 0!=(r1 = exprPartidxExprLookup(pParse, pExpr, target))
+      ){
+        return r1;
+      }
       assert( ExprUseYTab(pExpr) );
       assert( pExpr->y.pTab!=0 );
       iReg = sqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab,
@@ -5048,7 +5216,7 @@ expr_code_doover:
 ** once. If no functions are involved, then factor the code out and put it at
 ** the end of the prepared statement in the initialization section.
 **
-** If regDest>=0 then the result is always stored in that register and the
+** If regDest>0 then the result is always stored in that register and the
 ** result is not reusable.  If regDest<0 then this routine is free to
 ** store the value wherever it wants.  The register where the expression
 ** is stored is returned.  When regDest<0, two identical expressions might
@@ -5063,6 +5231,7 @@ int sqlite3ExprCodeRunJustOnce(
 ){
   ExprList *p;
   assert( ConstFactorOk(pParse) );
+  assert( regDest!=0 );
   p = pParse->pConstExpr;
   if( regDest<0 && p ){
     struct ExprList_item *pItem;
@@ -6347,6 +6516,12 @@ int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){
   assert( pExpr->op==TK_AGG_FUNCTION );
   assert( ExprUseXList(pExpr) );
   sqlite3WalkExprList(&w, pExpr->x.pList);
+  if( pExpr->pLeft ){
+    assert( pExpr->pLeft->op==TK_ORDER );
+    assert( ExprUseXList(pExpr->pLeft) );
+    assert( pExpr->pLeft->x.pList!=0 );
+    sqlite3WalkExprList(&w, pExpr->pLeft->x.pList);
+  }
 #ifndef SQLITE_OMIT_WINDOWFUNC
   if( ExprHasProperty(pExpr, EP_WinFunc) ){
     sqlite3WalkExpr(&w, pExpr->y.pWin->pFilter);
@@ -6611,14 +6786,42 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
           u8 enc = ENC(pParse->db);
           i = addAggInfoFunc(pParse->db, pAggInfo);
           if( i>=0 ){
+            int nArg;
             assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
             pItem = &pAggInfo->aFunc[i];
             pItem->pFExpr = pExpr;
             assert( ExprUseUToken(pExpr) );
+            nArg = pExpr->x.pList ? pExpr->x.pList->nExpr : 0;
             pItem->pFunc = sqlite3FindFunction(pParse->db,
-                   pExpr->u.zToken,
-                   pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0);
-            if( pExpr->flags & EP_Distinct ){
+                                         pExpr->u.zToken, nArg, enc, 0);
+            assert( pItem->bOBUnique==0 );
+            if( pExpr->pLeft
+             && (pItem->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL)==0
+            ){
+              /* The NEEDCOLL test above causes any ORDER BY clause on
+              ** aggregate min() or max() to be ignored. */
+              ExprList *pOBList;
+              assert( nArg>0 );
+              assert( pExpr->pLeft->op==TK_ORDER );
+              assert( ExprUseXList(pExpr->pLeft) );
+              pItem->iOBTab = pParse->nTab++;
+              pOBList = pExpr->pLeft->x.pList;
+              assert( pOBList->nExpr>0 );
+              assert( pItem->bOBUnique==0 );
+              if( pOBList->nExpr==1
+               && nArg==1
+               && sqlite3ExprCompare(0,pOBList->a[0].pExpr,
+                               pExpr->x.pList->a[0].pExpr,0)==0
+              ){
+                pItem->bOBPayload = 0;
+                pItem->bOBUnique = ExprHasProperty(pExpr, EP_Distinct);
+              }else{
+                pItem->bOBPayload = 1;
+              }
+            }else{
+              pItem->iOBTab = -1;
+            }
+            if( ExprHasProperty(pExpr, EP_Distinct) && !pItem->bOBUnique ){
               pItem->iDistinct = pParse->nTab++;
             }else{
               pItem->iDistinct = -1;
diff --git a/libsql-sqlite3/src/fkey.c b/libsql-sqlite3/src/fkey.c
index 3142e0ca68..bace1ae5e2 100644
--- a/libsql-sqlite3/src/fkey.c
+++ b/libsql-sqlite3/src/fkey.c
@@ -858,6 +858,7 @@ static int isSetNullAction(Parse *pParse, FKey *pFKey){
     if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull)
      || (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull)
     ){
+      assert( (pTop->db->flags & SQLITE_FkNoAction)==0 );
       return 1;
     }
   }
@@ -1052,6 +1053,8 @@ void sqlite3FkCheck(
       }
       if( regOld!=0 ){
         int eAction = pFKey->aAction[aChange!=0];
+        if( (db->flags & SQLITE_FkNoAction) ) eAction = OE_None;
+
         fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1);
         /* If this is a deferred FK constraint, or a CASCADE or SET NULL
         ** action applies, then any foreign key violations caused by
@@ -1167,7 +1170,11 @@ int sqlite3FkRequired(
       /* Check if any parent key columns are being modified. */
       for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
         if( fkParentIsModified(pTab, p, aChange, chngRowid) ){
-          if( p->aAction[1]!=OE_None ) return 2;
+          if( (pParse->db->flags & SQLITE_FkNoAction)==0 
+           && p->aAction[1]!=OE_None 
+          ){
+            return 2;
+          }
           bHaveFK = 1;
         }
       }
@@ -1217,6 +1224,7 @@ static Trigger *fkActionTrigger(
   int iAction = (pChanges!=0);    /* 1 for UPDATE, 0 for DELETE */
 
   action = pFKey->aAction[iAction];
+  if( (db->flags & SQLITE_FkNoAction) ) action = OE_None;
   if( action==OE_Restrict && (db->flags & SQLITE_DeferFKs) ){
     return 0;
   }
diff --git a/libsql-sqlite3/src/func.c b/libsql-sqlite3/src/func.c
index a15c03d9ab..2cdc22b907 100644
--- a/libsql-sqlite3/src/func.c
+++ b/libsql-sqlite3/src/func.c
@@ -1260,7 +1260,8 @@ static void hexFunc(
       *(z++) = hexdigits[c&0xf];
     }
     *z = 0;
-    sqlite3_result_text(context, zHex, n*2, sqlite3_free);
+    sqlite3_result_text64(context, zHex, (u64)(z-zHex),
+                          sqlite3_free, SQLITE_UTF8);
   }
 }
 
@@ -1554,6 +1555,81 @@ static void trimFunc(
   sqlite3_result_text(context, (char*)zIn, nIn, SQLITE_TRANSIENT);
 }
 
+/* The core implementation of the CONCAT(...) and CONCAT_WS(SEP,...)
+** functions.
+**
+** Return a string value that is the concatenation of all non-null
+** entries in argv[].  Use zSep as the separator.
+*/
+static void concatFuncCore(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv,
+  int nSep,
+  const char *zSep
+){
+  i64 j, k, n = 0;
+  int i;
+  char *z;
+  for(i=0; i<argc; i++){
+    n += sqlite3_value_bytes(argv[i]);
+  }
+  n += (argc-1)*nSep;
+  z = sqlite3_malloc64(n+1);
+  if( z==0 ){
+    sqlite3_result_error_nomem(context);
+    return;
+  }
+  j = 0;
+  for(i=0; i<argc; i++){
+    k = sqlite3_value_bytes(argv[i]);
+    if( k>0 ){
+      const char *v = (const char*)sqlite3_value_text(argv[i]);
+      if( v!=0 ){
+        if( j>0 && nSep>0 ){
+          memcpy(&z[j], zSep, nSep);
+          j += nSep;
+        }
+        memcpy(&z[j], v, k);
+        j += k;
+      }
+    }
+  }
+  z[j] = 0;
+  assert( j<=n );
+  sqlite3_result_text64(context, z, j, sqlite3_free, SQLITE_UTF8);
+}
+
+/*
+** The CONCAT(...) function.  Generate a string result that is the
+** concatentation of all non-null arguments.
+*/
+static void concatFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  concatFuncCore(context, argc, argv, 0, "");
+}
+
+/*
+** The CONCAT_WS(separator, ...) function.
+**
+** Generate a string that is the concatenation of 2nd through the Nth
+** argument.  Use the first argument (which must be non-NULL) as the
+** separator.
+*/
+static void concatwsFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  int nSep = sqlite3_value_bytes(argv[0]);
+  const char *zSep = (const char*)sqlite3_value_text(argv[0]);
+  if( zSep==0 ) return;
+  concatFuncCore(context, argc-1, argv+1, nSep, zSep);
+}
+
 
 #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
 /*
@@ -1820,8 +1896,10 @@ static void sumFinalize(sqlite3_context *context){
     if( p->approx ){
       if( p->ovrfl ){
         sqlite3_result_error(context,"integer overflow",-1);
-      }else{
+      }else if( !sqlite3IsNaN(p->rErr) ){
         sqlite3_result_double(context, p->rSum+p->rErr);
+      }else{
+        sqlite3_result_double(context, p->rSum);
       }
     }else{
       sqlite3_result_int64(context, p->iSum);
@@ -1834,7 +1912,8 @@ static void avgFinalize(sqlite3_context *context){
   if( p && p->cnt>0 ){
     double r;
     if( p->approx ){
-      r = p->rSum+p->rErr;
+      r = p->rSum;
+      if( !sqlite3IsNaN(p->rErr) ) r += p->rErr;
     }else{
       r = (double)(p->iSum);
     }
@@ -1847,7 +1926,8 @@ static void totalFinalize(sqlite3_context *context){
   p = sqlite3_aggregate_context(context, 0);
   if( p ){
     if( p->approx ){
-      r = p->rSum+p->rErr;
+      r = p->rSum;
+      if( !sqlite3IsNaN(p->rErr) ) r += p->rErr;
     }else{
       r = (double)(p->iSum);
     }
@@ -1971,6 +2051,7 @@ static void minMaxFinalize(sqlite3_context *context){
 
 /*
 ** group_concat(EXPR, ?SEPARATOR?)
+** string_agg(EXPR, SEPARATOR)
 **
 ** The SEPARATOR goes before the EXPR string.  This is tragic.  The
 ** groupConcatInverse() implementation would have been easier if the
@@ -2692,6 +2773,11 @@ void sqlite3RegisterBuiltinFunctions(void){
     FUNCTION(hex,                1, 0, 0, hexFunc          ),
     FUNCTION(unhex,              1, 0, 0, unhexFunc        ),
     FUNCTION(unhex,              2, 0, 0, unhexFunc        ),
+    FUNCTION(concat,            -1, 0, 0, concatFunc       ),
+    FUNCTION(concat,             0, 0, 0, 0                ),
+    FUNCTION(concat_ws,         -1, 0, 0, concatwsFunc     ),
+    FUNCTION(concat_ws,          0, 0, 0, 0                ),
+    FUNCTION(concat_ws,          1, 0, 0, 0                ),
     INLINE_FUNC(ifnull,          2, INLINEFUNC_coalesce, 0 ),
     VFUNCTION(random,            0, 0, 0, randomFunc       ),
     VFUNCTION(randomblob,        1, 0, 0, randomBlob       ),
@@ -2721,6 +2807,8 @@ void sqlite3RegisterBuiltinFunctions(void){
         groupConcatFinalize, groupConcatValue, groupConcatInverse, 0),
     WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep,
         groupConcatFinalize, groupConcatValue, groupConcatInverse, 0),
+    WAGGREGATE(string_agg,   2, 0, 0, groupConcatStep,
+        groupConcatFinalize, groupConcatValue, groupConcatInverse, 0),
  
     LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE),
 #ifdef SQLITE_CASE_SENSITIVE_LIKE
diff --git a/libsql-sqlite3/src/json.c b/libsql-sqlite3/src/json.c
index f8d4aa2a77..c2129a026e 100644
--- a/libsql-sqlite3/src/json.c
+++ b/libsql-sqlite3/src/json.c
@@ -602,7 +602,7 @@ static void jsonResult(JsonString *p){
     }else if( jsonForceRCStr(p) ){
       sqlite3RCStrRef(p->zBuf);
       sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed,
-                            (void(*)(void*))sqlite3RCStrUnref,
+                            sqlite3RCStrUnref,
                             SQLITE_UTF8);
     }
   }
@@ -1942,7 +1942,7 @@ static JsonParse *jsonParseCached(
   /* The input JSON was not found anywhere in the cache.  We will need
   ** to parse it ourselves and generate a new JsonParse object.
   */
-  bJsonRCStr = sqlite3ValueIsOfClass(pJson,(void(*)(void*))sqlite3RCStrUnref);
+  bJsonRCStr = sqlite3ValueIsOfClass(pJson,sqlite3RCStrUnref);
   p = sqlite3_malloc64( sizeof(*p) + (bJsonRCStr ? 0 : nJson+1) );
   if( p==0 ){
     sqlite3_result_error_nomem(pCtx);
@@ -2156,6 +2156,7 @@ static JsonNode *jsonLookupStep(
         if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i--;
         j += jsonNodeSize(&pRoot[j]);
       }
+      if( i==0 && j<=pRoot->n ) break;
       if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break;
       if( pParse->useMod==0 ) break;
       assert( pRoot->eU==2 );
@@ -2484,7 +2485,9 @@ static void jsonArrayLengthFunc(
   }
   if( pNode->eType==JSON_ARRAY ){
     while( 1 /*exit-by-break*/ ){
-      for(i=1; i<=pNode->n; n++){
+      i = 1;
+      while( i<=pNode->n ){
+        if( (pNode[i].jnFlags & JNODE_REMOVE)==0 ) n++;
         i += jsonNodeSize(&pNode[i]);
       }
       if( (pNode->jnFlags & JNODE_APPEND)==0 ) break;
@@ -2841,11 +2844,13 @@ static void jsonReplaceNode(
          break;
       }
       if( sqlite3_value_subtype(pValue)!=JSON_SUBTYPE ){
-        char *zCopy = sqlite3DbStrDup(0, z);
+        char *zCopy = sqlite3_malloc64( n+1 );
         int k;
         if( zCopy ){
+          memcpy(zCopy, z, n);
+          zCopy[n] = 0;
           jsonParseAddCleanup(p, sqlite3_free, zCopy);
-       }else{
+        }else{
           p->oom = 1;
           sqlite3_result_error_nomem(pCtx);
         }
@@ -2900,6 +2905,7 @@ static void jsonReplaceFunc(
   }
   pParse = jsonParseCached(ctx, argv[0], ctx, argc>1);
   if( pParse==0 ) return;
+  pParse->nJPRef++;
   for(i=1; i<(u32)argc; i+=2){
     zPath = (const char*)sqlite3_value_text(argv[i]);
     pParse->useMod = 1;
@@ -2912,6 +2918,7 @@ static void jsonReplaceFunc(
   jsonReturnJson(pParse, pParse->aNode, ctx, 1);
 replace_err:
   jsonDebugPrintParse(pParse);
+  jsonParseFree(pParse);
 }
 
 
@@ -2946,6 +2953,7 @@ static void jsonSetFunc(
   }
   pParse = jsonParseCached(ctx, argv[0], ctx, argc>1);
   if( pParse==0 ) return;
+  pParse->nJPRef++;
   for(i=1; i<(u32)argc; i+=2){
     zPath = (const char*)sqlite3_value_text(argv[i]);
     bApnd = 0;
@@ -2962,9 +2970,8 @@ static void jsonSetFunc(
   }
   jsonDebugPrintParse(pParse);
   jsonReturnJson(pParse, pParse->aNode, ctx, 1);
-
 jsonSetDone:
-  /* no cleanup required */;
+  jsonParseFree(pParse);
 }
 
 /*
@@ -3120,7 +3127,7 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){
     }else if( isFinal ){
       sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed,
                           pStr->bStatic ? SQLITE_TRANSIENT :
-                              (void(*)(void*))sqlite3RCStrUnref);
+                              sqlite3RCStrUnref);
       pStr->bStatic = 1;
     }else{
       sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);
@@ -3229,7 +3236,7 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){
     }else if( isFinal ){
       sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed,
                           pStr->bStatic ? SQLITE_TRANSIENT :
-                          (void(*)(void*))sqlite3RCStrUnref);
+                          sqlite3RCStrUnref);
       pStr->bStatic = 1;
     }else{
       sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);
@@ -3661,7 +3668,7 @@ static int jsonEachFilter(
   if( z==0 ) return SQLITE_OK;
   memset(&p->sParse, 0, sizeof(p->sParse));
   p->sParse.nJPRef = 1;
-  if( sqlite3ValueIsOfClass(argv[0], (void(*)(void*))sqlite3RCStrUnref) ){
+  if( sqlite3ValueIsOfClass(argv[0], sqlite3RCStrUnref) ){
     p->sParse.zJson = sqlite3RCStrRef((char*)z);
   }else{
     n = sqlite3_value_bytes(argv[0]);
@@ -3756,7 +3763,8 @@ static sqlite3_module jsonEachModule = {
   0,                         /* xSavepoint */
   0,                         /* xRelease */
   0,                         /* xRollbackTo */
-  0                          /* xShadowName */
+  0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 /* The methods of the json_tree virtual table. */
@@ -3784,7 +3792,8 @@ static sqlite3_module jsonTreeModule = {
   0,                         /* xSavepoint */
   0,                         /* xRelease */
   0,                         /* xRollbackTo */
-  0                          /* xShadowName */
+  0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
 #endif /* !defined(SQLITE_OMIT_JSON) */
diff --git a/libsql-sqlite3/src/loadext.c b/libsql-sqlite3/src/loadext.c
index be7fd691c9..d315a07bb9 100644
--- a/libsql-sqlite3/src/loadext.c
+++ b/libsql-sqlite3/src/loadext.c
@@ -514,7 +514,10 @@ static const sqlite3_api_routines sqlite3Apis = {
   /* Version 3.41.0 and later */
   sqlite3_is_interrupted,
   /* Version 3.43.0 and later */
-  sqlite3_stmt_explain
+  sqlite3_stmt_explain,
+  /* Version 3.44.0 and later */
+  sqlite3_get_clientdata,
+  sqlite3_set_clientdata
 };
 
 static const libsql_api_routines libsqlApis = {
@@ -736,6 +739,9 @@ void sqlite3CloseExtensions(sqlite3 *db){
 ** default so as not to open security holes in older applications.
 */
 int sqlite3_enable_load_extension(sqlite3 *db, int onoff){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
   sqlite3_mutex_enter(db->mutex);
   if( onoff ){
     db->flags |= SQLITE_LoadExtension|SQLITE_LoadExtFunc;
@@ -757,7 +763,7 @@ int sqlite3_enable_load_extension(sqlite3 *db, int onoff){
 */
 typedef struct sqlite3AutoExtList sqlite3AutoExtList;
 static SQLITE_WSD struct sqlite3AutoExtList {
-  u32 nExt;              /* Number of entries in aExt[] */          
+  u32 nExt;              /* Number of entries in aExt[] */
   void (**aExt)(void);   /* Pointers to the extension init functions */
 } sqlite3Autoext = { 0, 0 };
 
@@ -785,6 +791,9 @@ int sqlite3_auto_extension(
   void (*xInit)(void)
 ){
   int rc = SQLITE_OK;
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( xInit==0 ) return SQLITE_MISUSE_BKPT;
+#endif
 #ifndef SQLITE_OMIT_AUTOINIT
   rc = sqlite3_initialize();
   if( rc ){
@@ -837,6 +846,9 @@ int sqlite3_cancel_auto_extension(
   int i;
   int n = 0;
   wsdAutoextInit;
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( xInit==0 ) return 0;
+#endif
   sqlite3_mutex_enter(mutex);
   for(i=(int)wsdAutoext.nExt-1; i>=0; i--){
     if( wsdAutoext.aExt[i]==xInit ){
diff --git a/libsql-sqlite3/src/main.c b/libsql-sqlite3/src/main.c
index 18d88d84c6..3ceaadce37 100644
--- a/libsql-sqlite3/src/main.c
+++ b/libsql-sqlite3/src/main.c
@@ -47,7 +47,9 @@ int sqlite3Fts5Init(sqlite3*);
 #ifdef SQLITE_ENABLE_STMTVTAB
 int sqlite3StmtVtabInit(sqlite3*);
 #endif
-
+#ifdef SQLITE_EXTRA_AUTOEXT
+int SQLITE_EXTRA_AUTOEXT(sqlite3*);
+#endif
 /*
 ** An array of pointers to extension initializer functions for
 ** built-in extensions.
@@ -81,6 +83,9 @@ static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = {
 #ifdef SQLITE_ENABLE_BYTECODE_VTAB
   sqlite3VdbeBytecodeVtabInit,
 #endif
+#ifdef SQLITE_EXTRA_AUTOEXT
+  SQLITE_EXTRA_AUTOEXT,
+#endif
 };
 
 #ifndef SQLITE_AMALGAMATION
@@ -157,6 +162,32 @@ char *sqlite3_temp_directory = 0;
 */
 char *sqlite3_data_directory = 0;
 
+/*
+** Determine whether or not high-precision (long double) floating point
+** math works correctly on CPU currently running.
+*/
+static SQLITE_NOINLINE int hasHighPrecisionDouble(int rc){
+  if( sizeof(LONGDOUBLE_TYPE)<=8 ){
+    /* If the size of "long double" is not more than 8, then
+    ** high-precision math is not possible. */
+    return 0;
+  }else{
+    /* Just because sizeof(long double)>8 does not mean that the underlying
+    ** hardware actually supports high-precision floating point.  For example,
+    ** clearing the 0x100 bit in the floating-point control word on Intel
+    ** processors will make long double work like double, even though long
+    ** double takes up more space.  The only way to determine if long double
+    ** actually works is to run an experiment. */
+    LONGDOUBLE_TYPE a, b, c;
+    rc++;
+    a = 1.0+rc*0.1;
+    b = 1.0e+18+rc*25.0;
+    c = a+b;
+    return b!=c;
+  }
+}
+
+
 /*
 ** Initialize SQLite. 
 **
@@ -352,6 +383,12 @@ int sqlite3_initialize(void){
   }
 #endif
 
+  /* Experimentally determine if high-precision floating point is
+  ** available. */
+#ifndef SQLITE_OMIT_WSD
+  sqlite3Config.bUseLongDouble = hasHighPrecisionDouble(rc);
+#endif
+
   return rc;
 }
 
@@ -922,6 +959,10 @@ int sqlite3_db_cacheflush(sqlite3 *db){
 int sqlite3_db_config(sqlite3 *db, int op, ...){
   va_list ap;
   int rc;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
   sqlite3_mutex_enter(db->mutex);
   va_start(ap, op);
   switch( op ){
@@ -1255,6 +1296,14 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){
   }
 #endif
 
+  while( db->pDbData ){
+    DbClientData *p = db->pDbData;
+    db->pDbData = p->pNext;
+    assert( p->pData!=0 );
+    if( p->xDestructor ) p->xDestructor(p->pData);
+    sqlite3_free(p);
+  }
+
   /* Convert the connection into a zombie and then close it.
   */
   db->eOpenState = SQLITE_STATE_ZOMBIE;
@@ -2334,6 +2383,12 @@ void *sqlite3_preupdate_hook(
   void *pArg                /* First callback argument */
 ){
   void *pRet;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( db==0 ){
+    return 0;
+  }
+#endif
   sqlite3_mutex_enter(db->mutex);
   pRet = db->pPreUpdateArg;
   db->xPreUpdateCallback = xCallback;
@@ -2498,7 +2553,7 @@ int sqlite3_wal_checkpoint_v2(
   if( eMode<SQLITE_CHECKPOINT_PASSIVE || eMode>SQLITE_CHECKPOINT_TRUNCATE ){
     /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint
     ** mode: */
-    return SQLITE_MISUSE;
+    return SQLITE_MISUSE_BKPT;
   }
 
   sqlite3_mutex_enter(db->mutex);
@@ -3781,6 +3836,69 @@ int sqlite3_collation_needed16(
 }
 #endif /* SQLITE_OMIT_UTF16 */
 
+/*
+** Find existing client data.
+*/
+void *sqlite3_get_clientdata(sqlite3 *db, const char *zName){
+  DbClientData *p;
+  sqlite3_mutex_enter(db->mutex);
+  for(p=db->pDbData; p; p=p->pNext){
+    if( strcmp(p->zName, zName)==0 ){
+      void *pResult = p->pData;
+      sqlite3_mutex_leave(db->mutex);
+      return pResult;
+    }
+  }
+  sqlite3_mutex_leave(db->mutex);
+  return 0;
+}
+
+/*
+** Add new client data to a database connection.
+*/
+int sqlite3_set_clientdata(
+  sqlite3 *db,                   /* Attach client data to this connection */
+  const char *zName,             /* Name of the client data */
+  void *pData,                   /* The client data itself */
+  void (*xDestructor)(void*)     /* Destructor */
+){
+  DbClientData *p, **pp;
+  sqlite3_mutex_enter(db->mutex);
+  pp = &db->pDbData;
+  for(p=db->pDbData; p && strcmp(p->zName,zName); p=p->pNext){
+    pp = &p->pNext;
+  }
+  if( p ){
+    assert( p->pData!=0 );
+    if( p->xDestructor ) p->xDestructor(p->pData);
+    if( pData==0 ){
+      *pp = p->pNext;
+      sqlite3_free(p);
+      sqlite3_mutex_leave(db->mutex);
+      return SQLITE_OK;
+    }
+  }else if( pData==0 ){
+    sqlite3_mutex_leave(db->mutex);
+    return SQLITE_OK;
+  }else{
+    size_t n = strlen(zName);
+    p = sqlite3_malloc64( sizeof(DbClientData)+n+1 );
+    if( p==0 ){
+      if( xDestructor ) xDestructor(pData);
+      sqlite3_mutex_leave(db->mutex);
+      return SQLITE_NOMEM;
+    }
+    memcpy(p->zName, zName, n+1);
+    p->pNext = db->pDbData;
+    db->pDbData = p;
+  }
+  p->pData = pData;
+  p->xDestructor = xDestructor;
+  sqlite3_mutex_leave(db->mutex);
+  return SQLITE_OK;
+}
+
+
 #ifndef SQLITE_OMIT_DEPRECATED
 /*
 ** This function is now an anachronism. It used to be used to recover from a
@@ -4135,6 +4253,28 @@ int sqlite3_test_control(int op, ...){
     }
 #endif
 
+    /*  sqlite3_test_control(SQLITE_TESTCTRL_FK_NO_ACTION, sqlite3 *db, int b);
+    **
+    ** If b is true, then activate the SQLITE_FkNoAction setting.  If b is
+    ** false then clearn that setting.  If the SQLITE_FkNoAction setting is
+    ** abled, all foreign key ON DELETE and ON UPDATE actions behave as if
+    ** they were NO ACTION, regardless of how they are defined.
+    **
+    ** NB:  One must usually run "PRAGMA writable_schema=RESET" after
+    ** using this test-control, before it will take full effect.  failing
+    ** to reset the schema can result in some unexpected behavior.
+    */
+    case SQLITE_TESTCTRL_FK_NO_ACTION: {
+      sqlite3 *db = va_arg(ap, sqlite3*);
+      int b = va_arg(ap, int);
+      if( b ){
+        db->flags |= SQLITE_FkNoAction;
+      }else{
+        db->flags &= ~SQLITE_FkNoAction;
+      }
+      break;
+    }
+
     /*
     **  sqlite3_test_control(BITVEC_TEST, size, program)
     **
@@ -4559,11 +4699,11 @@ int sqlite3_test_control(int op, ...){
     **   X<0     Make no changes to the bUseLongDouble.  Just report value.
     **   X==0    Disable bUseLongDouble
     **   X==1    Enable bUseLongDouble
-    **   X==2    Set bUseLongDouble to its default value for this platform
+    **   X>=2    Set bUseLongDouble to its default value for this platform
     */
     case SQLITE_TESTCTRL_USELONGDOUBLE: {
       int b = va_arg(ap, int);
-      if( b==2 ) b = sizeof(LONGDOUBLE_TYPE)>8;
+      if( b>=2 ) b = hasHighPrecisionDouble(b);
       if( b>=0 ) sqlite3Config.bUseLongDouble = b>0;
       rc = sqlite3Config.bUseLongDouble!=0;
       break;
@@ -4977,7 +5117,7 @@ int sqlite3_compileoption_used(const char *zOptName){
   int nOpt;
   const char **azCompileOpt;
 
-#if SQLITE_ENABLE_API_ARMOR
+#ifdef SQLITE_ENABLE_API_ARMOR
   if( zOptName==0 ){
     (void)SQLITE_MISUSE_BKPT;
     return 0;
diff --git a/libsql-sqlite3/src/malloc.c b/libsql-sqlite3/src/malloc.c
index 48c4600606..356750682e 100644
--- a/libsql-sqlite3/src/malloc.c
+++ b/libsql-sqlite3/src/malloc.c
@@ -896,5 +896,5 @@ int sqlite3ApiExit(sqlite3* db, int rc){
   if( db->mallocFailed || rc ){
     return apiHandleError(db, rc);
   }
-  return rc & db->errMask;
+  return 0;
 }
diff --git a/libsql-sqlite3/src/mutex.c b/libsql-sqlite3/src/mutex.c
index 13a9fca15b..381ffbdfd5 100644
--- a/libsql-sqlite3/src/mutex.c
+++ b/libsql-sqlite3/src/mutex.c
@@ -133,7 +133,7 @@ static void checkMutexFree(sqlite3_mutex *p){
   assert( SQLITE_MUTEX_FAST<2 );
   assert( SQLITE_MUTEX_WARNONCONTENTION<2 );
 
-#if SQLITE_ENABLE_API_ARMOR
+#ifdef SQLITE_ENABLE_API_ARMOR
   if( ((CheckMutex*)p)->iType<2 )
 #endif
   {
diff --git a/libsql-sqlite3/src/mutex_unix.c b/libsql-sqlite3/src/mutex_unix.c
index ac4331a67b..beae877f98 100644
--- a/libsql-sqlite3/src/mutex_unix.c
+++ b/libsql-sqlite3/src/mutex_unix.c
@@ -223,7 +223,7 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
 */
 static void pthreadMutexFree(sqlite3_mutex *p){
   assert( p->nRef==0 );
-#if SQLITE_ENABLE_API_ARMOR
+#ifdef SQLITE_ENABLE_API_ARMOR
   if( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE )
 #endif
   {
diff --git a/libsql-sqlite3/src/notify.c b/libsql-sqlite3/src/notify.c
index 4960ab76b1..6a4cab8755 100644
--- a/libsql-sqlite3/src/notify.c
+++ b/libsql-sqlite3/src/notify.c
@@ -152,6 +152,9 @@ int sqlite3_unlock_notify(
 ){
   int rc = SQLITE_OK;
 
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
   sqlite3_mutex_enter(db->mutex);
   enterMutex();
 
diff --git a/libsql-sqlite3/src/os_unix.c b/libsql-sqlite3/src/os_unix.c
index 59f67d142a..a33e6f4dff 100644
--- a/libsql-sqlite3/src/os_unix.c
+++ b/libsql-sqlite3/src/os_unix.c
@@ -3150,9 +3150,6 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) {
   unixInodeInfo *pInode;
   afpLockingContext *context = (afpLockingContext *) pFile->lockingContext;
   int skipShared = 0;
-#ifdef SQLITE_TEST
-  int h = pFile->h;
-#endif
 
   assert( pFile );
   OSTRACE(("UNLOCK  %d %d was %d(%d,%d) pid=%d (afp)\n", pFile->h, eFileLock,
@@ -3168,9 +3165,6 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) {
   assert( pInode->nShared!=0 );
   if( pFile->eFileLock>SHARED_LOCK ){
     assert( pInode->eFileLock==pFile->eFileLock );
-    SimulateIOErrorBenign(1);
-    SimulateIOError( h=(-1) )
-    SimulateIOErrorBenign(0);
   
 #ifdef SQLITE_DEBUG
     /* When reducing a lock such that other processes can start
@@ -3219,9 +3213,6 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) {
     unsigned long long sharedLockByte = SHARED_FIRST+pInode->sharedByte;
     pInode->nShared--;
     if( pInode->nShared==0 ){
-      SimulateIOErrorBenign(1);
-      SimulateIOError( h=(-1) )
-      SimulateIOErrorBenign(0);
       if( !skipShared ){
         rc = afpSetLock(context->dbPath, pFile, sharedLockByte, 1, 0);
       }
diff --git a/libsql-sqlite3/src/pager.c b/libsql-sqlite3/src/pager.c
index 42c3372cd7..72c9d22099 100644
--- a/libsql-sqlite3/src/pager.c
+++ b/libsql-sqlite3/src/pager.c
@@ -1494,9 +1494,32 @@ static int writeJournalHdr(Pager *pPager){
     memset(zHeader, 0, sizeof(aJournalMagic)+4);
   }
 
+
+
   /* The random check-hash initializer */
-  sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit);
+  if( pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){
+    sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit);
+  }
+#ifdef SQLITE_DEBUG
+  else{
+    /* The Pager.cksumInit variable is usually randomized above to protect
+    ** against there being existing records in the journal file. This is
+    ** dangerous, as following a crash they may be mistaken for records
+    ** written by the current transaction and rolled back into the database
+    ** file, causing corruption. The following assert statements verify
+    ** that this is not required in "journal_mode=memory" mode, as in that
+    ** case the journal file is always 0 bytes in size at this point. 
+    ** It is advantageous to avoid the sqlite3_randomness() call if possible 
+    ** as it takes the global PRNG mutex.  */
+    i64 sz = 0;
+    sqlite3OsFileSize(pPager->jfd, &sz);
+    assert( sz==0 );
+    assert( pPager->journalOff==journalHdrOffset(pPager) );
+    assert( sqlite3JournalIsInMemory(pPager->jfd) );
+  }
+#endif
   put32bits(&zHeader[sizeof(aJournalMagic)+4], pPager->cksumInit);
+
   /* The initial database size */
   put32bits(&zHeader[sizeof(aJournalMagic)+8], pPager->dbOrigSize);
   /* The assumed sector size for this process */
@@ -2148,6 +2171,9 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){
   return (rc==SQLITE_OK?rc2:rc);
 }
 
+/* Forward reference */
+static int pager_playback(Pager *pPager, int isHot);
+
 /*
 ** Execute a rollback if a transaction is active and unlock the
 ** database file.
@@ -2176,6 +2202,21 @@ static void pagerUnlockAndRollback(Pager *pPager){
       assert( pPager->eState==PAGER_READER );
       pager_end_transaction(pPager, 0, 0);
     }
+  }else if( pPager->eState==PAGER_ERROR
+         && pPager->journalMode==PAGER_JOURNALMODE_MEMORY
+         && isOpen(pPager->jfd)
+  ){
+    /* Special case for a ROLLBACK due to I/O error with an in-memory
+    ** journal:  We have to rollback immediately, before the journal is
+    ** closed, because once it is closed, all content is forgotten. */
+    int errCode = pPager->errCode;
+    u8 eLock = pPager->eLock;
+    pPager->eState = PAGER_OPEN;
+    pPager->errCode = SQLITE_OK;
+    pPager->eLock = EXCLUSIVE_LOCK;
+    pager_playback(pPager, 1);
+    pPager->errCode = errCode;
+    pPager->eLock = eLock;
   }
   pager_unlock(pPager);
 }
@@ -5695,8 +5736,20 @@ int sqlite3PagerGet(
   DbPage **ppPage,    /* Write a pointer to the page here */
   int flags           /* PAGER_GET_XXX flags */
 ){
-  /* printf("PAGE %u\n", pgno); fflush(stdout); */
+#if 0   /* Trace page fetch by setting to 1 */
+  int rc;
+  printf("PAGE %u\n", pgno);
+  fflush(stdout);
+  rc = pPager->xGet(pPager, pgno, ppPage, flags);
+  if( rc ){ 
+    printf("PAGE %u failed with 0x%02x\n", pgno, rc);
+    fflush(stdout);
+  }
+  return rc;
+#else
+  /* Normal, high-speed version of sqlite3PagerGet() */
   return pPager->xGet(pPager, pgno, ppPage, flags);
+#endif
 }
 
 /*
@@ -6574,6 +6627,13 @@ int sqlite3PagerCommitPhaseOne(
         rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, 0);
         if( rc==SQLITE_OK ){
           rc = pager_write_pagelist(pPager, pList);
+          if( rc==SQLITE_OK && pPager->dbSize>pPager->dbFileSize ){
+            char *pTmp = pPager->pTmpSpace;
+            int szPage = (int)pPager->pageSize;
+            memset(pTmp, 0, szPage);
+            rc = sqlite3OsWrite(pPager->fd, pTmp, szPage,
+                      ((i64)pPager->dbSize*pPager->pageSize)-szPage);
+          }
           if( rc==SQLITE_OK ){
             rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE, 0);
           }
@@ -7405,7 +7465,7 @@ int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){
         }
         assert( state==pPager->eState );
       }
-    }else if( eMode==PAGER_JOURNALMODE_OFF ){
+    }else if( eMode==PAGER_JOURNALMODE_OFF || eMode==PAGER_JOURNALMODE_MEMORY ){
       sqlite3OsClose(pPager->jfd);
     }
   }
diff --git a/libsql-sqlite3/src/parse.y b/libsql-sqlite3/src/parse.y
index 088071d62e..07a0929d66 100644
--- a/libsql-sqlite3/src/parse.y
+++ b/libsql-sqlite3/src/parse.y
@@ -1176,6 +1176,10 @@ expr(A) ::= CAST LP expr(E) AS typetoken(T) RP. {
 expr(A) ::= idj(X) LP distinct(D) exprlist(Y) RP. {
   A = sqlite3ExprFunction(pParse, Y, &X, D);
 }
+expr(A) ::= idj(X) LP distinct(D) exprlist(Y) ORDER BY sortlist(O) RP. {
+  A = sqlite3ExprFunction(pParse, Y, &X, D);
+  sqlite3ExprAddFunctionOrderBy(pParse, A, O);
+}
 expr(A) ::= idj(X) LP STAR RP. {
   A = sqlite3ExprFunction(pParse, 0, &X, 0);
 }
@@ -1185,6 +1189,11 @@ expr(A) ::= idj(X) LP distinct(D) exprlist(Y) RP filter_over(Z). {
   A = sqlite3ExprFunction(pParse, Y, &X, D);
   sqlite3WindowAttach(pParse, A, Z);
 }
+expr(A) ::= idj(X) LP distinct(D) exprlist(Y) ORDER BY sortlist(O) RP filter_over(Z). {
+  A = sqlite3ExprFunction(pParse, Y, &X, D);
+  sqlite3WindowAttach(pParse, A, Z);
+  sqlite3ExprAddFunctionOrderBy(pParse, A, O);
+}
 expr(A) ::= idj(X) LP STAR RP filter_over(Z). {
   A = sqlite3ExprFunction(pParse, 0, &X, 0);
   sqlite3WindowAttach(pParse, A, Z);
diff --git a/libsql-sqlite3/src/pragma.c b/libsql-sqlite3/src/pragma.c
index f15c1be279..90076b0c26 100644
--- a/libsql-sqlite3/src/pragma.c
+++ b/libsql-sqlite3/src/pragma.c
@@ -1124,7 +1124,11 @@ void sqlite3Pragma(
 #endif
 
       if( sqlite3GetBoolean(zRight, 0) ){
-        db->flags |= mask;
+        if( (mask & SQLITE_WriteSchema)==0
+         || (db->flags & SQLITE_Defensive)==0
+        ){
+          db->flags |= mask;
+        }
       }else{
         db->flags &= ~mask;
         if( mask==SQLITE_DeferFKs ) db->nDeferredImmCons = 0;
@@ -1757,8 +1761,31 @@ void sqlite3Pragma(
         int r2;                 /* Previous key for WITHOUT ROWID tables */
         int mxCol;              /* Maximum non-virtual column number */
 
-        if( !IsOrdinaryTable(pTab) ) continue;
         if( pObjTab && pObjTab!=pTab ) continue;
+        if( !IsOrdinaryTable(pTab) ){
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+          sqlite3_vtab *pVTab;
+          int a1;
+          if( !IsVirtual(pTab) ) continue;
+          if( pTab->nCol<=0 ){
+            const char *zMod = pTab->u.vtab.azArg[0];
+            if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue;
+          }
+          sqlite3ViewGetColumnNames(pParse, pTab);
+          if( pTab->u.vtab.p==0 ) continue;
+          pVTab = pTab->u.vtab.p->pVtab;
+          if( NEVER(pVTab==0) ) continue;
+          if( NEVER(pVTab->pModule==0) ) continue;
+          if( pVTab->pModule->iVersion<4 ) continue;
+          if( pVTab->pModule->xIntegrity==0 ) continue;
+          sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick);
+          sqlite3VdbeAppendP4(v, pTab, P4_TABLE);
+          a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v);
+          integrityCheckResultRow(v);
+          sqlite3VdbeJumpHere(v, a1);
+#endif
+          continue;
+        }
         if( isQuick || HasRowid(pTab) ){
           pPk = 0;
           r2 = 0;
@@ -2884,7 +2911,8 @@ static const sqlite3_module pragmaVtabModule = {
   0,                           /* xSavepoint */
   0,                           /* xRelease */
   0,                           /* xRollbackTo */
-  0                            /* xShadowName */
+  0,                           /* xShadowName */
+  0                            /* xIntegrity */
 };
 
 /*
diff --git a/libsql-sqlite3/src/prepare.c b/libsql-sqlite3/src/prepare.c
index 9f843faa86..d3e134e764 100644
--- a/libsql-sqlite3/src/prepare.c
+++ b/libsql-sqlite3/src/prepare.c
@@ -598,8 +598,6 @@ void sqlite3ParseObjectReset(Parse *pParse){
   db->lookaside.sz = db->lookaside.bDisable ? 0 : db->lookaside.szTrue;
   assert( pParse->db->pParse==pParse );
   db->pParse = pParse->pOuterParse;
-  pParse->db = 0;
-  pParse->disableLookaside = 0;
 }
 
 /*
diff --git a/libsql-sqlite3/src/printf.c b/libsql-sqlite3/src/printf.c
index 87ad91f795..3c0b182d39 100644
--- a/libsql-sqlite3/src/printf.c
+++ b/libsql-sqlite3/src/printf.c
@@ -1389,7 +1389,7 @@ char *sqlite3RCStrRef(char *z){
 ** Decrease the reference count by one.  Free the string when the
 ** reference count reaches zero.
 */
-void sqlite3RCStrUnref(char *z){
+void sqlite3RCStrUnref(void *z){
   RCStr *p = (RCStr*)z;
   assert( p!=0 );
   p--;
diff --git a/libsql-sqlite3/src/resolve.c b/libsql-sqlite3/src/resolve.c
index 7fc0151ad2..0072f6b6aa 100644
--- a/libsql-sqlite3/src/resolve.c
+++ b/libsql-sqlite3/src/resolve.c
@@ -104,21 +104,36 @@ static void resolveAlias(
 }
 
 /*
-** Subqueries stores the original database, table and column names for their
-** result sets in ExprList.a[].zSpan, in the form "DATABASE.TABLE.COLUMN".
-** Check to see if the zSpan given to this routine matches the zDb, zTab,
-** and zCol.  If any of zDb, zTab, and zCol are NULL then those fields will
-** match anything.
+** Subqueries store the original database, table and column names for their
+** result sets in ExprList.a[].zSpan, in the form "DATABASE.TABLE.COLUMN",
+** and mark the expression-list item by setting ExprList.a[].fg.eEName
+** to ENAME_TAB.
+**
+** Check to see if the zSpan/eEName of the expression-list item passed to this
+** routine matches the zDb, zTab, and zCol.  If any of zDb, zTab, and zCol are
+** NULL then those fields will match anything. Return true if there is a match,
+** or false otherwise.
+**
+** SF_NestedFrom subqueries also store an entry for the implicit rowid (or
+** _rowid_, or oid) column by setting ExprList.a[].fg.eEName to ENAME_ROWID,
+** and setting zSpan to "DATABASE.TABLE.<rowid-alias>". This type of pItem
+** argument matches if zCol is a rowid alias. If it is not NULL, (*pbRowid)
+** is set to 1 if there is this kind of match.
 */
 int sqlite3MatchEName(
   const struct ExprList_item *pItem,
   const char *zCol,
   const char *zTab,
-  const char *zDb
+  const char *zDb,
+  int *pbRowid
 ){
   int n;
   const char *zSpan;
-  if( pItem->fg.eEName!=ENAME_TAB ) return 0;
+  int eEName = pItem->fg.eEName;
+  if( eEName!=ENAME_TAB && (eEName!=ENAME_ROWID || NEVER(pbRowid==0)) ){
+    return 0;
+  }
+  assert( pbRowid==0 || *pbRowid==0 );
   zSpan = pItem->zEName;
   for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){}
   if( zDb && (sqlite3StrNICmp(zSpan, zDb, n)!=0 || zDb[n]!=0) ){
@@ -130,9 +145,11 @@ int sqlite3MatchEName(
     return 0;
   }
   zSpan += n+1;
-  if( zCol && sqlite3StrICmp(zSpan, zCol)!=0 ){
-    return 0;
+  if( zCol ){
+    if( eEName==ENAME_TAB && sqlite3StrICmp(zSpan, zCol)!=0 ) return 0;
+    if( eEName==ENAME_ROWID && sqlite3IsRowid(zCol)==0 ) return 0;
   }
+  if( eEName==ENAME_ROWID ) *pbRowid = 1;
   return 1;
 }
 
@@ -265,7 +282,7 @@ static int lookupName(
 ){
   int i, j;                         /* Loop counters */
   int cnt = 0;                      /* Number of matching column names */
-  int cntTab = 0;                   /* Number of matching table names */
+  int cntTab = 0;                   /* Number of potential "rowid" matches */
   int nSubquery = 0;                /* How many levels of subquery */
   sqlite3 *db = pParse->db;         /* The database connection */
   SrcItem *pItem;                   /* Use for looping over pSrcList items */
@@ -342,39 +359,49 @@ static int lookupName(
           assert( pEList!=0 );
           assert( pEList->nExpr==pTab->nCol );
           for(j=0; j<pEList->nExpr; j++){
-            if( !sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){
+            int bRowid = 0;       /* True if possible rowid match */
+            if( !sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb, &bRowid) ){
               continue;
             }
-            if( cnt>0 ){
-              if( pItem->fg.isUsing==0
-               || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0
-              ){
-                /* Two or more tables have the same column name which is
-                ** not joined by USING.  This is an error.  Signal as much
-                ** by clearing pFJMatch and letting cnt go above 1. */
-                sqlite3ExprListDelete(db, pFJMatch);
-                pFJMatch = 0;
-              }else
-              if( (pItem->fg.jointype & JT_RIGHT)==0 ){
-                /* An INNER or LEFT JOIN.  Use the left-most table */
-                continue;
-              }else
-              if( (pItem->fg.jointype & JT_LEFT)==0 ){
-                /* A RIGHT JOIN.  Use the right-most table */
-                cnt = 0;
-                sqlite3ExprListDelete(db, pFJMatch);
-                pFJMatch = 0;
-              }else{
-                /* For a FULL JOIN, we must construct a coalesce() func */
-                extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn);
+            if( bRowid==0 ){
+              if( cnt>0 ){
+                if( pItem->fg.isUsing==0
+                 || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0
+                ){
+                  /* Two or more tables have the same column name which is
+                  ** not joined by USING.  This is an error.  Signal as much
+                  ** by clearing pFJMatch and letting cnt go above 1. */
+                  sqlite3ExprListDelete(db, pFJMatch);
+                  pFJMatch = 0;
+                }else
+                if( (pItem->fg.jointype & JT_RIGHT)==0 ){
+                  /* An INNER or LEFT JOIN.  Use the left-most table */
+                  continue;
+                }else
+                if( (pItem->fg.jointype & JT_LEFT)==0 ){
+                  /* A RIGHT JOIN.  Use the right-most table */
+                  cnt = 0;
+                  sqlite3ExprListDelete(db, pFJMatch);
+                  pFJMatch = 0;
+                }else{
+                  /* For a FULL JOIN, we must construct a coalesce() func */
+                  extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn);
+                }
               }
+              cnt++;
+              hit = 1;
+            }else if( cnt>0 ){
+              /* This is a potential rowid match, but there has already been
+              ** a real match found. So this can be ignored.  */
+              continue;
             }
-            cnt++;
-            cntTab = 2;
+            cntTab++;
             pMatch = pItem;
             pExpr->iColumn = j;
             pEList->a[j].fg.bUsed = 1;
-            hit = 1;
+
+            /* rowid cannot be part of a USING clause - assert() this. */
+            assert( bRowid==0 || pEList->a[j].fg.bUsingTerm==0 );
             if( pEList->a[j].fg.bUsingTerm ) break;
           }
           if( hit || zTab==0 ) continue;
@@ -569,10 +596,10 @@ static int lookupName(
      && pMatch
      && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0
      && sqlite3IsRowid(zCol)
-     && ALWAYS(VisibleRowid(pMatch->pTab))
+     && ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom)
     ){
       cnt = 1;
-      pExpr->iColumn = -1;
+      if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1;
       pExpr->affExpr = SQLITE_AFF_INTEGER;
     }
 
@@ -1025,6 +1052,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
       Window *pWin = (IsWindowFunc(pExpr) ? pExpr->y.pWin : 0);
 #endif
       assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) );
+      assert( pExpr->pLeft==0 || pExpr->pLeft->op==TK_ORDER );
       zId = pExpr->u.zToken;
       pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0);
       if( pDef==0 ){
@@ -1166,6 +1194,10 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
           pNC->nNcErr++;
         }
 #endif
+        else if( is_agg==0 && pExpr->pLeft ){
+          sqlite3ExprOrderByAggregateError(pParse, pExpr);
+          pNC->nNcErr++;
+        }
         if( is_agg ){
           /* Window functions may not be arguments of aggregate functions.
           ** Or arguments of other window functions. But aggregate functions
@@ -1184,6 +1216,11 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
 #endif
       sqlite3WalkExprList(pWalker, pList);
       if( is_agg ){
+        if( pExpr->pLeft ){
+          assert( pExpr->pLeft->op==TK_ORDER );
+          assert( ExprUseXList(pExpr->pLeft) );
+          sqlite3WalkExprList(pWalker, pExpr->pLeft->x.pList);
+        }
 #ifndef SQLITE_OMIT_WINDOWFUNC
         if( pWin ){
           Select *pSel = pNC->pWinSelect;
@@ -1747,10 +1784,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
   while( p ){
     assert( (p->selFlags & SF_Expanded)!=0 );
     assert( (p->selFlags & SF_Resolved)==0 );
-    assert( db->suppressErr==0 ); /* SF_Resolved not set if errors suppressed */
     p->selFlags |= SF_Resolved;
 
-
     /* Resolve the expressions in the LIMIT and OFFSET clauses. These
     ** are not allowed to refer to any names, so pass an empty NameContext.
     */
diff --git a/libsql-sqlite3/src/select.c b/libsql-sqlite3/src/select.c
index 321badd07f..7a79385e0b 100644
--- a/libsql-sqlite3/src/select.c
+++ b/libsql-sqlite3/src/select.c
@@ -454,6 +454,7 @@ static void unsetJoinExpr(Expr *p, int iTable, int nullable){
     }
     if( p->op==TK_FUNCTION ){
       assert( ExprUseXList(p) );
+      assert( p->pLeft==0 );
       if( p->x.pList ){
         int i;
         for(i=0; i<p->x.pList->nExpr; i++){
@@ -5301,12 +5302,12 @@ static int disableUnusedSubqueryResultColumns(SrcItem *pItem){
   assert( pItem->pSelect!=0 );
   pSub = pItem->pSelect;
   assert( pSub->pEList->nExpr==pTab->nCol );
-  if( (pSub->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){
-    testcase( pSub->selFlags & SF_Distinct );
-    testcase( pSub->selFlags & SF_Aggregate );
-    return 0;
-  }
   for(pX=pSub; pX; pX=pX->pPrior){
+    if( (pX->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){
+      testcase( pX->selFlags & SF_Distinct );
+      testcase( pX->selFlags & SF_Aggregate );
+      return 0;
+    }
     if( pX->pPrior && pX->op!=TK_ALL ){
       /* This optimization does not work for compound subqueries that
       ** use UNION, INTERSECT, or EXCEPT.  Only UNION ALL is allowed. */
@@ -6114,6 +6115,7 @@ static int selectExpander(Walker *pWalker, Select *p){
         char *zTName = 0;       /* text of name of TABLE */
         int iErrOfst;
         if( pE->op==TK_DOT ){
+          assert( (selFlags & SF_NestedFrom)==0 );
           assert( pE->pLeft!=0 );
           assert( !ExprHasProperty(pE->pLeft, EP_IntValue) );
           zTName = pE->pLeft->u.zToken;
@@ -6124,6 +6126,7 @@ static int selectExpander(Walker *pWalker, Select *p){
           iErrOfst = pE->w.iOfst;
         }
         for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){
+          int nAdd;                    /* Number of cols including rowid */
           Table *pTab = pFrom->pTab;   /* Table for this data source */
           ExprList *pNestedFrom;       /* Result-set of a nested FROM clause */
           char *zTabName;              /* AS name for this data source */
@@ -6141,6 +6144,7 @@ static int selectExpander(Walker *pWalker, Select *p){
             pNestedFrom = pFrom->pSelect->pEList;
             assert( pNestedFrom!=0 );
             assert( pNestedFrom->nExpr==pTab->nCol );
+            assert( VisibleRowid(pTab)==0 );
           }else{
             if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){
               continue;
@@ -6171,33 +6175,48 @@ static int selectExpander(Walker *pWalker, Select *p){
           }else{
             pUsing = 0;
           }
-          for(j=0; j<pTab->nCol; j++){
-            char *zName = pTab->aCol[j].zCnName;
+
+          nAdd = pTab->nCol + (VisibleRowid(pTab) && (selFlags&SF_NestedFrom));
+          for(j=0; j<nAdd; j++){
+            const char *zName; 
             struct ExprList_item *pX; /* Newly added ExprList term */
 
-            assert( zName );
-            if( zTName
-             && pNestedFrom
-             && sqlite3MatchEName(&pNestedFrom->a[j], 0, zTName, 0)==0
-            ){
-              continue;
-            }
+            if( j==pTab->nCol ){
+              zName = sqlite3RowidAlias(pTab);
+              if( zName==0 ) continue;
+            }else{
+              zName = pTab->aCol[j].zCnName;
 
-            /* If a column is marked as 'hidden', omit it from the expanded
-            ** result-set list unless the SELECT has the SF_IncludeHidden
-            ** bit set.
-            */
-            if( (p->selFlags & SF_IncludeHidden)==0
-             && IsHiddenColumn(&pTab->aCol[j])
-            ){
-              continue;
-            }
-            if( (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0
-             && zTName==0
-             && (selFlags & (SF_NestedFrom))==0
-            ){
-              continue;
+              /* If pTab is actually an SF_NestedFrom sub-select, do not
+              ** expand any ENAME_ROWID columns.  */
+              if( pNestedFrom && pNestedFrom->a[j].fg.eEName==ENAME_ROWID ){
+                continue;
+              }
+
+              if( zTName
+               && pNestedFrom
+               && sqlite3MatchEName(&pNestedFrom->a[j], 0, zTName, 0, 0)==0
+              ){
+                continue;
+              }
+
+              /* If a column is marked as 'hidden', omit it from the expanded
+              ** result-set list unless the SELECT has the SF_IncludeHidden
+              ** bit set.
+              */
+              if( (p->selFlags & SF_IncludeHidden)==0
+                && IsHiddenColumn(&pTab->aCol[j])
+              ){
+                continue;
+              }
+              if( (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0
+               && zTName==0
+               && (selFlags & (SF_NestedFrom))==0
+              ){
+                continue;
+              }
             }
+            assert( zName );
             tableSeen = 1;
 
             if( i>0 && zTName==0 && (selFlags & SF_NestedFrom)==0 ){
@@ -6247,11 +6266,11 @@ static int selectExpander(Walker *pWalker, Select *p){
                                            zSchemaName, zTabName, zName);
                 testcase( pX->zEName==0 );
               }
-              pX->fg.eEName = ENAME_TAB;
+              pX->fg.eEName = (j==pTab->nCol ? ENAME_ROWID : ENAME_TAB);
               if( (pFrom->fg.isUsing
                    && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0)
                || (pUsing && sqlite3IdListIndex(pUsing, zName)>=0)
-               || (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0
+               || (j<pTab->nCol && (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND))
               ){
                 pX->fg.bNoExpand = 1;
               }
@@ -6472,8 +6491,14 @@ static void analyzeAggFuncArgs(
   pNC->ncFlags |= NC_InAggFunc;
   for(i=0; i<pAggInfo->nFunc; i++){
     Expr *pExpr = pAggInfo->aFunc[i].pFExpr;
+    assert( pExpr->op==TK_FUNCTION || pExpr->op==TK_AGG_FUNCTION );
     assert( ExprUseXList(pExpr) );
     sqlite3ExprAnalyzeAggList(pNC, pExpr->x.pList);
+    if( pExpr->pLeft ){
+      assert( pExpr->pLeft->op==TK_ORDER );
+      assert( ExprUseXList(pExpr->pLeft) );
+      sqlite3ExprAnalyzeAggList(pNC, pExpr->pLeft->x.pList);
+    }
 #ifndef SQLITE_OMIT_WINDOWFUNC
     assert( !IsWindowFunc(pExpr) );
     if( ExprHasProperty(pExpr, EP_WinFunc) ){
@@ -6628,6 +6653,32 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){
                           pFunc->pFunc->zName));
       }
     }
+    if( pFunc->iOBTab>=0 ){
+      ExprList *pOBList;
+      KeyInfo *pKeyInfo;
+      int nExtra = 0;
+      assert( pFunc->pFExpr->pLeft!=0 );
+      assert( pFunc->pFExpr->pLeft->op==TK_ORDER );
+      assert( ExprUseXList(pFunc->pFExpr->pLeft) );
+      pOBList = pFunc->pFExpr->pLeft->x.pList;
+      if( !pFunc->bOBUnique ){
+        nExtra++;  /* One extra column for the OP_Sequence */
+      }
+      if( pFunc->bOBPayload ){
+        /* extra columns for the function arguments */
+        assert( ExprUseXList(pFunc->pFExpr) );
+        nExtra += pFunc->pFExpr->x.pList->nExpr;
+      }
+      pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOBList, 0, nExtra);
+      if( !pFunc->bOBUnique && pParse->nErr==0 ){
+        pKeyInfo->nKeyField++;
+      }
+      sqlite3VdbeAddOp4(v, OP_OpenEphemeral,
+            pFunc->iOBTab, pOBList->nExpr+nExtra, 0,
+            (char*)pKeyInfo, P4_KEYINFO);
+      ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s(ORDER BY)",
+                          pFunc->pFunc->zName));
+    }
   }
 }
 
@@ -6643,13 +6694,46 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){
     ExprList *pList;
     assert( ExprUseXList(pF->pFExpr) );
     pList = pF->pFExpr->x.pList;
+    if( pF->iOBTab>=0 ){
+      /* For an ORDER BY aggregate, calls to OP_AggStep where deferred and
+      ** all content was stored in emphermal table pF->iOBTab.  Extract that
+      ** content now (in ORDER BY order) and make all calls to OP_AggStep
+      ** before doing the OP_AggFinal call. */
+      int iTop;        /* Start of loop for extracting columns */
+      int nArg;        /* Number of columns to extract */
+      int nKey;        /* Key columns to be skipped */
+      int regAgg;      /* Extract into this array */
+      int j;           /* Loop counter */
+      
+      nArg = pList->nExpr;
+      regAgg = sqlite3GetTempRange(pParse, nArg);
+
+      if( pF->bOBPayload==0 ){
+        nKey = 0;
+      }else{
+        assert( pF->pFExpr->pLeft!=0 );
+        assert( ExprUseXList(pF->pFExpr->pLeft) );
+        assert( pF->pFExpr->pLeft->x.pList!=0 );
+        nKey = pF->pFExpr->pLeft->x.pList->nExpr;
+        if( ALWAYS(!pF->bOBUnique) ) nKey++;
+      }
+      iTop = sqlite3VdbeAddOp1(v, OP_Rewind, pF->iOBTab); VdbeCoverage(v);
+      for(j=nArg-1; j>=0; j--){
+        sqlite3VdbeAddOp3(v, OP_Column, pF->iOBTab, nKey+j, regAgg+j);
+      }
+      sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
+      sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+      sqlite3VdbeChangeP5(v, (u8)nArg);
+      sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v);
+      sqlite3VdbeJumpHere(v, iTop);
+      sqlite3ReleaseTempRange(pParse, regAgg, nArg);
+    }
     sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i),
                       pList ? pList->nExpr : 0);
     sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
   }
 }
 
-
 /*
 ** Generate code that will update the accumulator memory cells for an
 ** aggregate based on the current cursor position.
@@ -6658,6 +6742,13 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){
 ** in pAggInfo, then only populate the pAggInfo->nAccumulator accumulator
 ** registers if register regAcc contains 0. The caller will take care
 ** of setting and clearing regAcc.
+**
+** For an ORDER BY aggregate, the actual accumulator memory cell update
+** is deferred until after all input rows have been received, so that they
+** can be run in the requested order.  In that case, instead of invoking
+** OP_AggStep to update the accumulator, just add the arguments that would
+** have been passed into OP_AggStep into the sorting ephemeral table
+** (along with the appropriate sort key).
 */
 static void updateAccumulator(
   Parse *pParse,
@@ -6679,6 +6770,8 @@ static void updateAccumulator(
     int nArg;
     int addrNext = 0;
     int regAgg;
+    int regAggSz = 0;
+    int regDistinct = 0;
     ExprList *pList;
     assert( ExprUseXList(pF->pFExpr) );
     assert( !IsWindowFunc(pF->pFExpr) );
@@ -6705,9 +6798,44 @@ static void updateAccumulator(
       addrNext = sqlite3VdbeMakeLabel(pParse);
       sqlite3ExprIfFalse(pParse, pFilter, addrNext, SQLITE_JUMPIFNULL);
     }
-    if( pList ){
+    if( pF->iOBTab>=0 ){
+      /* Instead of invoking AggStep, we must push the arguments that would
+      ** have been passed to AggStep onto the sorting table. */
+      int jj;                /* Registered used so far in building the record */
+      ExprList *pOBList;     /* The ORDER BY clause */
+      assert( pList!=0 );
+      nArg = pList->nExpr;
+      assert( nArg>0 );
+      assert( pF->pFExpr->pLeft!=0 );
+      assert( pF->pFExpr->pLeft->op==TK_ORDER );
+      assert( ExprUseXList(pF->pFExpr->pLeft) );
+      pOBList = pF->pFExpr->pLeft->x.pList;
+      assert( pOBList!=0 );
+      assert( pOBList->nExpr>0 );
+      regAggSz = pOBList->nExpr;
+      if( !pF->bOBUnique ){
+        regAggSz++;   /* One register for OP_Sequence */
+      }
+      if( pF->bOBPayload ){
+        regAggSz += nArg;
+      }
+      regAggSz++;  /* One extra register to hold result of MakeRecord */
+      regAgg = sqlite3GetTempRange(pParse, regAggSz);
+      regDistinct = regAgg;
+      sqlite3ExprCodeExprList(pParse, pOBList, regAgg, 0, SQLITE_ECEL_DUP);
+      jj = pOBList->nExpr;
+      if( !pF->bOBUnique ){
+        sqlite3VdbeAddOp2(v, OP_Sequence, pF->iOBTab, regAgg+jj);
+        jj++;
+      }
+      if( pF->bOBPayload ){
+        regDistinct = regAgg+jj;
+        sqlite3ExprCodeExprList(pParse, pList, regDistinct, 0, SQLITE_ECEL_DUP);
+      }
+    }else if( pList ){
       nArg = pList->nExpr;
       regAgg = sqlite3GetTempRange(pParse, nArg);
+      regDistinct = regAgg;
       sqlite3ExprCodeExprList(pParse, pList, regAgg, 0, SQLITE_ECEL_DUP);
     }else{
       nArg = 0;
@@ -6718,26 +6846,37 @@ static void updateAccumulator(
         addrNext = sqlite3VdbeMakeLabel(pParse);
       }
       pF->iDistinct = codeDistinct(pParse, eDistinctType,
-          pF->iDistinct, addrNext, pList, regAgg);
+          pF->iDistinct, addrNext, pList, regDistinct);
     }
-    if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){
-      CollSeq *pColl = 0;
-      struct ExprList_item *pItem;
-      int j;
-      assert( pList!=0 );  /* pList!=0 if pF->pFunc has NEEDCOLL */
-      for(j=0, pItem=pList->a; !pColl && j<nArg; j++, pItem++){
-        pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
+    if( pF->iOBTab>=0 ){
+      /* Insert a new record into the ORDER BY table */
+      sqlite3VdbeAddOp3(v, OP_MakeRecord, regAgg, regAggSz-1,
+                        regAgg+regAggSz-1);
+      sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pF->iOBTab, regAgg+regAggSz-1,
+                           regAgg, regAggSz-1);
+      sqlite3ReleaseTempRange(pParse, regAgg, regAggSz);
+    }else{
+      /* Invoke the AggStep function */
+      if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){
+        CollSeq *pColl = 0;
+        struct ExprList_item *pItem;
+        int j;
+        assert( pList!=0 );  /* pList!=0 if pF->pFunc has NEEDCOLL */
+        for(j=0, pItem=pList->a; !pColl && j<nArg; j++, pItem++){
+          pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
+        }
+        if( !pColl ){
+          pColl = pParse->db->pDfltColl;
+        }
+        if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem;
+        sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
+                         (char *)pColl, P4_COLLSEQ);
       }
-      if( !pColl ){
-        pColl = pParse->db->pDfltColl;
-      }
-      if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem;
-      sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ);
+      sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
+      sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+      sqlite3VdbeChangeP5(v, (u8)nArg);
+      sqlite3ReleaseTempRange(pParse, regAgg, nArg);
     }
-    sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
-    sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
-    sqlite3VdbeChangeP5(v, (u8)nArg);
-    sqlite3ReleaseTempRange(pParse, regAgg, nArg);
     if( addrNext ){
       sqlite3VdbeResolveLabel(v, addrNext);
     }
diff --git a/libsql-sqlite3/src/shell.c.in b/libsql-sqlite3/src/shell.c.in
index 435d38d659..9eb925a441 100644
--- a/libsql-sqlite3/src/shell.c.in
+++ b/libsql-sqlite3/src/shell.c.in
@@ -449,18 +449,23 @@ static int bail_on_error = 0;
 */
 static int stdin_is_interactive = 1;
 
-#if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \
-  && !defined(SHELL_OMIT_WIN_UTF8)
-# define SHELL_WIN_UTF8_OPT 1
-#else
-# define SHELL_WIN_UTF8_OPT 0
-#endif
-
-#if SHELL_WIN_UTF8_OPT
 /*
-** Setup console for UTF-8 input/output when following variable true.
+** If build is for non-RT Windows, without 3rd-party line editing,
+** console input and output may be done in a UTF-8 compatible way,
+** if the OS is capable of it and the --no-utf8 option is not seen.
 */
-static int console_utf8 = 0;
+#if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \
+  && !defined(SHELL_OMIT_WIN_UTF8) && !SQLITE_OS_WINRT
+# define SHELL_WIN_UTF8_OPT 1
+/* Record whether to do UTF-8 console I/O translation per stream. */
+  static int console_utf8_in = 0;
+  static int console_utf8_out = 0;
+/* Record whether can do UTF-8 or --no-utf8 seen in invocation. */
+  static int mbcs_opted = 1; /* Assume cannot do until shown otherwise. */
+#else
+# define console_utf8_in 0
+# define console_utf8_out 0
+# define SHELL_WIN_UTF8_OPT 0
 #endif
 
 /*
@@ -596,13 +601,13 @@ static char *dynamicContinuePrompt(void){
 #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */
 
 #if SHELL_WIN_UTF8_OPT
-/* Following struct is used for -utf8 operation. */
+/* Following struct is used for UTF-8 console I/O. */
 static struct ConsoleState {
   int stdinEof;      /* EOF has been seen on console input */
   int infsMode;      /* Input file stream mode upon shell start */
   UINT inCodePage;   /* Input code page upon shell start */
   UINT outCodePage;  /* Output code page upon shell start */
-  HANDLE hConsoleIn; /* Console input handle */
+  HANDLE hConsole;   /* Console input or output handle */
   DWORD consoleMode; /* Console mode upon shell start */
 } conState = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 };
 
@@ -611,50 +616,123 @@ static struct ConsoleState {
 #endif
 
 /*
-** Prepare console, (if known to be a WIN32 console), for UTF-8
-** input (from either typing or suitable paste operations) and for
-** UTF-8 rendering. This may "fail" with a message to stderr, where
-** the preparation is not done and common "code page" issues occur.
+** If given stream number is a console, return 1 and get some attributes,
+** else return 0 and set the output attributes to invalid values.
 */
-static void console_prepare(void){
-  HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE);
-  DWORD consoleMode = 0;
-  if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR
-      && GetConsoleMode( hCI, &consoleMode) ){
-    if( !IsValidCodePage(CP_UTF8) ){
-      fprintf(stderr, "Cannot use UTF-8 code page.\n");
-      console_utf8 = 0;
-      return;
-    }
-    conState.hConsoleIn = hCI;
-    conState.consoleMode = consoleMode;
-    conState.inCodePage = GetConsoleCP();
-    conState.outCodePage = GetConsoleOutputCP();
-    SetConsoleCP(CP_UTF8);
+static short console_attrs(unsigned stnum, HANDLE *pH, DWORD *pConsMode){
+  static int stid[3] = { STD_INPUT_HANDLE,STD_OUTPUT_HANDLE,STD_ERROR_HANDLE };
+  HANDLE h;
+  *pH = INVALID_HANDLE_VALUE;
+  *pConsMode = 0;
+  if( stnum > 2 ) return 0;
+  h = GetStdHandle(stid[stnum]);
+  if( h!=*pH && GetFileType(h)==FILE_TYPE_CHAR && GetConsoleMode(h,pConsMode) ){
+    *pH = h;
+    return 1;
+  }
+  return 0;
+}
+
+/*
+** Perform a runtime test of Windows console to determine if it can
+** do char-stream I/O correctly when the code page is set to CP_UTF8.
+** Returns are: 1 => yes it can, 0 => no it cannot
+**
+** The console's output code page is momentarily set, then restored.
+** So this should only be run when the process is given use of the
+** console for either input or output.
+*/
+static short ConsoleDoesUTF8(void){
+  UINT ocp = GetConsoleOutputCP();
+  const char TrialUtf8[] = { '\xC8', '\xAB' }; /* "Θ«" or 2 MBCS characters */
+  WCHAR aReadBack[1] = { 0 }; /* Read back as 0x022B when decoded as UTF-8. */
+  CONSOLE_SCREEN_BUFFER_INFO csbInfo = {0};
+  /* Create an inactive screen buffer with which to do the experiment. */
+  HANDLE hCSB = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE, 0, 0,
+                                          CONSOLE_TEXTMODE_BUFFER, NULL);
+  if( hCSB!=INVALID_HANDLE_VALUE ){
+    COORD cpos = {0,0};
+    DWORD rbc;
+    SetConsoleCursorPosition(hCSB, cpos);
     SetConsoleOutputCP(CP_UTF8);
-    consoleMode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
-    SetConsoleMode(conState.hConsoleIn, consoleMode);
+    /* Write 2 chars which are a single character in UTF-8 but more in MBCS. */
+    WriteConsoleA(hCSB, TrialUtf8, sizeof(TrialUtf8), NULL, NULL);
+    ReadConsoleOutputCharacterW(hCSB, &aReadBack[0], 1, cpos, &rbc);
+    GetConsoleScreenBufferInfo(hCSB, &csbInfo);
+    SetConsoleOutputCP(ocp);
+    CloseHandle(hCSB);
+  }
+  /* Return 1 if cursor advanced by 1 position, else 0. */
+  return (short)(csbInfo.dwCursorPosition.X == 1 && aReadBack[0] == 0x022B);
+}
+
+static short in_console = 0;
+static short out_console = 0;
+
+/*
+** Determine whether either normal I/O stream is the console,
+** and whether it can do UTF-8 translation, setting globals
+** in_console, out_console and mbcs_opted accordingly.
+*/
+static void probe_console(void){
+  HANDLE h;
+  DWORD cMode;
+  in_console = console_attrs(0, &h, &cMode);
+  out_console = console_attrs(1, &h, &cMode);
+  if( in_console || out_console ) mbcs_opted = !ConsoleDoesUTF8();
+}
+
+/*
+** If console is used for normal I/O, absent a --no-utf8 option,
+** prepare console for UTF-8 input (from either typing or suitable
+** paste operations) and/or for UTF-8 output rendering.
+**
+** The console state upon entry is preserved, in conState, so that
+** console_restore() can later restore the same console state.
+**
+** The globals console_utf8_in and console_utf8_out are set, for
+** later use in selecting UTF-8 or MBCS console I/O translations.
+** This routine depends upon globals set by probe_console().
+*/
+static void console_prepare_utf8(void){
+  struct ConsoleState csWork = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 };
+
+  console_utf8_in = console_utf8_out = 0;
+  if( (!in_console && !out_console) || mbcs_opted ) return;
+  console_attrs((in_console)? 0 : 1, &conState.hConsole, &conState.consoleMode);
+  conState.inCodePage = GetConsoleCP();
+  conState.outCodePage = GetConsoleOutputCP();
+  if( in_console ){
+    SetConsoleCP(CP_UTF8);
+    DWORD newConsoleMode = conState.consoleMode
+      | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
+    SetConsoleMode(conState.hConsole, newConsoleMode);
     conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT);
-    console_utf8 = 1;
-  }else{
-    console_utf8 = 0;
+    console_utf8_in = 1;
+  }
+  if( out_console ){
+    SetConsoleOutputCP(CP_UTF8);
+    console_utf8_out = 1;
   }
 }
 
 /*
-** Undo the effects of console_prepare(), if any.
+** Undo the effects of console_prepare_utf8(), if any.
 */
 static void SQLITE_CDECL console_restore(void){
-  if( console_utf8 && conState.inCodePage!=0
-      && conState.hConsoleIn!=INVALID_HANDLE_VALUE ){
-    _setmode(_fileno(stdin), conState.infsMode);
-    SetConsoleCP(conState.inCodePage);
-    SetConsoleOutputCP(conState.outCodePage);
-    SetConsoleMode(conState.hConsoleIn, conState.consoleMode);
+  if( (console_utf8_in||console_utf8_out)
+      && conState.hConsole!=INVALID_HANDLE_VALUE ){
+    if( console_utf8_in ){
+      SetConsoleCP(conState.inCodePage);
+      _setmode(_fileno(stdin), conState.infsMode);
+    }
+    if( console_utf8_out ) SetConsoleOutputCP(conState.outCodePage);
+    SetConsoleMode(conState.hConsole, conState.consoleMode);
     /* Avoid multiple calls. */
-    conState.hConsoleIn = INVALID_HANDLE_VALUE;
+    conState.hConsole = INVALID_HANDLE_VALUE;
     conState.consoleMode = 0;
-    console_utf8 = 0;
+    console_utf8_in = 0;
+    console_utf8_out = 0;
   }
 }
 
@@ -662,11 +740,11 @@ static void SQLITE_CDECL console_restore(void){
 ** Collect input like fgets(...) with special provisions for input
 ** from the Windows console to get around its strange coding issues.
 ** Defers to plain fgets() when input is not interactive or when the
-** startup option, -utf8, has not been provided or taken effect.
+** UTF-8 input is unavailable or opted out.
 */
 static char* utf8_fgets(char *buf, int ncmax, FILE *fin){
   if( fin==0 ) fin = stdin;
-  if( fin==stdin && stdin_is_interactive && console_utf8 ){
+  if( fin==stdin && stdin_is_interactive && console_utf8_in ){
 # define SQLITE_IALIM 150
     wchar_t wbuf[SQLITE_IALIM];
     int lend = 0;
@@ -679,7 +757,7 @@ static char* utf8_fgets(char *buf, int ncmax, FILE *fin){
         ? SQLITE_IALIM : (ncmax-1 - noc)/4;
 # undef SQLITE_IALIM
       DWORD nbr = 0;
-      BOOL bRC = ReadConsoleW(conState.hConsoleIn, wbuf, na, &nbr, 0);
+      BOOL bRC = ReadConsoleW(conState.hConsole, wbuf, na, &nbr, 0);
       if( !bRC || (noc==0 && nbr==0) ) return 0;
       if( nbr > 0 ){
         int nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR,
@@ -725,20 +803,16 @@ static char* utf8_fgets(char *buf, int ncmax, FILE *fin){
 
 /*
 ** Render output like fprintf().  Except, if the output is going to the
-** console and if this is running on a Windows machine, and if the -utf8
-** option is unavailable or (available and inactive), translate the
+** console and if this is running on a Windows machine, and if UTF-8
+** output unavailable (or available but opted out), translate the
 ** output from UTF-8 into MBCS for output through 8-bit stdout stream.
-** (With -utf8 active, no translation is needed and must not be done.)
+** (Without -no-utf8, no translation is needed and must not be done.)
 */
 #if defined(_WIN32) || defined(WIN32)
 void utf8_printf(FILE *out, const char *zFormat, ...){
   va_list ap;
   va_start(ap, zFormat);
-  if( stdout_is_console && (out==stdout || out==stderr)
-# if SHELL_WIN_UTF8_OPT
-      && !console_utf8
-# endif
-  ){
+  if( stdout_is_console && (out==stdout || out==stderr) && !console_utf8_out ){
     char *z1 = sqlite3_vmprintf(zFormat, ap);
     char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0);
     sqlite3_free(z1);
@@ -950,14 +1024,10 @@ static char *local_getline(char *zLine, FILE *in){
     }
   }
 #if defined(_WIN32) || defined(WIN32)
-  /* For interactive input on Windows systems, without -utf8,
+  /* For interactive input on Windows systems, with -no-utf8,
   ** translate the multi-byte characterset characters into UTF-8.
-  ** This is the translation that predates the -utf8 option. */
-  if( stdin_is_interactive && in==stdin
-# if SHELL_WIN_UTF8_OPT
-      && !console_utf8
-# endif /* SHELL_WIN_UTF8_OPT */
-  ){
+  ** This is the translation that predates console UTF-8 input. */
+  if( stdin_is_interactive && in==stdin && !console_utf8_in ){
     char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0);
     if( zTrans ){
       i64 nTrans = strlen(zTrans)+1;
@@ -1243,7 +1313,7 @@ static void shellDtostr(
   char z[400];
   if( n<1 ) n = 1;
   if( n>350 ) n = 350;
-  sprintf(z, "%#+.*e", n, r);
+  snprintf(z, sizeof(z)-1, "%#+.*e", n, r);
   sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
 }
 
@@ -3377,7 +3447,7 @@ static void display_explain_scanstats(
     if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
       break;
     }
-    n = strlen(z) + scanStatsHeight(p, ii)*3;
+    n = (int)strlen(z) + scanStatsHeight(p, ii)*3;
     if( n>nWidth ) nWidth = n;
   }
   nWidth += 4;
@@ -3389,12 +3459,12 @@ static void display_explain_scanstats(
     i64 nCycle = 0;
     int iId = 0;
     int iPid = 0;
-    const char *z = 0;
+    const char *zo = 0;
     const char *zName = 0;
     char *zText = 0;
     double rEst = 0.0;
 
-    if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
+    if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){
       break;
     }
     sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_EST,f,(void*)&rEst);
@@ -3405,7 +3475,7 @@ static void display_explain_scanstats(
     sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
     sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName);
 
-    zText = sqlite3_mprintf("%s", z);
+    zText = sqlite3_mprintf("%s", zo);
     if( nCycle>=0 || nLoop>=0 || nRow>=0 ){
       char *z = 0;
       if( nCycle>=0 && nTotal>0 ){
@@ -8058,7 +8128,6 @@ static int do_meta_command(char *zLine, ShellState *p){
       azArg[nArg++] = &zLine[h];
       while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
       if( zLine[h] ) zLine[h++] = 0;
-      resolve_backslashes(azArg[nArg-1]);
     }
   }
   azArg[nArg] = 0;
@@ -8797,8 +8866,10 @@ static int do_meta_command(char *zLine, ShellState *p){
                "SELECT rowid FROM sqlite_schema"
                " WHERE name GLOB 'sqlite_stat[134]'",
                -1, &pStmt, 0);
-      doStats = sqlite3_step(pStmt)==SQLITE_ROW;
-      sqlite3_finalize(pStmt);
+      if( rc==SQLITE_OK ){
+        doStats = sqlite3_step(pStmt)==SQLITE_ROW;
+        sqlite3_finalize(pStmt);
+      }
     }
     if( doStats==0 ){
       raw_printf(p->out, "/* No STAT tables available */\n");
@@ -9087,6 +9158,14 @@ static int do_meta_command(char *zLine, ShellState *p){
         ** the remaining columns.
         */
         if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
+        /*
+        ** For CSV mode, per RFC 4180, accept EOF in lieu of final
+        ** record terminator but only for last field of multi-field row.
+        ** (If there are too few fields, it's not valid CSV anyway.)
+        */
+        if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){
+          z = "";
+        }
         sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
         if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
           utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
@@ -10937,6 +11016,7 @@ static int do_meta_command(char *zLine, ShellState *p){
     {"byteorder",          SQLITE_TESTCTRL_BYTEORDER, 0,  ""                },
     {"extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN"  },
   /*{"fault_install",      SQLITE_TESTCTRL_FAULT_INSTALL, 1,""              },*/
+    {"fk_no_action",       SQLITE_TESTCTRL_FK_NO_ACTION, 0, "BOOLEAN"       },
     {"imposter",         SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
     {"internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,""          },
     {"localtime_fault",    SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN"      },
@@ -11007,6 +11087,7 @@ static int do_meta_command(char *zLine, ShellState *p){
 
         /* sqlite3_test_control(int, db, int) */
         case SQLITE_TESTCTRL_OPTIMIZATIONS:
+        case SQLITE_TESTCTRL_FK_NO_ACTION:
           if( nArg==3 ){
             unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0);
             rc2 = sqlite3_test_control(testctrl, p->db, opt);
@@ -11977,6 +12058,9 @@ static const char zOptions[] =
   "   -multiplex           enable the multiplexor VFS\n"
 #endif
   "   -newline SEP         set output row separator. Default: '\\n'\n"
+#if SHELL_WIN_UTF8_OPT
+  "   -no-utf8             do not try to set up UTF-8 output (for legacy)\n"
+#endif
   "   -nofollow            refuse to open symbolic links to database files\n"
   "   -nonce STRING        set the safe-mode escape nonce\n"
   "   -nullvalue TEXT      set text string for NULL values. Default ''\n"
@@ -11993,7 +12077,7 @@ static const char zOptions[] =
   "   -table               set output mode to 'table'\n"
   "   -tabs                set output mode to 'tabs'\n"
   "   -unsafe-testing      allow unsafe commands and modes for testing\n"
-#if SHELL_WIN_UTF8_OPT
+#if SHELL_WIN_UTF8_OPT && 0 /* Option is accepted, but is now the default. */
   "   -utf8                setup interactive console code page for UTF-8\n"
 #endif
   "   -version             show SQLite version\n"
@@ -12144,7 +12228,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
   stdout_is_console = isatty(1);
 #endif
 #if SHELL_WIN_UTF8_OPT
-  atexit(console_restore); /* Needs revision for CLI as library call */
+  probe_console(); /* Check for console I/O and UTF-8 capability. */
+  if( !mbcs_opted ) atexit(console_restore);
 #endif
   atexit(sayAbnormalExit);
 #ifdef SQLITE_DEBUG
@@ -12231,8 +12316,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 
   /* Do an initial pass through the command-line argument to locate
   ** the name of the database file, the name of the initialization file,
-  ** the size of the alternative malloc heap,
-  ** and the first command to execute.
+  ** the size of the alternative malloc heap, options affecting commands
+  ** or SQL run from the command line, and the first command to execute.
   */
 #ifndef SQLITE_SHELL_FIDDLE
   verify_uninitialized();
@@ -12266,12 +12351,26 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
       (void)cmdline_option_value(argc, argv, ++i);
     }else if( cli_strcmp(z,"-init")==0 ){
       zInitFile = cmdline_option_value(argc, argv, ++i);
+    }else if( cli_strcmp(z,"-interactive")==0 ){
+      /* Need to check for interactive override here to so that it can
+      ** affect console setup (for Windows only) and testing thereof.
+      */
+      stdin_is_interactive = 1;
     }else if( cli_strcmp(z,"-batch")==0 ){
       /* Need to check for batch mode here to so we can avoid printing
       ** informational messages (like from process_sqliterc) before
       ** we do the actual processing of arguments later in a second pass.
       */
       stdin_is_interactive = 0;
+    }else if( cli_strcmp(z,"-utf8")==0 ){
+#if SHELL_WIN_UTF8_OPT
+      /* Option accepted, but is ignored except for this diagnostic. */
+      if( mbcs_opted ) fprintf(stderr, "Cannot do UTF-8 at this console.\n");
+#endif /* SHELL_WIN_UTF8_OPT */
+    }else if( cli_strcmp(z,"-no-utf8")==0 ){
+#if SHELL_WIN_UTF8_OPT
+      mbcs_opted = 1;
+#endif /* SHELL_WIN_UTF8_OPT */
     }else if( cli_strcmp(z,"-heap")==0 ){
 #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
       const char *zSize;
@@ -12410,6 +12509,15 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
       exit(1);
     }
   }
+#if SHELL_WIN_UTF8_OPT
+  /* Get indicated Windows console setup done before running invocation commands. */
+  if( in_console || out_console ){
+    console_prepare_utf8();
+  }
+  if( !in_console ){
+    setBinaryMode(stdin, 0);
+  }
+#endif
 
   if( data.pAuxDb->zDbFilename==0 ){
 #ifndef SQLITE_OMIT_MEMORYDB
@@ -12536,13 +12644,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
       printf("%s %s (libSQL %s) (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), libsql_libversion(), 8*(int)sizeof(char*));
       return 0;
     }else if( cli_strcmp(z,"-interactive")==0 ){
-      stdin_is_interactive = 1;
+      /* already handled */
     }else if( cli_strcmp(z,"-batch")==0 ){
-      stdin_is_interactive = 0;
+      /* already handled */
     }else if( cli_strcmp(z,"-utf8")==0 ){
-#if SHELL_WIN_UTF8_OPT
-      console_utf8 = 1;
-#endif /* SHELL_WIN_UTF8_OPT */
+      /* already handled */
+    }else if( cli_strcmp(z,"-no-utf8")==0 ){
+      /* already handled */
     }else if( cli_strcmp(z,"-heap")==0 ){
       i++;
     }else if( cli_strcmp(z,"-pagecache")==0 ){
@@ -12624,14 +12732,6 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     }
     data.cMode = data.mode;
   }
-#if SHELL_WIN_UTF8_OPT
-  if( console_utf8 && stdin_is_interactive ){
-    console_prepare();
-  }else{
-    setBinaryMode(stdin, 0);
-    console_utf8 = 0;
-  }
-#endif
 
   if( !readStdin ){
     /* Run all arguments that do not begin with '-' as if they were separate
@@ -12667,11 +12767,20 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     if( stdin_is_interactive ){
       char *zHome;
       char *zHistory;
+      const char *zCharset = "";
       int nHistory;
+#if SHELL_WIN_UTF8_OPT
+      switch( console_utf8_in+2*console_utf8_out ){
+      default: case 0: break;
+      case 1: zCharset = " (utf8 in)"; break;
+      case 2: zCharset = " (utf8 out)"; break;
+      case 3: zCharset = " (utf8 I/O)"; break;
+      }
+#endif
       printf(
-        "libSQL version %s (based on SQLite version %s) %.19s\n" /*extra-version-info*/
+        "libSQL version %s (based on SQLite version %s) %.19s%s\n" /*extra-version-info*/
         "Enter \".help\" for usage hints.\n",
-        libsql_libversion(), sqlite3_libversion(), sqlite3_sourceid()
+        libsql_libversion(), sqlite3_libversion(), sqlite3_sourceid(), zCharset
       );
       if( warnInmemoryDb ){
         printf("Connected to a ");
diff --git a/libsql-sqlite3/src/sqlite.h.in b/libsql-sqlite3/src/sqlite.h.in
index dbda02d2d7..3439f23355 100644
--- a/libsql-sqlite3/src/sqlite.h.in
+++ b/libsql-sqlite3/src/sqlite.h.in
@@ -2154,7 +2154,7 @@ struct sqlite3_mem_methods {
 ** is stored in each sorted record and the required column values loaded
 ** from the database as records are returned in sorted order. The default
 ** value for this option is to never use this optimization. Specifying a
-** negative value for this option restores the default behaviour.
+** negative value for this option restores the default behavior.
 ** This option is only available if SQLite is compiled with the
 ** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option.
 **
@@ -2329,7 +2329,7 @@ struct sqlite3_mem_methods {
 ** database handle, SQLite checks if this will mean that there are now no
 ** connections at all to the database. If so, it performs a checkpoint
 ** operation before closing the connection. This option may be used to
-** override this behaviour. The first parameter passed to this operation
+** override this behavior. The first parameter passed to this operation
 ** is an integer - positive to disable checkpoints-on-close, or zero (the
 ** default) to enable them, and negative to leave the setting unchanged.
 ** The second parameter is a pointer to an integer
@@ -4000,6 +4000,7 @@ void sqlite3_free_filename(sqlite3_filename);
 **
 ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
 ** text that describes the error, as either UTF-8 or UTF-16 respectively.
+** (See how SQLite handles [invalid UTF] for exceptions to this rule.)
 ** ^(Memory to hold the error message string is managed internally.
 ** The application does not need to worry about freeing the result.
 ** However, the error string might be overwritten or deallocated by
@@ -5370,6 +5371,7 @@ int sqlite3_finalize(sqlite3_stmt *pStmt);
 */
 int sqlite3_reset(sqlite3_stmt *pStmt);
 
+
 /*
 ** CAPI3REF: Create Or Redefine SQL Functions
 ** KEYWORDS: {function creation routines}
@@ -5924,32 +5926,32 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
 ** METHOD: sqlite3_context
 **
 ** These functions may be used by (non-aggregate) SQL functions to
-** associate metadata with argument values. If the same value is passed to
-** multiple invocations of the same SQL function during query execution, under
-** some circumstances the associated metadata may be preserved.  An example
-** of where this might be useful is in a regular-expression matching
-** function. The compiled version of the regular expression can be stored as
-** metadata associated with the pattern string. 
+** associate auxiliary data with argument values. If the same argument
+** value is passed to multiple invocations of the same SQL function during
+** query execution, under some circumstances the associated auxiliary data
+** might be preserved.  An example of where this might be useful is in a
+** regular-expression matching function. The compiled version of the regular
+** expression can be stored as auxiliary data associated with the pattern string. 
 ** Then as long as the pattern string remains the same,
 ** the compiled regular expression can be reused on multiple
 ** invocations of the same function.
 **
-** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the metadata
+** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the auxiliary data
 ** associated by the sqlite3_set_auxdata(C,N,P,X) function with the Nth argument
 ** value to the application-defined function.  ^N is zero for the left-most
-** function argument.  ^If there is no metadata
+** function argument.  ^If there is no auxiliary data
 ** associated with the function argument, the sqlite3_get_auxdata(C,N) interface
 ** returns a NULL pointer.
 **
-** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th
-** argument of the application-defined function.  ^Subsequent
+** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as auxiliary data for the
+** N-th argument of the application-defined function.  ^Subsequent
 ** calls to sqlite3_get_auxdata(C,N) return P from the most recent
-** sqlite3_set_auxdata(C,N,P,X) call if the metadata is still valid or
-** NULL if the metadata has been discarded.
+** sqlite3_set_auxdata(C,N,P,X) call if the auxiliary data is still valid or
+** NULL if the auxiliary data has been discarded.
 ** ^After each call to sqlite3_set_auxdata(C,N,P,X) where X is not NULL,
 ** SQLite will invoke the destructor function X with parameter P exactly
-** once, when the metadata is discarded.
-** SQLite is free to discard the metadata at any time, including: <ul>
+** once, when the auxiliary data is discarded.
+** SQLite is free to discard the auxiliary data at any time, including: <ul>
 ** <li> ^(when the corresponding function parameter changes)^, or
 ** <li> ^(when [sqlite3_reset()] or [sqlite3_finalize()] is called for the
 **      SQL statement)^, or
@@ -5965,7 +5967,7 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
 ** function implementation should not make any use of P after
 ** sqlite3_set_auxdata() has been called.
 **
-** ^(In practice, metadata is preserved between function calls for
+** ^(In practice, auxiliary data is preserved between function calls for
 ** function parameters that are compile-time constants, including literal
 ** values and [parameters] and expressions composed from the same.)^
 **
@@ -5975,10 +5977,67 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
 **
 ** These routines must be called from the same thread in which
 ** the SQL function is running.
+**
+** See also: [sqlite3_get_clientdata()] and [sqlite3_set_clientdata()].
 */
 void *sqlite3_get_auxdata(sqlite3_context*, int N);
 void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*));
 
+/*
+** CAPI3REF: Database Connection Client Data
+** METHOD: sqlite3
+**
+** These functions are used to associate one or more named pointers
+** with a [database connection].
+** A call to sqlite3_set_clientdata(D,N,P,X) causes the pointer P
+** to be attached to [database connection] D using name N.  Subsequent
+** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P
+** or a NULL pointer if there were no prior calls to
+** sqlite3_set_clientdata() with the same values of D and N.
+** Names are compared using strcmp() and are thus case sensitive.
+**
+** If P and X are both non-NULL, then the destructor X is invoked with
+** argument P on the first of the following occurrences:
+** <ul>
+** <li> An out-of-memory error occurs during the call to
+**      sqlite3_set_clientdata() which attempts to register pointer P.
+** <li> A subsequent call to sqlite3_set_clientdata(D,N,P,X) is made
+**      with the same D and N parameters.
+** <li> The database connection closes.  SQLite does not make any guarantees
+**      about the order in which destructors are called, only that all
+**      destructors will be called exactly once at some point during the
+**      database connection closing process.
+** </ul>
+**
+** SQLite does not do anything with client data other than invoke
+** destructors on the client data at the appropriate time.  The intended
+** use for client data is to provide a mechanism for wrapper libraries
+** to store additional information about an SQLite database connection.
+**
+** There is no limit (other than available memory) on the number of different
+** client data pointers (with different names) that can be attached to a
+** single database connection.  However, the implementation is optimized
+** for the case of having only one or two different client data names.
+** Applications and wrapper libraries are discouraged from using more than
+** one client data name each.
+**
+** There is no way to enumerate the client data pointers
+** associated with a database connection.  The N parameter can be thought
+** of as a secret key such that only code that knows the secret key is able
+** to access the associated data.
+**
+** Security Warning:  These interfaces should not be exposed in scripting
+** languages or in other circumstances where it might be possible for an
+** an attacker to invoke them.  Any agent that can invoke these interfaces
+** can probably also take control of the process.
+** 
+** Database connection client data is only available for SQLite
+** version 3.44.0 ([dateof:3.44.0]) and later.
+**
+** See also: [sqlite3_set_auxdata()] and [sqlite3_get_auxdata()].
+*/
+void *sqlite3_get_clientdata(sqlite3*,const char*);
+int sqlite3_set_clientdata(sqlite3*, const char*, void*, void(*)(void*));
 
 /*
 ** CAPI3REF: Constants Defining Special Destructor Behavior
@@ -6611,7 +6670,7 @@ int sqlite3_db_readonly(sqlite3 *db, const char *zDbName);
 int sqlite3_txn_state(sqlite3*,const char *zSchema);
 
 /*
-** CAPI3REF: Allowed return values from [sqlite3_txn_state()]
+** CAPI3REF: Allowed return values from sqlite3_txn_state()
 ** KEYWORDS: {transaction state}
 **
 ** These constants define the current transaction state of a database file.
@@ -6743,7 +6802,7 @@ void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
 ** ^Each call to the sqlite3_autovacuum_pages() interface overrides all
 ** previous invocations for that database connection.  ^If the callback
 ** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer,
-** then the autovacuum steps callback is cancelled.  The return value
+** then the autovacuum steps callback is canceled.  The return value
 ** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might
 ** be some other error code if something goes wrong.  The current
 ** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other
@@ -7265,6 +7324,10 @@ struct sqlite3_module {
   /* The methods below relate to features contributed by the community and
   ** are available for version 700 and greater. */
   int (*xPreparedSql)(sqlite3_vtab_cursor*, const char*);
+  /* The methods above are in versions 1 through 3 of the sqlite_module object.
+  ** Those below are for version 701 and greater. */
+  int (*xIntegrity)(sqlite3_vtab *pVTab, const char *zSchema,
+                    const char *zTabName, int mFlags, char **pzErr);
 };
 
 /*
@@ -7752,7 +7815,7 @@ int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
 ** code is returned and the transaction rolled back.
 **
 ** Calling this function with an argument that is not a NULL pointer or an
-** open blob handle results in undefined behaviour. ^Calling this routine
+** open blob handle results in undefined behavior. ^Calling this routine
 ** with a null pointer (such as would be returned by a failed call to
 ** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function
 ** is passed a valid open blob handle, the values returned by the
@@ -8260,6 +8323,7 @@ int sqlite3_test_control(int op, ...);
 #define SQLITE_TESTCTRL_PRNG_SAVE                5
 #define SQLITE_TESTCTRL_PRNG_RESTORE             6
 #define SQLITE_TESTCTRL_PRNG_RESET               7  /* NOT USED */
+#define SQLITE_TESTCTRL_FK_NO_ACTION             7
 #define SQLITE_TESTCTRL_BITVEC_TEST              8
 #define SQLITE_TESTCTRL_FAULT_INSTALL            9
 #define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS     10
@@ -9333,8 +9397,8 @@ int sqlite3_backup_pagecount(sqlite3_backup *p);
 ** blocked connection already has a registered unlock-notify callback,
 ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
 ** called with a NULL pointer as its second argument, then any existing
-** unlock-notify callback is cancelled. ^The blocked connections
-** unlock-notify callback may also be cancelled by closing the blocked
+** unlock-notify callback is canceled. ^The blocked connections
+** unlock-notify callback may also be canceled by closing the blocked
 ** connection using [sqlite3_close()].
 **
 ** The unlock-notify callback is not reentrant. If an application invokes
@@ -10653,6 +10717,13 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
 ** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy
 ** of the database exists.
 **
+** After the call, if the SQLITE_SERIALIZE_NOCOPY bit had been set,
+** the returned buffer content will remain accessible and unchanged
+** until either the next write operation on the connection or when
+** the connection is closed, and applications must not modify the
+** buffer. If the bit had been clear, the returned buffer will not
+** be accessed by SQLite after the call.
+**
 ** A call to sqlite3_serialize(D,S,P,F) might return NULL even if the
 ** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory
 ** allocation error occurs.
@@ -10701,6 +10772,9 @@ unsigned char *sqlite3_serialize(
 ** SQLite will try to increase the buffer size using sqlite3_realloc64()
 ** if writes on the database cause it to grow larger than M bytes.
 **
+** Applications must not modify the buffer P or invalidate it before
+** the database connection D is closed.
+**
 ** The sqlite3_deserialize() interface will fail with SQLITE_BUSY if the
 ** database is currently in a read transaction or is involved in a backup
 ** operation.
@@ -10709,6 +10783,13 @@ unsigned char *sqlite3_serialize(
 ** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the
 ** function returns SQLITE_ERROR.
 **
+** The deserialized database should not be in [WAL mode].  If the database
+** is in WAL mode, then any attempt to use the database file will result
+** in an [SQLITE_CANTOPEN] error.  The application can set the
+** [file format version numbers] (bytes 18 and 19) of the input database P
+** to 0x01 prior to invoking sqlite3_deserialize(D,S,P,N,M,F) to force the
+** database file into rollback mode and work around this limitation.
+**
 ** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the
 ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then
 ** [sqlite3_free()] is invoked on argument P prior to returning.
diff --git a/libsql-sqlite3/src/sqlite3ext.h b/libsql-sqlite3/src/sqlite3ext.h
index 87a988bc95..7ee07eade9 100644
--- a/libsql-sqlite3/src/sqlite3ext.h
+++ b/libsql-sqlite3/src/sqlite3ext.h
@@ -363,6 +363,9 @@ struct sqlite3_api_routines {
   int (*is_interrupted)(sqlite3*);
   /* Version 3.43.0 and later */
   int (*stmt_explain)(sqlite3_stmt*,int);
+  /* Version 3.44.0 and later */
+  void *(*get_clientdata)(sqlite3*,const char*);
+  int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
 };
 
 struct libsql_api_routines {
@@ -709,6 +712,9 @@ typedef int (*sqlite3_loadext_entry)(
 #define libsql_wal_methods_unregister  libsql_api->wal_methods_unregister
 /* libSQL 0.2.3 */
 #define libsql_close_hook              libsql_api->close_hook
+/* Version 3.44.0 and later */
+#define sqlite3_get_clientdata         sqlite3_api->get_clientdata
+#define sqlite3_set_clientdata         sqlite3_api->set_clientdata
 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
 
 #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
diff --git a/libsql-sqlite3/src/sqliteInt.h b/libsql-sqlite3/src/sqliteInt.h
index 280b02052e..cd02a8ad05 100644
--- a/libsql-sqlite3/src/sqliteInt.h
+++ b/libsql-sqlite3/src/sqliteInt.h
@@ -318,6 +318,16 @@
 #  endif
 #endif
 
+/*
+** Enable SQLITE_USE_SEH by default on MSVC builds.  Only omit
+** SEH support if the -DSQLITE_OMIT_SEH option is given.
+*/
+#if defined(_MSC_VER) && !defined(SQLITE_OMIT_SEH)
+# define SQLITE_USE_SEH 1
+#else
+# undef SQLITE_USE_SEH
+#endif
+
 /*
 ** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2.
 ** 0 means mutexes are permanently disable and the library is never
@@ -923,16 +933,33 @@ typedef INT16_TYPE LogEst;
 ** using C-preprocessor macros.  If that is unsuccessful, or if
 ** -DSQLITE_BYTEORDER=0 is set, then byte-order is determined
 ** at run-time.
+**
+** If you are building SQLite on some obscure platform for which the
+** following ifdef magic does not work, you can always include either:
+**
+**    -DSQLITE_BYTEORDER=1234
+**
+** or
+**
+**    -DSQLITE_BYTEORDER=4321
+**
+** to cause the build to work for little-endian or big-endian processors,
+** respectively.
 */
-#ifndef SQLITE_BYTEORDER
-# if defined(i386)      || defined(__i386__)      || defined(_M_IX86) ||    \
+#ifndef SQLITE_BYTEORDER  /* Replicate changes at tag-20230904a */
+# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__
+#   define SQLITE_BYTEORDER 4321
+# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
+#   define SQLITE_BYTEORDER 1234
+# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1
+#   define SQLITE_BYTEORDER 4321
+# elif defined(i386)    || defined(__i386__)      || defined(_M_IX86) ||    \
      defined(__x86_64)  || defined(__x86_64__)    || defined(_M_X64)  ||    \
      defined(_M_AMD64)  || defined(_M_ARM)        || defined(__x86)   ||    \
      defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64)
-#   define SQLITE_BYTEORDER    1234
-# elif defined(sparc)     || defined(__ppc__) || \
-       defined(__ARMEB__) || defined(__AARCH64EB__)
-#   define SQLITE_BYTEORDER    4321
+#   define SQLITE_BYTEORDER 1234
+# elif defined(sparc)   || defined(__ARMEB__)     || defined(__AARCH64EB__)
+#   define SQLITE_BYTEORDER 4321
 # else
 #   define SQLITE_BYTEORDER 0
 # endif
@@ -1256,6 +1283,7 @@ typedef struct Column Column;
 typedef struct Cte Cte;
 typedef struct CteUse CteUse;
 typedef struct Db Db;
+typedef struct DbClientData DbClientData;
 typedef struct DbFixer DbFixer;
 typedef struct Schema Schema;
 typedef struct Expr Expr;
@@ -1746,6 +1774,7 @@ struct sqlite3 {
   i64 nDeferredCons;            /* Net deferred constraints this transaction. */
   i64 nDeferredImmCons;         /* Net deferred immediate constraints */
   int *pnBytesFreed;            /* If not NULL, increment this in DbFree() */
+  DbClientData *pDbData;        /* sqlite3_set_clientdata() content */
 #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
   /* The following variables are all protected by the STATIC_MAIN
   ** mutex, not by sqlite3.mutex. They are used by code in notify.c.
@@ -1837,6 +1866,7 @@ struct sqlite3 {
                                           /*   the count using a callback. */
 #define SQLITE_CorruptRdOnly  HI(0x00002) /* Prohibit writes due to error */
 #define SQLITE_ReadUncommit   HI(0x00004) /* READ UNCOMMITTED in shared-cache */
+#define SQLITE_FkNoAction     HI(0x00008) /* Treat all FK as NO ACTION */
 
 /* Flags used only if debugging */
 #ifdef SQLITE_DEBUG
@@ -2856,6 +2886,9 @@ struct AggInfo {
     FuncDef *pFunc;          /* The aggregate function implementation */
     int iDistinct;           /* Ephemeral table used to enforce DISTINCT */
     int iDistAddr;           /* Address of OP_OpenEphemeral */
+    int iOBTab;              /* Ephemeral table to implement ORDER BY */
+    u8 bOBPayload;           /* iOBTab has payload columns separate from key */
+    u8 bOBUnique;            /* Enforce uniqueness on iOBTab keys */
   } *aFunc;
   int nFunc;              /* Number of entries in aFunc[] */
   u32 selId;              /* Select to which this AggInfo belongs */
@@ -3040,7 +3073,7 @@ struct Expr {
 #define EP_Reduced    0x004000 /* Expr struct EXPR_REDUCEDSIZE bytes only */
 #define EP_Win        0x008000 /* Contains window functions */
 #define EP_TokenOnly  0x010000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
-                   /* 0x020000 // Available for reuse */
+#define EP_FullSize   0x020000 /* Expr structure must remain full sized */
 #define EP_IfNullRow  0x040000 /* The TK_IF_NULL_ROW opcode */
 #define EP_Unlikely   0x080000 /* unlikely() or likelihood() function */
 #define EP_ConstFunc  0x100000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
@@ -3070,6 +3103,7 @@ struct Expr {
 #define ExprClearProperty(E,P)   (E)->flags&=~(P)
 #define ExprAlwaysTrue(E)   (((E)->flags&(EP_OuterON|EP_IsTrue))==EP_IsTrue)
 #define ExprAlwaysFalse(E)  (((E)->flags&(EP_OuterON|EP_IsFalse))==EP_IsFalse)
+#define ExprIsFullSize(E)   (((E)->flags&(EP_Reduced|EP_TokenOnly))==0)
 
 /* Macros used to ensure that the correct members of unions are accessed
 ** in Expr.
@@ -3187,6 +3221,7 @@ struct ExprList {
 #define ENAME_NAME  0       /* The AS clause of a result set */
 #define ENAME_SPAN  1       /* Complete text of the result set expression */
 #define ENAME_TAB   2       /* "DB.TABLE.NAME" for the result set */
+#define ENAME_ROWID 3       /* "DB.TABLE._rowid_" for * expansion of rowid */
 
 /*
 ** An instance of this structure can hold a simple list of identifiers,
@@ -3795,6 +3830,7 @@ struct Parse {
   int *aLabel;         /* Space to hold the labels */
   ExprList *pConstExpr;/* Constant expressions */
   IndexedExpr *pIdxEpr;/* List of expressions used by active indexes */
+  IndexedExpr *pIdxPartExpr; /* Exprs constrained by index WHERE clauses */
   Token constraintName;/* Name of the constraint currently being parsed */
   yDbMask writeMask;   /* Start a write transaction on these databases */
   yDbMask cookieMask;  /* Bitmask of schema verified databases */
@@ -4066,6 +4102,7 @@ struct Returning {
   int iRetCur;          /* Transient table holding RETURNING results */
   int nRetCol;          /* Number of in pReturnEL after expansion */
   int iRetReg;          /* Register array for holding a row of RETURNING */
+  char zName[40];       /* Name of trigger: "sqlite_returning_%p" */
 };
 
 /*
@@ -4366,6 +4403,16 @@ struct CteUse {
 };
 
 
+/* Client data associated with sqlite3_set_clientdata() and
+** sqlite3_get_clientdata().
+*/
+struct DbClientData {
+  DbClientData *pNext;        /* Next in a linked list */
+  void *pData;                /* The data */
+  void (*xDestructor)(void*); /* Destructor.  Might be NULL */
+  char zName[1];              /* Name of this client data. MUST BE LAST */
+};
+
 #ifdef SQLITE_DEBUG
 /*
 ** An instance of the TreeView object is used for printing the content of
@@ -4770,6 +4817,8 @@ void sqlite3PExprAddSelect(Parse*, Expr*, Select*);
 Expr *sqlite3ExprAnd(Parse*,Expr*, Expr*);
 Expr *sqlite3ExprSimplifiedAndOr(Expr*);
 Expr *sqlite3ExprFunction(Parse*,ExprList*, const Token*, int);
+void sqlite3ExprAddFunctionOrderBy(Parse*,Expr*,ExprList*);
+void sqlite3ExprOrderByAggregateError(Parse*,Expr*);
 void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*);
 void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32);
 void sqlite3ExprDelete(sqlite3*, Expr*);
@@ -5006,6 +5055,7 @@ int sqlite3ExprIsInteger(const Expr*, int*);
 int sqlite3ExprCanBeNull(const Expr*);
 int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
 int sqlite3IsRowid(const char*);
+const char *sqlite3RowidAlias(Table *pTab);
 void sqlite3GenerateRowDelete(
     Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int);
 void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int);
@@ -5278,7 +5328,8 @@ int sqlite3MatchEName(
   const struct ExprList_item*,
   const char*,
   const char*,
-  const char*
+  const char*,
+  int*
 );
 Bitmask sqlite3ExprColUsed(Expr*);
 u8 sqlite3StrIHash(const char*);
@@ -5335,7 +5386,7 @@ int sqlite3ApiExit(sqlite3 *db, int);
 int sqlite3OpenTempDatabase(Parse *);
 
 char *sqlite3RCStrRef(char*);
-void sqlite3RCStrUnref(char*);
+void sqlite3RCStrUnref(void*);
 char *sqlite3RCStrNew(u64);
 char *sqlite3RCStrResize(char*,u64);
 
diff --git a/libsql-sqlite3/src/test1.c b/libsql-sqlite3/src/test1.c
index 4dcf7bc11f..55be0596b0 100644
--- a/libsql-sqlite3/src/test1.c
+++ b/libsql-sqlite3/src/test1.c
@@ -4222,9 +4222,11 @@ static int SQLITE_TCLAPI test_bind_value_from_preupdate(
   sqlite3_stmt *pStmt;
   int idx;
   int bidx;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
   const char *z3 = 0;
   sqlite3 *db = 0;
   sqlite3_value *pVal = 0;
+#endif
 
   if( objc!=5 ){
     Tcl_WrongNumArgs(interp, 1, objv, "STMT N NEW|OLD IDX");
@@ -4233,11 +4235,11 @@ static int SQLITE_TCLAPI test_bind_value_from_preupdate(
 
   if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
   if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
-  z3 = Tcl_GetString(objv[3]);
   if( Tcl_GetIntFromObj(interp, objv[4], &bidx) ) return TCL_ERROR;
-  db = sqlite3_db_handle(pStmt);
 
 #ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+  z3 = Tcl_GetString(objv[3]);
+  db = sqlite3_db_handle(pStmt);
   if( z3[0]=='n' ){
     sqlite3_preupdate_new(db, bidx, &pVal);
   }else if( z3[0]=='o' ){
@@ -7579,6 +7581,41 @@ static int testLocaltime(const void *aliasT, void *aliasTM){
   return t==959609760; /* Special case: 2000-05-29 14:16:00 fails */
 }
 
+/*
+** TCLCMD:  strftime FORMAT UNIXTIMESTAMP
+**
+** Access to the C-library strftime() routine, so that its results
+** can be compared against SQLite's internal strftime() SQL function
+** implementation.
+*/
+static int SQLITE_TCLAPI strftime_cmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_WideInt ts;
+  time_t t;
+  struct tm *pTm;
+  const char *zFmt;
+  size_t n;
+  char zBuf[1000];
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FORMAT UNIXTIMESTAMP");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetWideIntFromObj(interp,  objv[2], &ts) ) return TCL_ERROR;
+  zFmt = Tcl_GetString(objv[1]);
+  t = (time_t)ts;
+  pTm = gmtime(&t);
+  n = strftime(zBuf, sizeof(zBuf)-1, zFmt, pTm);
+  if( n>=0 && n<sizeof(zBuf) ){
+    zBuf[n] = 0;
+    Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
+  }
+  return TCL_OK;
+}
+
 /*
 ** .treetrace N
 */
@@ -7616,6 +7653,7 @@ static int SQLITE_TCLAPI test_test_control(
     { "SQLITE_TESTCTRL_SORTER_MMAP",        SQLITE_TESTCTRL_SORTER_MMAP     }, 
     { "SQLITE_TESTCTRL_IMPOSTER",           SQLITE_TESTCTRL_IMPOSTER        },
     { "SQLITE_TESTCTRL_INTERNAL_FUNCTIONS", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS},
+    { "SQLITE_TESTCTRL_FK_NO_ACTION",       SQLITE_TESTCTRL_FK_NO_ACTION},
     { 0, 0 }
   };
   int iVerb;
@@ -7655,6 +7693,20 @@ static int SQLITE_TCLAPI test_test_control(
       break;
     }
 
+    case SQLITE_TESTCTRL_FK_NO_ACTION: {
+      int val = 0;
+      sqlite3 *db = 0;
+      if( objc!=4 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "DB BOOLEAN");
+        return TCL_ERROR;
+      }
+      if( getDbPointer(interp, Tcl_GetString(objv[2]), &db) ) return TCL_ERROR;
+      if( Tcl_GetBooleanFromObj(interp, objv[3], &val) ) return TCL_ERROR;
+
+      sqlite3_test_control(SQLITE_TESTCTRL_FK_NO_ACTION, db, val);
+      break;
+    }
+
     case SQLITE_TESTCTRL_SORTER_MMAP: {
       int val;
       sqlite3 *db;
@@ -9152,6 +9204,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
 #ifndef SQLITE_OMIT_EXPLAIN
      { "print_explain_query_plan", test_print_eqp, 0  },
 #endif
+     { "strftime",             strftime_cmd      },
      { "sqlite3_test_control", test_test_control },
      { ".treetrace",           test_treetrace    },
 #if SQLITE_OS_UNIX
diff --git a/libsql-sqlite3/src/test8.c b/libsql-sqlite3/src/test8.c
index 0da6bd6133..71f8884c01 100644
--- a/libsql-sqlite3/src/test8.c
+++ b/libsql-sqlite3/src/test8.c
@@ -1328,7 +1328,12 @@ static sqlite3_module echoModule = {
   echoCommit,                /* xCommit - commit transaction */
   echoRollback,              /* xRollback - rollback transaction */
   echoFindFunction,          /* xFindFunction - function overloading */
-  echoRename                 /* xRename - rename the table */
+  echoRename,                /* xRename - rename the table */
+  0,                         /* xSavepoint */
+  0,                         /* xRelease */
+  0,                         /* xRollbackTo */
+  0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 
 static sqlite3_module echoModuleV2 = {
@@ -1355,8 +1360,9 @@ static sqlite3_module echoModuleV2 = {
   echoSavepoint,
   echoRelease,
   echoRollbackTo,
-  echoShadowName,
+  0,                         /* xShadowName */
   echoPreparedSql,
+  0                          /* xIntegrity  */
 };
 
 /*
diff --git a/libsql-sqlite3/src/test_bestindex.c b/libsql-sqlite3/src/test_bestindex.c
index f6e0678ce0..8128530b40 100644
--- a/libsql-sqlite3/src/test_bestindex.c
+++ b/libsql-sqlite3/src/test_bestindex.c
@@ -814,6 +814,11 @@ static sqlite3_module tclModule = {
   0,                           /* xRollback */
   tclFindFunction,             /* xFindFunction */
   0,                           /* xRename */
+  0,                           /* xSavepoint */
+  0,                           /* xRelease */
+  0,                           /* xRollbackTo */
+  0,                           /* xShadowName */
+  0                            /* xIntegrity */
 };
 
 /*
diff --git a/libsql-sqlite3/src/test_fs.c b/libsql-sqlite3/src/test_fs.c
index ddfdc7fb59..f88f3a9425 100644
--- a/libsql-sqlite3/src/test_fs.c
+++ b/libsql-sqlite3/src/test_fs.c
@@ -816,6 +816,11 @@ static sqlite3_module fsModule = {
   0,                           /* xRollback */
   0,                           /* xFindMethod */
   0,                           /* xRename */
+  0,                           /* xSavepoint */
+  0,                           /* xRelease */
+  0,                           /* xRollbackTo */
+  0,                           /* xShadowName */
+  0                            /* xIntegrity */
 };
 
 static sqlite3_module fsdirModule = {
@@ -839,6 +844,11 @@ static sqlite3_module fsdirModule = {
   0,                              /* xRollback */
   0,                              /* xFindMethod */
   0,                              /* xRename */
+  0,                              /* xSavepoint */
+  0,                              /* xRelease */
+  0,                              /* xRollbackTo */
+  0,                              /* xShadowName */
+  0                               /* xIntegrity */
 };
 
 static sqlite3_module fstreeModule = {
@@ -862,6 +872,11 @@ static sqlite3_module fstreeModule = {
   0,                              /* xRollback */
   0,                              /* xFindMethod */
   0,                              /* xRename */
+  0,                              /* xSavepoint */
+  0,                              /* xRelease */
+  0,                              /* xRollbackTo */
+  0,                              /* xShadowName */
+  0                               /* xIntegrity */
 };
 
 /*
diff --git a/libsql-sqlite3/src/test_intarray.c b/libsql-sqlite3/src/test_intarray.c
index 8c74a04156..a978ed585d 100644
--- a/libsql-sqlite3/src/test_intarray.c
+++ b/libsql-sqlite3/src/test_intarray.c
@@ -205,6 +205,11 @@ static sqlite3_module intarrayModule = {
   0,                           /* xRollback */
   0,                           /* xFindMethod */
   0,                           /* xRename */
+  0,                           /* xSavepoint */
+  0,                           /* xRelease */
+  0,                           /* xRollbackTo */
+  0,                           /* xShadowName */
+  0                            /* xIntegrity */
 };
 
 #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */
diff --git a/libsql-sqlite3/src/test_osinst.c b/libsql-sqlite3/src/test_osinst.c
index 3e698c0324..062e83159b 100644
--- a/libsql-sqlite3/src/test_osinst.c
+++ b/libsql-sqlite3/src/test_osinst.c
@@ -1090,7 +1090,12 @@ int sqlite3_vfslog_register(sqlite3 *db){
     0,                            /* xRollback */
     0,                            /* xFindMethod */
     0,                            /* xRename */
-  };
+    0,                            /* xSavepoint */
+    0,                            /* xRelease */
+    0,                            /* xRollbackTo */
+    0,                            /* xShadowName */
+    0                             /* xIntegrity */
+ };
 
   sqlite3_create_module(db, "vfslog", &vfslog_module, 0);
   return SQLITE_OK;
diff --git a/libsql-sqlite3/src/test_schema.c b/libsql-sqlite3/src/test_schema.c
index d2cae7f2aa..2cbc18e2b2 100644
--- a/libsql-sqlite3/src/test_schema.c
+++ b/libsql-sqlite3/src/test_schema.c
@@ -292,6 +292,11 @@ static sqlite3_module schemaModule = {
   0,                           /* xRollback */
   0,                           /* xFindMethod */
   0,                           /* xRename */
+  0,                           /* xSavepoint */
+  0,                           /* xRelease */
+  0,                           /* xRollbackTo */
+  0,                           /* xShadowName */
+  0                            /* xIntegrity */
 };
 
 #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */
diff --git a/libsql-sqlite3/src/test_tclvar.c b/libsql-sqlite3/src/test_tclvar.c
index bf99a8eadb..36165bc27d 100644
--- a/libsql-sqlite3/src/test_tclvar.c
+++ b/libsql-sqlite3/src/test_tclvar.c
@@ -487,6 +487,11 @@ static sqlite3_module tclvarModule = {
   0,                           /* xRollback */
   0,                           /* xFindMethod */
   0,                           /* xRename */
+  0,                           /* xSavepoint */
+  0,                           /* xRelease */
+  0,                           /* xRollbackTo */
+  0,                           /* xShadowName */
+  0                            /* xIntegrity */
 };
 
 /*
diff --git a/libsql-sqlite3/src/treeview.c b/libsql-sqlite3/src/treeview.c
index d55adab384..1fad8673dd 100644
--- a/libsql-sqlite3/src/treeview.c
+++ b/libsql-sqlite3/src/treeview.c
@@ -412,6 +412,7 @@ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){
     sqlite3TreeViewItem(pView, "FILTER", 1);
     sqlite3TreeViewExpr(pView, pWin->pFilter, 0);
     sqlite3TreeViewPop(&pView);
+    if( pWin->eFrmType==TK_FILTER ) return;
   }
   sqlite3TreeViewPush(&pView, more);
   if( pWin->zName ){
@@ -421,7 +422,7 @@ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){
   }
   if( pWin->zBase )    nElement++;
   if( pWin->pOrderBy ) nElement++;
-  if( pWin->eFrmType ) nElement++;
+  if( pWin->eFrmType!=0 && pWin->eFrmType!=TK_FILTER ) nElement++;
   if( pWin->eExclude ) nElement++;
   if( pWin->zBase ){
     sqlite3TreeViewPush(&pView, (--nElement)>0);
@@ -434,7 +435,7 @@ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){
   if( pWin->pOrderBy ){
     sqlite3TreeViewExprList(pView, pWin->pOrderBy, (--nElement)>0, "ORDER-BY");
   }
-  if( pWin->eFrmType ){
+  if( pWin->eFrmType!=0 && pWin->eFrmType!=TK_FILTER ){
     char zBuf[30];
     const char *zFrmType = "ROWS";
     if( pWin->eFrmType==TK_RANGE ) zFrmType = "RANGE";
@@ -682,7 +683,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
         assert( ExprUseXList(pExpr) );
         pFarg = pExpr->x.pList;
 #ifndef SQLITE_OMIT_WINDOWFUNC
-        pWin = ExprHasProperty(pExpr, EP_WinFunc) ? pExpr->y.pWin : 0;
+        pWin = IsWindowFunc(pExpr) ? pExpr->y.pWin : 0;
 #else
         pWin = 0;
 #endif 
@@ -708,7 +709,13 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
         sqlite3TreeViewLine(pView, "FUNCTION %Q%s", pExpr->u.zToken, zFlgs);
       }
       if( pFarg ){
-        sqlite3TreeViewExprList(pView, pFarg, pWin!=0, 0);
+        sqlite3TreeViewExprList(pView, pFarg, pWin!=0 || pExpr->pLeft, 0);
+        if( pExpr->pLeft ){
+          Expr *pOB = pExpr->pLeft;
+          assert( pOB->op==TK_ORDER );
+          assert( ExprUseXList(pOB) );
+          sqlite3TreeViewExprList(pView, pOB->x.pList, pWin!=0, "ORDERBY");
+        }
       }
 #ifndef SQLITE_OMIT_WINDOWFUNC
       if( pWin ){
@@ -717,6 +724,10 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
 #endif
       break;
     }
+    case TK_ORDER: {
+      sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, "ORDERBY");
+      break;
+    }
 #ifndef SQLITE_OMIT_SUBQUERY
     case TK_EXISTS: {
       assert( ExprUseXSelect(pExpr) );
diff --git a/libsql-sqlite3/src/trigger.c b/libsql-sqlite3/src/trigger.c
index bcb2132f0b..97ca249be5 100644
--- a/libsql-sqlite3/src/trigger.c
+++ b/libsql-sqlite3/src/trigger.c
@@ -183,6 +183,10 @@ void sqlite3BeginTrigger(
     sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables");
     goto trigger_orphan_error;
   }
+  if( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(db) ){
+    sqlite3ErrorMsg(pParse, "cannot create triggers on shadow tables");
+    goto trigger_orphan_error;
+  }
 
   /* Check that the trigger name is not reserved and that no trigger of the
   ** specified name exists */
@@ -966,10 +970,17 @@ static void codeReturningTrigger(
   SrcList sFrom;
 
   assert( v!=0 );
-  assert( pParse->bReturning );
+  if( !pParse->bReturning ){
+    /* This RETURNING trigger must be for a different statement as
+    ** this statement lacks a RETURNING clause. */
+    return;
+  }
   assert( db->pParse==pParse );
   pReturning = pParse->u1.pReturning;
-  assert( pTrigger == &(pReturning->retTrig) );
+  if( pTrigger != &(pReturning->retTrig) ){
+    /* This RETURNING trigger is for a different statement */
+    return;
+  }
   memset(&sSelect, 0, sizeof(sSelect));
   memset(&sFrom, 0, sizeof(sFrom));
   sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0);
diff --git a/libsql-sqlite3/src/util.c b/libsql-sqlite3/src/util.c
index 97deb64cfd..e9c7cccb03 100644
--- a/libsql-sqlite3/src/util.c
+++ b/libsql-sqlite3/src/util.c
@@ -202,12 +202,16 @@ void sqlite3ProgressCheck(Parse *p){
     p->rc = SQLITE_INTERRUPT;
   }
 #ifndef SQLITE_OMIT_PROGRESS_CALLBACK
-  if( db->xProgress && (++p->nProgressSteps)>=db->nProgressOps ){
-    if( db->xProgress(db->pProgressArg) ){
-      p->nErr++;
-      p->rc = SQLITE_INTERRUPT;
+  if( db->xProgress ){
+    if( p->rc==SQLITE_INTERRUPT ){
+      p->nProgressSteps = 0;
+    }else if( (++p->nProgressSteps)>=db->nProgressOps ){
+      if( db->xProgress(db->pProgressArg) ){
+        p->nErr++;
+        p->rc = SQLITE_INTERRUPT;
+      }
+      p->nProgressSteps = 0;
     }
-    p->nProgressSteps = 0;
   }
 #endif
 }
@@ -1025,29 +1029,29 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){
     double rr[2];
     rr[0] = r;
     rr[1] = 0.0;
-    if( rr[0]>1.84e+19 ){
-      while( rr[0]>1.84e+119 ){
+    if( rr[0]>9.223372036854774784e+18 ){
+      while( rr[0]>9.223372036854774784e+118 ){
         exp += 100;
         dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117);
       }
-      while( rr[0]>1.84e+29 ){
+      while( rr[0]>9.223372036854774784e+28 ){
         exp += 10;
         dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27);
       }
-      while( rr[0]>1.84e+19 ){
+      while( rr[0]>9.223372036854774784e+18 ){
         exp += 1;
         dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18);
       }
     }else{
-      while( rr[0]<1.84e-82  ){
+      while( rr[0]<9.223372036854774784e-83  ){
         exp -= 100;
         dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83);
       }
-      while( rr[0]<1.84e+08  ){
+      while( rr[0]<9.223372036854774784e+07  ){
         exp -= 10;
         dekkerMul2(rr, 1.0e+10, 0.0);
       }
-      while( rr[0]<1.84e+18  ){
+      while( rr[0]<9.22337203685477478e+17  ){
         exp -= 1;
         dekkerMul2(rr, 1.0e+01, 0.0);
       }
@@ -1363,121 +1367,32 @@ u8 sqlite3GetVarint(const unsigned char *p, u64 *v){
 ** this function assumes the single-byte case has already been handled.
 */
 u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){
-  u32 a,b;
+  u64 v64;
+  u8 n;
 
-  /* The 1-byte case.  Overwhelmingly the most common.  Handled inline
-  ** by the getVarin32() macro */
-  a = *p;
-  /* a: p0 (unmasked) */
-#ifndef getVarint32
-  if (!(a&0x80))
-  {
-    /* Values between 0 and 127 */
-    *v = a;
-    return 1;
-  }
-#endif
+  /* Assume that the single-byte case has already been handled by
+  ** the getVarint32() macro */
+  assert( (p[0] & 0x80)!=0 );
 
-  /* The 2-byte case */
-  p++;
-  b = *p;
-  /* b: p1 (unmasked) */
-  if (!(b&0x80))
-  {
-    /* Values between 128 and 16383 */
-    a &= 0x7f;
-    a = a<<7;
-    *v = a | b;
+  if( (p[1] & 0x80)==0 ){
+    /* This is the two-byte case */
+    *v = ((p[0]&0x7f)<<7) | p[1];
     return 2;
   }
-
-  /* The 3-byte case */
-  p++;
-  a = a<<14;
-  a |= *p;
-  /* a: p0<<14 | p2 (unmasked) */
-  if (!(a&0x80))
-  {
-    /* Values between 16384 and 2097151 */
-    a &= (0x7f<<14)|(0x7f);
-    b &= 0x7f;
-    b = b<<7;
-    *v = a | b;
+  if( (p[2] & 0x80)==0 ){
+    /* This is the three-byte case */
+    *v = ((p[0]&0x7f)<<14) | ((p[1]&0x7f)<<7) | p[2];
     return 3;
   }
-
-  /* A 32-bit varint is used to store size information in btrees.
-  ** Objects are rarely larger than 2MiB limit of a 3-byte varint.
-  ** A 3-byte varint is sufficient, for example, to record the size
-  ** of a 1048569-byte BLOB or string.
-  **
-  ** We only unroll the first 1-, 2-, and 3- byte cases.  The very
-  ** rare larger cases can be handled by the slower 64-bit varint
-  ** routine.
-  */
-#if 1
-  {
-    u64 v64;
-    u8 n;
-
-    n = sqlite3GetVarint(p-2, &v64);
-    assert( n>3 && n<=9 );
-    if( (v64 & SQLITE_MAX_U32)!=v64 ){
-      *v = 0xffffffff;
-    }else{
-      *v = (u32)v64;
-    }
-    return n;
-  }
-
-#else
-  /* For following code (kept for historical record only) shows an
-  ** unrolling for the 3- and 4-byte varint cases.  This code is
-  ** slightly faster, but it is also larger and much harder to test.
-  */
-  p++;
-  b = b<<14;
-  b |= *p;
-  /* b: p1<<14 | p3 (unmasked) */
-  if (!(b&0x80))
-  {
-    /* Values between 2097152 and 268435455 */
-    b &= (0x7f<<14)|(0x7f);
-    a &= (0x7f<<14)|(0x7f);
-    a = a<<7;
-    *v = a | b;
-    return 4;
-  }
-
-  p++;
-  a = a<<14;
-  a |= *p;
-  /* a: p0<<28 | p2<<14 | p4 (unmasked) */
-  if (!(a&0x80))
-  {
-    /* Values  between 268435456 and 34359738367 */
-    a &= SLOT_4_2_0;
-    b &= SLOT_4_2_0;
-    b = b<<7;
-    *v = a | b;
-    return 5;
-  }
-
-  /* We can only reach this point when reading a corrupt database
-  ** file.  In that case we are not in any hurry.  Use the (relatively
-  ** slow) general-purpose sqlite3GetVarint() routine to extract the
-  ** value. */
-  {
-    u64 v64;
-    u8 n;
-
-    p -= 4;
-    n = sqlite3GetVarint(p, &v64);
-    assert( n>5 && n<=9 );
+  /* four or more bytes */
+  n = sqlite3GetVarint(p, &v64);
+  assert( n>3 && n<=9 );
+  if( (v64 & SQLITE_MAX_U32)!=v64 ){
+    *v = 0xffffffff;
+  }else{
     *v = (u32)v64;
-    return n;
   }
-#endif
+  return n;
 }
 
 /*
diff --git a/libsql-sqlite3/src/vdbe.c b/libsql-sqlite3/src/vdbe.c
index 5e644067b1..21a6147fbc 100644
--- a/libsql-sqlite3/src/vdbe.c
+++ b/libsql-sqlite3/src/vdbe.c
@@ -765,11 +765,11 @@ static SQLITE_NOINLINE int vdbeColumnFromOverflow(
     sqlite3RCStrRef(pBuf);
     if( t&1 ){
       rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, encoding,
-                                (void(*)(void*))sqlite3RCStrUnref);
+                                sqlite3RCStrUnref);
       pDest->flags |= MEM_Term;
     }else{
       rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, 0,
-                                (void(*)(void*))sqlite3RCStrUnref);
+                                sqlite3RCStrUnref);
     }
   }else{
     rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, iOffset, len, pDest);
@@ -3649,7 +3649,6 @@ case OP_MakeRecord: {
         /* NULL value.  No change in zPayload */
       }else{
         u64 v;
-        u32 i;
         if( serial_type==7 ){
           assert( sizeof(v)==sizeof(pRec->u.r) );
           memcpy(&v, &pRec->u.r, sizeof(v));
@@ -3657,12 +3656,17 @@ case OP_MakeRecord: {
         }else{
           v = pRec->u.i;
         }
-        len = i = sqlite3SmallTypeSizes[serial_type];
-        assert( i>0 );
-        while( 1 /*exit-by-break*/ ){
-          zPayload[--i] = (u8)(v&0xFF);
-          if( i==0 ) break;
-          v >>= 8;
+        len = sqlite3SmallTypeSizes[serial_type];
+        assert( len>=1 && len<=8 && len!=5 && len!=7 );
+        switch( len ){
+          default: zPayload[7] = (u8)(v&0xff); v >>= 8;
+                   zPayload[6] = (u8)(v&0xff); v >>= 8;
+          case 6:  zPayload[5] = (u8)(v&0xff); v >>= 8;
+                   zPayload[4] = (u8)(v&0xff); v >>= 8;
+          case 4:  zPayload[3] = (u8)(v&0xff); v >>= 8;
+          case 3:  zPayload[2] = (u8)(v&0xff); v >>= 8;
+          case 2:  zPayload[1] = (u8)(v&0xff); v >>= 8;
+          case 1:  zPayload[0] = (u8)(v&0xff);
         }
         zPayload += len;
       }
@@ -5828,8 +5832,13 @@ case OP_RowCell: {
 ** the "primary" delete.  The others are all on OPFLAG_FORDELETE
 ** cursors or else are marked with the AUXDELETE flag.
 **
-** If the OPFLAG_NCHANGE flag of P2 (NB: P2 not P5) is set, then the row
-** change count is incremented (otherwise not).
+** If the OPFLAG_NCHANGE (0x01) flag of P2 (NB: P2 not P5) is set, then
+** the row change count is incremented (otherwise not).
+**
+** If the OPFLAG_ISNOOP (0x40) flag of P2 (not P5!) is set, then the
+** pre-update-hook for deletes is run, but the btree is otherwise unchanged.
+** This happens when the OP_Delete is to be shortly followed by an OP_Insert
+** with the same key, causing the btree entry to be overwritten.
 **
 ** P1 must not be pseudo-table.  It has to be a real table with
 ** multiple rows.
@@ -6960,13 +6969,41 @@ case OP_CreateBtree: {          /* out2 */
 /* Opcode: SqlExec * * * P4 *
 **
 ** Run the SQL statement or statements specified in the P4 string.
+** Disable Auth and Trace callbacks while those statements are running if
+** P1 is true.
 */
 case OP_SqlExec: {
+  char *zErr;
+#ifndef SQLITE_OMIT_AUTHORIZATION
+  sqlite3_xauth xAuth;
+#endif
+  u8 mTrace;
+
   sqlite3VdbeIncrWriteCounter(p, 0);
   db->nSqlExec++;
-  rc = sqlite3_exec(db, pOp->p4.z, 0, 0, 0);
+  zErr = 0;
+#ifndef SQLITE_OMIT_AUTHORIZATION
+  xAuth = db->xAuth;
+#endif
+  mTrace = db->mTrace;
+  if( pOp->p1 ){
+#ifndef SQLITE_OMIT_AUTHORIZATION
+    db->xAuth = 0;
+#endif
+    db->mTrace = 0;
+  }
+  rc = sqlite3_exec(db, pOp->p4.z, 0, 0, &zErr);
   db->nSqlExec--;
-  if( rc ) goto abort_due_to_error;
+#ifndef SQLITE_OMIT_AUTHORIZATION
+  db->xAuth = xAuth;
+#endif
+  db->mTrace = mTrace;
+  if( zErr || rc ){
+    sqlite3VdbeError(p, "%s", zErr);
+    sqlite3_free(zErr);
+    if( rc==SQLITE_NOMEM ) goto no_mem;
+    goto abort_due_to_error;
+  }
   break;
 }
 
@@ -8187,6 +8224,53 @@ case OP_VOpen: {             /* ncycle */
 }
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
 
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VCheck P1 P2 P3 P4 *
+**
+** P4 is a pointer to a Table object that is a virtual table in schema P1
+** that supports the xIntegrity() method.  This opcode runs the xIntegrity()
+** method for that virtual table, using P3 as the integer argument.  If
+** an error is reported back, the table name is prepended to the error
+** message and that message is stored in P2.  If no errors are seen,
+** register P2 is set to NULL.
+*/
+case OP_VCheck: {             /* out2 */
+  Table *pTab;
+  sqlite3_vtab *pVtab;
+  const sqlite3_module *pModule;
+  char *zErr = 0;
+
+  pOut = &aMem[pOp->p2];
+  sqlite3VdbeMemSetNull(pOut);  /* Innocent until proven guilty */
+  assert( pOp->p4type==P4_TABLE );
+  pTab = pOp->p4.pTab;
+  assert( pTab!=0 );
+  assert( IsVirtual(pTab) );
+  assert( pTab->u.vtab.p!=0 );
+  pVtab = pTab->u.vtab.p->pVtab;
+  assert( pVtab!=0 );
+  pModule = pVtab->pModule;
+  assert( pModule!=0 );
+  assert( pModule->iVersion>=4 );
+  assert( pModule->xIntegrity!=0 );
+  pTab->nTabRef++;
+  sqlite3VtabLock(pTab->u.vtab.p);
+  assert( pOp->p1>=0 && pOp->p1<db->nDb );
+  rc = pModule->xIntegrity(pVtab, db->aDb[pOp->p1].zDbSName, pTab->zName,
+                           pOp->p3, &zErr);
+  sqlite3VtabUnlock(pTab->u.vtab.p);
+  sqlite3DeleteTable(db, pTab);
+  if( rc ){
+    sqlite3_free(zErr);
+    goto abort_due_to_error;
+  }
+  if( zErr ){
+    sqlite3VdbeMemSetStr(pOut, zErr, -1, SQLITE_UTF8, sqlite3_free);
+  }
+  break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
 #ifndef SQLITE_OMIT_VIRTUALTABLE
 /* Opcode: VInitIn P1 P2 P3 * *
 ** Synopsis: r[P2]=ValueList(P1,P3)
diff --git a/libsql-sqlite3/src/vdbeapi.c b/libsql-sqlite3/src/vdbeapi.c
index 750bb8982c..42069e634f 100644
--- a/libsql-sqlite3/src/vdbeapi.c
+++ b/libsql-sqlite3/src/vdbeapi.c
@@ -375,7 +375,7 @@ void sqlite3_value_free(sqlite3_value *pOld){
 ** is too big or if an OOM occurs.
 **
 ** The invokeValueDestructor(P,X) routine invokes destructor function X()
-** on value P is not going to be used and need to be destroyed.
+** on value P if P is not going to be used and need to be destroyed.
 */
 static void setResultStrOrError(
   sqlite3_context *pCtx,  /* Function context */
@@ -405,7 +405,7 @@ static void setResultStrOrError(
 static int invokeValueDestructor(
   const void *p,             /* Value to destroy */
   void (*xDel)(void*),       /* The destructor */
-  sqlite3_context *pCtx      /* Set a SQLITE_TOOBIG error if no NULL */
+  sqlite3_context *pCtx      /* Set a SQLITE_TOOBIG error if not NULL */
 ){
   assert( xDel!=SQLITE_DYNAMIC );
   if( xDel==0 ){
@@ -415,7 +415,14 @@ static int invokeValueDestructor(
   }else{
     xDel((void*)p);
   }
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx!=0 ){
+    sqlite3_result_error_toobig(pCtx);
+  }
+#else
+  assert( pCtx!=0 );
   sqlite3_result_error_toobig(pCtx);
+#endif
   return SQLITE_TOOBIG;
 }
 void sqlite3_result_blob(
@@ -424,6 +431,12 @@ void sqlite3_result_blob(
   int n,
   void (*xDel)(void *)
 ){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 || n<0 ){
+    invokeValueDestructor(z, xDel, pCtx);
+    return;
+  }
+#endif
   assert( n>=0 );
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   setResultStrOrError(pCtx, z, n, 0, xDel);
@@ -434,8 +447,14 @@ void sqlite3_result_blob64(
   sqlite3_uint64 n,
   void (*xDel)(void *)
 ){
-  assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   assert( xDel!=SQLITE_DYNAMIC );
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ){
+    invokeValueDestructor(z, xDel, 0);
+    return;
+  }
+#endif
+  assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   if( n>0x7fffffff ){
     (void)invokeValueDestructor(z, xDel, pCtx);
   }else{
@@ -443,30 +462,48 @@ void sqlite3_result_blob64(
   }
 }
 void sqlite3_result_double(sqlite3_context *pCtx, double rVal){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   sqlite3VdbeMemSetDouble(pCtx->pOut, rVal);
 }
 void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   pCtx->isError = SQLITE_ERROR;
   sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT);
 }
 #ifndef SQLITE_OMIT_UTF16
 void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   pCtx->isError = SQLITE_ERROR;
   sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT);
 }
 #endif
 void sqlite3_result_int(sqlite3_context *pCtx, int iVal){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   sqlite3VdbeMemSetInt64(pCtx->pOut, (i64)iVal);
 }
 void sqlite3_result_int64(sqlite3_context *pCtx, i64 iVal){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   sqlite3VdbeMemSetInt64(pCtx->pOut, iVal);
 }
 void sqlite3_result_null(sqlite3_context *pCtx){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   sqlite3VdbeMemSetNull(pCtx->pOut);
 }
@@ -476,14 +513,25 @@ void sqlite3_result_pointer(
   const char *zPType,
   void (*xDestructor)(void*)
 ){
-  Mem *pOut = pCtx->pOut;
+  Mem *pOut;
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ){
+    invokeValueDestructor(pPtr, xDestructor, 0);
+    return;
+  }
+#endif
+  pOut = pCtx->pOut;
   assert( sqlite3_mutex_held(pOut->db->mutex) );
   sqlite3VdbeMemRelease(pOut);
   pOut->flags = MEM_Null;
   sqlite3VdbeMemSetPointer(pOut, pPtr, zPType, xDestructor);
 }
 void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){
-  Mem *pOut = pCtx->pOut;
+  Mem *pOut;
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
+  pOut = pCtx->pOut;
   assert( sqlite3_mutex_held(pOut->db->mutex) );
   pOut->eSubtype = eSubtype & 0xff;
   pOut->flags |= MEM_Subtype;
@@ -494,6 +542,12 @@ void sqlite3_result_text(
   int n,
   void (*xDel)(void *)
 ){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ){
+    invokeValueDestructor(z, xDel, 0);
+    return;
+  }
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   setResultStrOrError(pCtx, z, n, SQLITE_UTF8, xDel);
 }
@@ -504,6 +558,12 @@ void sqlite3_result_text64(
   void (*xDel)(void *),
   unsigned char enc
 ){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ){
+    invokeValueDestructor(z, xDel, 0);
+    return;
+  }
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   assert( xDel!=SQLITE_DYNAMIC );
   if( enc!=SQLITE_UTF8 ){
@@ -547,7 +607,16 @@ void sqlite3_result_text16le(
 }
 #endif /* SQLITE_OMIT_UTF16 */
 void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){
-  Mem *pOut = pCtx->pOut;
+  Mem *pOut;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+  if( pValue==0 ){
+    sqlite3_result_null(pCtx);
+    return;
+  }
+#endif
+  pOut = pCtx->pOut;
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   sqlite3VdbeMemCopy(pOut, pValue);
   sqlite3VdbeChangeEncoding(pOut, pCtx->enc);
@@ -559,7 +628,12 @@ void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){
   sqlite3_result_zeroblob64(pCtx, n>0 ? n : 0);
 }
 int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){
-  Mem *pOut = pCtx->pOut;
+  Mem *pOut;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return SQLITE_MISUSE_BKPT;
+#endif
+  pOut = pCtx->pOut;
   assert( sqlite3_mutex_held(pOut->db->mutex) );
   if( n>(u64)pOut->db->aLimit[SQLITE_LIMIT_LENGTH] ){
     sqlite3_result_error_toobig(pCtx);
@@ -573,6 +647,9 @@ int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){
 #endif
 }
 void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
   pCtx->isError = errCode ? errCode : -1;
 #ifdef SQLITE_DEBUG
   if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode;
@@ -585,6 +662,9 @@ void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){
 
 /* Force an SQLITE_TOOBIG error. */
 void sqlite3_result_error_toobig(sqlite3_context *pCtx){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   pCtx->isError = SQLITE_TOOBIG;
   sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1,
@@ -593,6 +673,9 @@ void sqlite3_result_error_toobig(sqlite3_context *pCtx){
 
 /* An SQLITE_NOMEM error. */
 void sqlite3_result_error_nomem(sqlite3_context *pCtx){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
   sqlite3VdbeMemSetNull(pCtx->pOut);
   pCtx->isError = SQLITE_NOMEM_BKPT;
@@ -845,7 +928,11 @@ int sqlite3_step(sqlite3_stmt *pStmt){
 ** pointer to it.
 */
 void *sqlite3_user_data(sqlite3_context *p){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( p==0 ) return 0;
+#else
   assert( p && p->pFunc );
+#endif
   return p->pFunc->pUserData;
 }
 
@@ -860,7 +947,11 @@ void *sqlite3_user_data(sqlite3_context *p){
 ** application defined function.
 */
 sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( p==0 ) return 0;
+#else
   assert( p && p->pOut );
+#endif
   return p->pOut->db;
 }
 
@@ -879,7 +970,11 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){
 ** value, as a signal to the xUpdate routine that the column is unchanged.
 */
 int sqlite3_vtab_nochange(sqlite3_context *p){
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( p==0 ) return 0;
+#else
   assert( p );
+#endif
   return sqlite3_value_nochange(p->pOut);
 }
 
@@ -907,7 +1002,7 @@ static int valueFromValueList(
   ValueList *pRhs;
 
   *ppOut = 0;
-  if( pVal==0 ) return SQLITE_MISUSE;
+  if( pVal==0 ) return SQLITE_MISUSE_BKPT;
   if( (pVal->flags & MEM_Dyn)==0 || pVal->xDel!=sqlite3VdbeValueListFree ){
     return SQLITE_ERROR;
   }else{
@@ -1038,6 +1133,9 @@ void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){
 void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){
   AuxData *pAuxData;
 
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return 0;
+#endif
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
 #if SQLITE_ENABLE_STAT4
   if( pCtx->pVdbe==0 ) return 0;
@@ -1070,8 +1168,12 @@ void sqlite3_set_auxdata(
   void (*xDelete)(void*)
 ){
   AuxData *pAuxData;
-  Vdbe *pVdbe = pCtx->pVdbe;
+  Vdbe *pVdbe;
 
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pCtx==0 ) return;
+#endif
+  pVdbe= pCtx->pVdbe;
   assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
 #ifdef SQLITE_ENABLE_STAT4
   if( pVdbe==0 ) goto failed;
@@ -1508,7 +1610,7 @@ static int vdbeUnbind(Vdbe *p, unsigned int i){
   }
   sqlite3_mutex_enter(p->db->mutex);
   if( p->eVdbeState!=VDBE_READY_STATE ){
-    sqlite3Error(p->db, SQLITE_MISUSE);
+    sqlite3Error(p->db, SQLITE_MISUSE_BKPT);
     sqlite3_mutex_leave(p->db->mutex);
     sqlite3_log(SQLITE_MISUSE,
         "bind on a busy prepared statement: [%s]", p->zSql);
@@ -1737,6 +1839,9 @@ int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){
 int sqlite3_bind_zeroblob64(sqlite3_stmt *pStmt, int i, sqlite3_uint64 n){
   int rc;
   Vdbe *p = (Vdbe *)pStmt;
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( p==0 ) return SQLITE_MISUSE_BKPT;
+#endif
   sqlite3_mutex_enter(p->db->mutex);
   if( n>(u64)p->db->aLimit[SQLITE_LIMIT_LENGTH] ){
     rc = SQLITE_TOOBIG;
@@ -1863,6 +1968,9 @@ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){
 int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){
   Vdbe *v = (Vdbe*)pStmt;
   int rc;
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( pStmt==0 ) return SQLITE_MISUSE_BKPT;
+#endif
   sqlite3_mutex_enter(v->db->mutex);
   if( ((int)v->explain)==eMode ){
     rc = SQLITE_OK;
@@ -2032,10 +2140,16 @@ static UnpackedRecord *vdbeUnpackRecord(
 ** a field of the row currently being updated or deleted.
 */
 int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
-  PreUpdate *p = db->pPreUpdate;
+  PreUpdate *p;
   Mem *pMem;
   int rc = SQLITE_OK;
 
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( db==0 || ppValue==0 ){
+    return SQLITE_MISUSE_BKPT;
+  }
+#endif
+  p = db->pPreUpdate;
   /* Test that this call is being made from within an SQLITE_DELETE or
   ** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */
   if( !p || p->op==SQLITE_INSERT ){
@@ -2096,7 +2210,12 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
 ** the number of columns in the row being updated, deleted or inserted.
 */
 int sqlite3_preupdate_count(sqlite3 *db){
-  PreUpdate *p = db->pPreUpdate;
+  PreUpdate *p;
+#ifdef SQLITE_ENABLE_API_ARMOR
+  p = db!=0 ? db->pPreUpdate : 0;
+#else
+  p = db->pPreUpdate;
+#endif
   return (p ? p->keyinfo.nKeyField : 0);
 }
 #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -2114,7 +2233,12 @@ int sqlite3_preupdate_count(sqlite3 *db){
 ** or SET DEFAULT action is considered a trigger.
 */
 int sqlite3_preupdate_depth(sqlite3 *db){
-  PreUpdate *p = db->pPreUpdate;
+  PreUpdate *p;
+#ifdef SQLITE_ENABLE_API_ARMOR
+  p = db!=0 ? db->pPreUpdate : 0;
+#else
+  p = db->pPreUpdate;
+#endif
   return (p ? p->v->nFrame : 0);
 }
 #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -2125,7 +2249,12 @@ int sqlite3_preupdate_depth(sqlite3 *db){
 ** only.
 */
 int sqlite3_preupdate_blobwrite(sqlite3 *db){
-  PreUpdate *p = db->pPreUpdate;
+  PreUpdate *p;
+#ifdef SQLITE_ENABLE_API_ARMOR
+  p = db!=0 ? db->pPreUpdate : 0;
+#else
+  p = db->pPreUpdate;
+#endif
   return (p ? p->iBlobWrite : -1);
 }
 #endif
@@ -2136,10 +2265,16 @@ int sqlite3_preupdate_blobwrite(sqlite3 *db){
 ** a field of the row currently being updated or inserted.
 */
 int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
-  PreUpdate *p = db->pPreUpdate;
+  PreUpdate *p;
   int rc = SQLITE_OK;
   Mem *pMem;
 
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( db==0 || ppValue==0 ){
+    return SQLITE_MISUSE_BKPT;
+  }
+#endif
+  p = db->pPreUpdate;
   if( !p || p->op==SQLITE_DELETE ){
     rc = SQLITE_MISUSE_BKPT;
     goto preupdate_new_out;
@@ -2218,11 +2353,20 @@ int sqlite3_stmt_scanstatus_v2(
   void *pOut                      /* OUT: Write the answer here */
 ){
   Vdbe *p = (Vdbe*)pStmt;
-  VdbeOp *aOp = p->aOp;
-  int nOp = p->nOp;
+  VdbeOp *aOp;
+  int nOp;
   ScanStatus *pScan = 0;
   int idx;
 
+#ifdef SQLITE_ENABLE_API_ARMOR
+  if( p==0 || pOut==0
+      || iScanStatusOp<SQLITE_SCANSTAT_NLOOP
+      || iScanStatusOp>SQLITE_SCANSTAT_NCYCLE ){
+    return 1;
+  }
+#endif
+  aOp = p->aOp;
+  nOp = p->nOp;
   if( p->pFrame ){
     VdbeFrame *pFrame;
     for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent);
@@ -2369,7 +2513,7 @@ int sqlite3_stmt_scanstatus(
 void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){
   Vdbe *p = (Vdbe*)pStmt;
   int ii;
-  for(ii=0; ii<p->nOp; ii++){
+  for(ii=0; p!=0 && ii<p->nOp; ii++){
     Op *pOp = &p->aOp[ii];
     pOp->nExec = 0;
     pOp->nCycle = 0;
diff --git a/libsql-sqlite3/src/vdbeaux.c b/libsql-sqlite3/src/vdbeaux.c
index 225c8d12c9..54317b816f 100644
--- a/libsql-sqlite3/src/vdbeaux.c
+++ b/libsql-sqlite3/src/vdbeaux.c
@@ -1002,6 +1002,10 @@ void sqlite3VdbeNoJumpsOutsideSubrtn(
       int iDest = pOp->p2;   /* Jump destination */
       if( iDest==0 ) continue;
       if( pOp->opcode==OP_Gosub ) continue;
+      if( pOp->p3==20230325 && pOp->opcode==OP_NotNull ){
+        /* This is a deliberately taken illegal branch.  tag-20230325-2 */
+        continue;
+      }
       if( iDest<0 ){
         int j = ADDR(iDest);
         assert( j>=0 );
@@ -4461,20 +4465,33 @@ SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem *pB2){
   return n1 - n2;
 }
 
+/* The following two functions are used only within testcase() to prove
+** test coverage.  These functions do no exist for production builds.
+** We must use separate SQLITE_NOINLINE functions here, since otherwise
+** optimizer code movement causes gcov to become very confused.
+*/
+#if  defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG)
+static int SQLITE_NOINLINE doubleLt(double a, double b){ return a<b; }
+static int SQLITE_NOINLINE doubleEq(double a, double b){ return a==b; }
+#endif
+
 /*
 ** Do a comparison between a 64-bit signed integer and a 64-bit floating-point
 ** number.  Return negative, zero, or positive if the first (i64) is less than,
 ** equal to, or greater than the second (double).
 */
 int sqlite3IntFloatCompare(i64 i, double r){
-  if( sizeof(LONGDOUBLE_TYPE)>8 ){
+  if( sqlite3IsNaN(r) ){
+    /* SQLite considers NaN to be a NULL. And all integer values are greater
+    ** than NULL */
+    return 1;
+  }
+  if( sqlite3Config.bUseLongDouble ){
     LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i;
     testcase( x<r );
     testcase( x>r );
     testcase( x==r );
-    if( x<r ) return -1;
-    if( x>r ) return +1;  /*NO_TEST*/ /* work around bugs in gcov */
-    return 0;             /*NO_TEST*/ /* work around bugs in gcov */
+    return (x<r) ? -1 : (x>r);
   }else{
     i64 y;
     double s;
@@ -4484,9 +4501,10 @@ int sqlite3IntFloatCompare(i64 i, double r){
     if( i<y ) return -1;
     if( i>y ) return +1;
     s = (double)i;
-    if( s<r ) return -1;
-    if( s>r ) return +1;
-    return 0;
+    testcase( doubleLt(s,r) );
+    testcase( doubleLt(r,s) );
+    testcase( doubleEq(r,s) );
+    return (s<r) ? -1 : (s>r);
   }
 }
 
diff --git a/libsql-sqlite3/src/vdbeblob.c b/libsql-sqlite3/src/vdbeblob.c
index 32987da137..522447dbc1 100644
--- a/libsql-sqlite3/src/vdbeblob.c
+++ b/libsql-sqlite3/src/vdbeblob.c
@@ -59,8 +59,7 @@ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){
   /* Set the value of register r[1] in the SQL statement to integer iRow. 
   ** This is done directly as a performance optimization
   */
-  v->aMem[1].flags = MEM_Int;
-  v->aMem[1].u.i = iRow;
+  sqlite3VdbeMemSetInt64(&v->aMem[1], iRow);
 
   /* If the statement has been run before (and is paused at the OP_ResultRow)
   ** then back it up to the point where it does the OP_NotExists.  This could
@@ -143,7 +142,7 @@ int sqlite3_blob_open(
 #endif
   *ppBlob = 0;
 #ifdef SQLITE_ENABLE_API_ARMOR
-  if( !sqlite3SafetyCheckOk(db) || zTable==0 ){
+  if( !sqlite3SafetyCheckOk(db) || zTable==0 || zColumn==0 ){
     return SQLITE_MISUSE_BKPT;
   }
 #endif
diff --git a/libsql-sqlite3/src/vdbemem.c b/libsql-sqlite3/src/vdbemem.c
index e25efc9771..eee828143a 100644
--- a/libsql-sqlite3/src/vdbemem.c
+++ b/libsql-sqlite3/src/vdbemem.c
@@ -336,7 +336,7 @@ void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){
       pMem->flags |= MEM_Term;
       return;
     }
-    if( pMem->xDel==(void(*)(void*))sqlite3RCStrUnref ){
+    if( pMem->xDel==sqlite3RCStrUnref ){
       /* Blindly assume that all RCStr objects are zero-terminated */
       pMem->flags |= MEM_Term;
       return;
@@ -1716,6 +1716,7 @@ static int valueFromExpr(
     if( pVal ){
       pVal->flags = MEM_Int;
       pVal->u.i = pExpr->u.zToken[4]==0;
+      sqlite3ValueApplyAffinity(pVal, affinity, enc);
     }
   }
 
diff --git a/libsql-sqlite3/src/vdbesort.c b/libsql-sqlite3/src/vdbesort.c
index 2b7da94f7f..0083690308 100644
--- a/libsql-sqlite3/src/vdbesort.c
+++ b/libsql-sqlite3/src/vdbesort.c
@@ -186,7 +186,7 @@ struct SorterFile {
 struct SorterList {
   SorterRecord *pList;            /* Linked list of records */
   u8 *aMemory;                    /* If non-NULL, bulk memory to hold pList */
-  int szPMA;                      /* Size of pList as PMA in bytes */
+  i64 szPMA;                      /* Size of pList as PMA in bytes */
 };
 
 /*
@@ -295,10 +295,10 @@ typedef int (*SorterCompare)(SortSubtask*,int*,const void*,int,const void*,int);
 struct SortSubtask {
   SQLiteThread *pThread;          /* Background thread, if any */
   int bDone;                      /* Set if thread is finished but not joined */
+  int nPMA;                       /* Number of PMAs currently in file */
   VdbeSorter *pSorter;            /* Sorter that owns this sub-task */
   UnpackedRecord *pUnpacked;      /* Space to unpack a record */
   SorterList list;                /* List for thread to write to a PMA */
-  int nPMA;                       /* Number of PMAs currently in file */
   SorterCompare xCompare;         /* Compare function to use */
   SorterFile file;                /* Temp file for level-0 PMAs */
   SorterFile file2;               /* Space for other PMAs */
@@ -1772,8 +1772,8 @@ int sqlite3VdbeSorterWrite(
   int rc = SQLITE_OK;             /* Return Code */
   SorterRecord *pNew;             /* New list element */
   int bFlush;                     /* True to flush contents of memory to PMA */
-  int nReq;                       /* Bytes of memory required */
-  int nPMA;                       /* Bytes of PMA space required */
+  i64 nReq;                       /* Bytes of memory required */
+  i64 nPMA;                       /* Bytes of PMA space required */
   int t;                          /* serial type of first record field */
 
   assert( pCsr->eCurType==CURTYPE_SORTER );
diff --git a/libsql-sqlite3/src/vdbevtab.c b/libsql-sqlite3/src/vdbevtab.c
index 59030e0e12..b295dff7b6 100644
--- a/libsql-sqlite3/src/vdbevtab.c
+++ b/libsql-sqlite3/src/vdbevtab.c
@@ -428,7 +428,8 @@ static sqlite3_module bytecodevtabModule = {
   /* xSavepoint  */ 0,
   /* xRelease    */ 0,
   /* xRollbackTo */ 0,
-  /* xShadowName */ 0
+  /* xShadowName */ 0,
+  /* xIntegrity  */ 0
 };
 
 
diff --git a/libsql-sqlite3/src/vtab.c b/libsql-sqlite3/src/vtab.c
index 741518991a..a9cfcb9d73 100644
--- a/libsql-sqlite3/src/vtab.c
+++ b/libsql-sqlite3/src/vtab.c
@@ -821,7 +821,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
   sqlite3_mutex_enter(db->mutex);
   pCtx = db->pVtabCtx;
   if( !pCtx || pCtx->bDeclared ){
-    sqlite3Error(db, SQLITE_MISUSE);
+    sqlite3Error(db, SQLITE_MISUSE_BKPT);
     sqlite3_mutex_leave(db->mutex);
     return SQLITE_MISUSE_BKPT;
   }
diff --git a/libsql-sqlite3/src/where.c b/libsql-sqlite3/src/where.c
index 213df42230..05ae24f7bc 100644
--- a/libsql-sqlite3/src/where.c
+++ b/libsql-sqlite3/src/where.c
@@ -1142,13 +1142,17 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
   WhereLoop *pLoop = pLevel->pWLoop;   /* The loop being coded */
   int iCur;                            /* Cursor for table getting the filter */
   IndexedExpr *saved_pIdxEpr;          /* saved copy of Parse.pIdxEpr */
+  IndexedExpr *saved_pIdxPartExpr;     /* saved copy of Parse.pIdxPartExpr */
 
   saved_pIdxEpr = pParse->pIdxEpr;
+  saved_pIdxPartExpr = pParse->pIdxPartExpr;
   pParse->pIdxEpr = 0;
+  pParse->pIdxPartExpr = 0;
 
   assert( pLoop!=0 );
   assert( v!=0 );
   assert( pLoop->wsFlags & WHERE_BLOOMFILTER );
+  assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 );
 
   addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
   do{
@@ -1238,6 +1242,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
   }while( iLevel < pWInfo->nLevel );
   sqlite3VdbeJumpHere(v, addrOnce);
   pParse->pIdxEpr = saved_pIdxEpr;
+  pParse->pIdxPartExpr = saved_pIdxPartExpr;
 }
 
 
@@ -3497,6 +3502,100 @@ static SQLITE_NOINLINE u32 whereIsCoveringIndex(
   return rc;
 }
 
+/*
+** This is an sqlite3ParserAddCleanup() callback that is invoked to
+** free the Parse->pIdxEpr list when the Parse object is destroyed.
+*/
+static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){
+  IndexedExpr **pp = (IndexedExpr**)pObject;
+  while( *pp!=0 ){
+    IndexedExpr *p = *pp;
+    *pp = p->pIENext;
+    sqlite3ExprDelete(db, p->pExpr);
+    sqlite3DbFreeNN(db, p);
+  }
+}
+
+/*
+** This function is called for a partial index - one with a WHERE clause - in 
+** two scenarios. In both cases, it determines whether or not the WHERE 
+** clause on the index implies that a column of the table may be safely
+** replaced by a constant expression. For example, in the following 
+** SELECT:
+**
+**   CREATE INDEX i1 ON t1(b, c) WHERE a=<expr>;
+**   SELECT a, b, c FROM t1 WHERE a=<expr> AND b=?;
+**
+** The "a" in the select-list may be replaced by <expr>, iff:
+**
+**    (a) <expr> is a constant expression, and
+**    (b) The (a=<expr>) comparison uses the BINARY collation sequence, and
+**    (c) Column "a" has an affinity other than NONE or BLOB.
+**
+** If argument pItem is NULL, then pMask must not be NULL. In this case this 
+** function is being called as part of determining whether or not pIdx
+** is a covering index. This function clears any bits in (*pMask) 
+** corresponding to columns that may be replaced by constants as described
+** above.
+**
+** Otherwise, if pItem is not NULL, then this function is being called
+** as part of coding a loop that uses index pIdx. In this case, add entries
+** to the Parse.pIdxPartExpr list for each column that can be replaced
+** by a constant.
+*/
+static void wherePartIdxExpr(
+  Parse *pParse,                  /* Parse context */
+  Index *pIdx,                    /* Partial index being processed */
+  Expr *pPart,                    /* WHERE clause being processed */
+  Bitmask *pMask,                 /* Mask to clear bits in */
+  int iIdxCur,                    /* Cursor number for index */
+  SrcItem *pItem                  /* The FROM clause entry for the table */
+){
+  assert( pItem==0 || (pItem->fg.jointype & JT_RIGHT)==0 );
+  assert( (pItem==0 || pMask==0) && (pMask!=0 || pItem!=0) );
+
+  if( pPart->op==TK_AND ){
+    wherePartIdxExpr(pParse, pIdx, pPart->pRight, pMask, iIdxCur, pItem);
+    pPart = pPart->pLeft;
+  }
+
+  if( (pPart->op==TK_EQ || pPart->op==TK_IS) ){
+    Expr *pLeft = pPart->pLeft;
+    Expr *pRight = pPart->pRight;
+    u8 aff;
+
+    if( pLeft->op!=TK_COLUMN ) return;
+    if( !sqlite3ExprIsConstant(pRight) ) return;
+    if( !sqlite3IsBinary(sqlite3ExprCompareCollSeq(pParse, pPart)) ) return;
+    if( pLeft->iColumn<0 ) return;
+    aff = pIdx->pTable->aCol[pLeft->iColumn].affinity;
+    if( aff>=SQLITE_AFF_TEXT ){
+      if( pItem ){
+        sqlite3 *db = pParse->db;
+        IndexedExpr *p = (IndexedExpr*)sqlite3DbMallocRaw(db, sizeof(*p));
+        if( p ){
+          int bNullRow = (pItem->fg.jointype&(JT_LEFT|JT_LTORJ))!=0;
+          p->pExpr = sqlite3ExprDup(db, pRight, 0);
+          p->iDataCur = pItem->iCursor;
+          p->iIdxCur = iIdxCur;
+          p->iIdxCol = pLeft->iColumn;
+          p->bMaybeNullRow = bNullRow;
+          p->pIENext = pParse->pIdxPartExpr;
+          p->aff = aff;
+          pParse->pIdxPartExpr = p;
+          if( p->pIENext==0 ){
+            void *pArg = (void*)&pParse->pIdxPartExpr;
+            sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pArg);
+          }
+        }
+      }else if( pLeft->iColumn<(BMS-1) ){
+        *pMask &= ~((Bitmask)1 << pLeft->iColumn);
+      }
+    }
+  }
+}
+
+
 /*
 ** Add all WhereLoop objects for a single table of the join where the table
 ** is identified by pBuilder->pNew->iTab.  That table is guaranteed to be
@@ -3700,9 +3799,6 @@ static int whereLoopAddBtree(
 #else
       pNew->rRun = rSize + 16;
 #endif
-      if( IsView(pTab) || (pTab->tabFlags & TF_Ephemeral)!=0 ){
-        pNew->wsFlags |= WHERE_VIEWSCAN;
-      }
       ApplyCostMultiplier(pNew->rRun, pTab->costMult);
       whereLoopOutputAdjust(pWC, pNew, rSize);
       rc = whereLoopInsert(pBuilder, pNew);
@@ -3715,6 +3811,11 @@ static int whereLoopAddBtree(
         pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED;
       }else{
         m = pSrc->colUsed & pProbe->colNotIdxed;
+        if( pProbe->pPartIdxWhere ){
+          wherePartIdxExpr(
+              pWInfo->pParse, pProbe, pProbe->pPartIdxWhere, &m, 0, 0
+          );
+        }
         pNew->wsFlags = WHERE_INDEXED;
         if( m==TOPBIT || (pProbe->bHasExpr && !pProbe->bHasVCol && m!=0) ){
           u32 isCov = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor);
@@ -4097,7 +4198,7 @@ int sqlite3_vtab_rhs_value(
   sqlite3_value *pVal = 0;
   int rc = SQLITE_OK;
   if( iCons<0 || iCons>=pIdxInfo->nConstraint ){
-    rc = SQLITE_MISUSE; /* EV: R-30545-25046 */
+    rc = SQLITE_MISUSE_BKPT; /* EV: R-30545-25046 */
   }else{
     if( pH->aRhs[iCons]==0 ){
       WhereTerm *pTerm = &pH->pWC->a[pIdxInfo->aConstraint[iCons].iTermOffset];
@@ -5121,14 +5222,6 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
           rUnsorted -= 2;  /* TUNING:  Slight bias in favor of no-sort plans */
         }
 
-        /* TUNING:  A full-scan of a VIEW or subquery in the outer loop
-        ** is not so bad. */
-        if( iLoop==0 && (pWLoop->wsFlags & WHERE_VIEWSCAN)!=0 && nLoop>1 ){
-          rCost += -10;
-          nOut += -30;
-          WHERETRACE(0x80,("VIEWSCAN cost reduction for %c\n",pWLoop->cId));
-        }
-
         /* Check to see if pWLoop should be added to the set of
         ** mxChoice best-so-far paths.
         **
@@ -5678,20 +5771,6 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful(
   }
 }
 
-/*
-** This is an sqlite3ParserAddCleanup() callback that is invoked to
-** free the Parse->pIdxEpr list when the Parse object is destroyed.
-*/
-static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){
-  Parse *pParse = (Parse*)pObject;
-  while( pParse->pIdxEpr!=0 ){
-    IndexedExpr *p = pParse->pIdxEpr;
-    pParse->pIdxEpr = p->pIENext;
-    sqlite3ExprDelete(db, p->pExpr);
-    sqlite3DbFreeNN(db, p);
-  }
-}
-
 /*
 ** The index pIdx is used by a query and contains one or more expressions.
 ** In other words pIdx is an index on an expression.  iIdxCur is the cursor
@@ -5753,7 +5832,8 @@ static SQLITE_NOINLINE void whereAddIndexedExpr(
 #endif
     pParse->pIdxEpr = p;
     if( p->pIENext==0 ){
-      sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse);
+      void *pArg = (void*)&pParse->pIdxEpr;
+      sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pArg);
     }
   }
 }
@@ -6143,6 +6223,16 @@ WhereInfo *sqlite3WhereBegin(
        wherePathSolver(pWInfo, pWInfo->nRowOut+1);
        if( db->mallocFailed ) goto whereBeginError;
     }
+
+    /* TUNING:  Assume that a DISTINCT clause on a subquery reduces
+    ** the output size by a factor of 8 (LogEst -30).
+    */
+    if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){
+      WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n",
+                         pWInfo->nRowOut, pWInfo->nRowOut-30));
+      pWInfo->nRowOut -= 30;
+    }
+
   }
   assert( pWInfo->pTabList!=0 );
   if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){
@@ -6355,6 +6445,11 @@ WhereInfo *sqlite3WhereBegin(
         if( pIx->bHasExpr && OptimizationEnabled(db, SQLITE_IndexedExpr) ){
           whereAddIndexedExpr(pParse, pIx, iIndexCur, pTabItem);
         }
+        if( pIx->pPartIdxWhere && (pTabItem->fg.jointype & JT_RIGHT)==0 ){
+          wherePartIdxExpr(
+              pParse, pIx, pIx->pPartIdxWhere, 0, iIndexCur, pTabItem
+          );
+        }
       }
       pLevel->iIdxCur = iIndexCur;
       assert( pIx!=0 );
diff --git a/libsql-sqlite3/src/whereInt.h b/libsql-sqlite3/src/whereInt.h
index 759e774e3a..343aca9f36 100644
--- a/libsql-sqlite3/src/whereInt.h
+++ b/libsql-sqlite3/src/whereInt.h
@@ -633,7 +633,7 @@ void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*);
 #define WHERE_BLOOMFILTER  0x00400000  /* Consider using a Bloom-filter */
 #define WHERE_SELFCULL     0x00800000  /* nOut reduced by extra WHERE terms */
 #define WHERE_OMIT_OFFSET  0x01000000  /* Set offset counter to zero */
-#define WHERE_VIEWSCAN     0x02000000  /* A full-scan of a VIEW or subquery */
+                      /*   0x02000000  -- available for reuse */
 #define WHERE_EXPRIDX      0x04000000  /* Uses an index-on-expressions */
 
 #endif /* !defined(SQLITE_WHEREINT_H) */
diff --git a/libsql-sqlite3/src/window.c b/libsql-sqlite3/src/window.c
index d46eabc3b4..2c449592d7 100644
--- a/libsql-sqlite3/src/window.c
+++ b/libsql-sqlite3/src/window.c
@@ -1312,8 +1312,9 @@ void sqlite3WindowAttach(Parse *pParse, Expr *p, Window *pWin){
   if( p ){
     assert( p->op==TK_FUNCTION );
     assert( pWin );
+    assert( ExprIsFullSize(p) );
     p->y.pWin = pWin;
-    ExprSetProperty(p, EP_WinFunc);
+    ExprSetProperty(p, EP_WinFunc|EP_FullSize);
     pWin->pOwner = p;
     if( (p->flags & EP_Distinct) && pWin->eFrmType!=TK_FILTER ){
       sqlite3ErrorMsg(pParse,
diff --git a/libsql-sqlite3/test/aggnested.test b/libsql-sqlite3/test/aggnested.test
index 1b8b608803..5f033a246c 100644
--- a/libsql-sqlite3/test/aggnested.test
+++ b/libsql-sqlite3/test/aggnested.test
@@ -25,19 +25,19 @@ do_test aggnested-1.1 {
     INSERT INTO t1 VALUES(1), (2), (3);
     CREATE TABLE t2(b1 INTEGER);
     INSERT INTO t2 VALUES(4), (5);
-    SELECT (SELECT group_concat(a1,'x') FROM t2) FROM t1;
+    SELECT (SELECT string_agg(a1,'x') FROM t2) FROM t1;
   }
 } {1x2x3}
 do_test aggnested-1.2 {
   db eval {
     SELECT
-     (SELECT group_concat(a1,'x') || '-' || group_concat(b1,'y') FROM t2)
+     (SELECT string_agg(a1,'x') || '-' || string_agg(b1,'y') FROM t2)
     FROM t1;
   }
 } {1x2x3-4y5}
 do_test aggnested-1.3 {
   db eval {
-    SELECT (SELECT group_concat(b1,a1) FROM t2) FROM t1;
+    SELECT (SELECT string_agg(b1,a1) FROM t2) FROM t1;
   }
 } {415 425 435}
 do_test aggnested-1.4 {
@@ -309,7 +309,7 @@ do_execsql_test 5.4 {
 do_execsql_test 5.5 {
   CREATE TABLE a(b);
   WITH c AS(SELECT a)
-    SELECT(SELECT(SELECT group_concat(b, b)
+    SELECT(SELECT(SELECT string_agg(b, b)
           LIMIT(SELECT 0.100000 *
             AVG(DISTINCT(SELECT 0 FROM a ORDER BY b, b, b))))
         FROM a GROUP BY b,
diff --git a/libsql-sqlite3/test/aggorderby.test b/libsql-sqlite3/test/aggorderby.test
new file mode 100644
index 0000000000..f94c5ec898
--- /dev/null
+++ b/libsql-sqlite3/test/aggorderby.test
@@ -0,0 +1,122 @@
+# 2023-10-18
+#
+# 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 implements tests for ORDER BY on aggregate functions.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+do_execsql_test aggorderby-1.1 {
+  CREATE TABLE t1(a TEXT,b INT,c INT,d INT);
+  WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<9)
+  INSERT INTO t1(a,b,c,d) SELECT printf('%d',(x*7)%10),1,x,10-x FROM c;
+  INSERT INTO t1(a,b,c,d) SELECT a, 2, c, 10-d FROM t1;
+  CREATE INDEX t1b ON t1(b);
+}
+do_catchsql_test aggorderby-1.2 {
+  SELECT b, group_concat(a ORDER BY max(d)) FROM t1 GROUP BY b;
+} {1 {misuse of aggregate function max()}}
+do_catchsql_test aggorderby-1.3 {
+  SELECT abs(a ORDER BY max(d)) FROM t1;
+} {1 {ORDER BY may not be used with non-aggregate abs()}}
+
+do_execsql_test aggorderby-2.0 {
+  SELECT group_concat(a ORDER BY a) FROM t1 WHERE b=1;
+} {0,1,2,3,4,5,6,7,8,9}
+do_execsql_test aggorderby-2.1 {
+  SELECT group_concat(a ORDER BY c) FROM t1 WHERE b=1;
+} {0,7,4,1,8,5,2,9,6,3}
+do_execsql_test aggorderby-2.2 {
+  SELECT group_concat(a ORDER BY b, d) FROM t1;
+} {3,6,9,2,5,8,1,4,7,0,0,7,4,1,8,5,2,9,6,3}
+do_execsql_test aggorderby-2.3 {
+  SELECT string_agg(a, ',' ORDER BY b DESC, d) FROM t1;
+} {0,7,4,1,8,5,2,9,6,3,3,6,9,2,5,8,1,4,7,0}
+do_execsql_test aggorderby-2.4 {
+  SELECT b, group_concat(a ORDER BY d) FROM t1 GROUP BY b ORDER BY b;
+} {1 3,6,9,2,5,8,1,4,7,0 2 0,7,4,1,8,5,2,9,6,3}
+
+do_execsql_test aggorderby-3.0 {
+  SELECT group_concat(DISTINCT a ORDER BY a) FROM t1;
+} {0,1,2,3,4,5,6,7,8,9}
+do_execsql_test aggorderby-3.1 {
+  SELECT group_concat(DISTINCT a ORDER BY c) FROM t1;
+} {0,7,4,1,8,5,2,9,6,3}
+
+do_execsql_test aggorderby-4.0 {
+  SELECT count(ORDER BY a) FROM t1;
+} 20
+do_execsql_test aggorderby-4.1 {
+  SELECT c, max(a ORDER BY a) FROM t1;
+} {7 9}
+
+
+do_execsql_test aggorderby-5.0 {
+  DROP TABLE IF EXISTS t1;
+  DROP TABLE IF EXISTS t3;
+  CREATE TABLE t1(a TEXT);  INSERT INTO t1 VALUES('aaa'),('bbb');
+  CREATE TABLE t3(d TEXT);  INSERT INTO t3 VALUES('/'),('-');
+  SELECT (SELECT string_agg(a,d) FROM t3) FROM t1;
+} {aaa-aaa bbb-bbb}
+do_execsql_test aggorderby-5.1 {
+  SELECT (SELECT group_concat(a,d ORDER BY d) FROM t3) FROM t1;
+} {aaa/aaa bbb/bbb}
+do_execsql_test aggorderby-5.2 {
+  SELECT (SELECT string_agg(a,d ORDER BY d DESC) FROM t3) FROM t1;
+} {aaa-aaa bbb-bbb}
+do_execsql_test aggorderby-5.3 {
+  SELECT (SELECT string_agg(a,'#' ORDER BY d) FROM t3) FROM t1;
+} {aaa#aaa bbb#bbb}
+
+# COLLATE works on the ORDER BY.
+#
+do_execsql_test aggorderby-6.0 {
+  WITH c(x) AS (VALUES('abc'),('DEF'),('xyz'),('ABC'),('XYZ'))
+  SELECT string_agg(x,',' ORDER BY x COLLATE nocase),
+         string_agg(x,',' ORDER BY x) FROM c;
+} {abc,ABC,DEF,xyz,XYZ ABC,DEF,XYZ,abc,xyz}
+do_execsql_test aggorderby-6.1 {
+  WITH c(x,y) AS (VALUES(1,'a'),(2,'B'),(3,'c'),(4,'D'))
+  SELECT group_concat(x ORDER BY y COLLATE nocase),
+         group_concat(x ORDER BY y COLLATE binary) FROM c;
+} {1,2,3,4 2,4,1,3}
+
+# NULLS FIRST and NULLS LAST work on the ORDER BY
+#
+do_execsql_test aggorderby-7.0 {
+  WITH c(x) AS (VALUES(1),(NULL),(2.5),(NULL),('three'))
+  SELECT json_group_array(x ORDER BY x NULLS FIRST),
+         json_group_array(x ORDER BY x NULLS LAST) FROM c;
+} {[null,null,1,2.5,"three"] [1,2.5,"three",null,null]}
+do_execsql_test aggorderby-7.1 {
+  WITH c(x,y) AS (VALUES(1,9),(2,null),(3,5),(4,null),(5,1))
+  SELECT json_group_array(x ORDER BY y NULLS FIRST, x),
+         json_group_array(x ORDER BY y NULLS LAST, x) FROM c;
+} {[2,4,5,3,1] [5,3,1,2,4]}
+
+# The DISTINCT only applies to the function arguments, not to the
+# ORDER BY arguments.
+#
+do_execsql_test aggorderby-8.0 {
+  WITH c(x,y,z) AS (VALUES('a',4,5),('b',3,6),('c',2,7),('c',1,8))
+  SELECT group_concat(DISTINCT x ORDER BY y, z) FROM c;
+} {c,b,a}
+do_execsql_test aggorderby-8.1 {
+  WITH c(x,y,z) AS (VALUES('a',4,5),('b',3,6),('b',2,7),('c',1,8))
+  SELECT group_concat(DISTINCT x ORDER BY y, z) FROM c;
+} {c,b,a}
+do_execsql_test aggorderby-8.2 {
+  WITH c(x,y) AS (VALUES(1,1),(2,2),(3,3),(3,4),(3,5),(3,6))
+  SELECT sum(DISTINCT x ORDER BY y) FROM c;
+} 6
+
+
+finish_test
diff --git a/libsql-sqlite3/test/alter.test b/libsql-sqlite3/test/alter.test
index 0088858a15..ee8e6c0b90 100644
--- a/libsql-sqlite3/test/alter.test
+++ b/libsql-sqlite3/test/alter.test
@@ -934,5 +934,24 @@ do_execsql_test alter-19.3 {
   SELECT name FROM sqlite_schema WHERE sql LIKE '%t3%' ORDER BY name;
 } {r1 t3}
 
+# 2023-10-14
+# On an ALTER TABLE ADD COLUMN with a DEFAULT clause on a STRICT table
+# make sure that the DEFAULT has a compatible type.
+#
+reset_db
+do_execsql_test alter-20.1 {
+  CREATE TABLE t1(a INT) STRICT;
+  INSERT INTO t1(a) VALUES(45);
+} {}
+do_catchsql_test alter-20.2 {
+  ALTER TABLE t1 ADD COLUMN b TEXT DEFAULT x'313233';
+} {1 {type mismatch on DEFAULT}}
+do_execsql_test alter-20.2 {
+  DELETE FROM t1;
+  ALTER TABLE t1 ADD COLUMN b TEXT DEFAULT x'313233';
+} {}
+do_catchsql_test alter-20.3 {
+  INSERT INTO t1(a) VALUES(45);
+} {1 {cannot store BLOB value in TEXT column t1.b}}
 
 finish_test
diff --git a/libsql-sqlite3/test/altercol.test b/libsql-sqlite3/test/altercol.test
index e39793aa9f..f44aa2e065 100644
--- a/libsql-sqlite3/test/altercol.test
+++ b/libsql-sqlite3/test/altercol.test
@@ -343,6 +343,21 @@ do_catchsql_test 8.4.5 {
   ALTER TABLE b1 RENAME a TO aaa;
 } {1 {error in view zzz: no such column: george}}
 
+do_execsql_test 8.5 {
+  DROP VIEW zzz;
+  CREATE TABLE t5(a TEXT, b INT);
+  INSERT INTO t5(a,b) VALUES('aaa',7),('bbb',3),('ccc',4);
+  CREATE VIEW vt5(x) AS SELECT group_concat(a ORDER BY b) FROM t5;
+  SELECT x FROM vt5;
+} {bbb,ccc,aaa}
+do_execsql_test 8.5.1 {
+  ALTER TABLE t5 RENAME COLUMN b TO bbb;
+  SELECT sql FROM sqlite_schema WHERE name='vt5';
+} {{CREATE VIEW vt5(x) AS SELECT group_concat(a ORDER BY bbb) FROM t5}}
+do_execsql_test 8.5.2 {
+  SELECT x FROM vt5;
+} {bbb,ccc,aaa}
+
 #-------------------------------------------------------------------------
 # More triggers.
 #
diff --git a/libsql-sqlite3/test/altermalloc3.test b/libsql-sqlite3/test/altermalloc3.test
index 2dc0b46f29..47efebe228 100644
--- a/libsql-sqlite3/test/altermalloc3.test
+++ b/libsql-sqlite3/test/altermalloc3.test
@@ -26,6 +26,7 @@ set ::TMPDBERROR [list 1 \
   {unable to open a temporary database file for storing temporary tables}
 ]
 
+sqlite3_db_config db SQLITE_DBCONFIG_DQS_DDL 1
 do_execsql_test 1.0 {
   CREATE TABLE x1(
       one, two, three, PRIMARY KEY(one), 
diff --git a/libsql-sqlite3/test/alterqf.test b/libsql-sqlite3/test/alterqf.test
index 423a9fa865..b248c7c7f4 100644
--- a/libsql-sqlite3/test/alterqf.test
+++ b/libsql-sqlite3/test/alterqf.test
@@ -65,7 +65,7 @@ foreach {tn before after} {
 
  11 {CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN 
       SELECT max("str", new."a") FROM t1 
-          WHERE group_concat("b", ",") OVER (ORDER BY c||"str");
+          WHERE string_agg("b", ",") OVER (ORDER BY c||"str");
       UPDATE t1 SET c= b + "str";
       DELETE FROM t1 WHERE EXISTS (
         SELECT 1 FROM t1 AS o WHERE o."a" = "o.a" AND t1.b IN("t1.b")
@@ -73,7 +73,7 @@ foreach {tn before after} {
     END;
  } {CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN 
       SELECT max('str', new."a") FROM t1 
-          WHERE group_concat("b", ',') OVER (ORDER BY c||'str');
+          WHERE string_agg("b", ',') OVER (ORDER BY c||'str');
       UPDATE t1 SET c= b + 'str';
       DELETE FROM t1 WHERE EXISTS (
         SELECT 1 FROM t1 AS o WHERE o."a" = 'o.a' AND t1.b IN('t1.b')
diff --git a/libsql-sqlite3/test/altertrig.test b/libsql-sqlite3/test/altertrig.test
index 934a636669..556dc3fea4 100644
--- a/libsql-sqlite3/test/altertrig.test
+++ b/libsql-sqlite3/test/altertrig.test
@@ -160,4 +160,3 @@ foreach {tn alter update final} {
 }
 
 finish_test
-
diff --git a/libsql-sqlite3/test/bestindex9.test b/libsql-sqlite3/test/bestindex9.test
index 94b9da0d38..d591b30efe 100644
--- a/libsql-sqlite3/test/bestindex9.test
+++ b/libsql-sqlite3/test/bestindex9.test
@@ -102,7 +102,3 @@ do_bestindex9_test 4 {
 
 
 finish_test
-
-
-
-
diff --git a/libsql-sqlite3/test/bestindexA.test b/libsql-sqlite3/test/bestindexA.test
index 650404eaa0..1976986471 100644
--- a/libsql-sqlite3/test/bestindexA.test
+++ b/libsql-sqlite3/test/bestindexA.test
@@ -133,6 +133,3 @@ do_xbestindex_test 1.9 {
 
 
 finish_test
-
-
-
diff --git a/libsql-sqlite3/test/bestindexB.test b/libsql-sqlite3/test/bestindexB.test
new file mode 100644
index 0000000000..b50e74fee3
--- /dev/null
+++ b/libsql-sqlite3/test/bestindexB.test
@@ -0,0 +1,87 @@
+# 2023-10-26
+#
+# 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.
+#
+#***********************************************************************
+# 
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix bestindexB
+
+ifcapable !vtab {
+  finish_test
+  return
+}
+
+register_tcl_module db
+
+proc vtab_command {method args} {
+  switch -- $method {
+    xConnect {
+      return "CREATE TABLE t1(a, b, c)"
+    }
+
+    xBestIndex {
+      set hdl [lindex $args 0]
+      set clist [$hdl constraints]
+      set orderby [$hdl orderby]
+
+      if {[info exists ::xbestindex_sql]} { 
+        explain_i $::xbestindex_sql 
+        set ::xbestindex_res [ execsql $::xbestindex_sql ]
+      }
+
+      return "cost 1000000 rows 1000000 idxnum 0 idxstr hello"
+    }
+
+    xFilter {
+      return "sql {SELECT 0, 1, 2, 3}"
+    }
+  }
+
+  return {}
+}
+
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE x1 USING tcl(vtab_command);
+  CREATE TABLE y1(a, b);
+  CREATE TABLE y2(a, b);
+} {}
+
+do_execsql_test 1.1 {
+  SELECT * FROM x1
+} {1 2 3}
+
+do_execsql_test 1.2 {
+  INSERT INTO y1 VALUES(1, 2) RETURNING rowid;
+} {1}
+
+do_execsql_test 1.3 {
+  CREATE TRIGGER y1tr BEFORE INSERT ON y1 BEGIN
+    SELECT * FROM x1;
+  END;
+  INSERT INTO y1 VALUES(3, 4) RETURNING rowid;
+} {2}
+
+
+# This time, rig the xBestIndex() method of the vtab to invoke an SQL
+# statement that uses RETURNING.
+set ::xbestindex_sql {
+  INSERT INTO y2 VALUES(NULL, NULL) RETURNING rowid;
+}
+do_execsql_test 1.4 {
+  INSERT INTO y1 VALUES(5, 6) RETURNING rowid;
+} {3}
+
+do_test 1.5 {
+  set ::xbestindex_res
+} {1}
+
+finish_test
diff --git a/libsql-sqlite3/test/changes.test b/libsql-sqlite3/test/changes.test
index 21db075f96..b3a2ae1eef 100644
--- a/libsql-sqlite3/test/changes.test
+++ b/libsql-sqlite3/test/changes.test
@@ -86,5 +86,3 @@ foreach {tn nRow wor} {
 }
 
 finish_test
-
-
diff --git a/libsql-sqlite3/test/changes2.test b/libsql-sqlite3/test/changes2.test
index 46e25c03a7..5b2684a8df 100644
--- a/libsql-sqlite3/test/changes2.test
+++ b/libsql-sqlite3/test/changes2.test
@@ -92,4 +92,3 @@ do_execsql_test 2.4 {
 } {{2 changes} {2 changes}}
 
 finish_test
-
diff --git a/libsql-sqlite3/test/date.test b/libsql-sqlite3/test/date.test
index 3e93181896..fb76dac8ac 100644
--- a/libsql-sqlite3/test/date.test
+++ b/libsql-sqlite3/test/date.test
@@ -207,15 +207,33 @@ datetest 3.16 "strftime('[repeat 200 %Y]','2003-10-31')" [repeat 200 2003]
 datetest 3.17 "strftime('[repeat 200 abc%m123]','2003-10-31')" \
     [repeat 200 abc10123]
 
-foreach c {a b c e g h i k l n o p q r t v x y z
-           A B C D E F G I K L N O P Q R T U V Z
+foreach c {a b c g h i n o q r t v x y z
+           A B C D E G K L N O Q V Z
            0 1 2 3 4 5 6 6 7 9 _} {
   datetest 3.18.$c "strftime('%$c','2003-10-31')" NULL
 }
+datetest 3.20 {strftime('%e','2023-08-09')} { 9}
+datetest 3.21 {strftime('%F %T','2023-08-09 01:23')} {2023-08-09 01:23:00}
+datetest 3.22 {strftime('%k','2023-08-09 04:59:59')} { 4}
+datetest 3.23 {strftime('%I%P','2023-08-09 11:59:59')} {11am}
+datetest 3.24 {strftime('%I%p','2023-08-09 12:00:00')} {12PM}
+datetest 3.25 {strftime('%I%P','2023-08-09 12:59:59.9')} {12pm}
+datetest 3.26 {strftime('%I%p','2023-08-09 13:00:00')} {01PM}
+datetest 3.27 {strftime('%I%P','2023-08-09 23:59:59')} {11pm}
+datetest 3.28 {strftime('%I%p','2023-08-09 00:00:00')} {12AM}
+datetest 3.29 {strftime('%l:%M%P','2023-08-09 13:00:00')} { 1:00pm}
+datetest 3.30 {strftime('%F %R','2023-08-09 12:34:56')} {2023-08-09 12:34}
+datetest 3.31 {strftime('%w %u','2023-01-01')} {0 7}
+datetest 3.32 {strftime('%w %u','2023-01-02')} {1 1}
+datetest 3.33 {strftime('%w %u','2023-01-03')} {2 2}
+datetest 3.34 {strftime('%w %u','2023-01-04')} {3 3}
+datetest 3.35 {strftime('%w %u','2023-01-05')} {4 4}
+datetest 3.36 {strftime('%w %u','2023-01-06')} {5 5}
+datetest 3.37 {strftime('%w %u','2023-01-07')} {6 6}
 
 # Ticket #2276.  Make sure leading zeros are inserted where appropriate.
 #
-datetest 3.20 \
+datetest 3.40 \
    {strftime('%d/%f/%H/%W/%j/%m/%M/%S/%Y','0421-01-02 03:04:05.006')} \
    02/05.006/03/00/002/01/04/05/0421
 
diff --git a/libsql-sqlite3/test/date4.test b/libsql-sqlite3/test/date4.test
new file mode 100644
index 0000000000..0d820a0a40
--- /dev/null
+++ b/libsql-sqlite3/test/date4.test
@@ -0,0 +1,38 @@
+# 2023-08-29
+#
+# 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.
+#
+#***********************************************************************
+#
+# Test cases for the strftime() SQL function.  Comparisons against the
+# C-library strftime() function.
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# Skip this whole file if date and time functions are omitted
+# at compile-time
+#
+ifcapable {!datetime} {
+  finish_test
+  return
+}
+
+if {$tcl_platform(os)=="Linux"} {
+  set FMT {%d,%e,%F,%H,%k,%I,%l,%j,%m,%M,%u,%w,%W,%Y,%%,%P,%p}
+} else {
+  set FMT {%d,%e,%F,%H,%I,%j,%p,%R,%u,%w,%W,%%}
+}
+for {set i 0} {$i<=24854} {incr i} {
+  set TS [expr {$i*86401}]
+  do_execsql_test date4-$i {
+    SELECT strftime($::FMT,$::TS,'unixepoch');
+  } [list [strftime $FMT $TS]]
+}
+
+finish_test
diff --git a/libsql-sqlite3/test/dbfuzz001.test b/libsql-sqlite3/test/dbfuzz001.test
index 2a430de12e..228dd16db6 100644
--- a/libsql-sqlite3/test/dbfuzz001.test
+++ b/libsql-sqlite3/test/dbfuzz001.test
@@ -371,4 +371,27 @@ do_catchsql_test dbfuzz001-330 {
 } {1 {database disk image is malformed}}
 extra_schema_checks 1
 
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test dbfuzz001-430 {
+  CREATE TABLE t1(a INTEGER, b INT, c DEFAULT 0);
+}
+
+do_execsql_test dbfuzz001-420 {
+  PRAGMA locking_mode=EXCLUSIVE;
+  PRAGMA journal_mode = memory;
+  INSERT INTO t1 VALUES(1,2,3);
+  PRAGMA journal_mode=PERSIST;
+} {exclusive memory persist}
+
+do_execsql_test dbfuzz001-430 {
+  INSERT INTO t1 VALUES(4, 5, 6);
+}
+
+do_execsql_test dbfuzz001-440 {
+  PRAGMA journal_mode=MEMORY;
+  INSERT INTO t1 VALUES(7, 8, 9);
+} {memory}
+
 finish_test
diff --git a/libsql-sqlite3/test/dbpagefault.test b/libsql-sqlite3/test/dbpagefault.test
index 544d279ce9..f27741cba1 100644
--- a/libsql-sqlite3/test/dbpagefault.test
+++ b/libsql-sqlite3/test/dbpagefault.test
@@ -84,5 +84,3 @@ do_catchsql_test 3.2 {
 
 
 finish_test
-
-
diff --git a/libsql-sqlite3/test/distinctagg.test b/libsql-sqlite3/test/distinctagg.test
index 6e46c88616..199ca0666e 100644
--- a/libsql-sqlite3/test/distinctagg.test
+++ b/libsql-sqlite3/test/distinctagg.test
@@ -56,7 +56,7 @@ do_test distinctagg-2.1 {
 } {1 {DISTINCT aggregates must have exactly one argument}}
 do_test distinctagg-2.2 {
   catchsql {
-    SELECT group_concat(distinct a,b) FROM t1;
+    SELECT string_agg(distinct a,b) FROM t1;
   }
 } {1 {DISTINCT aggregates must have exactly one argument}}
 
@@ -215,4 +215,3 @@ do_execsql_test 7.0 {
 
 
 finish_test
-
diff --git a/libsql-sqlite3/test/e_expr.test b/libsql-sqlite3/test/e_expr.test
index 5ad5993bb4..0db63a8ac4 100644
--- a/libsql-sqlite3/test/e_expr.test
+++ b/libsql-sqlite3/test/e_expr.test
@@ -1930,7 +1930,7 @@ foreach {tn expr restype resval} {
     6  { ( SELECT y FROM t4 ORDER BY y DESC ) }        text    two
 
     7  { ( SELECT sum(x) FROM t4 )           }         integer 6
-    8  { ( SELECT group_concat(y,'') FROM t4 ) }       text    onetwothree
+    8  { ( SELECT string_agg(y,'') FROM t4 ) }       text    onetwothree
     9  { ( SELECT max(x) FROM t4 WHERE y LIKE '___') } integer 2 
 
 } {
diff --git a/libsql-sqlite3/test/e_select.test b/libsql-sqlite3/test/e_select.test
index 5a3f0d30dc..e2e969dcf9 100644
--- a/libsql-sqlite3/test/e_select.test
+++ b/libsql-sqlite3/test/e_select.test
@@ -943,7 +943,7 @@ do_select_tests e_select-4.6 {
   4 "SELECT *, count(*) FROM a1 JOIN a2"                       {1 1 1 1 16}
   5 "SELECT *, sum(three) FROM a1 NATURAL JOIN a2"             {1 1 1 3}
   6 "SELECT *, sum(three) FROM a1 NATURAL JOIN a2"             {1 1 1 3}
-  7 "SELECT group_concat(three, ''), a1.* FROM a1 NATURAL JOIN a2" {12 1 1}
+  7 "SELECT string_agg(three, ''), a1.* FROM a1 NATURAL JOIN a2" {12 1 1}
 }
 
 # EVIDENCE-OF: R-04486-07266 Or, if the dataset contains zero rows, then
diff --git a/libsql-sqlite3/test/filter2.test b/libsql-sqlite3/test/filter2.test
index 21ee6659ff..06cfd2a4c3 100644
--- a/libsql-sqlite3/test/filter2.test
+++ b/libsql-sqlite3/test/filter2.test
@@ -113,7 +113,7 @@ do_execsql_test 1.12 {
 
 do_execsql_test 1.13 {
   SELECT 
-    group_concat(CAST(b AS TEXT), '_') FILTER (WHERE b%2!=0),
+    string_agg(CAST(b AS TEXT), '_') FILTER (WHERE b%2!=0),
     group_concat(CAST(b AS TEXT), '_') FILTER (WHERE b%2!=1),
     count(*) FILTER (WHERE b%2!=0),
     count(*) FILTER (WHERE b%2!=1)
diff --git a/libsql-sqlite3/test/fts3conf.test b/libsql-sqlite3/test/fts3conf.test
index 6ceef2c47e..cd48290195 100644
--- a/libsql-sqlite3/test/fts3conf.test
+++ b/libsql-sqlite3/test/fts3conf.test
@@ -198,7 +198,8 @@ do_execsql_test 4.1.2 {
 do_execsql_test 4.1.3 {
   SELECT * FROM t0 WHERE t0 MATCH 'abc';
   INSERT INTO t0(t0) VALUES('integrity-check');
-} {}
+  PRAGMA integrity_check;
+} {ok}
 
 do_execsql_test 4.2.1 {
   CREATE VIRTUAL TABLE t01 USING fts4;
@@ -211,7 +212,8 @@ do_execsql_test 4.2.1 {
 do_execsql_test 4.2.2 {
   SELECT * FROM t01 WHERE t01 MATCH 'b';
   INSERT INTO t01(t01) VALUES('integrity-check');
-} {}
+  PRAGMA integrity_check;
+} {ok}
 
 do_execsql_test 4.3.1 {
   CREATE VIRTUAL TABLE t02 USING fts4;
diff --git a/libsql-sqlite3/test/fts3corrupt4.test b/libsql-sqlite3/test/fts3corrupt4.test
index f8e89b3a75..433a486359 100644
--- a/libsql-sqlite3/test/fts3corrupt4.test
+++ b/libsql-sqlite3/test/fts3corrupt4.test
@@ -2595,17 +2595,13 @@ do_execsql_test 17.1 {
     UPDATE t1 SET b=quote(zeroblob(200)) WHERE a MATCH 'thread*';
 }
 
-do_catchsql_test 17.2 {
-  DROP TABLE IF EXISTS t1;
-} {1 {SQL logic error}}
-
-do_execsql_test 17.3 {
+do_execsql_test 17.2 {
   INSERT INTO t1(t1) VALUES('optimize');
 }
 
-do_catchsql_test 17.4 {
+do_catchsql_test 17.3 {
   DROP TABLE IF EXISTS t1;
-} {1 {SQL logic error}}
+} {0 {}}
 
 #-------------------------------------------------------------------------
 reset_db
@@ -5812,6 +5808,9 @@ do_execsql_test 35.0 {
 do_catchsql_test 35.1 {
   INSERT INTO f(f) VALUES ('integrity-check');
 } {1 {database disk image is malformed}}
+do_execsql_test 35.2 {
+  PRAGMA integrity_check;
+} {{malformed inverted index for FTS3 table main.f}}
 
 reset_db
 do_catchsql_test 36.0 {
diff --git a/libsql-sqlite3/test/fts3fault3.test b/libsql-sqlite3/test/fts3fault3.test
new file mode 100644
index 0000000000..6e1c0cd672
--- /dev/null
+++ b/libsql-sqlite3/test/fts3fault3.test
@@ -0,0 +1,54 @@
+# 2023 October 23 
+#
+# 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.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+set ::testprefix fts3fault
+
+# If SQLITE_ENABLE_FTS3 is not defined, omit this file.
+ifcapable !fts3 { finish_test ; return }
+
+set ::TMPDBERROR [list 1 \
+  {unable to open a temporary database file for storing temporary tables}
+]
+
+
+# Test error handling in an "ALTER TABLE ... RENAME TO" statement on an
+# FTS3 table. Specifically, test renaming the table within a transaction
+# after it has been written to.
+#
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE t1 USING fts3(a);
+  INSERT INTO t1 VALUES('test renaming the table');
+  INSERT INTO t1 VALUES(' after it has been written');
+  INSERT INTO t1 VALUES(' actually other stuff instead');
+}
+faultsim_save_and_close
+do_faultsim_test 1 -faults oom* -prep { 
+  faultsim_restore_and_reopen
+  execsql {
+    BEGIN;
+      DELETE FROM t1 WHERE rowid=2;
+  }
+} -body {
+  execsql {
+    DELETE FROM t1;
+  }
+} -test {
+  catchsql { COMMIT }
+  faultsim_integrity_check
+  faultsim_test_result {0 {}}
+}
+
+
+finish_test
diff --git a/libsql-sqlite3/test/fts3fuzz001.test b/libsql-sqlite3/test/fts3fuzz001.test
index 41b22d33da..6b1ae90ee4 100644
--- a/libsql-sqlite3/test/fts3fuzz001.test
+++ b/libsql-sqlite3/test/fts3fuzz001.test
@@ -13,6 +13,7 @@
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
+set testprefix fts3fuzz001
 
 ifcapable !deserialize||!fts3 {
   finish_test
@@ -110,5 +111,31 @@ do_test fts3fuzz001-121 {
   }
 } {1 {database disk image is malformed}}
 
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 200 {
+  CREATE VIRTUAL TABLE x1 USING fts3(x);
+
+  INSERT INTO x1 VALUES('braes brag bragged bragger bragging');
+  INSERT INTO x1 VALUES('brags braid braided braiding braids');
+  INSERT INTO x1 VALUES('brain brainchild brained braining brains');
+  INSERT INTO x1 VALUES('brainstem brainstems brainstorm brainstorms');
+  INSERT INTO x1(x1) VALUES('nodesize=24');
+}
+
+do_execsql_test 210 {
+  PRAGMA integrity_check;
+} {ok}
+
+do_execsql_test 220 {
+  INSERT INTO x1(x1) VALUES('merge=10,2')
+}
+
+do_execsql_test 220 {
+  PRAGMA integrity_check;
+} {ok}
+
+
+
 
 finish_test
diff --git a/libsql-sqlite3/test/fts4check.test b/libsql-sqlite3/test/fts4check.test
index c94c35910b..7f1004d8b3 100644
--- a/libsql-sqlite3/test/fts4check.test
+++ b/libsql-sqlite3/test/fts4check.test
@@ -71,7 +71,10 @@ foreach {tn disruption} {
   do_catchsql_test 1.2.2.$tn {
     INSERT INTO t1 (t1) VALUES('integrity-check')
   } {1 {database disk image is malformed}}
-  do_execsql_test  1.2.3.$tn "ROLLBACK"
+  do_execsql_test 1.2.3.$tn {
+    PRAGMA integrity_check;
+  } {{malformed inverted index for FTS4 table main.t1}}
+  do_execsql_test  1.2.4.$tn "ROLLBACK"
 }
 
 do_test 1.3 { fts_integrity db t1 } {ok}
@@ -106,7 +109,10 @@ foreach {tn disruption} {
   do_catchsql_test 2.2.2.$tn {
     INSERT INTO t2 (t2) VALUES('integrity-check')
   } {1 {database disk image is malformed}}
-  do_execsql_test  2.2.3.$tn "ROLLBACK"
+  do_test 2.2.3.$tn {
+    db eval {PRAGMA integrity_check(t2);}
+  } {{malformed inverted index for FTS4 table main.t2}}
+  do_execsql_test  2.2.4.$tn "ROLLBACK"
 }
 
 
diff --git a/libsql-sqlite3/test/fts4intck1.test b/libsql-sqlite3/test/fts4intck1.test
new file mode 100644
index 0000000000..abdc46bf52
--- /dev/null
+++ b/libsql-sqlite3/test/fts4intck1.test
@@ -0,0 +1,58 @@
+# 2023-10-23
+#
+#    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.
+#
+#***********************************************************************
+# 
+# Test PRAGMA integrity_check against and FTS3/FTS4 table.
+#
+
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+ifcapable !fts3 { finish_test ; return }
+
+set ::testprefix fts4intck1
+
+proc slang {in} {
+  return [string map {th d e eh} $in]
+}
+
+db function slang -deterministic -innocuous slang
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT AS (slang(b)));
+  INSERT INTO t1(b) VALUES('the quick fox jumps over the lazy brown dog');
+  SELECT c FROM t1;
+} {{deh quick fox jumps ovehr deh lazy brown dog}}
+
+do_execsql_test 1.1 {
+  CREATE VIRTUAL TABLE t2 USING fts4(content="t1", c);
+  INSERT INTO t2(t2) VALUES('rebuild');
+  SELECT docid FROM t2 WHERE t2 MATCH 'deh';
+} {1}
+
+do_execsql_test 1.2 {
+  PRAGMA integrity_check(t2);
+} {ok}
+
+db close
+sqlite3 db test.db
+do_execsql_test 2.1 {
+  PRAGMA integrity_check(t2);
+} {{unable to validate the inverted index for FTS4 table main.t2: SQL logic error}}
+
+db function slang -deterministic -innocuous slang
+do_execsql_test 2.2 {
+  PRAGMA integrity_check(t2);
+} {ok}
+
+proc slang {in} {return $in}
+do_execsql_test 2.3 {
+  PRAGMA integrity_check(t2);
+} {{malformed inverted index for FTS4 table main.t2}}
+
+
+finish_test
diff --git a/libsql-sqlite3/test/fts4langid.test b/libsql-sqlite3/test/fts4langid.test
index 45e851f940..7be594bd5f 100644
--- a/libsql-sqlite3/test/fts4langid.test
+++ b/libsql-sqlite3/test/fts4langid.test
@@ -499,7 +499,8 @@ do_execsql_test 6.0 {
 }
 do_execsql_test 6.1 {
   INSERT INTO vt0(vt0) VALUES('integrity-check');
-}
+  PRAGMA integrity_check;
+} {ok}
 do_execsql_test 6.2 {
   COMMIT;
   INSERT INTO vt0(vt0) VALUES('integrity-check');
diff --git a/libsql-sqlite3/test/fts4merge.test b/libsql-sqlite3/test/fts4merge.test
index 3cd693209d..ffef0e9334 100644
--- a/libsql-sqlite3/test/fts4merge.test
+++ b/libsql-sqlite3/test/fts4merge.test
@@ -37,7 +37,7 @@ foreach mod {fts3 fts4} {
   do_test 1.0 { fts3_build_db_1 -module $mod 1004 } {}
   do_test 1.1 { fts3_integrity_check t1 } {ok}
   do_execsql_test 1.1 { 
-    SELECT level, group_concat(idx, ' ') FROM t1_segdir GROUP BY level 
+    SELECT level, string_agg(idx, ' ') FROM t1_segdir GROUP BY level 
   } {
     0 {0 1 2 3 4 5 6 7 8 9 10 11} 
     1 {0 1 2 3 4 5 6 7 8 9 10 11 12 13}
@@ -67,7 +67,7 @@ foreach mod {fts3 fts4} {
   }
   
   do_execsql_test 1.5 { 
-    SELECT level, group_concat(idx, ' ') FROM t1_segdir GROUP BY level 
+    SELECT level, string_agg(idx, ' ') FROM t1_segdir GROUP BY level 
   } {
     3 0
   }
@@ -103,7 +103,7 @@ foreach mod {fts3 fts4} {
   do_test 3.1 { fts3_integrity_check t2 } {ok}
   
   do_execsql_test 3.2 { 
-    SELECT level, group_concat(idx, ' ') FROM t2_segdir GROUP BY level 
+    SELECT level, string_agg(idx, ' ') FROM t2_segdir GROUP BY level 
   } {
     0 {0 1 2 3 4 5 6} 
     1 {0 1 2 3 4} 
@@ -132,7 +132,7 @@ foreach mod {fts3 fts4} {
     foreach x {a c b d e f g h i j k l m n o p} {
       execsql "INSERT INTO t4 VALUES('[string repeat $x 600]')"
     }
-    execsql {SELECT level, group_concat(idx, ' ') FROM t4_segdir GROUP BY level}
+    execsql {SELECT level, string_agg(idx, ' ') FROM t4_segdir GROUP BY level}
   } {0 {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15}}
   
   foreach {tn expect} {
@@ -160,7 +160,7 @@ foreach mod {fts3 fts4} {
   do_execsql_test 4.4.2 {
     DELETE FROM t4_stat WHERE rowid=1;
     INSERT INTO t4(t4) VALUES('merge=1,12');
-    SELECT level, group_concat(idx, ' ') FROM t4_segdir GROUP BY level;
+    SELECT level, string_agg(idx, ' ') FROM t4_segdir GROUP BY level;
   } "0 {0 1 2 3 4 5}                     1 0"
   
   
@@ -194,7 +194,7 @@ foreach mod {fts3 fts4} {
   do_execsql_test 5.3 {
     INSERT INTO t1(t1) VALUES('merge=1,5');
     INSERT INTO t1(t1) VALUES('merge=1,5');
-    SELECT level, group_concat(idx, ' ') FROM t1_segdir GROUP BY level;
+    SELECT level, string_agg(idx, ' ') FROM t1_segdir GROUP BY level;
   } {
     1 {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14} 
     2 {0 1 2 3}
@@ -249,7 +249,7 @@ foreach mod {fts3 fts4} {
   
   do_execsql_test 5.11 {
     INSERT INTO t1(t1) VALUES('merge=1,6');
-    SELECT level, group_concat(idx, ' ') FROM t1_segdir GROUP BY level;
+    SELECT level, string_agg(idx, ' ') FROM t1_segdir GROUP BY level;
     SELECT quote(value) from t1_stat WHERE rowid=1;
   } {
     1 {0 1} 2 0 3 0 X'010E'
diff --git a/libsql-sqlite3/test/func.test b/libsql-sqlite3/test/func.test
index 3d71060465..883950a0c4 100644
--- a/libsql-sqlite3/test/func.test
+++ b/libsql-sqlite3/test/func.test
@@ -1136,18 +1136,18 @@ ifcapable deprecated {
   } {3}
 }
 
-# The group_concat() function.
+# The group_concat() and string_agg() functions.
 #
 do_test func-24.1 {
   execsql {
-    SELECT group_concat(t1) FROM tbl1
+    SELECT group_concat(t1), string_agg(t1,',') FROM tbl1
   }
-} {this,program,is,free,software}
+} {this,program,is,free,software this,program,is,free,software}
 do_test func-24.2 {
   execsql {
-    SELECT group_concat(t1,' ') FROM tbl1
+    SELECT group_concat(t1,' '), string_agg(t1,' ') FROM tbl1
   }
-} {{this program is free software}}
+} {{this program is free software} {this program is free software}}
 do_test func-24.3 {
   execsql {
     SELECT group_concat(t1,' ' || rowid || ' ') FROM tbl1
@@ -1160,9 +1160,9 @@ do_test func-24.4 {
 } {{}}
 do_test func-24.5 {
   execsql {
-    SELECT group_concat(t1,NULL) FROM tbl1
+    SELECT group_concat(t1,NULL), string_agg(t1,NULL) FROM tbl1
   }
-} {thisprogramisfreesoftware}
+} {thisprogramisfreesoftware thisprogramisfreesoftware}
 do_test func-24.6 {
   execsql {
     SELECT 'BEGIN-'||group_concat(t1) FROM tbl1
@@ -1545,4 +1545,12 @@ do_catchsql_test func-37.120 {
   SELECT sum(x) FROM c;
 } {1 {integer overflow}}
 
+# 2023-08-28 forum post https://sqlite.org/forum/forumpost/1c06ddcacc86032a
+# Incorrect handling of infinity by SUM().
+#
+do_execsql_test func-38.100 {
+  WITH t1(x) AS (VALUES(9e+999)) SELECT sum(x), avg(x), total(x) FROM t1;
+  WITH t1(x) AS (VALUES(-9e+999)) SELECT sum(x), avg(x), total(x) FROM t1;
+} {Inf Inf Inf -Inf -Inf -Inf}
+
 finish_test
diff --git a/libsql-sqlite3/test/func9.test b/libsql-sqlite3/test/func9.test
new file mode 100644
index 0000000000..6cf9fc31ec
--- /dev/null
+++ b/libsql-sqlite3/test/func9.test
@@ -0,0 +1,40 @@
+# 2023-08-29
+#
+# 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.
+#
+#*************************************************************************
+#
+# Test cases for SQL newer functions
+#
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+do_execsql_test func9-100 {
+  SELECT concat('abc',123,null,'xyz');
+} {abc123xyz}
+do_execsql_test func9-110 {
+  SELECT typeof(concat(null));
+} {text}
+do_catchsql_test func9-120 {
+  SELECT concat();
+} {1 {wrong number of arguments to function concat()}}
+do_execsql_test func9-130 {
+  SELECT concat_ws(',',1,2,3,4,5,6,7,8,NULL,9,10,11,12);
+} {1,2,3,4,5,6,7,8,9,10,11,12}
+do_execsql_test func9-140 {
+  SELECT concat_ws(NULL,1,2,3,4,5,6,7,8,NULL,9,10,11,12);
+} {{}}
+do_catchsql_test func9-150 {
+  SELECT concat_ws();
+} {1 {wrong number of arguments to function concat_ws()}}
+do_catchsql_test func9-160 {
+  SELECT concat_ws(',');
+} {1 {wrong number of arguments to function concat_ws()}}
+
+
+finish_test
diff --git a/libsql-sqlite3/test/fuzzdata8.db b/libsql-sqlite3/test/fuzzdata8.db
index 6b434f6bbf..3e34180071 100644
Binary files a/libsql-sqlite3/test/fuzzdata8.db and b/libsql-sqlite3/test/fuzzdata8.db differ
diff --git a/libsql-sqlite3/test/gcfault.test b/libsql-sqlite3/test/gcfault.test
index d54b78fafc..2d77f9ef2c 100644
--- a/libsql-sqlite3/test/gcfault.test
+++ b/libsql-sqlite3/test/gcfault.test
@@ -10,7 +10,7 @@
 #***********************************************************************
 # This file implements regression tests for SQLite library.  The
 # focus of this file is testing OOM error handling within the built-in 
-# group_concat() function.
+# group_concat() and string_agg() functions.
 #
 
 set testdir [file dirname $argv0]
@@ -40,7 +40,7 @@ foreach {enc} {
   }
 
   do_faultsim_test 1.$enc.2 -faults oom-t* -body {
-    execsql { SELECT group_concat(e, (SELECT s FROM s WHERE i=2)) FROM e }
+    execsql { SELECT string_agg(e, (SELECT s FROM s WHERE i=2)) FROM e }
   }
 
   do_faultsim_test 1.$enc.3 -faults oom-t* -prep {
diff --git a/libsql-sqlite3/test/gencol1.test b/libsql-sqlite3/test/gencol1.test
index f3fbb0dfba..ed7ea567d4 100644
--- a/libsql-sqlite3/test/gencol1.test
+++ b/libsql-sqlite3/test/gencol1.test
@@ -662,11 +662,8 @@ do_execsql_test gencol1-23.4 {
 
 # 2023-03-07 https://sqlite.org/forum/forumpost/b312e075b5
 #
-do_execsql_test gencol1-23.5 {
+do_catchsql_test gencol1-23.5 {
   CREATE TABLE v0(c1 INT, c2 AS (RAISE(IGNORE)));
-}
-do_catchsql_test gencol1-23.6 {
-  SELECT * FROM v0;
 } {1 {RAISE() may only be used within a trigger-program}}
 
 finish_test
diff --git a/libsql-sqlite3/test/indexA.test b/libsql-sqlite3/test/indexA.test
new file mode 100644
index 0000000000..518d7e18ad
--- /dev/null
+++ b/libsql-sqlite3/test/indexA.test
@@ -0,0 +1,350 @@
+# 2023 September 23
+#
+# 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.
+#
+#***********************************************************************
+#
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix indexA
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a TEXT, b, c);
+  CREATE INDEX i1 ON t1(b, c) WHERE a='abc';
+  INSERT INTO t1 VALUES('abc', 1, 2);
+}
+
+do_execsql_test 1.1 {
+  SELECT * FROM t1 WHERE a='abc'
+} {abc 1 2}
+
+do_eqp_test 1.2 {
+  SELECT * FROM t1 WHERE a='abc'
+} {USING COVERING INDEX i1}
+
+do_execsql_test 1.3 {
+  CREATE INDEX i2 ON t1(b, c) WHERE a=5;
+  INSERT INTO t1 VALUES(5, 4, 3);
+
+  SELECT a, typeof(a), b, c FROM t1 WHERE a=5;
+} {5 text 4 3}
+
+do_execsql_test 1.4 {
+  CREATE TABLE t2(x);
+  INSERT INTO t2 VALUES('v');
+}
+
+do_execsql_test 1.5 {
+  SELECT x, a, b, c FROM t2 LEFT JOIN t1 ON (a=5 AND b=x)
+} {v {} {} {}}
+
+do_execsql_test 1.6 {
+  SELECT x, a, b, c FROM t2 RIGHT JOIN t1 ON (t1.a=5 AND t1.b=t2.x)
+} {{} abc 1 2   {} 5 4 3}
+
+do_eqp_test 1.7 {
+  SELECT x, a, b, c FROM t2 RIGHT JOIN t1 ON (t1.a=5 AND t1.b=t2.x)
+} {USING INDEX i2}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 2.0 {
+  CREATE TABLE x1(a TEXT, b, c);
+  INSERT INTO x1 VALUES('2', 'two', 'ii');
+  INSERT INTO x1 VALUES('2.0', 'twopointoh', 'ii.0');
+
+  CREATE TABLE x2(a NUMERIC, b, c);
+  INSERT INTO x2 VALUES('2', 'two', 'ii');
+  INSERT INTO x2 VALUES('2.0', 'twopointoh', 'ii.0');
+
+  CREATE TABLE x3(a REAL, b, c);
+  INSERT INTO x3 VALUES('2', 'two', 'ii');
+  INSERT INTO x3 VALUES('2.0', 'twopointoh', 'ii.0');
+}
+
+foreach {tn idx} {
+  0 {
+  }
+  1 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a=2;
+    CREATE INDEX i2 ON x2(b, c) WHERE a=2;
+    CREATE INDEX i3 ON x3(b, c) WHERE a=2;
+  }
+  2 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a=2.0;
+    CREATE INDEX i2 ON x2(b, c) WHERE a=2.0;
+    CREATE INDEX i3 ON x3(b, c) WHERE a=2.0;
+  }
+  3 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a='2.0';
+    CREATE INDEX i2 ON x2(b, c) WHERE a='2.0';
+    CREATE INDEX i3 ON x3(b, c) WHERE a='2.0';
+  }
+  4 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a='2';
+    CREATE INDEX i2 ON x2(b, c) WHERE a='2';
+    CREATE INDEX i3 ON x3(b, c) WHERE a='2';
+  }
+} {
+  execsql { DROP INDEX IF EXISTS i1 }
+  execsql { DROP INDEX IF EXISTS i2 }
+  execsql { DROP INDEX IF EXISTS i3 }
+
+  execsql $idx
+  do_execsql_test 2.1.$tn.1 {
+    SELECT *, typeof(a) FROM x1 WHERE a=2
+  } {2 two ii text}
+  do_execsql_test 2.1.$tn.2 {
+    SELECT *, typeof(a) FROM x1 WHERE a=2.0
+  } {2.0 twopointoh ii.0 text}
+  do_execsql_test 2.1.$tn.3 {
+    SELECT *, typeof(a) FROM x1 WHERE a='2'
+  } {2 two ii text}
+  do_execsql_test 2.1.$tn.4 {
+    SELECT *, typeof(a) FROM x1 WHERE a='2.0'
+  } {2.0 twopointoh ii.0 text}
+
+  do_execsql_test 2.1.$tn.5 {
+    SELECT *, typeof(a) FROM x2 WHERE a=2
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+  do_execsql_test 2.1.$tn.6 {
+    SELECT *, typeof(a) FROM x2 WHERE a=2.0
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+  do_execsql_test 2.1.$tn.7 {
+    SELECT *, typeof(a) FROM x2 WHERE a='2'
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+  do_execsql_test 2.1.$tn.8 {
+    SELECT *, typeof(a) FROM x2 WHERE a='2.0'
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+
+  do_execsql_test 2.1.$tn.9 {
+    SELECT *, typeof(a) FROM x3 WHERE a=2
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+  do_execsql_test 2.1.$tn.10 {
+    SELECT *, typeof(a) FROM x3 WHERE a=2.0
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+  do_execsql_test 2.1.$tn.11 {
+    SELECT *, typeof(a) FROM x3 WHERE a='2'
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+  do_execsql_test 2.1.$tn.12 {
+    SELECT *, typeof(a) FROM x3 WHERE a='2.0'
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+
+}
+
+reset_db
+do_execsql_test 3.0 {
+  CREATE TABLE x1(a TEXT, d PRIMARY KEY, b, c) WITHOUT ROWID;
+  INSERT INTO x1 VALUES('2', 1, 'two', 'ii');
+  INSERT INTO x1 VALUES('2.0', 2, 'twopointoh', 'ii.0');
+
+  CREATE TABLE x2(a NUMERIC, b, c, d PRIMARY KEY) WITHOUT ROWID;
+  INSERT INTO x2 VALUES('2', 'two', 'ii', 1);
+  INSERT INTO x2 VALUES('2.0', 'twopointoh', 'ii.0', 2);
+
+  CREATE TABLE x3(d PRIMARY KEY, a REAL, b, c) WITHOUT ROWID;
+  INSERT INTO x3 VALUES(34, '2', 'two', 'ii');
+  INSERT INTO x3 VALUES(35, '2.0', 'twopointoh', 'ii.0');
+}
+
+foreach {tn idx} {
+  0 {
+  }
+  1 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a=2;
+    CREATE INDEX i2 ON x2(b, c) WHERE a=2;
+    CREATE INDEX i3 ON x3(b, c) WHERE a=2;
+  }
+  2 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a=2.0;
+    CREATE INDEX i2 ON x2(b, c) WHERE a=2.0;
+    CREATE INDEX i3 ON x3(b, c) WHERE a=2.0;
+  }
+  3 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a='2.0';
+    CREATE INDEX i2 ON x2(b, c) WHERE a='2.0';
+    CREATE INDEX i3 ON x3(b, c) WHERE a='2.0';
+  }
+  4 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a='2';
+    CREATE INDEX i2 ON x2(b, c) WHERE a='2';
+    CREATE INDEX i3 ON x3(b, c) WHERE a='2';
+  }
+} {
+  execsql { DROP INDEX IF EXISTS i1 }
+  execsql { DROP INDEX IF EXISTS i2 }
+  execsql { DROP INDEX IF EXISTS i3 }
+
+  execsql $idx
+  do_execsql_test 3.1.$tn.1 {
+    SELECT a, b, c, typeof(a) FROM x1 WHERE a=2
+  } {2 two ii text}
+  do_execsql_test 3.1.$tn.2 {
+    SELECT a, b, c, typeof(a) FROM x1 WHERE a=2.0
+  } {2.0 twopointoh ii.0 text}
+  do_execsql_test 3.1.$tn.3 {
+    SELECT a, b, c, typeof(a) FROM x1 WHERE a='2'
+  } {2 two ii text}
+  do_execsql_test 3.1.$tn.4 {
+    SELECT a, b, c, typeof(a) FROM x1 WHERE a='2.0'
+  } {2.0 twopointoh ii.0 text}
+
+  do_execsql_test 3.1.$tn.5 {
+    SELECT a, b, c, typeof(a) FROM x2 WHERE a=2
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+  do_execsql_test 3.1.$tn.6 {
+    SELECT a, b, c, typeof(a) FROM x2 WHERE a=2.0
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+  do_execsql_test 3.1.$tn.7 {
+    SELECT a, b, c, typeof(a) FROM x2 WHERE a='2'
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+  do_execsql_test 3.1.$tn.8 {
+    SELECT a, b, c, typeof(a) FROM x2 WHERE a='2.0'
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+
+  do_execsql_test 3.1.$tn.9 {
+    SELECT a, b, c, typeof(a) FROM x3 WHERE a=2
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+  do_execsql_test 3.1.$tn.10 {
+    SELECT a, b, c, typeof(a) FROM x3 WHERE a=2.0
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+  do_execsql_test 3.1.$tn.11 {
+    SELECT a, b, c, typeof(a) FROM x3 WHERE a='2'
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+  do_execsql_test 3.1.$tn.12 {
+    SELECT a, b, c, typeof(a) FROM x3 WHERE a='2.0'
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+  CREATE TABLE t2(a INTEGER, b TEXT);
+  INSERT INTO t2 VALUES(1, 'two');
+  INSERT INTO t2 VALUES(2, 'two');
+  INSERT INTO t2 VALUES(3, 'two');
+  INSERT INTO t2 VALUES(1, 'three');
+  INSERT INTO t2 VALUES(2, 'three');
+  INSERT INTO t2 VALUES(3, 'three');
+
+  CREATE INDEX t2a_two ON t2(a) WHERE b='two';
+}
+
+# explain_i { SELECT sum(a), b FROM t2 WHERE b='two' }
+do_execsql_test 4.1.1 {
+  SELECT sum(a), b FROM t2 WHERE b='two'
+} {6 two}
+do_eqp_test 4.1.2 {
+  SELECT sum(a), b FROM t2 WHERE b='two'
+} {USING COVERING INDEX t2a_two}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 5.0 {
+  CREATE TABLE t1(a INTEGER PRIMQRY KEY, b, c);
+}
+do_catchsql_test 5.1 {
+  CREATE INDEX ex1 ON t1(c) WHERE b IS 'abc' COLLATE g;
+} {1 {no such collation sequence: g}}
+
+proc xyz {lhs rhs} {
+  return [string compare $lhs $rhs]
+}
+db collate xyz xyz
+do_execsql_test 5.2 {
+  CREATE INDEX ex1 ON t1(c) WHERE b IS 'abc' COLLATE xyz;
+}
+db close
+sqlite3 db test.db
+do_execsql_test 5.3 {
+  SELECT * FROM t1
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 6.0 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+  CREATE TABLE t2(x INTEGER PRIMARY KEY, y INTEGER, z INTEGER);
+  INSERT INTO t1 VALUES(1, 1, 1);
+  INSERT INTO t1 VALUES(2, 1, 2);
+  INSERT INTO t2 VALUES(1, 5, 1);
+  INSERT INTO t2 VALUES(2, 5, 2);
+
+  CREATE INDEX t2z ON t2(z) WHERE y=5;
+}
+
+do_execsql_test 6.1 {
+  ANALYZE;
+  UPDATE sqlite_stat1 SET stat = '50 1' WHERE idx='t2z';
+  UPDATE sqlite_stat1 SET stat = '50' WHERE tbl='t2' AND idx IS NULL;
+  UPDATE sqlite_stat1 SET stat = '5000' WHERE tbl='t1' AND idx IS NULL;
+  ANALYZE sqlite_schema;
+}
+
+do_execsql_test 6.2 {
+  SELECT * FROM t1, t2 WHERE b=1 AND z=c AND y=5;
+} {
+  1 1 1  1 5 1
+  2 1 2  2 5 2
+}
+
+do_eqp_test 6.3 {
+  SELECT * FROM t1, t2 WHERE b=1 AND z=c AND y=5;
+} {BLOOM FILTER ON t2}
+
+do_execsql_test 6.4 {
+  SELECT * FROM t1 LEFT JOIN t2 ON (y=5) WHERE b=1 AND z IS c;
+} {
+  1 1 1  1 5 1
+  2 1 2  2 5 2
+}
+
+do_eqp_test 6.5 {
+  SELECT * FROM t1 LEFT JOIN t2 ON (y=5) WHERE b=1 AND z IS c;
+} {BLOOM FILTER ON t2}
+
+do_execsql_test 6.6 {
+  CREATE INDEX t2yz ON t2(y, z) WHERE y=5;
+}
+
+do_execsql_test 6.7 {
+  SELECT * FROM t1 LEFT JOIN t2 ON (y=5) WHERE b=1 AND z IS c;
+} {
+  1 1 1  1 5 1
+  2 1 2  2 5 2
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 7.0 {
+  CREATE TABLE t1(i INTEGER PRIMARY KEY, b TEXT, c TEXT);
+  CREATE INDEX i1 ON t1(c) WHERE b='abc' AND i=5;
+  INSERT INTO t1 VALUES(5, 'abc', 'xyz');
+  SELECT * FROM t1 INDEXED BY i1 WHERE b='abc' AND i=5 ORDER BY c;
+} {5 abc xyz}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 8.0 {
+  CREATE TABLE t1(a, b, c);
+  CREATE INDEX ex2 ON t1(a, 4);
+  CREATE INDEX ex1 ON t1(a) WHERE 4=b;
+  INSERT INTO t1 VALUES(1, 4, 1);
+  INSERT INTO t1 VALUES(1, 5, 1);
+  INSERT INTO t1 VALUES(2, 4, 2);
+}
+do_execsql_test 8.1 {
+  SELECT * FROM t1 WHERE b=4;
+} {
+  1 4 1  2 4 2
+}
+
+finish_test
diff --git a/libsql-sqlite3/test/joinH.test b/libsql-sqlite3/test/joinH.test
index edba26de2d..0fed7f2aa0 100644
--- a/libsql-sqlite3/test/joinH.test
+++ b/libsql-sqlite3/test/joinH.test
@@ -132,4 +132,123 @@ do_execsql_test 6.0 {
 do_execsql_test 6.1 {
   SELECT * FROM t1 LEFT JOIN t2 ON true WHERE CASE WHEN t2.b THEN 0 ELSE 1 END;
 } {3 NULL}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 7.0 {
+  CREATE TABLE t1(a, b);
+  CREATE TABLE t2(c);
+  CREATE TABLE t3(d);
+
+  INSERT INTO t1 VALUES ('a', 'a');
+  INSERT INTO t2 VALUES ('ddd');
+  INSERT INTO t3 VALUES(1234);
+}
+
+do_execsql_test 7.1 {
+  SELECT t2.rowid FROM t1 JOIN (t2 JOIN t3);
+} {1}
+
+do_execsql_test 7.1 {
+  UPDATE t1 SET b = t2.rowid FROM t2, t3;
+}
+
+do_execsql_test 7.2 { 
+  SELECT * FROM t1
+} {a 1}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 8.0 {
+  CREATE TABLE x1(a INTEGER PRIMARY KEY, b);
+  CREATE TABLE x2(c, d);
+  CREATE TABLE x3(rowid, _rowid_);
+
+  CREATE TABLE x4(rowid, _rowid_, oid);
+
+  INSERT INTO x1 VALUES(1000, 'thousand');
+  INSERT INTO x2 VALUES('c', 'd');
+  INSERT INTO x3(oid, rowid, _rowid_) VALUES(43, 'hello', 'world');
+  INSERT INTO x4(oid, rowid, _rowid_) VALUES('forty three', 'hello', 'world');
+}
+
+do_execsql_test 8.1 {
+  SELECT x3.oid FROM x1 JOIN (x2 JOIN x3 ON c='c')
+} 43
+
+breakpoint
+do_execsql_test 8.2 {
+  SELECT x3.rowid FROM x1 JOIN (x2 JOIN x3 ON c='c')
+} {hello}
+
+do_execsql_test 8.3 {
+  SELECT x4.oid FROM x1 JOIN (x2 JOIN x4 ON c='c')
+} {{forty three}}
+
+
+#---------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 9.0 {
+  CREATE TABLE x1(a);
+  CREATE TABLE x2(b);
+  CREATE TABLE x3(c);
+
+  CREATE TABLE wo1(a PRIMARY KEY, b) WITHOUT ROWID;
+  CREATE TABLE wo2(a PRIMARY KEY, rowid) WITHOUT ROWID;
+  CREATE TABLE wo3(a PRIMARY KEY, b) WITHOUT ROWID;
+}
+
+do_catchsql_test 9.1 {
+  SELECT rowid FROM wo1, x1, x2;
+} {1 {no such column: rowid}}
+do_catchsql_test 9.2 {
+  SELECT rowid FROM wo1, (x1, x2);
+} {1 {no such column: rowid}}
+do_catchsql_test 9.3 {
+  SELECT rowid FROM wo1 JOIN (x1 JOIN x2);
+} {1 {no such column: rowid}}
+do_catchsql_test 9.4 {
+  SELECT a FROM wo1, x1, x2;
+} {1 {ambiguous column name: a}}
+
+
+# It is not possible to use "rowid" in a USING clause.
+#
+do_catchsql_test 9.5 {
+  SELECT * FROM x1 JOIN x2 USING (rowid);
+} {1 {cannot join using column rowid - column not present in both tables}}
+do_catchsql_test 9.6 {
+  SELECT * FROM wo2 JOIN x2 USING (rowid);
+} {1 {cannot join using column rowid - column not present in both tables}}
+
+# "rowid" columns are not matched by NATURAL JOIN. If they were, then
+# the SELECT below would return zero rows.
+do_execsql_test 9.7 {
+  INSERT INTO x1(rowid, a) VALUES(101, 'A');
+  INSERT INTO x2(rowid, b) VALUES(55, 'B');
+  SELECT * FROM x1 NATURAL JOIN x2;
+} {A B}
+
+do_execsql_test 9.8 {
+  INSERT INTO wo1(a, b) VALUES('mya', 'myb');
+  INSERT INTO wo2(a, rowid) VALUES('mypk', 'myrowid');
+  INSERT INTO wo3(a, b) VALUES('MYA', 'MYB');
+  INSERT INTO x3(rowid, c) VALUES(99, 'x3B');
+}
+
+do_catchsql_test 9.8 {
+  SELECT rowid FROM x1 JOIN (x2 JOIN wo2);
+} {0 myrowid}
+do_catchsql_test 9.9 {
+  SELECT _rowid_ FROM wo1 JOIN (wo3 JOIN x3)
+} {0 99}
+do_catchsql_test 9.10 {
+  SELECT oid FROM wo1 JOIN (wo3 JOIN x3)
+} {0 99}
+do_catchsql_test 9.11 {
+  SELECT oid FROM wo2 JOIN (wo3 JOIN x3)
+} {0 99}
+
+
 finish_test
diff --git a/libsql-sqlite3/test/json101.test b/libsql-sqlite3/test/json101.test
index 4da1d132cc..c62991bbbf 100644
--- a/libsql-sqlite3/test/json101.test
+++ b/libsql-sqlite3/test/json101.test
@@ -119,6 +119,9 @@ do_execsql_test json101-4.7 {
 do_execsql_test json101-4.8 {
   SELECT x FROM j1 WHERE json_insert(x)<>x;
 } {}
+do_execsql_test json101-4.9 {
+  SELECT json_insert('{"a":1}','$.b',CAST(x'0000' AS text));
+} {{{"a":1,"b":"\u0000\u0000"}}}
 
 # json_extract(JSON,'$') will return objects and arrays without change.
 #
@@ -1013,7 +1016,43 @@ do_execsql_test json101-21.27 {
   SELECT json_group_object(x,y) FROM c;
 } {{{"a":1,"b":2.0,"c":null,:"three","e":"four"}}}
 
+# 2023-10-09 https://sqlite.org/forum/forumpost/b25edc1d46
+# UAF due to JSON cache overflow
+#
+do_execsql_test json101-22.1 {
+  SELECT json_set(
+    '{}',
+    '$.a', json('1'),
+    '$.a', json('2'),
+    '$.b', json('3'),
+    '$.b', json('4'),
+    '$.c', json('5'),
+    '$.c', json('6')
+  );
+} {{{"a":2,"b":4,"c":6}}}
+do_execsql_test json101-22.2 {
+  SELECT json_replace(
+    '{"a":7,"b":8,"c":9}',
+    '$.a', json('1'),
+    '$.a', json('2'),
+    '$.b', json('3'),
+    '$.b', json('4'),
+    '$.c', json('5'),
+    '$.c', json('6')
+  );
+} {{{"a":2,"b":4,"c":6}}}
 
+# 2023-10-17 https://sqlite.org/forum/forumpost/fc0e3f1e2a
+# Incorrect accesss to '$[0]' in parsed + edited JSON.
+#
+do_execsql_test json101-23.1 {
+  SELECT j, j->>0, j->>1
+    FROM (SELECT json_set(json_set('[]','$[#]',0), '$[#]',1) AS j);
+} {{[0,1]} 0 1}
+do_execsql_test json101-23.2 {
+  SELECT j, j->>0, j->>1
+    FROM (SELECT json_set('[]','$[#]',0,'$[#]',1) AS j);
+} {{[0,1]} 0 1}
 
 
 
diff --git a/libsql-sqlite3/test/json102.test b/libsql-sqlite3/test/json102.test
index 276c1816a7..ce901efeb1 100644
--- a/libsql-sqlite3/test/json102.test
+++ b/libsql-sqlite3/test/json102.test
@@ -48,6 +48,9 @@ do_execsql_test json102-180 {
 do_execsql_test json102-190 {
   SELECT json_array_length('[1,2,3,4]');
 } {{4}}
+do_execsql_test json102-191 {
+  SELECT json_array_length( json_remove('[1,2,3,4]','$[2]') );
+} {{3}}
 do_execsql_test json102-200 {
   SELECT json_array_length('[1,2,3,4]', '$');
 } {{4}}
diff --git a/libsql-sqlite3/test/memdb2.test b/libsql-sqlite3/test/memdb2.test
index 286bfc3f84..7c2144991f 100644
--- a/libsql-sqlite3/test/memdb2.test
+++ b/libsql-sqlite3/test/memdb2.test
@@ -74,4 +74,3 @@ foreach {tn fname} {
 }
 
 finish_test
-
diff --git a/libsql-sqlite3/test/memjournal2.test b/libsql-sqlite3/test/memjournal2.test
index ec5ba56da3..d08bcb5a6a 100644
--- a/libsql-sqlite3/test/memjournal2.test
+++ b/libsql-sqlite3/test/memjournal2.test
@@ -59,5 +59,3 @@ for {set jj 200} {$jj <= 300} {incr jj} {
 
 
 finish_test
-
-
diff --git a/libsql-sqlite3/test/mutex1.test b/libsql-sqlite3/test/mutex1.test
index 0fc8368f76..cb189a7a8a 100644
--- a/libsql-sqlite3/test/mutex1.test
+++ b/libsql-sqlite3/test/mutex1.test
@@ -115,6 +115,14 @@ ifcapable threadsafe1&&shared_cache {
     }
   } {
 
+    # For journal_mode=memory, the static_prng mutex is not required. This
+    # is because the header of an in-memory journal does not contain
+    # any random bytes, and so no call to sqlite3_randomness() is made.
+    if {[permutation]=="inmemory_journal"} {
+      set idx [lsearch $mutexes static_prng]
+      if {$idx>=0} { set mutexes [lreplace $mutexes $idx $idx] }
+    }
+
     do_test mutex1.2.$mode.1 {
       catch {db close}
       sqlite3_shutdown
diff --git a/libsql-sqlite3/test/pendingrace.test b/libsql-sqlite3/test/pendingrace.test
index f0e1a18ffa..ef42578f21 100644
--- a/libsql-sqlite3/test/pendingrace.test
+++ b/libsql-sqlite3/test/pendingrace.test
@@ -121,6 +121,3 @@ tvfs delete
 tvfs2 delete
 
 finish_test
-
-
-
diff --git a/libsql-sqlite3/test/quickcheck.test b/libsql-sqlite3/test/quickcheck.test
index 94016e845f..18c42a13d0 100644
--- a/libsql-sqlite3/test/quickcheck.test
+++ b/libsql-sqlite3/test/quickcheck.test
@@ -31,4 +31,3 @@ do_execsql_test 1.1 {
 }
 
 finish_test
-
diff --git a/libsql-sqlite3/test/rowvalue9.test b/libsql-sqlite3/test/rowvalue9.test
index aee5e7ea4f..baa13f4f94 100644
--- a/libsql-sqlite3/test/rowvalue9.test
+++ b/libsql-sqlite3/test/rowvalue9.test
@@ -350,5 +350,3 @@ do_eqp_test 9.5e {
 
 
 finish_test
-
-
diff --git a/libsql-sqlite3/test/rowvalueA.test b/libsql-sqlite3/test/rowvalueA.test
index dc5a7a014b..8760c2c396 100644
--- a/libsql-sqlite3/test/rowvalueA.test
+++ b/libsql-sqlite3/test/rowvalueA.test
@@ -74,4 +74,3 @@ do_catchsql_test 2.3 {
 } {1 {row value misused}}
 
 finish_test
-
diff --git a/libsql-sqlite3/test/scanstatus2.test b/libsql-sqlite3/test/scanstatus2.test
index cbffee0185..e4b510d20f 100644
--- a/libsql-sqlite3/test/scanstatus2.test
+++ b/libsql-sqlite3/test/scanstatus2.test
@@ -145,8 +145,8 @@ do_graph_test 1.4 {
 QUERY (nCycle=nnn)
 --MATERIALIZE v2 (nCycle=nnn)
 ----SCAN t2 (nCycle=nnn)
---SCAN v2 (nCycle=nnn)
 --SCAN t1 (nCycle=nnn)
+--SCAN v2 (nCycle=nnn)
 --USE TEMP B-TREE FOR ORDER BY (nCycle=nnn)
 }
 
@@ -332,5 +332,3 @@ QUERY (nCycle=nnn)
 #puts_debug_info { SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1 }
 
 finish_test
-
-
diff --git a/libsql-sqlite3/test/select3.test b/libsql-sqlite3/test/select3.test
index 4c9d71b4f5..4bbd70cc75 100644
--- a/libsql-sqlite3/test/select3.test
+++ b/libsql-sqlite3/test/select3.test
@@ -434,4 +434,3 @@ do_execsql_test 12.8 {
 }
 
 finish_test
-
diff --git a/libsql-sqlite3/test/selectH.test b/libsql-sqlite3/test/selectH.test
index a97679bda2..41f0999fe3 100644
--- a/libsql-sqlite3/test/selectH.test
+++ b/libsql-sqlite3/test/selectH.test
@@ -113,6 +113,33 @@ do_execsql_test 4.1 {
   CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
   SELECT 1 FROM (SELECT DISTINCT name COLLATE rtrim FROM sqlite_schema
                  UNION ALL SELECT a FROM t1);
-} 1
+} {1 1}
+
+do_execsql_test 4.2 {
+  SELECT DISTINCT name COLLATE rtrim FROM sqlite_schema 
+    UNION ALL 
+  SELECT a FROM t1
+} {v1 t1}
+
+#-------------------------------------------------------------------------
+# forum post https://sqlite.org/forum/forumpost/b83c7b2168
+#
+reset_db
+do_execsql_test 5.0 {
+  CREATE TABLE t1 (val1);
+  INSERT INTO t1 VALUES(4);
+  INSERT INTO t1 VALUES(5);
+  CREATE TABLE t2 (val2);
+}
+do_execsql_test 5.1 {
+  SELECT DISTINCT val1 FROM t1 UNION ALL SELECT val2 FROM t2;
+} {
+  4 5
+}
+do_execsql_test 5.2 {
+  SELECT count(1234) FROM (
+    SELECT DISTINCT val1 FROM t1 UNION ALL SELECT val2 FROM t2
+  )
+} {2}
 
 finish_test
diff --git a/libsql-sqlite3/test/sessionfuzz.c b/libsql-sqlite3/test/sessionfuzz.c
index f2e4cd5a68..093c2b043d 100644
--- a/libsql-sqlite3/test/sessionfuzz.c
+++ b/libsql-sqlite3/test/sessionfuzz.c
@@ -66,14 +66,6 @@
 #define SQLITE_ENABLE_DESERIALIZE 1
 #include "sqlite3.c"
 
-/* Create a test database.  This will be an in-memory database */
-static const char zInitSql[] = 
-  "CREATE TABLE t1(a INTEGER PRIMARY KEY,b,c,d);\n"
-  "CREATE TABLE t2(e TEXT PRIMARY KEY NOT NULL,f,g);\n"
-  "CREATE TABLE t3(w REAL PRIMARY KEY NOT NULL,x,y);\n"
-  "CREATE TABLE t4(z PRIMARY KEY) WITHOUT ROWID;\n"
-;
-
 /* Code to populate the database */
 static const char zFillSql[] = 
   "INSERT INTO t1(a,b,c,d) VALUES\n"
diff --git a/libsql-sqlite3/test/shell1.test b/libsql-sqlite3/test/shell1.test
index d017379107..19848549ac 100644
--- a/libsql-sqlite3/test/shell1.test
+++ b/libsql-sqlite3/test/shell1.test
@@ -1081,10 +1081,10 @@ do_test shell1-5.0 {
       #
       set escapes [list \
           \t \\t \n \\n \v \\v \f \\f \
-          " " "\" \"" \" \\\" ' \"'\" \\ \\\\]
+          " " "\" \"" \" \\\" \\ \\\\]
     }
     set char [string map $escapes $char]
-    set x [catchcmdex test.db ".print $char\n"]
+    set x [catchcmdex test.db ".print \"$char\"\n"]
     set code [lindex $x 0]
     set res [lindex $x 1]
     if {$code ne "0"} {
@@ -1165,21 +1165,21 @@ do_test shell1-7.1.2 {
 } {0 {CREATE TABLE Z (x TEXT PRIMARY KEY);
 CREATE TABLE _ (x TEXT PRIMARY KEY);}}
 do_test shell1-7.1.3 {
-  catchcmd "test.db" ".schema \\\\_"
+  catchcmd "test.db" ".schema \"\\\\_\""
 } {0 {CREATE TABLE _ (x TEXT PRIMARY KEY);}}
 do_test shell1-7.1.4 {
   catchcmd "test.db" ".schema __"
 } {0 {CREATE TABLE YY (x TEXT PRIMARY KEY);
 CREATE TABLE __ (x TEXT PRIMARY KEY);}}
 do_test shell1-7.1.5 {
-  catchcmd "test.db" ".schema \\\\_\\\\_"
+  catchcmd "test.db" ".schema \"\\\\_\\\\_\""
 } {0 {CREATE TABLE __ (x TEXT PRIMARY KEY);}}
 do_test shell1-7.1.6 {
   catchcmd "test.db" ".schema ___"
 } {0 {CREATE TABLE WWW (x TEXT PRIMARY KEY);
 CREATE TABLE ___ (x TEXT PRIMARY KEY);}}
 do_test shell1-7.1.7 {
-  catchcmd "test.db" ".schema \\\\_\\\\_\\\\_"
+  catchcmd "test.db" ".schema \"\\\\_\\\\_\\\\_\""
 } {0 {CREATE TABLE ___ (x TEXT PRIMARY KEY);}}
 
 }
diff --git a/libsql-sqlite3/test/statfault.test b/libsql-sqlite3/test/statfault.test
index b5980d417d..19e0a67874 100644
--- a/libsql-sqlite3/test/statfault.test
+++ b/libsql-sqlite3/test/statfault.test
@@ -52,4 +52,3 @@ do_faultsim_test 2 -faults * -prep {
 }
 
 finish_test
-
diff --git a/libsql-sqlite3/test/subquery.test b/libsql-sqlite3/test/subquery.test
index a048f9ed4e..c51edba040 100644
--- a/libsql-sqlite3/test/subquery.test
+++ b/libsql-sqlite3/test/subquery.test
@@ -11,8 +11,6 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this script is testing correlated subqueries
 #
-# $Id: subquery.test,v 1.17 2009/01/09 01:12:28 drh Exp $
-#
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -613,4 +611,45 @@ do_execsql_test subquery-9.4 {
   SELECT (SELECT DISTINCT x FROM t1 ORDER BY +x LIMIT 1 OFFSET 2) FROM t1;
 } {{} {} {} {}}
 
+# 2023-09-15
+# Query planner performance regression reported by private email 
+# on 2023-09-14, caused by VIEWSCAN optimization of check-in 609fbb94b8f01d67
+# from 2022-09-01.
+#
+reset_db
+do_execsql_test subquery-10.1 {
+  CREATE TABLE t1(aa TEXT, bb INT, cc TEXT);
+  CREATE INDEX x11 on t1(bb);
+  CREATE INDEX x12 on t1(aa);
+  CREATE TABLE t2(aa TEXT, xx INT);
+  ANALYZE sqlite_master;
+  INSERT INTO sqlite_stat1(tbl, idx, stat) VALUES('t1', 'x11', '156789 28');
+  INSERT INTO sqlite_stat1(tbl, idx, stat) VALUES('t1', 'x12', '156789 1');
+  ANALYZE sqlite_master;
+}
+do_eqp_test subquery-10.2 {
+  WITH v1(aa,cc,bb) AS (SELECT aa, cc, bb FROM t1 WHERE bb=12345),
+       v2(aa,mx)    AS (SELECT aa, max(xx) FROM t2 GROUP BY aa)
+  SELECT * FROM v1 JOIN v2 ON v1.aa=v2.aa;
+} {
+  QUERY PLAN
+  |--CO-ROUTINE v2
+  |  |--SCAN t2
+  |  `--USE TEMP B-TREE FOR GROUP BY
+  |--SEARCH t1 USING INDEX x11 (bb=?)
+  `--SEARCH v2 USING AUTOMATIC COVERING INDEX (aa=?)
+}
+# ^^^^^^^^^^^^^
+# Prior to the fix the incorrect (slow) plan caused by the
+# VIEWSCAN optimization was:
+#
+# QUERY PLAN
+# |--CO-ROUTINE v2
+# |  |--SCAN t2
+# |  `--USE TEMP B-TREE FOR GROUP BY
+# |--SCAN v2
+# `--SEARCH t1 USING INDEX x12 (aa=?)
+#
+
+
 finish_test
diff --git a/libsql-sqlite3/test/table.test b/libsql-sqlite3/test/table.test
index 7be6b37695..b961207f8b 100644
--- a/libsql-sqlite3/test/table.test
+++ b/libsql-sqlite3/test/table.test
@@ -784,13 +784,13 @@ do_catchsql_test table-16.5 {
 } {1 {unknown function: count()}}
 do_catchsql_test table-16.6 {
   DROP TABLE t16;
-  CREATE TABLE t16(x DEFAULT(group_concat('x',',')));
+  CREATE TABLE t16(x DEFAULT(string_agg('x',',')));
   INSERT INTO t16(rowid) VALUES(123);
   SELECT rowid, x FROM t16;
-} {1 {unknown function: group_concat()}}
+} {1 {unknown function: string_agg()}}
 do_catchsql_test table-16.7 {
   INSERT INTO t16 DEFAULT VALUES;
-} {1 {unknown function: group_concat()}}
+} {1 {unknown function: string_agg()}}
 
 # Ticket [https://www.sqlite.org/src/info/094d39a4c95ee4abbc417f04214617675ba15c63]
 # describes a assertion fault that occurs on a CREATE TABLE .. AS SELECT statement.
diff --git a/libsql-sqlite3/test/testrunner.tcl b/libsql-sqlite3/test/testrunner.tcl
index 22e3b17bf4..1f1862ffdd 100644
--- a/libsql-sqlite3/test/testrunner.tcl
+++ b/libsql-sqlite3/test/testrunner.tcl
@@ -59,7 +59,6 @@ Usage:
 
   where SWITCHES are:
     --jobs NUMBER-OF-JOBS
-    --fuzztest
     --zipvfs ZIPVFS-SOURCE-DIR
 
 Interesting values for PERMUTATION are:
@@ -83,11 +82,6 @@ If a PERMUTATION is specified and is followed by the path to a Tcl script
 instead of a list of patterns, then that single Tcl test script is run
 with the specified permutation.
 
-The --fuzztest option is ignored if the PERMUTATION is "release". Otherwise,
-if it is present, then "make -C <dir> fuzztest" is run as part of the tests,
-where <dir> is the directory containing the testfixture binary used to
-run the script.
-
 The "status" and "njob" commands are designed to be run from the same
 directory as a running testrunner.tcl script that is running tests. The
 "status" command prints a report describing the current state and progress 
@@ -157,19 +151,28 @@ set TRG(zipvfs) ""                  ;# -zipvfs option, if any
 
 switch -nocase -glob -- $tcl_platform(os) {
   *darwin* {
-    set TRG(platform) osx
-    set TRG(make)     make.sh
-    set TRG(makecmd)  "bash make.sh"
+    set TRG(platform)    osx
+    set TRG(make)        make.sh
+    set TRG(makecmd)     "bash make.sh"
+    set TRG(testfixture) testfixture
+    set TRG(run)         run.sh
+    set TRG(runcmd)      "bash run.sh"
   }
   *linux* {
-    set TRG(platform) linux
-    set TRG(make)     make.sh
-    set TRG(makecmd)  "bash make.sh"
+    set TRG(platform)    linux
+    set TRG(make)        make.sh
+    set TRG(makecmd)     "bash make.sh"
+    set TRG(testfixture) testfixture
+    set TRG(run)         run.sh
+    set TRG(runcmd)      "bash run.sh"
   }
   *win* {
-    set TRG(platform) win
-    set TRG(make)     make.bat
-    set TRG(makecmd)  make.bat
+    set TRG(platform)    win
+    set TRG(make)        make.bat
+    set TRG(makecmd)     make.bat
+    set TRG(testfixture) testfixture.exe
+    set TRG(run)         run.bat
+    set TRG(runcmd)      "run.bat"
   }
   default {
     error "cannot determine platform!"
@@ -181,20 +184,59 @@ switch -nocase -glob -- $tcl_platform(os) {
 # The database schema used by the testrunner.db database.
 #
 set TRG(schema) {
-  DROP TABLE IF EXISTS script;
+  DROP TABLE IF EXISTS jobs;
   DROP TABLE IF EXISTS config;
 
-  CREATE TABLE script(
-    build TEXT DEFAULT '',
-    config TEXT,
-    filename TEXT,                -- full path to test script
-    slow BOOLEAN,                 -- true if script is "slow"
+  /*
+  ** This table contains one row for each job that testrunner.tcl must run
+  ** before the entire test run is finished.
+  **
+  ** jobid:
+  **   Unique identifier for each job. Must be a +ve non-zero number.
+  **
+  ** displaytype:
+  **   3 or 4 letter mnemonic for the class of tests this belongs to e.g.
+  **   "fuzz", "tcl", "make" etc.
+  **
+  ** displayname:
+  **   Name/description of job. For display purposes.
+  **
+  ** build:
+  **   If the job requires a make.bat/make.sh make wrapper (i.e. to build
+  **   something), the name of the build configuration it uses. See 
+  **   testrunner_data.tcl for a list of build configs. e.g. "Win32-MemDebug".
+  **
+  ** dirname:
+  **   If the job should use a well-known directory name for its 
+  **   sub-directory instead of an anonymous "testdir[1234...]" sub-dir
+  **   that is deleted after the job is finished.
+  **
+  ** cmd:
+  **   Bash or batch script to run the job.
+  **
+  ** depid:
+  **   The jobid value of a job that this job depends on. This job may not
+  **   be run before its depid job has finished successfully.
+  **
+  ** priority:
+  **   Higher values run first. Sometimes.
+  */
+  CREATE TABLE jobs(
+    /* Fields populated when db is initialized */
+    jobid INTEGER PRIMARY KEY,          -- id to identify job
+    displaytype TEXT NOT NULL,          -- Type of test (for one line report)
+    displayname TEXT NOT NULL,          -- Human readable job name
+    build TEXT NOT NULL DEFAULT '',     -- make.sh/make.bat file request, if any
+    dirname TEXT NOT NULL DEFAULT '',   -- directory name, if required
+    cmd TEXT NOT NULL,                  -- shell command to run
+    depid INTEGER,                      -- identifier of dependency (or '')
+    priority INTEGER NOT NULL,          -- higher priority jobs may run earlier
+  
+    /* Fields updated as jobs run */
+    starttime INTEGER, 
+    endtime INTEGER,
     state TEXT CHECK( state IN ('', 'ready', 'running', 'done', 'failed') ),
-    time INTEGER,                 -- Time in ms
-    output TEXT,                  -- full output of test script
-    priority INTEGER,
-    jobtype TEXT CHECK( jobtype IN ('script', 'build', 'make') ),
-    PRIMARY KEY(build, config, filename)
+    output TEXT
   );
 
   CREATE TABLE config(
@@ -202,8 +244,8 @@ set TRG(schema) {
     value 
   ) WITHOUT ROWID;
 
-  CREATE INDEX i1 ON script(state, jobtype);
-  CREATE INDEX i2 ON script(state, priority);
+  CREATE INDEX i1 ON jobs(state, priority);
+  CREATE INDEX i2 ON jobs(depid);
 }
 #-------------------------------------------------------------------------
 
@@ -303,39 +345,14 @@ if {[llength $argv]==1
  && [string compare -nocase status [lindex $argv 0]]==0 
 } {
 
-  proc display_job {build config filename {tm ""}} {
-    if {$config=="build"} {
-      set fname "build: $filename"
-      set config ""
-    } elseif {$config=="make"} {
-      set fname "make: $filename"
-      set config ""
-    } else {
-      set fname [file normalize $filename]
-      if {[string first $::srcdir $fname]==0} {
-        set fname [string range $fname [string length $::srcdir]+1 end]
-      }
-    }
-    set dfname [format %-33s $fname]
+  proc display_job {jobdict {tm ""}} {
+    array set job $jobdict
+
+    set dfname [format %-60s $job(displayname)]
 
-    set dbuild ""
-    set dconfig ""
-    set dparams ""
     set dtm ""
-    if {$build!=""} { set dbuild $build }
-    if {$config!="" && $config!="full"} { set dconfig $config }
-    if {$dbuild!="" || $dconfig!=""} {
-      append dparams "("
-      if {$dbuild!=""}                 {append dparams "build=$dbuild"}
-      if {$dbuild!="" && $dconfig!=""} {append dparams " "}
-      if {$dconfig!=""}                {append dparams "config=$dconfig"}
-      append dparams ")"
-      set dparams [format %-33s $dparams]
-    }
-    if {$tm!=""} {
-      set dtm "\[${tm}ms\]"
-    }
-    puts "  $dfname $dparams $dtm"
+    if {$tm!=""} { set dtm "\[[expr {$tm-$job(starttime)}]ms\]" }
+    puts "  $dfname $dtm"
   }
 
   sqlite3 mydb $TRG(dbname)
@@ -355,7 +372,7 @@ if {[llength $argv]==1
   set total 0
   foreach s {"" ready running done failed} { set S($s) 0 }
   mydb eval {
-    SELECT state, count(*) AS cnt FROM script GROUP BY 1
+    SELECT state, count(*) AS cnt FROM jobs GROUP BY 1
   } {
     incr S($state) $cnt
     incr total $cnt
@@ -375,19 +392,17 @@ if {[llength $argv]==1
   if {$S(running)>0} {
     puts "Running: "
     mydb eval {
-      SELECT build, config, filename, time FROM script WHERE state='running'
-      ORDER BY time 
-    } {
-      display_job $build $config $filename [expr $now-$time]
+      SELECT * FROM jobs WHERE state='running' ORDER BY starttime 
+    } job {
+      display_job [array get job] $now
     }
   }
   if {$S(failed)>0} {
     puts "Failures: "
     mydb eval {
-      SELECT build, config, filename FROM script WHERE state='failed'
-      ORDER BY 3
-    } {
-      display_job $build $config $filename
+      SELECT * FROM jobs WHERE state='failed' ORDER BY starttime
+    } job {
+      display_job [array get job]
     }
   }
  
@@ -408,8 +423,6 @@ for {set ii 0} {$ii < [llength $argv]} {incr ii} {
       incr ii
       set TRG(nJob) [lindex $argv $ii]
       if {$isLast} { usage }
-    } elseif {($n>2 && [string match "$a*" --fuzztest]) || $a=="-f"} {
-      set TRG(fuzztest) 1
     } elseif {($n>2 && [string match "$a*" --zipvfs]) || $a=="-z"} {
       incr ii
       set TRG(zipvfs) [lindex $argv $ii]
@@ -480,120 +493,12 @@ proc create_or_clear_dir {dir} {
   }
 }
 
-proc copy_dir {from to} {
-  foreach f [glob -nocomplain [file join $from *]] {
-    catch { file copy -force $f $to }
-  }
-}
-
 proc build_to_dirname {bname} {
   set fold [string tolower [string map {- _} $bname]]
   return "testrunner_build_$fold"
 }
 
 #-------------------------------------------------------------------------
-# Return a list of tests to run. Each element of the list is itself a
-# list of two elements - the name of a permuations.test configuration
-# followed by the full path to a test script. i.e.:
-#
-#    {BUILD CONFIG FILENAME} {BUILD CONFIG FILENAME} ...
-#
-proc testset_patternlist {patternlist} {
-  global TRG
-
-  set testset [list]              ;# return value
-
-  set first [lindex $patternlist 0]
-
-  if {$first=="sdevtest" || $first=="mdevtest"} {
-    set CONFIGS(sdevtest) {All-Debug All-Sanitize}
-    set CONFIGS(mdevtest) {All-Debug All-O0}
-
-    set patternlist [lrange $patternlist 1 end]
-
-    foreach b $CONFIGS($first) {
-      lappend testset [list $b build testfixture]
-      lappend testset [list $b make fuzztest]
-      testset_append testset $b veryquick $patternlist
-    }
-  } elseif {$first=="release"} {
-    set platform $::TRG(platform)
-
-    set patternlist [lrange $patternlist 1 end]
-    foreach b [trd_builds $platform] {
-      foreach c [trd_configs $platform $b] {
-        testset_append testset $b $c $patternlist
-      }
-
-      if {[llength $patternlist]==0 || $b=="User-Auth"} {
-        set target testfixture
-      } else {
-        set target coretestprogs
-      }
-      lappend testset [list $b build $target]
-    }
-
-    if {[llength $patternlist]==0} {
-      foreach b [trd_builds $platform] {
-        foreach e [trd_extras $platform $b] {
-          lappend testset [list $b make $e]
-        }
-      }
-    }
-
-    set TRG(fuzztest) 0           ;# ignore --fuzztest option in this case
-
-  } elseif {$first=="all"} {
-
-    set clist [trd_all_configs]
-    set patternlist [lrange $patternlist 1 end]
-    foreach c $clist {
-      testset_append testset "" $c $patternlist
-    }
-
-  } elseif {[info exists ::testspec($first)]} {
-    set clist $first
-    testset_append testset "" $first [lrange $patternlist 1 end]
-  } elseif { [llength $patternlist]==0 } {
-    testset_append testset "" veryquick $patternlist
-  } else {
-    testset_append testset "" full $patternlist
-  }
-  if {$TRG(fuzztest)} {
-    if {$TRG(platform)=="win"} { error "todo" }
-    lappend testset [list "" make fuzztest]
-  }
-
-  set testset
-}
-
-proc testset_append {listvar build config patternlist} {
-  upvar $listvar lvar
-
-  catch { array unset O }
-  array set O $::testspec($config)
-
-  foreach f $O(-files) {
-    if {[llength $patternlist]>0} {
-      set bMatch 0
-      foreach p $patternlist {
-        if {[string match $p [file tail $f]]} {
-          set bMatch 1
-          break
-        }
-      }
-      if {$bMatch==0} continue
-    }
-
-    if {[file pathtype $f]!="absolute"} {
-      set f [file join $::testdir $f]
-    }
-    lappend lvar [list $build $config $f]
-  }
-}
-
-#--------------------------------------------------------------------------
-
 
 proc r_write_db {tcl} {
   trdb eval { BEGIN EXCLUSIVE }
@@ -615,51 +520,307 @@ proc r_get_next_job {iJob} {
     set orderby "ORDER BY priority DESC"
   }
 
+  set ret [list]
+
   r_write_db {
-    set f ""
-    set c ""
-    trdb eval "
-      SELECT build, config, filename 
-        FROM script 
-        WHERE state='ready' 
-        $orderby LIMIT 1
-    " {
-      set b $build
-      set c $config
-      set f $filename
-    }
-    if {$f!=""} {
+    set query "
+      SELECT * FROM jobs AS j WHERE state='ready' $orderby LIMIT 1
+    " 
+    trdb eval $query job {
       set tm [clock_milliseconds]
       set T($iJob) $tm
-      trdb eval { 
-        UPDATE script SET state='running', time=$tm
-        WHERE (build, config, filename) = ($b, $c, $f)
+      set jobid $job(jobid)
+
+      trdb eval {
+        UPDATE jobs SET starttime=$tm, state='running' WHERE jobid=$jobid
       }
+
+      set ret [array get job]
     }
   }
 
-  if {$f==""} { return "" }
-  list $b $c $f
+  return $ret
 }
 
 #rename r_get_next_job r_get_next_job_r
 #proc r_get_next_job {iJob} {
-#  puts [time { set res [r_get_next_job_r $iJob] }]
-#  set res
+  #puts [time { set res [r_get_next_job_r $iJob] }]
+  #set res
 #}
 
+# Usage:
+#
+#   add_job OPTION ARG OPTION ARG...
+#
+# where available OPTIONS are:
+#
+#   -displaytype
+#   -displayname
+#   -build
+#   -dirname     
+#   -cmd 
+#   -depid 
+#   -priority 
+#
+# Returns the jobid value for the new job.
+# 
+proc add_job {args} {
+
+  set options {
+      -displaytype -displayname -build -dirname 
+      -cmd -depid -priority
+  }
+
+  # Set default values of options.
+  set A(-dirname) ""
+  set A(-depid)   ""
+  set A(-priority) 0
+  set A(-build)   ""
+
+  array set A $args
+
+  # Check all required options are present. And that no extras are present.
+  foreach o $options {
+    if {[info exists A($o)]==0} { error "missing required option $o" }
+  }
+  foreach o [array names A] {
+    if {[lsearch -exact $options $o]<0} { error "unrecognized option: $o" }
+  }
+
+  set state ""
+  if {$A(-depid)==""} { set state ready }
+
+  trdb eval {
+    INSERT INTO jobs(
+      displaytype, displayname, build, dirname, cmd, depid, priority,
+      state
+    ) VALUES (
+      $A(-displaytype),
+      $A(-displayname),
+      $A(-build),
+      $A(-dirname),
+      $A(-cmd),
+      $A(-depid),
+      $A(-priority),
+      $state
+    )
+  }
+
+  trdb last_insert_rowid
+}
+
+proc add_tcl_jobs {build config patternlist} {
+  global TRG
+
+  set topdir [file dirname $::testdir]
+  set testrunner_tcl [file normalize [info script]]
+
+  if {$build==""} {
+    set testfixture [info nameofexec]
+  } else {
+    set testfixture [file join [lindex $build 1] $TRG(testfixture)]
+  }
+  if {[lindex $build 2]=="Valgrind"} {
+    set setvar "export OMIT_MISUSE=1\n"
+    set testfixture "${setvar}valgrind -v --error-exitcode=1 $testfixture"
+  }
+
+  # The ::testspec array is populated by permutations.test
+  foreach f [dict get $::testspec($config) -files] {
+
+    if {[llength $patternlist]>0} {
+      set bMatch 0
+      foreach p $patternlist {
+        if {[string match $p [file tail $f]]} {
+          set bMatch 1
+          break
+        }
+      }
+      if {$bMatch==0} continue
+    }
+
+    if {[file pathtype $f]!="absolute"} { set f [file join $::testdir $f] }
+    set f [file normalize $f]
+
+    set displayname [string map [list $topdir/ {}] $f]
+    if {$config=="full" || $config=="veryquick"} {
+      set cmd "$testfixture $f"
+    } else {
+      set cmd "$testfixture $testrunner_tcl $config $f"
+      set displayname "config=$config $displayname"
+    }
+    if {$build!=""} {
+      set displayname "[lindex $build 2] $displayname"
+    }
+
+    set lProp [trd_test_script_properties $f]
+    set priority 0
+    if {[lsearch $lProp slow]>=0} { set priority 2 }
+    if {[lsearch $lProp superslow]>=0} { set priority 4 }
+
+    add_job                            \
+        -displaytype tcl               \
+        -displayname $displayname      \
+        -cmd $cmd                      \
+        -depid [lindex $build 0]       \
+        -priority $priority
+
+  }
+}
+
+proc add_build_job {buildname target} {
+  global TRG
+
+  set dirname "[string tolower [string map {- _} $buildname]]_$target"
+  set dirname "testrunner_bld_$dirname"
+
+  set id [add_job                                \
+    -displaytype bld                             \
+    -displayname "Build $buildname ($target)"    \
+    -dirname $dirname                            \
+    -build $buildname                            \
+    -cmd  "$TRG(makecmd) $target"                \
+    -priority 3
+  ]
+
+  list $id [file normalize $dirname] $buildname
+}
+
+proc add_make_job {bld target} {
+  global TRG
+
+  if {$TRG(platform)=="win"} {
+    set path [string map {/ \\} [lindex $bld 1]]
+    set cmd "xcopy /S $path\\* ."
+  } else {
+    set cmd "cp -r [lindex $bld 1]/* ."
+  }
+  append cmd "\n$TRG(makecmd) $target"
+
+  add_job                                       \
+    -displaytype make                           \
+    -displayname "[lindex $bld 2] make $target" \
+    -cmd $cmd                                   \
+    -depid [lindex $bld 0]                      \
+    -priority 1
+}
+
+proc add_fuzztest_jobs {buildname} {
+
+  foreach {interpreter scripts} [trd_fuzztest_data] {
+    set subcmd [lrange $interpreter 1 end]
+    set interpreter [lindex $interpreter 0]
+
+    set bld [add_build_job $buildname $interpreter]
+    foreach {depid dirname displayname} $bld {}
+
+    foreach s $scripts {
+
+      # Fuzz data files fuzzdata1.db and fuzzdata2.db are larger than
+      # the others. So ensure that these are run as a higher priority.
+      set tail [file tail $s]
+      if {$tail=="fuzzdata1.db" || $tail=="fuzzdata2.db"} {
+        set priority 5
+      } else {
+        set priority 1
+      }
+
+      add_job                                                   \
+        -displaytype fuzz                                       \
+        -displayname "$buildname $interpreter $tail"            \
+        -depid $depid                                           \
+        -cmd "[file join $dirname $interpreter] $subcmd $s"     \
+        -priority $priority
+    }
+  }
+}
+
+proc add_zipvfs_jobs {} {
+  global TRG
+  source [file join $TRG(zipvfs) test zipvfs_testrunner.tcl]
+
+  set bld [add_build_job Zipvfs $TRG(testfixture)]
+  foreach s [zipvfs_testrunner_files] {
+    set cmd "[file join [lindex $bld 1] $TRG(testfixture)] $s"
+    add_job                                  \
+        -displaytype tcl                     \
+        -displayname "Zipvfs [file tail $s]" \
+        -cmd $cmd                            \
+        -depid [lindex $bld 0]
+  }
+
+  set ::env(SQLITE_TEST_DIR) $::testdir
+}
+
+proc add_jobs_from_cmdline {patternlist} {
+  global TRG
+
+  if {$TRG(zipvfs)!=""} {
+    add_zipvfs_jobs
+    if {[llength $patternlist]==0} return
+  }
+
+  if {[llength $patternlist]==0} {
+    set patternlist [list veryquick]
+  }
+
+  set first [lindex $patternlist 0]
+  switch -- $first {
+    all {
+      set patternlist [lrange $patternlist 1 end]
+      set clist [trd_all_configs]
+      foreach c $clist {
+        add_tcl_jobs "" $c $patternlist
+      }
+    }
+
+    mdevtest {
+      foreach b [list All-O0 All-Debug] {
+        set bld [add_build_job $b $TRG(testfixture)]
+        add_tcl_jobs $bld veryquick ""
+        add_fuzztest_jobs $b
+      }
+    }
+
+    sdevtest {
+      foreach b [list All-Sanitize All-Debug] {
+        set bld [add_build_job $b $TRG(testfixture)]
+        add_tcl_jobs $bld veryquick ""
+        add_fuzztest_jobs $b
+      }
+    }
+
+    release {
+      foreach b [trd_builds $TRG(platform)] {
+        set bld [add_build_job $b $TRG(testfixture)]
+        foreach c [trd_configs $TRG(platform) $b] {
+          add_tcl_jobs $bld $c ""
+        }
+
+        foreach e [trd_extras $TRG(platform) $b] {
+          if {$e=="fuzztest"} {
+            add_fuzztest_jobs $b
+          } else {
+            add_make_job $bld $e
+          }
+        }
+      }
+    }
+
+    default {
+      if {[info exists ::testspec($first)]} {
+        add_tcl_jobs "" $first [lrange $patternlist 1 end]
+      } else {
+        add_tcl_jobs "" full $patternlist
+      }
+    }
+  }
+}
+
 proc make_new_testset {} {
   global TRG
 
-  set tests [testset_patternlist $TRG(patternlist)]
-
-  if {$TRG(zipvfs)!=""} {
-    source [file join $TRG(zipvfs) test zipvfs_testrunner.tcl]
-    set tests [concat $tests [zipvfs_testrunner_testset]]
-  }
-
   r_write_db {
-
     trdb eval $TRG(schema)
     set nJob $TRG(nJob)
     set cmdline $TRG(cmdline)
@@ -668,88 +829,52 @@ proc make_new_testset {} {
     trdb eval { REPLACE INTO config VALUES('cmdline', $cmdline ); }
     trdb eval { REPLACE INTO config VALUES('start', $tm ); }
 
-    foreach t $tests {
-      foreach {b c s} $t {}
-      set slow 0
-
-      if {$c!="make" && $c!="build"} {
-        set fd [open $s]
-        for {set ii 0} {$ii<100 && ![eof $fd]} {incr ii} {
-          set line [gets $fd]
-          if {[string match -nocase *testrunner:* $line]} {
-            regexp -nocase {.*testrunner:(.*)} $line -> properties
-            foreach p $properties {
-              if {$p=="slow"} { set slow 1 }
-              if {$p=="superslow"} { set slow 2 }
-            }
-          }
-        }
-        close $fd
-      }
-
-      if {$c=="make" && $b==""} {
-        # --fuzztest option
-        set slow 1
-      }
-
-      if {$c=="veryquick"} {
-        set c ""
-      }
-
-      set state ready
-      if {$b!="" && $c!="build"} {
-        set state ""
-      }
-
-      set priority [expr {$slow*2}]
-      if {$c=="make"} { incr priority 3 }
-      if {$c=="build"} { incr priority 1 }
-
-      if {$c=="make" || $c=="build"} {
-        set jobtype $c
-      } else {
-        set jobtype "script"
-      }
-
-      trdb eval { 
-        INSERT INTO script
-                   (build, config, filename, slow, state, priority, jobtype) 
-            VALUES ($b, $c, $s, $slow, $state, $priority, $jobtype) 
-      }
-    }
+    add_jobs_from_cmdline $TRG(patternlist)
   }
+
 }
 
-proc script_input_ready {fd iJob b c f} {
+proc script_input_ready {fd iJob jobid} {
   global TRG
   global O
   global T
 
   if {[eof $fd]} {
+    trdb eval { SELECT * FROM jobs WHERE jobid=$jobid } job {}
+
+    # If this job specified a directory name, then delete the run.sh/run.bat
+    # file from it before continuing. This is because the contents of this
+    # directory might be copied by some other job, and we don't want to copy
+    # the run.sh file in this case.
+    if {$job(dirname)!=""} {
+      file delete -force [file join $job(dirname) $TRG(run)]
+    }
+
     set ::done 1
     fconfigure $fd -blocking 1
     set state "done"
     set rc [catch { close $fd } msg]
     if {$rc} { 
-      puts "FAILED: $b $c $f"
+      if {[info exists TRG(reportlength)]} {
+        puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
+      }
+      puts "FAILED: $job(displayname) ($iJob)"
       set state "failed" 
     }
 
-    set tm [expr [clock_milliseconds] - $T($iJob)]
+    set tm [clock_milliseconds]
+    set jobtm [expr {$tm - $job(starttime)}]
 
-    puts $TRG(log) "### $b ### $c ### $f ${tm}ms ($state)"
+    puts $TRG(log) "### $job(displayname) ${jobtm}ms ($state)"
     puts $TRG(log) [string trim $O($iJob)]
 
     r_write_db {
       set output $O($iJob)
       trdb eval {
-        UPDATE script SET output = $output, state=$state, time=$tm
-        WHERE (build, config, filename) = ($b, $c, $f)
-      }
-      if {$state=="done" && $c=="build"} {
-        trdb eval {
-          UPDATE script SET state = 'ready' WHERE (build, state)==($b, '')
-        }
+        UPDATE jobs 
+          SET output=$output, state=$state, endtime=$tm
+          WHERE jobid=$jobid;
+        UPDATE jobs SET state='ready' WHERE depid=$jobid;
       }
     }
 
@@ -780,79 +905,39 @@ proc launch_another_job {iJob} {
   set testfixture [info nameofexec]
   set script $TRG(info_script)
 
-  set dir [dirname $iJob]
-  create_or_clear_dir $dir
-
   set O($iJob) ""
   
-  set job [r_get_next_job $iJob]
-  if {$job==""} { return 0 }
+  set jobdict [r_get_next_job $iJob]
+  if {$jobdict==""} { return 0 }
+  array set job $jobdict
 
-  foreach {b c f} $job {}
+  set dir $job(dirname)
+  if {$dir==""} { set dir [dirname $iJob] }
+  create_or_clear_dir $dir
 
-  if {$c=="build"} {
-    set testdir [file dirname $TRG(info_script)]
-    set srcdir [file dirname $testdir]
-    set builddir [build_to_dirname $b]
-    create_or_clear_dir $builddir
-
-    if {$b=="Zipvfs"} {
+  if {$job(build)!=""} {
+    set srcdir [file dirname $::testdir]
+    if {$job(build)=="Zipvfs"} {
       set script [zipvfs_testrunner_script]
     } else {
-      set script [trd_buildscript $b $srcdir [expr {$TRG(platform)=="win"}]]
+      set bWin [expr {$TRG(platform)=="win"}]
+      set script [trd_buildscript $job(build) $srcdir $bWin]
     }
-
-    set fd [open [file join $builddir $TRG(make)] w]
+    set fd [open [file join $dir $TRG(make)] w]
     puts $fd $script
     close $fd
-
-    puts "Launching build \"$b\" in directory $builddir..."
-    set target coretestprogs
-    if {$b=="User-Auth"}  { set target testfixture }
-
-    set cmd "$TRG(makecmd) $target"
-    set dir $builddir
-
-  } elseif {$c=="make"} {
-    if {$b==""} {
-      if {$f!="fuzztest"} { error "corruption in testrunner.db!" }
-      # Special case - run [make fuzztest] 
-      set makedir [file dirname $testfixture]
-      if {$TRG(platform)=="win"} {
-        error "how?"
-      } else {
-        set cmd [list make -C $makedir fuzztest]
-      }
-    } else {
-      set builddir [build_to_dirname $b]
-      copy_dir $builddir $dir
-      set cmd "$TRG(makecmd) $f"
-    }
-  } else {
-    if {$b==""} {
-      set testfixture [info nameofexec]
-    } else {
-      set tail testfixture
-      if {$TRG(platform)=="win"} { set tail testfixture.exe }
-      set testfixture [file normalize [file join [build_to_dirname $b] $tail]]
-    }
-
-    if {$c=="valgrind"} {
-      set testfixture "valgrind -v --error-exitcode=1 $testfixture"
-      set ::env(OMIT_MISUSE) 1
-    }
-    set cmd [concat $testfixture [list $script $c $f]]
   }
 
   set pwd [pwd]
   cd $dir
-  set fd [open "|$cmd 2>@1" r]
+  set fd [open $TRG(run) w]
+  puts $fd $job(cmd) 
+  close $fd
+  set fd [open "|$TRG(runcmd) 2>@1" r]
   cd $pwd
-  set pid [pid $fd]
 
   fconfigure $fd -blocking false
-  fileevent $fd readable [list script_input_ready $fd $iJob $b $c $f]
-  unset -nocomplain ::env(OMIT_MISUSE)
+  fileevent $fd readable [list script_input_ready $fd $iJob $job(jobid)]
 
   return 1
 }
@@ -863,39 +948,36 @@ proc one_line_report {} {
   set tm [expr [clock_milliseconds] - $TRG(starttime)]
   set tm [format "%d" [expr int($tm/1000.0 + 0.5)]]
 
-  foreach s {ready running done failed} {
-    set v($s,build) 0
-    set v($s,make) 0
-    set v($s,script) 0
-  }
-
   r_write_db {
-    trdb eval {
-      SELECT state, jobtype, count(*) AS cnt 
-      FROM script 
-      GROUP BY state, jobtype
+    trdb eval { 
+      SELECT displaytype, state, count(*) AS cnt 
+      FROM jobs 
+      GROUP BY 1, 2 
     } {
-      set v($state,$jobtype) $cnt
-      if {[info exists t($jobtype)]} {
-        incr t($jobtype) $cnt
-      } else {
-        set t($jobtype) $cnt
-      }
+      set v($state,$displaytype) $cnt
+      incr t($displaytype) $cnt
     }
   }
 
   set text ""
-  foreach j [array names t] {
+  foreach j [lsort [array names t]] {
+    foreach k {done failed running} { incr v($k,$j) 0 }
     set fin [expr $v(done,$j) + $v(failed,$j)]
-    lappend text "$j ($fin/$t($j)) f=$v(failed,$j) r=$v(running,$j)"
+    lappend text "${j}($fin/$t($j))"
+    if {$v(failed,$j)>0} {
+      lappend text "f$v(failed,$j)"
+    }
+    if {$v(running,$j)>0} {
+      lappend text "r$v(running,$j)"
+    }
   }
 
   if {[info exists TRG(reportlength)]} {
     puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
   }
-  set report "${tm}s: [join $text { }]"
+  set report "${tm} [join $text { }]"
   set TRG(reportlength) [string length $report]
-  if {[string length $report]<80} {
+  if {[string length $report]<100} {
     puts -nonewline "$report\r"
     flush stdout
   } else {
@@ -907,9 +989,8 @@ proc one_line_report {} {
 
 proc launch_some_jobs {} {
   global TRG
-  r_write_db {
-    set nJob [trdb one { SELECT value FROM config WHERE name='njob' }]
-  }
+  set nJob [trdb one { SELECT value FROM config WHERE name='njob' }]
+
   while {[dirs_nHelper]<$nJob} {
     set iDir [dirs_allocDir]
     if {0==[launch_another_job $iDir]} {
@@ -927,7 +1008,6 @@ proc run_testset {} {
   set TRG(log) [open $TRG(logname) w]
 
   launch_some_jobs
-    # launch_another_job $ii
 
   one_line_report
   while {[dirs_nHelper]>0} {
@@ -940,13 +1020,13 @@ proc run_testset {} {
   r_write_db {
     set tm [clock_milliseconds]
     trdb eval { REPLACE INTO config VALUES('end', $tm ); }
-    set nErr [trdb one {SELECT count(*) FROM script WHERE state='failed'}]
+    set nErr [trdb one {SELECT count(*) FROM jobs WHERE state='failed'}]
     if {$nErr>0} {
       puts "$nErr failures:"
       trdb eval {
-        SELECT build, config, filename FROM script WHERE state='failed'
+        SELECT displayname FROM jobs WHERE state='failed'
       } {
-        puts "FAILED: $build $config $filename"
+        puts "FAILED: $displayname"
       }
     }
   }
@@ -960,7 +1040,7 @@ sqlite3 trdb $TRG(dbname)
 trdb timeout $TRG(timeout)
 set tm [lindex [time { make_new_testset }] 0]
 if {$TRG(nJob)>1} {
-  puts "splitting work across $TRG(nJob) cores"
+  puts "splitting work across $TRG(nJob) jobs"
 }
 puts "built testset in [expr $tm/1000]ms.."
 run_testset
diff --git a/libsql-sqlite3/test/testrunner_data.tcl b/libsql-sqlite3/test/testrunner_data.tcl
index ce2ce01dd6..9032ced4dd 100644
--- a/libsql-sqlite3/test/testrunner_data.tcl
+++ b/libsql-sqlite3/test/testrunner_data.tcl
@@ -98,11 +98,15 @@ namespace eval trd {
   set build(All-O0) {
     -O0 --enable-all
   }
-  set build(All-Sanitize) { --enable-all -fsanitize=address,undefined }
+  set build(All-Sanitize) { 
+    -DSQLITE_OMIT_LOOKASIDE=1
+    --enable-all -fsanitize=address,undefined 
+  }
 
   set build(Sanitize) {
     CC=clang -fsanitize=address,undefined
     -DSQLITE_ENABLE_STAT4
+    -DSQLITE_OMIT_LOOKASIDE=1
     -DCONFIG_SLOWDOWN_FACTOR=5.0
     --enable-debug
     --enable-all
@@ -370,15 +374,45 @@ proc trd_configs {platform bld} {
 
 proc trd_extras {platform bld} {
   trd_import
+  if {[info exists extra($platform.$bld)]==0} { return [list] }
+  return $extra($platform.$bld)
+}
 
-  set elist [list]
-  if {[info exists extra($platform.$bld)]} {
-    set elist $extra($platform.$bld)
+# Usage: 
+#
+#     trd_fuzztest_data
+#
+# This returns data used by testrunner.tcl to run commands equivalent 
+# to [make fuzztest]. The returned value is a list, which should be
+# interpreted as a sequence of pairs. The first element of each pair
+# is an interpreter name. The second element is a list of files.
+# testrunner.tcl automatically creates one job to build each interpreter,
+# and one to run each of the files with it once it has been built.
+#
+# In practice, the returned value looks like this:
+#
+# {
+#   {fuzzcheck {$testdir/fuzzdata1.db $testdir/fuzzdata2.db ...}}
+#   {{sessionfuzz run} $testdir/sessionfuzz-data1.db}
+# }
+#
+# where $testdir is replaced by the full-path to the test-directory (the
+# directory containing this file). "fuzzcheck" and "sessionfuzz" have .exe
+# extensions on windows.
+#
+proc trd_fuzztest_data {} {
+  set EXE ""
+  set lFuzzDb    [glob [file join $::testdir fuzzdata*.db]] 
+  set lSessionDb [glob [file join $::testdir sessionfuzz-data*.db]]
+
+  if {$::tcl_platform(platform)=="windows"} {
+    return [list fuzzcheck.exe $lFuzzDb]
   }
 
-  set elist
+  return [list fuzzcheck $lFuzzDb {sessionfuzz run} $lSessionDb]
 }
 
+
 proc trd_all_configs {} {
   trd_import
   set all_configs
@@ -394,7 +428,7 @@ proc make_sh_script {srcdir opts cflags makeOpts configOpts} {
   set myopts ""
   if {[info exists ::env(OPTS)]} {
     append myopts "# From environment variable:\n"
-    append myopts "OPTS=$::env(OPTS)\n"
+    append myopts "OPTS=$::env(OPTS)\n\n"
   }
   foreach o [lsort $opts] { 
     append myopts "OPTS=\"\$OPTS $o\"\n"
@@ -509,7 +543,6 @@ proc make_script {cfg srcdir bMsvc} {
           default {
             error "Cannot translate $param for MSVC"
           }
-
         }
       }
 
@@ -517,11 +550,19 @@ proc make_script {cfg srcdir bMsvc} {
     }
 
     if {[string range $param 0 0]=="-"} {
-      if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} {
-        lappend makeOpts OPTIMIZATIONS=$level
-      } else {
-        lappend cflags $param
+
+      if {$bMsvc} {
+        if {[regexp -- {^-O(\d+)$} $param -> level]} {
+          lappend makeOpts OPTIMIZATIONS=$level
+          continue
+        }
+        if {$param eq "-fsanitize=address,undefined"} {
+          lappend makeOpts ASAN=1
+          continue
+        }
       }
+
+      lappend cflags $param
       continue
     }
 
@@ -560,4 +601,36 @@ proc trd_buildscript {config srcdir bMsvc} {
   return [make_script $build($config) $srcdir $bMsvc]
 }
 
+# Usage:
+#
+#    trd_test_script_properties PATH
+#
+# The argument must be a path to a Tcl test script. This function scans the
+# first 100 lines of the script for lines that look like:
+#
+#    TESTRUNNER: <properties>
+#
+# where <properties> is a list of identifiers, each of which defines a 
+# property of the test script. Example properties are "slow" or "superslow".
+#
+proc trd_test_script_properties {path} {
+  # Use this global array as a cache:
+  global trd_test_script_properties_cache
+
+  if {![info exists trd_test_script_properties_cache($path)]} {
+    set fd [open $path]
+    set ret [list]
+    for {set line 0} {$line < 100 && ![eof $fd]} {incr line} {
+      set text [gets $fd]
+      if {[string match -nocase *testrunner:* $text]} {
+        regexp -nocase {.*testrunner:(.*)} $text -> properties
+        lappend ret {*}$properties
+      }
+    }
+    set trd_test_script_properties_cache($path) $ret
+    close $fd
+  }
+
+  set trd_test_script_properties_cache($path)
+}
 
diff --git a/libsql-sqlite3/test/thread3.test b/libsql-sqlite3/test/thread3.test
index 25699b7655..79a75bdbb7 100644
--- a/libsql-sqlite3/test/thread3.test
+++ b/libsql-sqlite3/test/thread3.test
@@ -75,4 +75,3 @@ do_execsql_test "1.Total BUSY errors: $nTotalBusy .2" {
 } $nAttempt
 
 finish_test
-
diff --git a/libsql-sqlite3/test/tkt-cbd054fa6b.test b/libsql-sqlite3/test/tkt-cbd054fa6b.test
index 86248ca21d..435d807873 100644
--- a/libsql-sqlite3/test/tkt-cbd054fa6b.test
+++ b/libsql-sqlite3/test/tkt-cbd054fa6b.test
@@ -65,7 +65,7 @@ do_test tkt-cbd05-1.2 {
 } {}
 do_test tkt-cbd05-1.3 {
   execsql { 
-    SELECT tbl,idx,group_concat(s(sample),' ') 
+    SELECT tbl,idx,string_agg(s(sample),' ') 
     FROM vvv 
     WHERE idx = 't1_x' 
     GROUP BY tbl,idx
diff --git a/libsql-sqlite3/test/trigger2.test b/libsql-sqlite3/test/trigger2.test
index 6e007e969a..70d59f3a0b 100644
--- a/libsql-sqlite3/test/trigger2.test
+++ b/libsql-sqlite3/test/trigger2.test
@@ -790,4 +790,3 @@ do_catchsql_test 11.2 {
 
 
 finish_test
-
diff --git a/libsql-sqlite3/test/unhex.test b/libsql-sqlite3/test/unhex.test
index f41e906a8f..af2cfae390 100644
--- a/libsql-sqlite3/test/unhex.test
+++ b/libsql-sqlite3/test/unhex.test
@@ -55,6 +55,8 @@ do_catchsql_test 3.1 {
 #--------------------------------------------------------------------------
 # Test the 2-argument version.
 #
+# Zap global x array set in some previous test.
+if {[array exists x]} {array unset x}
 foreach {tn hex} {
   1 "FFFF  ABCD"
   2 "FFFF ABCD"
@@ -98,5 +100,3 @@ do_execsql_test 6.4.3 { SELECT typeof(unhex('1234', NULL)) } {null}
 
 
 finish_test
-
-
diff --git a/libsql-sqlite3/test/vacuum-into.test b/libsql-sqlite3/test/vacuum-into.test
index 98692a108a..698d65f540 100644
--- a/libsql-sqlite3/test/vacuum-into.test
+++ b/libsql-sqlite3/test/vacuum-into.test
@@ -185,6 +185,3 @@ tvfs delete
 
 
 finish_test
-
-
-
diff --git a/libsql-sqlite3/test/vt02.c b/libsql-sqlite3/test/vt02.c
index ddad136fba..06fade0969 100644
--- a/libsql-sqlite3/test/vt02.c
+++ b/libsql-sqlite3/test/vt02.c
@@ -580,8 +580,11 @@ static void sqlite3BestIndexLog(
     sqlite3_finalize(pStmt);
   }
   sqlite3RunSql(db,pVTab,
-    "INSERT INTO temp.\"%w\"(bi,vn,ix) VALUES(%d,'nConstraint',%d)",
-    zLogTab, iBI, pInfo->nConstraint
+    "INSERT INTO temp.\"%w\"(bi,vn,ix) VALUES(%d,'nConstraint',%d)"
+      "RETURNING iif(bi=%d,'ok',RAISE(ABORT,'wrong trigger'))",
+    /* The RETURNING clause checks to see that the returning trigger fired
+    ** for the correct INSERT in the case of nested INSERT RETURNINGs. */
+    zLogTab, iBI, pInfo->nConstraint, iBI
   );
   for(i=0; i<pInfo->nConstraint; i++){
     sqlite3_value *pVal;
@@ -983,7 +986,9 @@ const sqlite3_module vt02Module = {
   /* xRename       */  0,
   /* xSavepoint    */  0,
   /* xRelease      */  0,
-  /* xRollbackTo   */  0
+  /* xRollbackTo   */  0,
+  /* xShadowName   */  0,
+  /* xIntegrity    */  0
 };
 
 static void vt02CoreInit(sqlite3 *db){
diff --git a/libsql-sqlite3/test/walseh1.test b/libsql-sqlite3/test/walseh1.test
index c3a655f534..225b69f742 100644
--- a/libsql-sqlite3/test/walseh1.test
+++ b/libsql-sqlite3/test/walseh1.test
@@ -146,5 +146,3 @@ do_faultsim_test 6 -faults seh -prep {
 catch { db close }
 
 finish_test
-
-
diff --git a/libsql-sqlite3/test/window1.test b/libsql-sqlite3/test/window1.test
index 37a5183f90..c9bbae3ee0 100644
--- a/libsql-sqlite3/test/window1.test
+++ b/libsql-sqlite3/test/window1.test
@@ -158,7 +158,7 @@ do_execsql_test 4.9 {
 do_execsql_test 4.10.1 {
   SELECT a, 
     count() OVER (ORDER BY a DESC),
-    group_concat(a, '.') OVER (ORDER BY a DESC) 
+    string_agg(a, '.') OVER (ORDER BY a DESC) 
   FROM t2 ORDER BY a DESC
 } {
   6 1 6
@@ -825,7 +825,7 @@ foreach {tn sql error} {
 }
 
 do_execsql_test 18.3.1 {
-  SELECT group_concat(c, '.') OVER (PARTITION BY b ORDER BY c)
+  SELECT string_agg(c, '.') OVER (PARTITION BY b ORDER BY c)
   FROM t1
 } {four four.six four.six.two five five.one five.one.three}
 
@@ -836,7 +836,7 @@ do_execsql_test 18.3.2 {
 } {four four.six four.six.two five five.one five.one.three}
 
 do_execsql_test 18.3.3 {
-  SELECT group_concat(c, '.') OVER win2
+  SELECT string_agg(c, '.') OVER win2
   FROM t1
   WINDOW win1 AS (PARTITION BY b),
          win2 AS (win1 ORDER BY c)
@@ -850,7 +850,7 @@ do_execsql_test 18.3.4 {
 } {four four.six four.six.two five five.one five.one.three}
 
 do_execsql_test 18.3.5 {
-  SELECT group_concat(c, '.') OVER win5
+  SELECT string_agg(c, '.') OVER win5
   FROM t1
   WINDOW win1 AS (PARTITION BY b),
          win2 AS (win1),
@@ -1127,7 +1127,7 @@ do_execsql_test 28.1.1 {
 }
 
 do_execsql_test 28.1.2 {
-  SELECT group_concat(b,'') OVER w1 FROM t1
+  SELECT string_agg(b,'') OVER w1 FROM t1
     WINDOW w1 AS (ORDER BY a RANGE BETWEEN 3 PRECEDING AND 1 PRECEDING)
 } {
   {} {}
diff --git a/libsql-sqlite3/test/window3.test b/libsql-sqlite3/test/window3.test
index 4f759abb7e..1893b539d3 100644
--- a/libsql-sqlite3/test/window3.test
+++ b/libsql-sqlite3/test/window3.test
@@ -1181,7 +1181,7 @@ do_execsql_test 1.1.13.6 {
   {}   {}   {}   {}   {}   {}   {}   {}   {}   {}   {}}
 
 do_execsql_test 1.1.14.1 {
-  SELECT group_concat(CAST(b AS TEXT), '.') OVER (ORDER BY a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t2
+  SELECT string_agg(CAST(b AS TEXT), '.') OVER (ORDER BY a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t2
 } {89   89.81   89.81.96   89.81.96.59   89.81.96.59.38   89.81.96.59.38.68
   89.81.96.59.38.68.39   89.81.96.59.38.68.39.62   89.81.96.59.38.68.39.62.91
   89.81.96.59.38.68.39.62.91.46   89.81.96.59.38.68.39.62.91.46.6
@@ -1471,7 +1471,7 @@ do_execsql_test 1.1.14.2 {
   89.59.39.99.29.59.89.89.29.9.79.49.59.29.59.19.39.9.9.99.69.39}
 
 do_execsql_test 1.1.14.3 {
-  SELECT group_concat(CAST(b AS TEXT), '.') OVER ( ORDER BY b,a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) FROM t2
+  SELECT string_agg(CAST(b AS TEXT), '.') OVER ( ORDER BY b,a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) FROM t2
 } {1   1.1   1.1.2   1.1.2.2   1.1.2.2.3   1.1.2.2.3.3   1.1.2.2.3.3.4
   1.1.2.2.3.3.4.5   1.1.2.2.3.3.4.5.6   1.1.2.2.3.3.4.5.6.7
   1.1.2.2.3.3.4.5.6.7.7   1.1.2.2.3.3.4.5.6.7.7.7   1.1.2.2.3.3.4.5.6.7.7.7.8
@@ -1758,7 +1758,7 @@ do_execsql_test 1.1.14.4 {
   9.9.9.19.29.29.29.39.39.39.49.59.59.59.59.69.79.89.89.89.99.99}
 
 do_execsql_test 1.1.14.5 {
-  SELECT group_concat(CAST(b AS TEXT), '.') OVER ( ORDER BY b%10,a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) FROM t2
+  SELECT string_agg(CAST(b AS TEXT), '.') OVER ( ORDER BY b%10,a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) FROM t2
 } {90   90.40   90.40.30   90.40.30.80   90.40.30.80.20   90.40.30.80.20.90
   90.40.30.80.20.90.60   90.40.30.80.20.90.60.70   90.40.30.80.20.90.60.70.80
   90.40.30.80.20.90.60.70.80.90   90.40.30.80.20.90.60.70.80.90.30
@@ -1960,7 +1960,7 @@ do_execsql_test 1.1.14.6 {
   83   27   17   7}
 
 do_execsql_test 1.1.14.7 {
-  SELECT group_concat(CAST(b AS TEXT), '.') OVER (win1 ORDER BY b%10 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
+  SELECT string_agg(CAST(b AS TEXT), '.') OVER (win1 ORDER BY b%10 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
     FROM t2
     WINDOW win1 AS (PARTITION BY b%2,a)
     ORDER BY 1
diff --git a/libsql-sqlite3/test/with3.test b/libsql-sqlite3/test/with3.test
index 650740dcc1..9b110debf3 100644
--- a/libsql-sqlite3/test/with3.test
+++ b/libsql-sqlite3/test/with3.test
@@ -132,8 +132,8 @@ do_eqp_test 3.2.2 {
   |  |  `--SCALAR SUBQUERY xxxxxx
   |  |     `--SCAN w2
   |  `--RECURSIVE STEP
-  |     |--SCAN c
-  |     `--SCAN w1
+  |     |--SCAN w1
+  |     `--SCAN c
   |--SCAN c
   |--SEARCH w2 USING INTEGER PRIMARY KEY (rowid=?)
   `--SEARCH w1 USING INTEGER PRIMARY KEY (rowid=?)
diff --git a/libsql-sqlite3/tool/cktclsh.sh b/libsql-sqlite3/tool/cktclsh.sh
new file mode 100644
index 0000000000..1928a40998
--- /dev/null
+++ b/libsql-sqlite3/tool/cktclsh.sh
@@ -0,0 +1,11 @@
+# Fail with an error if the TCLSH named in $2 is not tclsh version $1 or later.
+#
+echo "set vers $1" >cktclsh$1.tcl
+echo 'if {$tcl_version<$vers} {exit 1}' >>cktclsh$1.tcl
+if ! $2 cktclsh$1.tcl
+then
+   echo "ERROR: This makefile target requires tclsh $1 or later."
+   rm cktclsh$1.tcl
+   exit 1
+fi
+rm cktclsh$1.tcl
diff --git a/libsql-sqlite3/tool/fuzzershell.c b/libsql-sqlite3/tool/fuzzershell.c
index 9a27103597..7a7aef0290 100644
--- a/libsql-sqlite3/tool/fuzzershell.c
+++ b/libsql-sqlite3/tool/fuzzershell.c
@@ -680,6 +680,11 @@ static sqlite3_module seriesModule = {
   0,                         /* xRollback */
   0,                         /* xFindMethod */
   0,                         /* xRename */
+  0,                         /* xSavepoint */
+  0,                         /* xRelease */
+  0,                         /* xRollbackTo */
+  0,                         /* xShadowName */
+  0                          /* xIntegrity */
 };
 /* END the generate_series(START,END,STEP) implementation
 *********************************************************************************/
diff --git a/libsql-sqlite3/tool/mkautoconfamal.sh b/libsql-sqlite3/tool/mkautoconfamal.sh
index eacd9fa515..35dbfb41e0 100644
--- a/libsql-sqlite3/tool/mkautoconfamal.sh
+++ b/libsql-sqlite3/tool/mkautoconfamal.sh
@@ -25,6 +25,14 @@ VERSION=`cat $TOP/VERSION`
 HASH=`sed 's/^\(..........\).*/\1/' $TOP/manifest.uuid`
 DATETIME=`grep '^D' $TOP/manifest | sed -e 's/[^0-9]//g' -e 's/\(............\).*/\1/'`
 
+# Verify that the version number in the TEA autoconf file is correct.
+# Fail with an error if not.
+#
+if grep $VERSION $TOP/autoconf/tea/configure.ac
+then echo "TEA version number ok"
+else echo "TEA version number mismatch.  Should be $VERSION"; exit 1
+fi
+
 # If this script is given an argument of --snapshot, then generate a
 # snapshot tarball named for the current checkout SHA1 hash, rather than
 # the version number.
diff --git a/libsql-sqlite3/tool/mkctimec.tcl b/libsql-sqlite3/tool/mkctimec.tcl
index 317f30a35f..098bf16e3b 100755
--- a/libsql-sqlite3/tool/mkctimec.tcl
+++ b/libsql-sqlite3/tool/mkctimec.tcl
@@ -239,6 +239,7 @@ set boolean_defnil_options {
   SQLITE_OMIT_REINDEX
   SQLITE_OMIT_SCHEMA_PRAGMAS
   SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
+  SQLITE_OMIT_SEH
   SQLITE_OMIT_SHARED_CACHE
   SQLITE_OMIT_SHUTDOWN_DIRECTORIES
   SQLITE_OMIT_SUBQUERY
@@ -308,6 +309,7 @@ set value_options {
   SQLITE_ENABLE_8_3_NAMES
   SQLITE_ENABLE_CEROD
   SQLITE_ENABLE_LOCKING_STYLE
+  SQLITE_EXTRA_AUTOEXT
   SQLITE_EXTRA_INIT
   SQLITE_EXTRA_SHUTDOWN
   SQLITE_FTS3_MAX_EXPR_DEPTH
diff --git a/libsql-sqlite3/tool/mksqlite3c.tcl b/libsql-sqlite3/tool/mksqlite3c.tcl
index 582c44f622..815c864968 100644
--- a/libsql-sqlite3/tool/mksqlite3c.tcl
+++ b/libsql-sqlite3/tool/mksqlite3c.tcl
@@ -116,7 +116,7 @@ if {$tcl_platform(platform)=="windows"} {
 if {[file executable $vsrcprog] && [file readable $srcroot/manifest]} {
   set res [string trim [split [exec $vsrcprog -x $srcroot]] \n]
   puts $out "** The content in this amalgamation comes from Fossil check-in"
-  puts -nonewline $out "** [string range [lindex $res 0] 1 35]"
+  puts -nonewline $out "** [string range [lindex $res 0] 0 35]"
   if {[llength $res]==1} {
     puts $out "."
   } else {
diff --git a/libsql-sqlite3/tool/mktoolzip.tcl b/libsql-sqlite3/tool/mktoolzip.tcl
new file mode 100644
index 0000000000..885bae960b
--- /dev/null
+++ b/libsql-sqlite3/tool/mktoolzip.tcl
@@ -0,0 +1,69 @@
+#!/usr/bin/tclsh
+#
+# Run this script in order to generate a ZIP archive containing various
+# command-line tools.
+#
+# The makefile that invokes this script must first build the following
+# binaries:
+#
+#     testfixture             -- used to run this script
+#     sqlite3                 -- the SQLite CLI
+#     sqldiff                 -- Program to diff two databases
+#     sqlite3_analyzer        -- Space analyzer
+#
+switch $tcl_platform(os) {
+  {Windows NT} {
+    set OS win32
+    set EXE .exe
+  }
+  Linux {
+    set OS linux
+    set EXE {}
+  }
+  Darwin {
+    set OS osx
+    set EXE {}
+  }
+  default {
+    set OS unknown
+    set EXE {}
+  }
+}
+switch $tcl_platform(machine) {
+  arm64 {
+    set ARCH arm64
+  }
+  x86_64 {
+    set ARCH x64
+  }
+  amd64 -
+  intel {
+    if {$tcl_platform(pointerSize)==4} {
+      set ARCH x86
+    } else {
+      set ARCH x64
+    }
+  }
+  default {
+    set ARCH unk
+  }
+}
+set in [open [file join [file dirname [file dirname [info script]]] VERSION]]
+set vers [read $in]
+close $in
+scan $vers %d.%d.%d v1 v2 v3
+set v2 [format 3%02d%02d00 $v2 $v3]
+set name sqlite-tools-$OS-$ARCH-$v2.zip
+
+if {$OS=="win32"} {
+  # The win32 tar.exe supports the -a ("auto-compress") option. This causes
+  # tar to create an archive type based on the extension of the output file.
+  # In this case, a zip file.
+  puts "tar -a -cf $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE"
+  puts [exec tar -a -cf $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE]
+  puts "$name: [file size $name] bytes"
+} else {
+  puts "zip $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE"
+  puts [exec zip $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE]
+  puts [exec ls -l $name]
+}
diff --git a/libsql-sqlite3/tool/showdb.c b/libsql-sqlite3/tool/showdb.c
index 0e99331d7b..1b80c7f170 100644
--- a/libsql-sqlite3/tool/showdb.c
+++ b/libsql-sqlite3/tool/showdb.c
@@ -959,6 +959,10 @@ static void page_usage_freelist(u32 pgno){
     a = fileRead((pgno-1)*g.pagesize, g.pagesize);
     iNext = decodeInt32(a);
     n = decodeInt32(a+4);
+    if( n>(g.pagesize - 8)/4 ){
+      printf("ERROR: page %d too many freelist entries (%d)\n", pgno, n);
+      n = (g.pagesize - 8)/4;
+    }
     for(i=0; i<n; i++){
       int child = decodeInt32(a + (i*4+8));
       page_usage_msg(child, "freelist leaf, child %d of trunk page %d",
diff --git a/libsql-sqlite3/tool/showwal.c b/libsql-sqlite3/tool/showwal.c
index a8e9b53b32..7e6c0e17cd 100644
--- a/libsql-sqlite3/tool/showwal.c
+++ b/libsql-sqlite3/tool/showwal.c
@@ -543,15 +543,26 @@ int main(int argc, char **argv){
   }
   zPgSz[0] = 0;
   zPgSz[1] = 0;
-  lseek(fd, 8, SEEK_SET);
-  read(fd, zPgSz, 4);
+  fstat(fd, &sbuf);
+  if( sbuf.st_size<32 ){
+    printf("%s: file too small to be a WAL - only %d bytes\n",
+           argv[1], (int)sbuf.st_size);
+    return 0;
+  }
+  if( lseek(fd, 8, SEEK_SET)!=8 ){
+    printf("\"%s\" seems to not be a valid WAL file\n", argv[1]);
+    return 1;
+  }
+  if( read(fd, zPgSz, 4)!=4 ){
+    printf("\"%s\": cannot read the page size\n", argv[1]);
+    return 1;
+  }
   pagesize = zPgSz[1]*65536 + zPgSz[2]*256 + zPgSz[3];
   if( pagesize==0 ) pagesize = 1024;
   printf("Pagesize: %d\n", pagesize);
-  fstat(fd, &sbuf);
-  if( sbuf.st_size<32 ){
-    printf("file too small to be a WAL\n");
-    return 0;
+  if( (pagesize & (pagesize-1))!=0 || pagesize<512 || pagesize>65536 ){
+    printf("\"%s\": invalid page size.\n", argv[1]);
+    return 1;
   }
   mxFrame = (sbuf.st_size - 32)/(pagesize + 24);
   printf("Available pages: 1..%d\n", mxFrame);
diff --git a/libsql-sqlite3/tool/spaceanal.tcl b/libsql-sqlite3/tool/spaceanal.tcl
index d0c5e65e38..8fe72b99b1 100644
--- a/libsql-sqlite3/tool/spaceanal.tcl
+++ b/libsql-sqlite3/tool/spaceanal.tcl
@@ -581,6 +581,7 @@ set inuse_pgcnt   [expr wide([mem eval $sql])]
 set inuse_percent [percent $inuse_pgcnt $file_pgcnt]
 
 set free_pgcnt    [expr {$file_pgcnt-$inuse_pgcnt-$av_pgcnt}]
+if {$file_bytes>1073741824 && $free_pgcnt>0} {incr free_pgcnt -1}
 set free_percent  [percent $free_pgcnt $file_pgcnt]
 set free_pgcnt2   [db one {PRAGMA freelist_count}]
 set free_percent2 [percent $free_pgcnt2 $file_pgcnt]
diff --git a/libsql-sqlite3/tool/sqldiff.c b/libsql-sqlite3/tool/sqldiff.c
index 0d27ff89e7..a612566112 100644
--- a/libsql-sqlite3/tool/sqldiff.c
+++ b/libsql-sqlite3/tool/sqldiff.c
@@ -596,9 +596,9 @@ static void diff_one_table(const char *zTab, FILE *out){
 
   /* Build the comparison query */
   for(n2=n; az2[n2]; n2++){
-    char *zTab = safeId(az2[n2]);
-    fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zTab);
-    sqlite3_free(zTab);
+    char *zNTab = safeId(az2[n2]);
+    fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zNTab);
+    sqlite3_free(zNTab);
   }
   nQ = nPk2+1+2*(n2-nPk2);
   if( n2>nPk2 ){
diff --git a/libsql-sqlite3/tool/srctree-check.tcl b/libsql-sqlite3/tool/srctree-check.tcl
new file mode 100644
index 0000000000..234e70fae9
--- /dev/null
+++ b/libsql-sqlite3/tool/srctree-check.tcl
@@ -0,0 +1,79 @@
+#!/usr/bin/tclsh
+#
+# Run this script from the top of the source tree in order to confirm that
+# various aspects of the source tree are up-to-date.  Items checked include:
+#
+#     *    Makefile.msc and autoconf/Makefile.msc agree
+#     *    src/ctime.tcl is consistent with tool/mkctimec.tcl
+#     *    VERSION agrees with autoconf/tea/configure.ac
+#     *    src/pragma.h agrees with tool/mkpragmatab.tcl
+#
+# Other tests might be added later.  
+#
+# Error messages are printed and the process exists non-zero if problems
+# are found.  If everything is ok, no output is generated and the process
+# exits with 0.
+#
+
+# Read an entire file.
+#
+proc readfile {filename} {
+  set fd [open $filename rb]
+  set txt [read $fd]
+  close $fd
+  return $txt
+}
+
+# Find the root of the tree.
+#
+set ROOT [file dir [file dir [file normalize $argv0]]]
+cd $ROOT
+
+# Name of the TCL interpreter
+#
+set TCLSH [info nameofexe]
+
+######################### autoconf/tea/configure.ac ###########################
+
+set confac [readfile $ROOT/autoconf/tea/configure.ac]
+set vers [readfile $ROOT/VERSION]
+set pattern {AC_INIT([sqlite],[}
+append pattern [string trim $vers]
+append pattern {])}
+if {[string first $pattern $confac]<=0} {
+  puts "ERROR: ./autoconf/tea/configure.ac does not agree with ./VERSION"
+  exit 1
+}
+
+######################### autoconf/Makefile.msc ###############################
+
+set f1 [readfile $ROOT/autoconf/Makefile.msc]
+exec mv $ROOT/autoconf/Makefile.msc $ROOT/autoconf/Makefile.msc.tmp
+exec $TCLSH $ROOT/tool/mkmsvcmin.tcl
+set f2 [readfile $ROOT/autoconf/Makefile.msc]
+exec mv $ROOT/autoconf/Makefile.msc.tmp $ROOT/autoconf/Makefile.msc
+if {$f1 != $f2} {
+  puts "ERROR: ./autoconf/Makefile.msc does not agree with ./Makefile.msc"
+}
+
+######################### src/pragma.h ########################################
+
+set f1 [readfile $ROOT/src/pragma.h]
+exec mv $ROOT/src/pragma.h $ROOT/src/pragma.h.tmp
+exec $TCLSH $ROOT/tool/mkpragmatab.tcl
+set f2 [readfile $ROOT/src/pragma.h]
+exec mv $ROOT/src/pragma.h.tmp $ROOT/src/pragma.h
+if {$f1 != $f2} {
+  puts "ERROR: ./src/pragma.h does not agree with ./tool/mkpragmatab.tcl"
+}
+
+######################### src/ctime.c ########################################
+
+set f1 [readfile $ROOT/src/ctime.c]
+exec mv $ROOT/src/ctime.c $ROOT/src/ctime.c.tmp
+exec $TCLSH $ROOT/tool/mkctimec.tcl
+set f2 [readfile $ROOT/src/ctime.c]
+exec mv $ROOT/src/ctime.c.tmp $ROOT/src/ctime.c
+if {$f1 != $f2} {
+  puts "ERROR: ./src/ctime.c does not agree with ./tool/mkctimec.tcl"
+}