From 62ff45455238141c8a1d2b7c54c55847f03d1ffc Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Mon, 12 Jan 2026 14:43:45 -0500
Subject: [PATCH 01/15] Corrects encoding for sqlcipher_export function
registration
---
src/sqlcipher.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/sqlcipher.c b/src/sqlcipher.c
index ef9c57dab..160abfaef 100644
--- a/src/sqlcipher.c
+++ b/src/sqlcipher.c
@@ -412,7 +412,7 @@ static void (*const sqlcipher_fini_func)(void) __attribute__((used, section(".fi
static void sqlcipher_exportFunc(sqlite3_context*, int, sqlite3_value**);
static int sqlcipher_export_init(sqlite3* db, const char** errmsg, const struct sqlite3_api_routines* api) {
- sqlite3_create_function_v2(db, "sqlcipher_export", -1, SQLITE_TEXT, 0, sqlcipher_exportFunc, 0, 0, 0);
+ sqlite3_create_function_v2(db, "sqlcipher_export", -1, SQLITE_UTF8, 0, sqlcipher_exportFunc, 0, 0, 0);
return SQLITE_OK;
}
From da8f6fb68a36b9e740cb342206b4efc239f9f123 Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Fri, 16 Jan 2026 13:10:05 -0500
Subject: [PATCH 02/15] Updates version number to 4.13.0
---
CHANGELOG.md | 4 ++++
src/sqlcipher.c | 2 +-
test/sqlcipher-pragmas.test | 2 +-
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 068a09b8e..d64ce7b7d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
# SQLCipher Change Log
Notable changes to this project are documented in this file.
+## [4.13.0] - (? - [4.13.0 changes])
+
## [4.12.0] - (December 2025 - [4.12.0 changes])
- Updates baseline to SQLite 3.51.1
- Adds `PRAGMA cipher_status` so applications can verify a database handle is using encryption
@@ -318,6 +320,8 @@ Notable changes to this project are documented in this file.
### Security
- Change KDF iteration length from 4,000 to 64,000
+[4.13.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.13.0
+[4.13.0 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.12.0...v4.13.0
[4.12.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.12.0
[4.12.0 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.11.0...v4.12.0
[4.11.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.11.0
diff --git a/src/sqlcipher.c b/src/sqlcipher.c
index 160abfaef..889e0b725 100644
--- a/src/sqlcipher.c
+++ b/src/sqlcipher.c
@@ -95,7 +95,7 @@ void sqlite3pager_reset(Pager *pPager);
#define CIPHER_STR(s) #s
#ifndef CIPHER_VERSION_NUMBER
-#define CIPHER_VERSION_NUMBER 4.12.0
+#define CIPHER_VERSION_NUMBER 4.13.0
#endif
#ifndef CIPHER_VERSION_BUILD
diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test
index 88cf71d5a..2ad946bd0 100644
--- a/test/sqlcipher-pragmas.test
+++ b/test/sqlcipher-pragmas.test
@@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version {
execsql {
PRAGMA cipher_version;
}
-} {{4.12.0 community}}
+} {{4.13.0 community}}
db close
file delete -force test.db
From 0d21200fcaa6fb66d43bee4929f9bf26d3c94984 Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Fri, 16 Jan 2026 13:14:33 -0500
Subject: [PATCH 03/15] Snapshot of upstream SQLite 3.51.2
---
Makefile.msc | 134 ++++++++++++++++++++++++++-
VERSION | 2 +-
autoconf/Makefile.msc | 4 +
ext/fts5/fts5Int.h | 8 +-
ext/fts5/fts5_index.c | 2 +-
ext/fts5/test/fts5merge.test | 16 ++++
ext/misc/compress.c | 4 +-
ext/misc/csv.c | 12 ++-
ext/misc/fileio.c | 1 +
ext/misc/zipfile.c | 2 +-
ext/rtree/rtree.c | 2 +-
ext/rtree/rtreeB.test | 10 ++
ext/wasm/api/sqlite3-api-oo1.c-pp.js | 2 +-
manifest | 56 +++++------
manifest.tags | 2 +-
manifest.uuid | 2 +-
src/expr.c | 17 +++-
src/os_unix.c | 39 ++++----
src/select.c | 21 ++++-
src/shell.c.in | 3 +-
src/where.c | 53 ++++++-----
test/existsexpr.test | 39 +++++++-
test/fuzzcheck.c | 1 +
test/subquery2.test | 68 ++++++++++++++
test/zipfile.test | 7 ++
25 files changed, 415 insertions(+), 92 deletions(-)
diff --git a/Makefile.msc b/Makefile.msc
index 3c075fac3..52807ff7f 100644
--- a/Makefile.msc
+++ b/Makefile.msc
@@ -805,17 +805,21 @@ BCC = $(BCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
!IF $(DEBUG)>1
TCC = $(TCC) -MDd
BCC = $(BCC) -MDd
+ZLIBCFLAGS = -nologo -MDd -W3 -O2 -Oy- -Zi
!ELSE
TCC = $(TCC) -MD
BCC = $(BCC) -MD
+ZLIBCFLAGS = -nologo -MD -W3 -O2 -Oy- -Zi
!ENDIF
!ELSE
!IF $(DEBUG)>1
TCC = $(TCC) -MTd
BCC = $(BCC) -MTd
+ZLIBCFLAGS = -nologo -MTd -W3 -O2 -Oy- -Zi
!ELSE
TCC = $(TCC) -MT
BCC = $(BCC) -MT
+ZLIBCFLAGS = -nologo -MT -W3 -O2 -Oy- -Zi
!ENDIF
!ENDIF
@@ -2416,7 +2420,7 @@ shell.c: $(SHELL_DEP) $(TOP)\tool\mkshellc.tcl $(JIM_TCLSH)
$(JIM_TCLSH) $(TOP)\tool\mkshellc.tcl shell.c
zlib:
- pushd $(ZLIBDIR) && $(MAKE) /f win32\Makefile.msc clean $(ZLIBLIB) && popd
+ pushd $(ZLIBDIR) && $(MAKE) /f win32\Makefile.msc clean $(ZLIBLIB) CFLAGS="$(ZLIBCFLAGS)" && popd
# Rules to build the extension objects.
#
@@ -2811,6 +2815,134 @@ tcl-env:
@echo JIM_TCLSH = $(JIM_TCLSH)
@echo VISUALSTUDIOVERSION = $(VISUALSTUDIOVERSION)
+env:
+ @echo ALL_TCL_TARGETS = $(ALL_TCL_TARGETS)
+ @echo API_ARMOR = $(API_ARMOR)
+ @echo ASAN = $(ASAN)
+ @echo BCC = $(BCC)
+ @echo BUILD_ZLIB = $(BUILD_ZLIB)
+ @echo CC = $(CC)
+ @echo CCOPTS = $(CCOPTS)
+ @echo CHECKER_DEPS = $(CHECKER_DEPS)
+ @echo CORE_CCONV_OPTS = $(CORE_CCONV_OPTS)
+ @echo CORE_COMPILE_OPTS = $(CORE_COMPILE_OPTS)
+ @echo CORE_LINK_DEP = $(CORE_LINK_DEP)
+ @echo CORE_LINK_OPTS = $(CORE_LINK_OPTS)
+ @echo CRTLIBPATH = $(CRTLIBPATH)
+ @echo CSC = $(CSC)
+ @echo DBFUZZ_COMPILE_OPTS = $(DBFUZZ_COMPILE_OPTS)
+ @echo DEBUG = $(DEBUG)
+ @echo DYNAMIC_SHELL = $(DYNAMIC_SHELL)
+ @echo EXT_FEATURE_FLAGS = $(EXT_FEATURE_FLAGS)
+ @echo EXTHDR = $(EXTHDR)
+ @echo EXTRA_SRC = $(EXTRA_SRC)
+ @echo FOR_UWP = $(FOR_UWP)
+ @echo FUZZCHECK_OPTS = $(FUZZCHECK_OPTS)
+ @echo FUZZCHECK_SRC = $(FUZZCHECK_SRC)
+ @echo FUZZDATA = $(FUZZDATA)
+ @echo FUZZERSHELL_COMPILE_OPTS = $(FUZZERSHELL_COMPILE_OPTS)
+ @echo HDR = $(HDR)
+ @echo ICUDIR = $(ICUDIR)
+ @echo ICUINCDIR = $(ICUINCDIR)
+ @echo ICULIBDIR = $(ICULIBDIR)
+ @echo JIM_TCLSH = $(JIM_TCLSH)
+ @echo KV_COMPILE_OPTS = $(KV_COMPILE_OPTS)
+ @echo LDFLAGS = $(LDFLAGS)
+ @echo LD = $(LD)
+ @echo LIBICU = $(LIBICU)
+ @echo LIBOBJ = $(LIBOBJ)
+ @echo LIBREADLINE = $(LIBREADLINE)
+ @echo LIBRESOBJS = $(LIBRESOBJS)
+ @echo LIBTCLPATH = $(LIBTCLPATH)
+ @echo LIBTCLSTUB = $(LIBTCLSTUB)
+ @echo LIBTCL = $(LIBTCL)
+ @echo LTCOMPILE = $(LTCOMPILE)
+ @echo LTLIB = $(LTLIB)
+ @echo LTLIBOPTS = $(LTLIBOPTS)
+ @echo LTLIBPATHS = $(LTLIBPATHS)
+ @echo LTLIBS = $(LTLIBS)
+ @echo LTLINK = $(LTLINK)
+ @echo LTLINKOPTS = $(LTLINKOPTS)
+ @echo LTRCOMPILE = $(LTRCOMPILE)
+ @echo MEMDEBUG = $(MEMDEBUG)
+ @echo MINIMAL_AMALGAMATION = $(MINIMAL_AMALGAMATION)
+ @echo MPTESTER_COMPILE_OPTS = $(MPTESTER_COMPILE_OPTS)
+ @echo NCC = $(NCC)
+ @echo NCRTLIBPATH = $(NCRTLIBPATH)
+ @echo NLTLIBPATHS = $(NLTLIBPATHS)
+ @echo NO_LINEMACROS = $(NO_LINEMACROS)
+ @echo NO_TCL = $(NO_TCL)
+ @echo NO_WARN = $(NO_WARN)
+ @echo NSDKLIBPATH = $(NSDKLIBPATH)
+ @echo NUCRTLIBPATH = $(NUCRTLIBPATH)
+ @echo OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS)
+ @echo OPTIMIZATIONS = $(OPTIMIZATIONS)
+ @echo OSSSHELL_SRC = $(OSSSHELL_SRC)
+ @echo OSTRACE = $(OSTRACE)
+ @echo RBU = $(RBU)
+ @echo RCC = $(RCC)
+ @echo RC = $(RC)
+ @echo READLINE_FLAGS = $(READLINE_FLAGS)
+ @echo REQ_FEATURE_FLAGS = $(REQ_FEATURE_FLAGS)
+ @echo RSYNC_OPT = $(RSYNC_OPT)
+ @echo RSYNC_SRC = $(RSYNC_SRC)
+ @echo SESSION = $(SESSION)
+ @echo SETLK_TIMEOUT = $(SETLK_TIMEOUT)
+ @echo SHELL_CCONV_OPTS = $(SHELL_CCONV_OPTS)
+ @echo SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS)
+ @echo SHELL_CORE_DEP = $(SHELL_CORE_DEP)
+ @echo SHELL_CORE_LIB = $(SHELL_CORE_LIB)
+ @echo SHELL_CORE_SRC = $(SHELL_CORE_SRC)
+ @echo SHELL_DEP = $(SHELL_DEP)
+ @echo SHELL_LINK_OPTS = $(SHELL_LINK_OPTS)
+ @echo SPLIT_AMALGAMATION = $(SPLIT_AMALGAMATION)
+ @echo SQLITETCLDECLSH = $(SQLITETCLDECLSH)
+ @echo SQLITE_TCL_DEP = $(SQLITE_TCL_DEP)
+ @echo SQLITETCLH = $(SQLITETCLH)
+ @echo SRC = $(SRC)
+ @echo STATICALLY_LINK_TCL = $(STATICALLY_LINK_TCL)
+ @echo ST_COMPILE_OPTS = $(ST_COMPILE_OPTS)
+ @echo STORELIBPATH = $(STORELIBPATH)
+ @echo SYMBOLS = $(SYMBOLS)
+ @echo TCC = $(TCC)
+ @echo TCLDIR = $(TCLDIR)
+ @echo TCLINCDIR = $(TCLINCDIR)
+ @echo TCLLIBDIR = $(TCLLIBDIR)
+ @echo TCLLIBPATHS = $(TCLLIBPATHS)
+ @echo TCLLIBS = $(TCLLIBS)
+ @echo TCLSH_CMD = $(TCLSH_CMD)
+ @echo TCLSQLITEEX = $(TCLSQLITEEX)
+ @echo TCLSUFFIX = $(TCLSUFFIX)
+ @echo TCLVERSION = $(TCLVERSION)
+ @echo TEST_CCONV_OPTS = $(TEST_CCONV_OPTS)
+ @echo TESTEXT = $(TESTEXT)
+ @echo TESTFIXTURE_DEP = $(TESTFIXTURE_DEP)
+ @echo TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS)
+ @echo TESTFIXTURE_SRC = $(TESTFIXTURE_SRC)
+ @echo TESTOPTS = $(TESTOPTS)
+ @echo TESTPROGS = $(TESTPROGS)
+ @echo TESTSRC = $(TESTSRC)
+ @echo TLIBS = $(TLIBS)
+ @echo TOP = $(TOP)
+ @echo UCRTLIBPATH = $(UCRTLIBPATH)
+ @echo USE_AMALGAMATION = $(USE_AMALGAMATION)
+ @echo USE_CRT_DLL = $(USE_CRT_DLL)
+ @echo USE_FATAL_WARN = $(USE_FATAL_WARN)
+ @echo USE_FULLWARN = $(USE_FULLWARN)
+ @echo USE_ICU = $(USE_ICU)
+ @echo USE_LISTINGS = $(USE_LISTINGS)
+ @echo USE_NATIVE_LIBPATHS = $(USE_NATIVE_LIBPATHS)
+ @echo USE_RC = $(USE_RC)
+ @echo USE_RUNTIME_CHECKS = $(USE_RUNTIME_CHECKS)
+ @echo USE_SEH = $(USE_SEH)
+ @echo USE_STDCALL = $(USE_STDCALL)
+ @echo USE_ZLIB = $(USE_ZLIB)
+ @echo XCOMPILE = $(XCOMPILE)
+ @echo ZLIBDIR = $(ZLIBDIR)
+ @echo ZLIBINCDIR = $(ZLIBINCDIR)
+ @echo ZLIBLIBDIR = $(ZLIBLIBDIR)
+ @echo ZLIBLIB = $(ZLIBLIB)
+
moreclean: clean
del /Q $(SQLITE3C) $(SQLITE3H) 2>NUL
# <>
diff --git a/VERSION b/VERSION
index d1278a467..0306a564d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.51.1
+3.51.2
diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc
index 4f96e3b18..365e1f0fb 100644
--- a/autoconf/Makefile.msc
+++ b/autoconf/Makefile.msc
@@ -695,17 +695,21 @@ BCC = $(BCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
!IF $(DEBUG)>1
TCC = $(TCC) -MDd
BCC = $(BCC) -MDd
+ZLIBCFLAGS = -nologo -MDd -W3 -O2 -Oy- -Zi
!ELSE
TCC = $(TCC) -MD
BCC = $(BCC) -MD
+ZLIBCFLAGS = -nologo -MD -W3 -O2 -Oy- -Zi
!ENDIF
!ELSE
!IF $(DEBUG)>1
TCC = $(TCC) -MTd
BCC = $(BCC) -MTd
+ZLIBCFLAGS = -nologo -MTd -W3 -O2 -Oy- -Zi
!ELSE
TCC = $(TCC) -MT
BCC = $(BCC) -MT
+ZLIBCFLAGS = -nologo -MT -W3 -O2 -Oy- -Zi
!ENDIF
!ENDIF
diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h
index a13a65d3c..d5404535c 100644
--- a/ext/fts5/fts5Int.h
+++ b/ext/fts5/fts5Int.h
@@ -81,7 +81,13 @@ typedef sqlite3_uint64 u64;
# define FLEXARRAY 1
#endif
-#endif
+#endif /* SQLITE_AMALGAMATION */
+
+/*
+** Constants for the largest and smallest possible 32-bit signed integers.
+*/
+# define LARGEST_INT32 ((int)(0x7fffffff))
+# define SMALLEST_INT32 ((int)((-1) - LARGEST_INT32))
/* Truncate very long tokens to this many bytes. Hard limit is
** (65536-1-1-4-9)==65521 bytes. The limiting factor is the 16-bit offset
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c
index 7e25731ed..acd0570a5 100644
--- a/ext/fts5/fts5_index.c
+++ b/ext/fts5/fts5_index.c
@@ -5931,7 +5931,7 @@ int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
fts5StructureRelease(pStruct);
pStruct = pNew;
nMin = 1;
- nMerge = nMerge*-1;
+ nMerge = (nMerge==SMALLEST_INT32 ? LARGEST_INT32 : (nMerge*-1));
}
if( pStruct && pStruct->nLevel ){
if( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){
diff --git a/ext/fts5/test/fts5merge.test b/ext/fts5/test/fts5merge.test
index c57c21ded..09c18245f 100644
--- a/ext/fts5/test/fts5merge.test
+++ b/ext/fts5/test/fts5merge.test
@@ -238,6 +238,22 @@ do_execsql_test 6.3 {
INSERT INTO g1(g1) VALUES('integrity-check');
}
+#--------------------------------------------------------------------------
+# Check that passing -2147483648 as the parameter to a merge command
+# does not cause a signed integer overflow error.
+#
+reset_db
+do_execsql_test 7.0 {
+ CREATE VIRTUAL TABLE f1 USING fts5(a);
+}
+do_execsql_test 7.1 {
+ INSERT INTO f1 VALUES('one two three');
+ INSERT INTO f1 VALUES('four five six');
+ INSERT INTO f1 VALUES('seven eight nine');
+}
+do_execsql_test 7.2 {
+ INSERT INTO f1(f1, rank) VALUES('merge', -2147483648);
+}
finish_test
diff --git a/ext/misc/compress.c b/ext/misc/compress.c
index 6b034eb45..48ea5182d 100644
--- a/ext/misc/compress.c
+++ b/ext/misc/compress.c
@@ -59,7 +59,7 @@ static void compressFunc(
pIn = sqlite3_value_blob(argv[0]);
nIn = sqlite3_value_bytes(argv[0]);
nOut = 13 + nIn + (nIn+999)/1000;
- pOut = sqlite3_malloc( nOut+5 );
+ pOut = sqlite3_malloc64( nOut+5 );
for(i=4; i>=0; i--){
x[i] = (nIn >> (7*(4-i)))&0x7f;
}
@@ -98,7 +98,7 @@ static void uncompressFunc(
nOut = (nOut<<7) | (pIn[i]&0x7f);
if( (pIn[i]&0x80)!=0 ){ i++; break; }
}
- pOut = sqlite3_malloc( nOut+1 );
+ pOut = sqlite3_malloc64( nOut+1 );
rc = uncompress(pOut, &nOut, &pIn[i], nIn-i);
if( rc==Z_OK ){
sqlite3_result_blob(context, pOut, nOut, sqlite3_free);
diff --git a/ext/misc/csv.c b/ext/misc/csv.c
index 8331265aa..1caaaec87 100644
--- a/ext/misc/csv.c
+++ b/ext/misc/csv.c
@@ -62,6 +62,9 @@ SQLITE_EXTENSION_INIT1
# define CSV_NOINLINE
#endif
+#ifndef SQLITEINT_H
+typedef sqlite3_int64 i64;
+#endif
/* Max size of the error message in a CsvReader */
#define CSV_MXERR 200
@@ -74,9 +77,9 @@ typedef struct CsvReader CsvReader;
struct CsvReader {
FILE *in; /* Read the CSV text from this input stream */
char *z; /* Accumulated text for a field */
- int n; /* Number of bytes in z */
- int nAlloc; /* Space allocated for z[] */
- int nLine; /* Current line number */
+ i64 n; /* Number of bytes in z */
+ i64 nAlloc; /* Space allocated for z[] */
+ i64 nLine; /* Current line number */
int bNotFirst; /* True if prior text has been seen */
int cTerm; /* Character that terminated the most recent field */
size_t iIn; /* Next unread character in the input buffer */
@@ -174,7 +177,7 @@ static int csv_getc(CsvReader *p){
** Return 0 on success and non-zero if there is an OOM error */
static CSV_NOINLINE int csv_resize_and_append(CsvReader *p, char c){
char *zNew;
- int nNew = p->nAlloc*2 + 100;
+ i64 nNew = p->nAlloc*2 + 100;
zNew = sqlite3_realloc64(p->z, nNew);
if( zNew ){
p->z = zNew;
@@ -510,7 +513,6 @@ static int csvtabConnect(
# define CSV_DATA (azPValue[1])
# define CSV_SCHEMA (azPValue[2])
-
assert( sizeof(azPValue)==sizeof(azParam) );
memset(&sRdr, 0, sizeof(sRdr));
memset(azPValue, 0, sizeof(azPValue));
diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c
index d78b14877..6cc2ae008 100644
--- a/ext/misc/fileio.c
+++ b/ext/misc/fileio.c
@@ -329,6 +329,7 @@ static int fileStat(
b1[sz] = 0;
rc = _wstat(b1, pStatBuf);
if( rc==0 ) statTimesToUtc(zPath, pStatBuf);
+ sqlite3_free(b1);
return rc;
#else
return stat(zPath, pStatBuf);
diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c
index 58cfba658..2f74906d9 100644
--- a/ext/misc/zipfile.c
+++ b/ext/misc/zipfile.c
@@ -875,7 +875,7 @@ static int zipfileGetEntry(
);
}else{
aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ];
- if( (iOff + ZIPFILE_LFH_FIXED_SZ + nFile + nExtra)>nBlob ){
+ if( (iOff + ZIPFILE_CDS_FIXED_SZ + nFile + nExtra)>nBlob ){
rc = zipfileCorrupt(pzErr);
}
}
diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c
index 8b913ef2d..b3d29283e 100644
--- a/ext/rtree/rtree.c
+++ b/ext/rtree/rtree.c
@@ -3775,7 +3775,7 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
if( node.zData==0 ) return;
nData = sqlite3_value_bytes(apArg[1]);
if( nData<4 ) return;
- if( nDatanMem += nReg;
if( pExpr->op==TK_SELECT ){
dest.eDest = SRT_Mem;
- dest.iSdst = dest.iSDParm;
+ if( (pSel->selFlags&SF_Distinct) && pSel->pLimit && pSel->pLimit->pRight ){
+ /* If there is both a DISTINCT and an OFFSET clause, then allocate
+ ** a separate dest.iSdst array for sqlite3Select() and other
+ ** routines to populate. In this case results will be copied over
+ ** into the dest.iSDParm array only after OFFSET processing. This
+ ** ensures that in the case where OFFSET excludes all rows, the
+ ** dest.iSDParm array is not left populated with the contents of the
+ ** last row visited - it should be all NULLs if all rows were
+ ** excluded by OFFSET. */
+ dest.iSdst = pParse->nMem+1;
+ pParse->nMem += nReg;
+ }else{
+ dest.iSdst = dest.iSDParm;
+ }
dest.nSdst = nReg;
- sqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, dest.iSDParm+nReg-1);
+ sqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, pParse->nMem);
VdbeComment((v, "Init subquery result"));
}else{
dest.eDest = SRT_Exists;
diff --git a/src/os_unix.c b/src/os_unix.c
index 6b679c4dc..d73d89924 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -2027,12 +2027,18 @@ static int unixLock(sqlite3_file *id, int eFileLock){
pInode->nLock++;
pInode->nShared = 1;
}
- }else if( (eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1)
- || unixIsSharingShmNode(pFile)
- ){
+ }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){
/* We are trying for an exclusive lock but another thread in this
** same process is still holding a shared lock. */
rc = SQLITE_BUSY;
+ }else if( unixIsSharingShmNode(pFile) ){
+ /* We are in WAL mode and attempting to delete the SHM and WAL
+ ** files due to closing the connection or changing out of WAL mode,
+ ** but another process still holds locks on the SHM file, thus
+ ** indicating that database locks have been broken, perhaps due
+ ** to a rogue close(open(dbFile)) or similar.
+ */
+ rc = SQLITE_BUSY;
}else{
/* The request was for a RESERVED or EXCLUSIVE lock. It is
** assumed that there is a SHARED or greater lock on the file
@@ -4671,26 +4677,21 @@ static int unixFcntlExternalReader(unixFile *pFile, int *piOut){
** still not a disaster.
*/
static int unixIsSharingShmNode(unixFile *pFile){
- int rc;
unixShmNode *pShmNode;
+ struct flock lock;
if( pFile->pShm==0 ) return 0;
if( pFile->ctrlFlags & UNIXFILE_EXCL ) return 0;
pShmNode = pFile->pShm->pShmNode;
- rc = 1;
- unixEnterMutex();
- if( ALWAYS(pShmNode->nRef==1) ){
- struct flock lock;
- lock.l_whence = SEEK_SET;
- lock.l_start = UNIX_SHM_DMS;
- lock.l_len = 1;
- lock.l_type = F_WRLCK;
- osFcntl(pShmNode->hShm, F_GETLK, &lock);
- if( lock.l_type==F_UNLCK ){
- rc = 0;
- }
- }
- unixLeaveMutex();
- return rc;
+#if SQLITE_ATOMIC_INTRINSICS
+ assert( AtomicLoad(&pShmNode->nRef)==1 );
+#endif
+ memset(&lock, 0, sizeof(lock));
+ lock.l_whence = SEEK_SET;
+ lock.l_start = UNIX_SHM_DMS;
+ lock.l_len = 1;
+ lock.l_type = F_WRLCK;
+ osFcntl(pShmNode->hShm, F_GETLK, &lock);
+ return (lock.l_type!=F_UNLCK);
}
/*
diff --git a/src/select.c b/src/select.c
index bec00ecb9..bc17ecf84 100644
--- a/src/select.c
+++ b/src/select.c
@@ -1445,9 +1445,14 @@ static void selectInnerLoop(
assert( nResultCol<=pDest->nSdst );
pushOntoSorter(
pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg);
+ pDest->iSDParm = regResult;
}else{
assert( nResultCol==pDest->nSdst );
- assert( regResult==iParm );
+ if( regResult!=iParm ){
+ /* This occurs in cases where the SELECT had both a DISTINCT and
+ ** an OFFSET clause. */
+ sqlite3VdbeAddOp3(v, OP_Copy, regResult, iParm, nResultCol-1);
+ }
/* The LIMIT clause will jump out of the loop for us */
}
break;
@@ -7462,12 +7467,24 @@ static SQLITE_NOINLINE void existsToJoin(
&& (pSub->selFlags & SF_Aggregate)==0
&& !pSub->pSrc->a[0].fg.isSubquery
&& pSub->pLimit==0
+ && pSub->pPrior==0
){
+ /* Before combining the sub-select with the parent, renumber the
+ ** cursor used by the subselect. This is because the EXISTS expression
+ ** might be a copy of another EXISTS expression from somewhere
+ ** else in the tree, and in this case it is important that it use
+ ** a unique cursor number. */
+ sqlite3 *db = pParse->db;
+ int *aCsrMap = sqlite3DbMallocZero(db, (pParse->nTab+2)*sizeof(int));
+ if( aCsrMap==0 ) return;
+ aCsrMap[0] = (pParse->nTab+1);
+ renumberCursors(pParse, pSub, -1, aCsrMap);
+ sqlite3DbFree(db, aCsrMap);
+
memset(pWhere, 0, sizeof(*pWhere));
pWhere->op = TK_INTEGER;
pWhere->u.iValue = 1;
ExprSetProperty(pWhere, EP_IntValue);
-
assert( p->pWhere!=0 );
pSub->pSrc->a[0].fg.fromExists = 1;
pSub->pSrc->a[0].fg.jointype |= JT_CROSS;
diff --git a/src/shell.c.in b/src/shell.c.in
index 23aed0de5..bd4483ff7 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -13807,7 +13807,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
if( nCmd>0 ){
sqlite3_fprintf(stderr,"Error: cannot mix regular SQL or dot-commands"
" with \"%s\"\n", z);
- return 1;
+ rc = 1;
+ goto shell_main_exit;
}
open_db(&data, OPEN_DB_ZIPFILE);
if( z[2] ){
diff --git a/src/where.c b/src/where.c
index 6313b87ca..c4f2c5543 100644
--- a/src/where.c
+++ b/src/where.c
@@ -7380,6 +7380,9 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
sqlite3 *db = pParse->db;
int iEnd = sqlite3VdbeCurrentAddr(v);
int nRJ = 0;
+#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT
+ int addrSeek = 0;
+#endif
/* Generate loop termination code.
*/
@@ -7392,7 +7395,10 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
** the RIGHT JOIN table */
WhereRightJoin *pRJ = pLevel->pRJ;
sqlite3VdbeResolveLabel(v, pLevel->addrCont);
- pLevel->addrCont = 0;
+ /* Replace addrCont with a new label that will never be used, just so
+ ** the subsequent call to resolve pLevel->addrCont will have something
+ ** to resolve. */
+ pLevel->addrCont = sqlite3VdbeMakeLabel(pParse);
pRJ->endSubrtn = sqlite3VdbeCurrentAddr(v);
sqlite3VdbeAddOp3(v, OP_Return, pRJ->regReturn, pRJ->addrSubrtn, 1);
VdbeCoverage(v);
@@ -7401,7 +7407,6 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
pLoop = pLevel->pWLoop;
if( pLevel->op!=OP_Noop ){
#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT
- int addrSeek = 0;
Index *pIdx;
int n;
if( pWInfo->eDistinct==WHERE_DISTINCT_ORDERED
@@ -7424,25 +7429,26 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
sqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2);
}
#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */
- if( pTabList->a[pLevel->iFrom].fg.fromExists && i==pWInfo->nLevel-1 ){
- /* If the EXISTS-to-JOIN optimization was applied, then the EXISTS
- ** loop(s) will be the inner-most loops of the join. There might be
- ** multiple EXISTS loops, but they will all be nested, and the join
- ** order will not have been changed by the query planner. If the
- ** inner-most EXISTS loop sees a single successful row, it should
- ** break out of *all* EXISTS loops. But only the inner-most of the
- ** nested EXISTS loops should do this breakout. */
- int nOuter = 0; /* Nr of outer EXISTS that this one is nested within */
- while( nOutera[pLevel[-nOuter-1].iFrom].fg.fromExists ) break;
- nOuter++;
- }
- testcase( nOuter>0 );
- sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel[-nOuter].addrBrk);
- VdbeComment((v, "EXISTS break"));
+ }
+ if( pTabList->a[pLevel->iFrom].fg.fromExists && i==pWInfo->nLevel-1 ){
+ /* If the EXISTS-to-JOIN optimization was applied, then the EXISTS
+ ** loop(s) will be the inner-most loops of the join. There might be
+ ** multiple EXISTS loops, but they will all be nested, and the join
+ ** order will not have been changed by the query planner. If the
+ ** inner-most EXISTS loop sees a single successful row, it should
+ ** break out of *all* EXISTS loops. But only the inner-most of the
+ ** nested EXISTS loops should do this breakout. */
+ int nOuter = 0; /* Nr of outer EXISTS that this one is nested within */
+ while( nOutera[pLevel[-nOuter-1].iFrom].fg.fromExists ) break;
+ nOuter++;
}
- /* The common case: Advance to the next row */
- if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont);
+ testcase( nOuter>0 );
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel[-nOuter].addrBrk);
+ VdbeComment((v, "EXISTS break"));
+ }
+ sqlite3VdbeResolveLabel(v, pLevel->addrCont);
+ if( pLevel->op!=OP_Noop ){
sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3);
sqlite3VdbeChangeP5(v, pLevel->p5);
VdbeCoverage(v);
@@ -7455,10 +7461,11 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
VdbeCoverage(v);
}
#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT
- if( addrSeek ) sqlite3VdbeJumpHere(v, addrSeek);
+ if( addrSeek ){
+ sqlite3VdbeJumpHere(v, addrSeek);
+ addrSeek = 0;
+ }
#endif
- }else if( pLevel->addrCont ){
- sqlite3VdbeResolveLabel(v, pLevel->addrCont);
}
if( (pLoop->wsFlags & WHERE_IN_ABLE)!=0 && pLevel->u.in.nIn>0 ){
struct InLoop *pIn;
diff --git a/test/existsexpr.test b/test/existsexpr.test
index 38392b07f..28029359b 100644
--- a/test/existsexpr.test
+++ b/test/existsexpr.test
@@ -15,7 +15,6 @@ source $testdir/tester.tcl
source $testdir/lock_common.tcl
set testprefix existsexpr
-
do_execsql_test 1.0 {
CREATE TABLE x1(a, b, PRIMARY KEY(a)) WITHOUT ROWID;
INSERT INTO x1 VALUES(1, 2), (3, 4), (5, 6);
@@ -477,4 +476,42 @@ catch { optimization_control db exists-to-join 0 }
db cache flush
do_execsql_test 9.6 $Q {{big string value}}
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 10.0 {
+ CREATE TABLE t1(a);
+ CREATE TABLE x1(x);
+}
+
+do_execsql_test 10.1 {
+ SELECT EXISTS( SELECT 1 FROM t1 ) aaa FROM x1 WHERE aaa AND aaa
+}
+
+do_execsql_test 10.2 {
+ SELECT
+ EXISTS( SELECT 1 FROM t1 ) aaa
+ WHERE (
+ SELECT 1 FROM x1 WHERE aaa AND aaa
+ );
+}
+
+# https://sqlite.org/forum/forumpost/2026-01-03T14:05:48z
+do_execsql_test 11.0 {
+ CREATE TABLE parent (id TEXT PRIMARY KEY);
+ CREATE TABLE child_a (id TEXT);
+ CREATE TABLE child_b (id TEXT);
+ INSERT INTO parent VALUES ('p1');
+ INSERT INTO child_a VALUES ('p1');
+}
+do_execsql_test 11.1 {
+ SELECT count(*), parent.id FROM parent
+ WHERE EXISTS (
+ SELECT 1 FROM child_a WHERE child_a.id = parent.id
+ UNION
+ SELECT 1 FROM child_b WHERE child_b.id = parent.id
+ )
+ GROUP BY id;
+} {1 p1}
+
finish_test
diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c
index d8dbf932d..a3377770a 100644
--- a/test/fuzzcheck.c
+++ b/test/fuzzcheck.c
@@ -1977,6 +1977,7 @@ int main(int argc, char **argv){
int iSliceIdx = 0; /* Only run the piece with this index */
sqlite3_config(SQLITE_CONFIG_URI,1);
+ sqlite3_config(SQLITE_CONFIG_MEMSTATUS,1);
registerOomSimulator();
sqlite3_initialize();
iBegin = timeOfDay();
diff --git a/test/subquery2.test b/test/subquery2.test
index 8513dc75c..99a1e903f 100644
--- a/test/subquery2.test
+++ b/test/subquery2.test
@@ -215,4 +215,72 @@ do_execsql_test 5.1 {
SELECT ( SELECT y FROM t2 WHERE x = y ORDER BY y, z) FROM t1;
} {ALFKI ANATR}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 6.0 {
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES(1234);
+}
+
+do_execsql_test 6.1 {
+ SELECT DISTINCT 'string' FROM t1 LIMIT 1 OFFSET 5;
+}
+
+do_execsql_test 6.2 {
+ SELECT (
+ SELECT 'string' FROM t1 LIMIT 1 OFFSET 5
+ );
+} {{}}
+
+do_execsql_test 6.3 {
+ SELECT (
+ SELECT DISTINCT 'string' FROM t1 LIMIT 1 OFFSET 5
+ );
+} {{}}
+
+do_execsql_test 6.4 {
+ SELECT (
+ SELECT DISTINCT 'string' FROM t1 ORDER BY 1 LIMIT 1 OFFSET 5
+ );
+} {{}}
+
+do_execsql_test 6.5 {
+ SELECT (
+ SELECT 'string' FROM t1 ORDER BY 1 LIMIT 1 OFFSET 5
+ );
+} {{}}
+
+do_execsql_test 6.6 {
+ SELECT (SELECT DISTINCT x, x FROM t1 LIMIT 1 OFFSET 5)==(1234, 1234)
+} {{}}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 7.0 {
+ CREATE TABLE t1(x);
+ CREATE INDEX i1 ON t1(x);
+ INSERT INTO t1 VALUES(1234);
+}
+
+do_execsql_test 7.1 {
+ SELECT (
+ SELECT DISTINCT 'string' FROM t1 ORDER BY x LIMIT 1 OFFSET 5
+ );
+} {{}}
+
+do_execsql_test 7.2 {
+ DROP INDEX i1;
+ CREATE UNIQUE INDEX i1 ON t1(x);
+}
+
+do_execsql_test 7.3 {
+ SELECT (
+ SELECT DISTINCT x FROM t1 ORDER BY 1 LIMIT 1 OFFSET 5
+ );
+} {{}}
+
+do_execsql_test 7.4 {
+ SELECT (SELECT DISTINCT x FROM t1 ORDER BY +x LIMIT 1 OFFSET 0);
+} {1234}
+
finish_test
diff --git a/test/zipfile.test b/test/zipfile.test
index 2b3be6278..9bb35ea5d 100644
--- a/test/zipfile.test
+++ b/test/zipfile.test
@@ -885,7 +885,10 @@ do_test 19.1 {
INSERT INTO v0 DEFAULT VALUES;
}
} {}
+db close
forcedelete zipfile19.zip
+sqlite3 db :memory:
+load_static_extension db zipfile
#-------------------------------------------------------------------------
do_catchsql_test 20.0 {
@@ -901,4 +904,8 @@ d42728f602000000020000000500ffff0000000000000000a4810000000068
00000000',char(0x0a,0x0d)));
} {1 {zip archive is corrupt}}
+# https://sqlite.org/forum/forumpost/2025-12-30T23:57:19z
+do_catchsql_test 20.2 {
+ SELECT * FROM zipfile(unhex('504b0304140000000000000000008b9ed9d30100000001000000010000007841504b01021e03140000000000000000008b9ed9d3010000000100000001001e000000000000000000a4810000000078504b050600000000010001002f000000200000000000'));
+} {1 {zip archive is corrupt}}
finish_test
From 222bdcafad462a1080360de1928cd900a8bccd0a Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Mon, 19 Jan 2026 12:01:34 -0500
Subject: [PATCH 04/15] Updates CHANGELOG.md with relevant changes for 4.13.0
---
CHANGELOG.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d64ce7b7d..015ab3404 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,9 @@
# SQLCipher Change Log
Notable changes to this project are documented in this file.
-## [4.13.0] - (? - [4.13.0 changes])
+## [4.13.0] - (January 2026 - [4.13.0 changes])
+- Updates baseline to SQLite 3.51.2
+- Corrects encoding for `sqlcipher_export()` function registration
## [4.12.0] - (December 2025 - [4.12.0 changes])
- Updates baseline to SQLite 3.51.1
From 693abfa5f5ee5b60f3645d900053b8d55e491ef6 Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Tue, 20 Jan 2026 15:27:19 -0500
Subject: [PATCH 05/15] Updates version number to 4.14.0
---
CHANGELOG.md | 4 ++++
src/sqlcipher.c | 2 +-
test/sqlcipher-pragmas.test | 2 +-
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 015ab3404..446d65cae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
# SQLCipher Change Log
Notable changes to this project are documented in this file.
+## [4.14.0] - (? 2026 - [4.14.0 changes])
+
## [4.13.0] - (January 2026 - [4.13.0 changes])
- Updates baseline to SQLite 3.51.2
- Corrects encoding for `sqlcipher_export()` function registration
@@ -322,6 +324,8 @@ Notable changes to this project are documented in this file.
### Security
- Change KDF iteration length from 4,000 to 64,000
+[4.14.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.14.0
+[4.14.0 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.13.0...v4.14.0
[4.13.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.13.0
[4.13.0 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.12.0...v4.13.0
[4.12.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.12.0
diff --git a/src/sqlcipher.c b/src/sqlcipher.c
index 889e0b725..bf07153f8 100644
--- a/src/sqlcipher.c
+++ b/src/sqlcipher.c
@@ -95,7 +95,7 @@ void sqlite3pager_reset(Pager *pPager);
#define CIPHER_STR(s) #s
#ifndef CIPHER_VERSION_NUMBER
-#define CIPHER_VERSION_NUMBER 4.13.0
+#define CIPHER_VERSION_NUMBER 4.14.0
#endif
#ifndef CIPHER_VERSION_BUILD
diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test
index 2ad946bd0..74c758baf 100644
--- a/test/sqlcipher-pragmas.test
+++ b/test/sqlcipher-pragmas.test
@@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version {
execsql {
PRAGMA cipher_version;
}
-} {{4.13.0 community}}
+} {{4.14.0 community}}
db close
file delete -force test.db
From d9175b66da236ad8a0c65e29d7a2b7314baaf0ea Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Sun, 15 Feb 2026 10:39:17 -0500
Subject: [PATCH 06/15] Partial revert of 3607cf68 to restore LibTomCrypt
provider
---
Makefile.msc | 1 +
main.mk | 4 +
src/crypto_libtomcrypt.c | 316 +++++++++++++++++++++++++++++++++++++++
src/sqlcipher.c | 4 +
tool/mksqlite3c.tcl | 1 +
5 files changed, 326 insertions(+)
create mode 100644 src/crypto_libtomcrypt.c
diff --git a/Makefile.msc b/Makefile.msc
index d3fe57de6..3ef50a338 100644
--- a/Makefile.msc
+++ b/Makefile.msc
@@ -1403,6 +1403,7 @@ LIBRESOBJS =
SRC00 = \
$(TOP)\src\sqlcipher.c \
$(TOP)\src\crypto_cc.c \
+ $(TOP)\src\crypto_libtomcrypt.c \
$(TOP)\src\crypto_openssl.c \
$(TOP)\src\sqlcipher.h \
$(TOP)\src\alter.c \
diff --git a/main.mk b/main.mk
index c6bdeafaa..2f02f87fd 100644
--- a/main.mk
+++ b/main.mk
@@ -534,11 +534,13 @@ clean: clean-sanity-check
SQLCIPHER_OBJ = \
sqlcipher.o \
crypto_openssl.o \
+ crypto_libtomcrypt.o \
crypto_cc.o
SQLCIPHER_SRC = \
$(TOP)/src/sqlcipher.h \
$(TOP)/src/sqlcipher.c \
+ $(TOP)/src/crypto_libtomcrypt.c \
$(TOP)/src/crypto_openssl.c \
$(TOP)/src/crypto_cc.c
@@ -546,6 +548,8 @@ sqlcipher.o: $(TOP)/src/sqlcipher.c $(DEPS_OBJ_COMMON)
$(T.cc.sqlite) $(CFLAGS.libsqlite3) -c $(TOP)/src/sqlcipher.c
crypto_openssl.o: $(TOP)/src/crypto_openssl.c $(DEPS_OBJ_COMMON)
$(T.cc.sqlite) $(CFLAGS.libsqlite3) -c $(TOP)/src/crypto_openssl.c
+crypto_libtomcrypt.o: $(TOP)/src/crypto_libtomcrypt.c $(DEPS_OBJ_COMMON)
+ $(T.cc.sqlite) $(CFLAGS.libsqlite3) -c $(TOP)/src/crypto_libtomcrypt.c
crypto_cc.o: $(TOP)/src/crypto_cc.c $(DEPS_OBJ_COMMON)
$(T.cc.sqlite) $(CFLAGS.libsqlite3) -c $(TOP)/src/crypto_cc.c
diff --git a/src/crypto_libtomcrypt.c b/src/crypto_libtomcrypt.c
new file mode 100644
index 000000000..c8ee378d3
--- /dev/null
+++ b/src/crypto_libtomcrypt.c
@@ -0,0 +1,316 @@
+/*
+** SQLCipher
+** http://sqlcipher.net
+**
+** Copyright (c) 2008 - 2013, ZETETIC LLC
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of the ZETETIC LLC nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
+** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
+** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+*/
+/* BEGIN SQLCIPHER */
+#ifdef SQLITE_HAS_CODEC
+#ifdef SQLCIPHER_CRYPTO_LIBTOMCRYPT
+#include "sqliteInt.h"
+#include "sqlcipher.h"
+#include
+
+#define FORTUNA_MAX_SZ 32
+static prng_state prng;
+static volatile unsigned int ltc_init = 0;
+static volatile unsigned int ltc_ref_count = 0;
+
+#define LTC_CIPHER "rijndael"
+
+static int sqlcipher_ltc_add_random(void *ctx, const void *buffer, int length) {
+ int rc = 0;
+ int data_to_read = length;
+ int block_sz = data_to_read < FORTUNA_MAX_SZ ? data_to_read : FORTUNA_MAX_SZ;
+ const unsigned char * data = (const unsigned char *)buffer;
+
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND");
+ sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND");
+
+ while(data_to_read > 0){
+ rc = fortuna_add_entropy(data, block_sz, &prng);
+ rc = rc != CRYPT_OK ? SQLITE_ERROR : SQLITE_OK;
+ if(rc != SQLITE_OK){
+ break;
+ }
+ data_to_read -= block_sz;
+ data += block_sz;
+ block_sz = data_to_read < FORTUNA_MAX_SZ ? data_to_read : FORTUNA_MAX_SZ;
+ }
+ fortuna_ready(&prng);
+
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND");
+ sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND");
+
+ return rc;
+}
+
+static int sqlcipher_ltc_activate(void *ctx) {
+ unsigned char random_buffer[FORTUNA_MAX_SZ];
+ int bytes = 0;
+
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
+ sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
+
+ sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ);
+ if(ltc_init == 0) {
+ if(register_prng(&fortuna_desc) < 0) return SQLITE_ERROR;
+ if(register_cipher(&rijndael_desc) < 0) return SQLITE_ERROR;
+ if(register_hash(&sha512_desc) < 0) return SQLITE_ERROR;
+ if(register_hash(&sha256_desc) < 0) return SQLITE_ERROR;
+ if(register_hash(&sha1_desc) < 0) return SQLITE_ERROR;
+ if(fortuna_start(&prng) != CRYPT_OK) {
+ return SQLITE_ERROR;
+ }
+
+ ltc_init = 1;
+ }
+ ltc_ref_count++;
+
+#ifndef SQLCIPHER_TEST
+ bytes = rng_get_bytes(random_buffer, FORTUNA_MAX_SZ, NULL);
+#endif
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_PROVIDER, "sqlcipher_ltc_activate: seeded fortuna with %d bytes from rng_get_bytes", bytes);
+
+ if(sqlcipher_ltc_add_random(ctx, random_buffer, FORTUNA_MAX_SZ) != SQLITE_OK) {
+ return SQLITE_ERROR;
+ }
+ sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ);
+
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
+ sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
+
+ return SQLITE_OK;
+}
+
+static int sqlcipher_ltc_deactivate(void *ctx) {
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
+ sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
+
+ ltc_ref_count--;
+ if(ltc_ref_count == 0){
+ fortuna_done(&prng);
+ sqlcipher_memset((void *)&prng, 0, sizeof(prng));
+ }
+
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
+ sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
+
+ return SQLITE_OK;
+}
+
+static const char* sqlcipher_ltc_get_provider_name(void *ctx) {
+ return "libtomcrypt";
+}
+
+static const char* sqlcipher_ltc_get_provider_version(void *ctx) {
+ return SCRYPT;
+}
+
+static int sqlcipher_ltc_random(void *ctx, void *buffer, int length) {
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND");
+ sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND");
+
+ fortuna_read(buffer, length, &prng);
+
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND");
+ sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
+ sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: left SQLCIPHER_MUTEX_PROVIDER_RAND");
+
+ return SQLITE_OK;
+}
+
+static int sqlcipher_ltc_hmac(
+ void *ctx, int algorithm,
+ const unsigned char *hmac_key, int key_sz,
+ const unsigned char *in, int in_sz,
+ const unsigned char *in2, int in2_sz,
+ unsigned char *out
+) {
+ int rc, hash_idx;
+ hmac_state hmac;
+ unsigned long outlen;
+ switch(algorithm) {
+ case SQLCIPHER_HMAC_SHA1:
+ hash_idx = find_hash("sha1");
+ break;
+ case SQLCIPHER_HMAC_SHA256:
+ hash_idx = find_hash("sha256");
+ break;
+ case SQLCIPHER_HMAC_SHA512:
+ hash_idx = find_hash("sha512");
+ break;
+ default:
+ return SQLITE_ERROR;
+ }
+
+ if(hash_idx < 0) return SQLITE_ERROR;
+ outlen = hash_descriptor[hash_idx].hashsize;
+
+ if(in == NULL) return SQLITE_ERROR;
+ if((rc = hmac_init(&hmac, hash_idx, hmac_key, key_sz)) != CRYPT_OK) return SQLITE_ERROR;
+ if((rc = hmac_process(&hmac, in, in_sz)) != CRYPT_OK) return SQLITE_ERROR;
+ if(in2 != NULL && (rc = hmac_process(&hmac, in2, in2_sz)) != CRYPT_OK) return SQLITE_ERROR;
+ if((rc = hmac_done(&hmac, out, &outlen)) != CRYPT_OK) return SQLITE_ERROR;
+ return SQLITE_OK;
+}
+
+static int sqlcipher_ltc_kdf(
+ void *ctx, int algorithm,
+ const unsigned char *pass, int pass_sz,
+ const unsigned char* salt, int salt_sz,
+ int workfactor,
+ int key_sz, unsigned char *key
+) {
+ int rc, hash_idx;
+ unsigned long outlen = key_sz;
+
+ switch(algorithm) {
+ case SQLCIPHER_HMAC_SHA1:
+ hash_idx = find_hash("sha1");
+ break;
+ case SQLCIPHER_HMAC_SHA256:
+ hash_idx = find_hash("sha256");
+ break;
+ case SQLCIPHER_HMAC_SHA512:
+ hash_idx = find_hash("sha512");
+ break;
+ default:
+ return SQLITE_ERROR;
+ }
+ if(hash_idx < 0) return SQLITE_ERROR;
+
+ if((rc = pkcs_5_alg2(pass, pass_sz, salt, salt_sz,
+ workfactor, hash_idx, key, &outlen)) != CRYPT_OK) {
+ return SQLITE_ERROR;
+ }
+ return SQLITE_OK;
+}
+
+static const char* sqlcipher_ltc_get_cipher(void *ctx) {
+ return "aes-256-cbc";
+}
+
+static int sqlcipher_ltc_cipher(
+ void *ctx, int mode,
+ const unsigned char *key, int key_sz,
+ const unsigned char *iv,
+ const unsigned char *in, int in_sz,
+ unsigned char *out
+) {
+ int rc, cipher_idx;
+ symmetric_CBC cbc;
+
+ if((cipher_idx = find_cipher(LTC_CIPHER)) == -1) return SQLITE_ERROR;
+ if((rc = cbc_start(cipher_idx, iv, key, key_sz, 0, &cbc)) != CRYPT_OK) return SQLITE_ERROR;
+ rc = mode == SQLCIPHER_ENCRYPT ? cbc_encrypt(in, out, in_sz, &cbc) : cbc_decrypt(in, out, in_sz, &cbc);
+ if(rc != CRYPT_OK) return SQLITE_ERROR;
+ cbc_done(&cbc);
+ return SQLITE_OK;
+}
+
+static int sqlcipher_ltc_get_key_sz(void *ctx) {
+ int cipher_idx = find_cipher(LTC_CIPHER);
+ return cipher_descriptor[cipher_idx].max_key_length;
+}
+
+static int sqlcipher_ltc_get_iv_sz(void *ctx) {
+ int cipher_idx = find_cipher(LTC_CIPHER);
+ return cipher_descriptor[cipher_idx].block_length;
+}
+
+static int sqlcipher_ltc_get_block_sz(void *ctx) {
+ int cipher_idx = find_cipher(LTC_CIPHER);
+ return cipher_descriptor[cipher_idx].block_length;
+}
+
+static int sqlcipher_ltc_get_hmac_sz(void *ctx, int algorithm) {
+ int hash_idx;
+ switch(algorithm) {
+ case SQLCIPHER_HMAC_SHA1:
+ hash_idx = find_hash("sha1");
+ break;
+ case SQLCIPHER_HMAC_SHA256:
+ hash_idx = find_hash("sha256");
+ break;
+ case SQLCIPHER_HMAC_SHA512:
+ hash_idx = find_hash("sha512");
+ break;
+ default:
+ return 0;
+ }
+
+ if(hash_idx < 0) return 0;
+
+ return hash_descriptor[hash_idx].hashsize;
+}
+
+static int sqlcipher_ltc_ctx_init(void **ctx) {
+ sqlcipher_ltc_activate(NULL);
+ return SQLITE_OK;
+}
+
+static int sqlcipher_ltc_ctx_free(void **ctx) {
+ sqlcipher_ltc_deactivate(&ctx);
+ return SQLITE_OK;
+}
+
+static int sqlcipher_ltc_fips_status(void *ctx) {
+ return 0;
+}
+
+int sqlcipher_ltc_setup(sqlcipher_provider *p) {
+ p->init = NULL;
+ p->shutdown = NULL;
+ p->get_provider_name = sqlcipher_ltc_get_provider_name;
+ p->random = sqlcipher_ltc_random;
+ p->hmac = sqlcipher_ltc_hmac;
+ p->kdf = sqlcipher_ltc_kdf;
+ p->cipher = sqlcipher_ltc_cipher;
+ p->get_cipher = sqlcipher_ltc_get_cipher;
+ p->get_key_sz = sqlcipher_ltc_get_key_sz;
+ p->get_iv_sz = sqlcipher_ltc_get_iv_sz;
+ p->get_block_sz = sqlcipher_ltc_get_block_sz;
+ p->get_hmac_sz = sqlcipher_ltc_get_hmac_sz;
+ p->ctx_init = sqlcipher_ltc_ctx_init;
+ p->ctx_free = sqlcipher_ltc_ctx_free;
+ p->add_random = sqlcipher_ltc_add_random;
+ p->fips_status = sqlcipher_ltc_fips_status;
+ p->get_provider_version = sqlcipher_ltc_get_provider_version;
+ return SQLITE_OK;
+}
+
+#endif
+#endif
+/* END SQLCIPHER */
diff --git a/src/sqlcipher.c b/src/sqlcipher.c
index bf07153f8..c93dcf26f 100644
--- a/src/sqlcipher.c
+++ b/src/sqlcipher.c
@@ -84,6 +84,7 @@ void sqlite3pager_reset(Pager *pPager);
/* end extensions defined in pager.c */
#if !defined (SQLCIPHER_CRYPTO_CC) \
+ && !defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT) \
&& !defined (SQLCIPHER_CRYPTO_OPENSSL) \
&& !defined (SQLCIPHER_CRYPTO_CUSTOM)
#define SQLCIPHER_CRYPTO_OPENSSL
@@ -504,6 +505,9 @@ int sqlcipher_extra_init(const char* arg) {
#if defined (SQLCIPHER_CRYPTO_CC)
extern int sqlcipher_cc_setup(sqlcipher_provider *p);
sqlcipher_cc_setup(p);
+#elif defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT)
+ extern int sqlcipher_ltc_setup(sqlcipher_provider *p);
+ sqlcipher_ltc_setup(p);
#elif defined (SQLCIPHER_CRYPTO_OPENSSL)
extern int sqlcipher_openssl_setup(sqlcipher_provider *p);
sqlcipher_openssl_setup(p);
diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl
index 93af23994..c5e45cd5d 100644
--- a/tool/mksqlite3c.tcl
+++ b/tool/mksqlite3c.tcl
@@ -436,6 +436,7 @@ set flist {
memjournal.c
sqlcipher.c
+ crypto_libtomcrypt.c
crypto_openssl.c
crypto_cc.c
From 3c37ad152c5870109be7013108bbfd86c97277ac Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Mon, 23 Feb 2026 16:26:16 -0500
Subject: [PATCH 07/15] Fixes provider specific test detection
---
test/sqlcipher.tcl | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/test/sqlcipher.tcl b/test/sqlcipher.tcl
index 4752316c6..062a52ffe 100644
--- a/test/sqlcipher.tcl
+++ b/test/sqlcipher.tcl
@@ -61,10 +61,12 @@ proc setup {file key} {
proc get_cipher_provider {} {
sqlite_orig db test.db
- return [execsql {
- PRAGMA key = 'test';
- PRAGMA cipher_provider;
- }];
+ execsql {
+ PRAGMA key = 'test';
+ }
+ return [execsql {
+ PRAGMA cipher_provider;
+ }];
}
proc if_built_with_openssl {name cmd expected} {
From 03c64f7b78125c730ff539eb8baba113cfd7e719 Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Thu, 26 Feb 2026 10:00:31 -0500
Subject: [PATCH 08/15] Fixes double close of database handle in compat test
---
test/sqlcipher-compatibility.test | 1 -
1 file changed, 1 deletion(-)
diff --git a/test/sqlcipher-compatibility.test b/test/sqlcipher-compatibility.test
index 656b1c458..edaa0f6cc 100644
--- a/test/sqlcipher-compatibility.test
+++ b/test/sqlcipher-compatibility.test
@@ -1294,7 +1294,6 @@ if_built_with_libtomcrypt verify-random-data-alters-file-content {
lappend rc [cmpFilesChunked test.db test2.db]
lappend rc [cmpFilesChunked test2.db test3.db]
} {0 1}
-db close
file delete -force test.db
file delete -force test2.db
file delete -force test3.db
From c66691b89097fd9bbda2b16d843f942842d7065b Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Thu, 26 Feb 2026 14:14:35 -0500
Subject: [PATCH 09/15] Always seed libtomcrypt CSPRNG (even for tests)
---
src/crypto_libtomcrypt.c | 2 --
test/sqlcipher-compatibility.test | 34 -------------------------------
2 files changed, 36 deletions(-)
diff --git a/src/crypto_libtomcrypt.c b/src/crypto_libtomcrypt.c
index c8ee378d3..0ade64b9b 100644
--- a/src/crypto_libtomcrypt.c
+++ b/src/crypto_libtomcrypt.c
@@ -94,9 +94,7 @@ static int sqlcipher_ltc_activate(void *ctx) {
}
ltc_ref_count++;
-#ifndef SQLCIPHER_TEST
bytes = rng_get_bytes(random_buffer, FORTUNA_MAX_SZ, NULL);
-#endif
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_PROVIDER, "sqlcipher_ltc_activate: seeded fortuna with %d bytes from rng_get_bytes", bytes);
if(sqlcipher_ltc_add_random(ctx, random_buffer, FORTUNA_MAX_SZ) != SQLITE_OK) {
diff --git a/test/sqlcipher-compatibility.test b/test/sqlcipher-compatibility.test
index edaa0f6cc..4053d78d9 100644
--- a/test/sqlcipher-compatibility.test
+++ b/test/sqlcipher-compatibility.test
@@ -1264,40 +1264,6 @@ db close
file delete -force test.db
file delete -force new.db
-
-# Requires SQLCipher to be built with -DSQLCIPHER_TEST
-if_built_with_libtomcrypt verify-random-data-alters-file-content {
- file delete -force test.db
- file delete -force test2.db
- file delete -force test3.db
- set rc {}
-
- sqlite_orig db test.db
- execsql {
- PRAGMA key="x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'";
- create table t1(a,b);
- }
- db close
- sqlite_orig db test2.db
- execsql {
- PRAGMA key="x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'";
- create table t1(a,b);
- }
- db close
- sqlite_orig db test3.db
- execsql {
- PRAGMA key="x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'";
- PRAGMA cipher_add_random = "x'deadbaad'";
- create table t1(a,b);
- }
- db close
- lappend rc [cmpFilesChunked test.db test2.db]
- lappend rc [cmpFilesChunked test2.db test3.db]
-} {0 1}
-file delete -force test.db
-file delete -force test2.db
-file delete -force test3.db
-
do_test can-migrate-with-keys-longer-than-64-characters {
sqlite_orig db test.db
execsql {
From 50ce5b8b383c6369fb436616fd3e583c54d8659e Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Thu, 26 Feb 2026 14:19:26 -0500
Subject: [PATCH 10/15] Modernize libtomcrypt identifiers from rijndael to aes
---
src/crypto_libtomcrypt.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/crypto_libtomcrypt.c b/src/crypto_libtomcrypt.c
index 0ade64b9b..34fde7dcd 100644
--- a/src/crypto_libtomcrypt.c
+++ b/src/crypto_libtomcrypt.c
@@ -40,7 +40,7 @@ static prng_state prng;
static volatile unsigned int ltc_init = 0;
static volatile unsigned int ltc_ref_count = 0;
-#define LTC_CIPHER "rijndael"
+#define LTC_CIPHER "aes"
static int sqlcipher_ltc_add_random(void *ctx, const void *buffer, int length) {
int rc = 0;
@@ -82,7 +82,7 @@ static int sqlcipher_ltc_activate(void *ctx) {
sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ);
if(ltc_init == 0) {
if(register_prng(&fortuna_desc) < 0) return SQLITE_ERROR;
- if(register_cipher(&rijndael_desc) < 0) return SQLITE_ERROR;
+ if(register_cipher(&aes_desc) < 0) return SQLITE_ERROR;
if(register_hash(&sha512_desc) < 0) return SQLITE_ERROR;
if(register_hash(&sha256_desc) < 0) return SQLITE_ERROR;
if(register_hash(&sha1_desc) < 0) return SQLITE_ERROR;
From e4a682c03e6224a4a82c7906c390294f2d969f9b Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Thu, 5 Mar 2026 14:25:17 -0500
Subject: [PATCH 11/15] Improves logging and error handling for LibTomCrypt
---
src/crypto_libtomcrypt.c | 103 ++++++++++++++++++++++++++++++++-------
1 file changed, 86 insertions(+), 17 deletions(-)
diff --git a/src/crypto_libtomcrypt.c b/src/crypto_libtomcrypt.c
index 34fde7dcd..3447294a6 100644
--- a/src/crypto_libtomcrypt.c
+++ b/src/crypto_libtomcrypt.c
@@ -157,8 +157,11 @@ static int sqlcipher_ltc_hmac(
unsigned char *out
) {
int rc, hash_idx;
- hmac_state hmac;
unsigned long outlen;
+ hmac_state *hmac = NULL;
+
+ if(in == NULL) goto error;
+
switch(algorithm) {
case SQLCIPHER_HMAC_SHA1:
hash_idx = find_hash("sha1");
@@ -170,18 +173,47 @@ static int sqlcipher_ltc_hmac(
hash_idx = find_hash("sha512");
break;
default:
- return SQLITE_ERROR;
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: unsupported hmac algorithm", __func__);
+ goto error;
+ }
+
+ if(hash_idx < 0) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed find_hash lookup", __func__);
+ goto error;
}
- if(hash_idx < 0) return SQLITE_ERROR;
outlen = hash_descriptor[hash_idx].hashsize;
- if(in == NULL) return SQLITE_ERROR;
- if((rc = hmac_init(&hmac, hash_idx, hmac_key, key_sz)) != CRYPT_OK) return SQLITE_ERROR;
- if((rc = hmac_process(&hmac, in, in_sz)) != CRYPT_OK) return SQLITE_ERROR;
- if(in2 != NULL && (rc = hmac_process(&hmac, in2, in2_sz)) != CRYPT_OK) return SQLITE_ERROR;
- if((rc = hmac_done(&hmac, out, &outlen)) != CRYPT_OK) return SQLITE_ERROR;
- return SQLITE_OK;
+ if(!(hmac = sqlcipher_malloc(sizeof(hmac_state)))) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed to allocate hmac_state", __func__);
+ goto error;
+ }
+ if((rc = hmac_init(hmac, hash_idx, hmac_key, key_sz)) != CRYPT_OK) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: hmac_init failed %d", __func__, rc);
+ goto error;
+ }
+ if((rc = hmac_process(hmac, in, in_sz)) != CRYPT_OK) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: hmac_process failed %d", __func__, rc);
+ goto error;
+ }
+ if(in2 != NULL && (rc = hmac_process(hmac, in2, in2_sz)) != CRYPT_OK) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: hmac_process 2 failed %d", __func__, rc);
+ goto error;
+ }
+ if((rc = hmac_done(hmac, out, &outlen)) != CRYPT_OK) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: done failed %d", __func__, rc);
+ goto error;
+ }
+
+ rc = SQLITE_OK;
+ goto cleanup;
+
+error:
+ rc = SQLITE_ERROR;
+
+cleanup:
+ if(hmac) sqlcipher_free(hmac, sizeof(hmac_state));
+ return rc;
}
static int sqlcipher_ltc_kdf(
@@ -205,12 +237,18 @@ static int sqlcipher_ltc_kdf(
hash_idx = find_hash("sha512");
break;
default:
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: unsupported hmac algorithm", __func__);
return SQLITE_ERROR;
}
- if(hash_idx < 0) return SQLITE_ERROR;
+
+ if(hash_idx < 0) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed find_hash lookup", __func__);
+ return SQLITE_ERROR;
+ }
if((rc = pkcs_5_alg2(pass, pass_sz, salt, salt_sz,
workfactor, hash_idx, key, &outlen)) != CRYPT_OK) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed pkc_5_alg2 %d", __func__, rc);
return SQLITE_ERROR;
}
return SQLITE_OK;
@@ -228,14 +266,45 @@ static int sqlcipher_ltc_cipher(
unsigned char *out
) {
int rc, cipher_idx;
- symmetric_CBC cbc;
+ symmetric_CBC *cbc = NULL;
- if((cipher_idx = find_cipher(LTC_CIPHER)) == -1) return SQLITE_ERROR;
- if((rc = cbc_start(cipher_idx, iv, key, key_sz, 0, &cbc)) != CRYPT_OK) return SQLITE_ERROR;
- rc = mode == SQLCIPHER_ENCRYPT ? cbc_encrypt(in, out, in_sz, &cbc) : cbc_decrypt(in, out, in_sz, &cbc);
- if(rc != CRYPT_OK) return SQLITE_ERROR;
- cbc_done(&cbc);
- return SQLITE_OK;
+ if((cipher_idx = find_cipher(LTC_CIPHER)) == -1) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed find_cipher lookup", __func__);
+ goto error;
+ }
+ if(!(cbc = sqlcipher_malloc(sizeof(symmetric_CBC)))) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed to allocate symmetric_CBC", __func__);
+ goto error;
+ }
+ if((rc = cbc_start(cipher_idx, iv, key, key_sz, 0, cbc)) != CRYPT_OK) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed cbc_start %d", __func__, rc);
+ goto error;
+ }
+ if(mode == SQLCIPHER_ENCRYPT) {
+ if((rc = cbc_encrypt(in, out, in_sz, cbc)) != CRYPT_OK) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed cbc_encrypt %d", __func__, rc);
+ goto error;
+ }
+ } else {
+ if((rc = cbc_decrypt(in, out, in_sz, cbc)) != CRYPT_OK) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed cbc_decrypt %d", __func__, rc);
+ goto error;
+ }
+ }
+ if((rc = cbc_done(cbc)) != CRYPT_OK) {
+ sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "%s: failed cbc_done %d", __func__, rc);
+ goto error;
+ }
+
+ rc = SQLITE_OK;
+ goto cleanup;
+
+error:
+ rc = SQLITE_ERROR;
+
+cleanup:
+ if(cbc) sqlcipher_free(cbc, sizeof(symmetric_CBC));
+ return rc;
}
static int sqlcipher_ltc_get_key_sz(void *ctx) {
From 7b0662cd0733e9064ccd1827d6b6779658d5f913 Mon Sep 17 00:00:00 2001
From: Stephen Lombardo
Date: Fri, 6 Mar 2026 21:37:56 -0500
Subject: [PATCH 12/15] Snapshot of upstream SQLite 3.52.0
---
Makefile.msc | 203 +-
README.md | 99 +-
VERSION | 2 +-
autoconf/Makefile.in | 15 +-
autoconf/Makefile.msc | 100 +-
autosetup/README.md | 10 +
autosetup/jimsh0.c | 10 +-
autosetup/proj.tcl | 4 +-
autosetup/sqlite-config.tcl | 24 +-
doc/compile-for-unix.md | 13 +-
doc/compile-for-windows.md | 36 +
doc/lemon.html | 32 +-
doc/testrunner.md | 159 +-
ext/expert/expert1.test | 2 +-
ext/fts3/fts3.c | 9 +-
ext/fts3/fts3Int.h | 9 +
ext/fts3/fts3_write.c | 42 +-
ext/fts5/fts5_aux.c | 2 +-
ext/fts5/fts5_buffer.c | 2 +-
ext/fts5/fts5_config.c | 4 +-
ext/fts5/fts5_expr.c | 4 +-
ext/fts5/fts5_hash.c | 2 +-
ext/fts5/fts5_index.c | 74 +-
ext/fts5/fts5_main.c | 5 +-
ext/fts5/fts5_tcl.c | 8 +-
ext/fts5/fts5_test_tok.c | 10 +-
ext/fts5/fts5_tokenize.c | 8 +-
ext/fts5/fts5_vocab.c | 2 +-
ext/fts5/test/fts5corrupt9.test | 129 +
ext/fts5/test/fts5integrity.test | 3 -
ext/fts5/test/fts5interrupt.test | 17 +
ext/intck/sqlite3intck.c | 2 +-
ext/jni/README.md | 35 +-
ext/jni/src/c/sqlite3-jni.c | 94 +-
ext/jni/src/c/sqlite3-jni.h | 4 +-
ext/jni/src/org/sqlite/jni/capi/CApi.java | 8 +-
ext/jni/src/org/sqlite/jni/capi/Tester1.java | 4 +-
.../src/org/sqlite/jni/wrapper1/Sqlite.java | 1 +
ext/misc/btreeinfo.c | 6 +-
ext/misc/csv.c | 4 +-
ext/misc/decimal.c | 43 +-
ext/misc/fileio.c | 240 +-
ext/misc/fossildelta.c | 10 +-
ext/misc/fuzzer.c | 2 +-
ext/misc/ieee754.c | 36 +-
ext/misc/regexp.c | 28 +-
ext/misc/sha1.c | 15 +-
ext/misc/sqlite3_stdio.c | 20 +-
ext/misc/sqlite3_stdio.h | 3 +
ext/misc/tmstmpvfs.c | 1042 +++
ext/misc/vtablog.c | 54 +-
ext/misc/zipfile.c | 60 +-
ext/qrf/README.md | 762 ++
ext/qrf/dev-notes.md | 14 +
ext/qrf/qrf.c | 2983 ++++++
ext/qrf/qrf.h | 200 +
ext/rbu/rbu11.test | 28 +
ext/rbu/sqlite3rbu.c | 6 +-
ext/repair/README.md | 16 -
ext/repair/checkfreelist.c | 310 -
ext/repair/checkindex.c | 929 --
ext/repair/sqlite3_checker.c.in | 85 -
ext/repair/sqlite3_checker.tcl | 264 -
ext/repair/test/README.md | 13 -
ext/repair/test/checkfreelist01.test | 92 -
ext/repair/test/checkindex01.test | 349 -
ext/repair/test/test.tcl | 67 -
ext/rtree/geopoly.c | 2 +-
ext/session/session4.test | 1 +
ext/session/sessionC.test | 10 +
ext/session/sqlite3session.c | 60 +-
ext/session/test_session.c | 66 +-
ext/wasm/EXPORTED_FUNCTIONS.fiddle.in | 10 -
ext/wasm/GNUmakefile | 483 +-
...S.sqlite3-core => EXPORTED_FUNCTIONS.c-pp} | 90 +
.../api/EXPORTED_FUNCTIONS.sqlite3-extras | 68 -
ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see | 5 -
.../api/EXPORTED_RUNTIME_METHODS.sqlite3-api | 3 -
ext/wasm/api/README.md | 84 +-
ext/wasm/api/extern-post-js.c-pp.js | 4 +-
ext/wasm/api/post-js-footer.js | 69 +
ext/wasm/api/post-js-header.js | 10 +-
ext/wasm/api/pre-js.c-pp.js | 50 +-
ext/wasm/api/sqlite3-api-cleanup.js | 83 -
ext/wasm/api/sqlite3-api-glue.c-pp.js | 155 +-
ext/wasm/api/sqlite3-api-oo1.c-pp.js | 293 +-
ext/wasm/api/sqlite3-api-prologue.js | 385 +-
.../api/sqlite3-license-version-header.js | 3 +-
ext/wasm/api/sqlite3-opfs-async-proxy.js | 2 +-
ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js | 2095 +++++
ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js | 18 +-
ext/wasm/api/sqlite3-vfs-opfs.c-pp.js | 8 +-
ext/wasm/api/sqlite3-vtab-helper.c-pp.js | 5 +-
ext/wasm/api/sqlite3-wasm.c | 294 +-
ext/wasm/api/sqlite3-worker1-promiser.c-pp.js | 26 +-
ext/wasm/api/sqlite3-worker1.c-pp.js | 4 +-
ext/wasm/c-pp-lite.c | 69 +-
ext/wasm/common/SqliteTestUtil.js | 3 +-
ext/wasm/common/whwasmutil.js | 67 +-
ext/wasm/demo-jsstorage.js | 8 +-
ext/wasm/demo-worker1-promiser.c-pp.html | 8 +-
ext/wasm/demo-worker1.js | 6 +-
ext/wasm/fiddle/fiddle-worker.js | 4 -
.../fiddle/{index.html => index.c-pp.html} | 17 +-
ext/wasm/index.html | 5 +
ext/wasm/jaccwabyt/jaccwabyt.js | 851 +-
ext/wasm/jaccwabyt/jaccwabyt.md | 698 +-
ext/wasm/mkdist.sh | 2 +-
ext/wasm/mkwasmbuilds.c | 109 +-
ext/wasm/speedtest1-worker.js | 13 +-
ext/wasm/speedtest1.html | 203 +-
ext/wasm/tester1-worker.c-pp.html | 16 +-
ext/wasm/tester1.c-pp.html | 18 +-
ext/wasm/tester1.c-pp.js | 983 +-
main.mk | 95 +-
make.bat | 2 +
manifest | 519 +-
manifest.tags | 7 +-
manifest.uuid | 2 +-
src/alter.c | 779 +-
src/attach.c | 2 +-
src/auth.c | 2 +-
src/btree.c | 30 +-
src/build.c | 17 +-
src/carray.c | 39 +-
src/date.c | 16 +-
src/delete.c | 3 +-
src/expr.c | 93 +-
src/fkey.c | 12 +-
src/func.c | 16 +-
src/hwtime.h | 41 +-
src/json.c | 113 +-
src/loadext.c | 47 +-
src/main.c | 36 +-
src/malloc.c | 24 +-
src/mutex.c | 29 +-
src/mutex_w32.c | 8 -
src/os_kv.c | 266 +-
src/os_unix.c | 2 +-
src/os_win.c | 436 +-
src/os_win.h | 10 +-
src/pager.c | 12 +-
src/parse.y | 206 +-
src/pcache.h | 4 +-
src/prepare.c | 3 +-
src/printf.c | 21 +-
src/resolve.c | 18 +-
src/select.c | 721 +-
src/shell.c.in | 7958 ++++++++---------
src/sqlite.h.in | 241 +-
src/sqlite3ext.h | 11 +-
src/sqliteInt.h | 128 +-
src/sqliteLimit.h | 50 +-
src/tclsqlite.c | 417 +-
src/test1.c | 43 +-
src/test_bestindex.c | 1 -
src/test_config.c | 16 +-
src/test_quota.c | 4 -
src/tokenize.c | 4 +-
src/treeview.c | 8 +-
src/trigger.c | 137 +-
src/util.c | 665 +-
src/vacuum.c | 12 +-
src/vdbe.c | 21 +-
src/vdbe.h | 2 +-
src/vdbeInt.h | 4 +-
src/vdbeapi.c | 52 +-
src/vdbeaux.c | 2 +-
src/vdbemem.c | 201 +-
src/wal.c | 131 +-
src/where.c | 227 +-
src/wherecode.c | 2 +-
src/whereexpr.c | 79 +-
src/window.c | 6 +-
test/alterauth2.test | 51 +
test/altercol.test | 13 +
test/altercons.test | 442 +
test/altercons2.test | 247 +
test/alterfault.test | 37 +
test/altertab3.test | 48 +
test/altertrig.test | 2 +-
test/atof2.test | 35 +
test/autoindex1.test | 3 +-
test/avfs.test | 2 +
test/bestindex8.test | 16 +-
test/bestindexB.test | 2 +-
test/bestindexF.test | 294 +
test/carray01.test | 8 +
test/collate5.test | 30 +-
test/corruptL.test | 2 +-
test/cost.test | 2 +-
test/dblwidth-a.sql | 38 +-
test/distinct2.test | 2 +-
test/dotcmd01.sql | 63 +
test/e_expr.test | 8 +-
test/e_update.test | 14 +-
test/e_walckpt.test | 17 +-
test/eqp.test | 55 +-
test/filectrl.test | 12 +-
test/fpconv1.test | 66 +-
test/fptest01.sql | 76 +
test/fts3comp1.test | 77 +
test/fts4content.test | 59 +-
test/fts4merge5.test | 3 +
test/fuzzcheck.c | 14 +-
test/fuzzinvariants.c | 9 +-
test/import01.sql | 217 +
test/imposter1.sql | 32 +
test/insert5.test | 19 +-
test/intck01.sql | 23 +
test/join.test | 41 +
test/joinI.test | 47 +
test/json102.test | 21 +
test/json103.test | 7 +
test/json105.test | 5 +
test/json109.test | 72 +
test/misc5.test | 2 +-
test/modeA.sql | 303 +
test/mutex1.test | 26 +-
test/notnull2.test | 2 +-
test/offset1.test | 28 +
test/qrf01.test | 1167 +++
test/qrf02.test | 47 +
test/qrf03.test | 176 +
test/qrf04.test | 750 ++
test/qrf05.test | 37 +
test/qrf06.test | 576 ++
test/recover.test | 2 +-
test/regexp1.sql | 32 +
test/regexp1.test | 24 +
test/rowvalue4.test | 3 +-
test/rowvalueA.test | 25 +
test/schema.test | 4 +-
test/select9.test | 2 +-
test/shell1.test | 227 +-
test/shell2.test | 92 +-
test/shell4.test | 8 +-
test/shell5.test | 124 +-
test/shell8.test | 40 +
test/shellA.test | 233 +-
test/shellB.test | 53 +
test/speedtest.md | 5 +-
test/speedtest.tcl | 2 +-
test/tabfunc01.test | 14 +
test/tclsqlite.test | 2 +-
test/temptrigfault.tes | 120 +
test/temptrigger.test | 187 +
test/tester.tcl | 5 -
test/testrunner.tcl | 128 +-
test/testrunner_data.tcl | 8 +-
test/testrunner_estwork.tcl | 1 +
test/tkt-99378177930f87bd.test | 28 +
test/tkt2339.test | 6 +-
test/values.test | 6 +-
test/vt02.c | 125 +-
test/vt100-a.sql | 35 +-
test/walckptnoop.test | 6 +-
test/walrestart.test | 89 +
test/where2.test | 37 +
test/whereK.test | 13 +
test/win32longpath.test | 4 -
test/with1.test | 19 +
test/zipfile.test | 6 +
test/zipfile2.test | 15 +
tool/build-all-msvc.bat | 3 +-
tool/dbtotxt.c | 10 +-
tool/lemon.c | 63 +-
tool/lempar.c | 17 +-
tool/mkautoconfamal.sh | 3 +-
tool/mkcombo.tcl | 106 +
tool/mkkeywordhash.c | 4 +-
tool/mkshellc.tcl | 120 +-
tool/omittest.tcl | 2 +-
tool/showdb.c | 247 +-
tool/showtmlog.c | 254 +
tool/sqldiff.c | 42 +-
tool/sqlite3_rsync.c | 33 +-
tool/sqltclsh.c.in | 2 +-
tool/winmain.c | 79 +
279 files changed, 26906 insertions(+), 11541 deletions(-)
create mode 100644 ext/fts5/test/fts5corrupt9.test
create mode 100644 ext/misc/tmstmpvfs.c
create mode 100644 ext/qrf/README.md
create mode 100644 ext/qrf/dev-notes.md
create mode 100644 ext/qrf/qrf.c
create mode 100644 ext/qrf/qrf.h
delete mode 100644 ext/repair/README.md
delete mode 100644 ext/repair/checkfreelist.c
delete mode 100644 ext/repair/checkindex.c
delete mode 100644 ext/repair/sqlite3_checker.c.in
delete mode 100644 ext/repair/sqlite3_checker.tcl
delete mode 100644 ext/repair/test/README.md
delete mode 100644 ext/repair/test/checkfreelist01.test
delete mode 100644 ext/repair/test/checkindex01.test
delete mode 100644 ext/repair/test/test.tcl
delete mode 100644 ext/wasm/EXPORTED_FUNCTIONS.fiddle.in
rename ext/wasm/api/{EXPORTED_FUNCTIONS.sqlite3-core => EXPORTED_FUNCTIONS.c-pp} (61%)
delete mode 100644 ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras
delete mode 100644 ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see
delete mode 100644 ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api
delete mode 100644 ext/wasm/api/sqlite3-api-cleanup.js
create mode 100644 ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js
rename ext/wasm/fiddle/{index.html => index.c-pp.html} (95%)
create mode 100644 make.bat
create mode 100644 test/altercons.test
create mode 100644 test/altercons2.test
create mode 100644 test/atof2.test
create mode 100644 test/bestindexF.test
create mode 100644 test/dotcmd01.sql
create mode 100644 test/fptest01.sql
create mode 100644 test/import01.sql
create mode 100644 test/imposter1.sql
create mode 100644 test/intck01.sql
create mode 100644 test/json109.test
create mode 100644 test/modeA.sql
create mode 100644 test/qrf01.test
create mode 100644 test/qrf02.test
create mode 100644 test/qrf03.test
create mode 100644 test/qrf04.test
create mode 100644 test/qrf05.test
create mode 100644 test/qrf06.test
create mode 100644 test/regexp1.sql
create mode 100644 test/shellB.test
create mode 100644 test/temptrigfault.tes
create mode 100644 test/walrestart.test
create mode 100644 tool/mkcombo.tcl
create mode 100644 tool/showtmlog.c
create mode 100644 tool/winmain.c
diff --git a/Makefile.msc b/Makefile.msc
index 52807ff7f..763616ce9 100644
--- a/Makefile.msc
+++ b/Makefile.msc
@@ -11,6 +11,8 @@
TOP = .
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
# Set this non-0 to create and use the SQLite amalgamation file.
#
!IFNDEF USE_AMALGAMATION
@@ -101,13 +103,6 @@ NO_WARN = $(NO_WARN) -wd4210 -wd4232 -wd4244 -wd4305 -wd4306 -wd4702 -wd4706
!ENDIF
!ENDIF
-# Set this non-0 to use the library paths and other options necessary for
-# Windows Phone 8.1.
-#
-!IFNDEF USE_WP81_OPTS
-USE_WP81_OPTS = 0
-!ENDIF
-
# Set this non-0 to split the SQLite amalgamation file into chunks to
# be used for debugging with Visual Studio.
#
@@ -116,14 +111,8 @@ SPLIT_AMALGAMATION = 0
!ENDIF
# <>
-# Set this non-0 to have this makefile assume the Tcl shell executable
-# (tclsh*.exe) is available in the PATH. By default, this is disabled
-# for compatibility with older build environments. This setting only
-# applies if TCLSH_CMD is not set manually.
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
#
-!IFNDEF USE_TCLSH_IN_PATH
-USE_TCLSH_IN_PATH = 0
-!ENDIF
# Set this non-0 to use zlib, possibly compiling it from source code.
#
@@ -186,14 +175,6 @@ USE_NATIVE_LIBPATHS = 0
USE_RC = 1
!ENDIF
-# Set this non-0 to compile binaries suitable for the WinRT environment.
-# This setting does not apply to any binaries that require Tcl to operate
-# properly (i.e. the text fixture, etc).
-#
-!IFNDEF FOR_WINRT
-FOR_WINRT = 0
-!ENDIF
-
# Set this non-0 to compile binaries suitable for the UWP environment.
# This setting does not apply to any binaries that require Tcl to operate
# properly (i.e. the text fixture, etc).
@@ -209,6 +190,8 @@ FOR_WIN10 = 0
!ENDIF
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
# Set this non-0 to skip attempting to look for and/or link with the Tcl
# runtime library.
#
@@ -265,6 +248,8 @@ DEBUG = 0
!ENDIF
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
# By default, use --linemacros=1 argument to the mksqlite3c.tcl tool, which
# is used to build the amalgamation. This can be turned off to ease debug
# of the amalgamation away from the source tree.
@@ -360,6 +345,8 @@ SQLITE3EXEPDB = /pdb:sqlite3sh.pdb
!ENDIF
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
# These are the names of the customized Tcl header files used by various parts
# of this makefile when the stdcall calling convention is in use. It is not
# used for any other purpose.
@@ -641,16 +628,24 @@ RCC = $(RC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) -I$(TOP)\src $(RCOPTS) $(RCCOPTS)
!IF "$(PLATFORM)"=="x86"
CORE_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
SHELL_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
+
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
TEST_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall -DINCLUDE_SQLITE_TCL_H=1 -DSQLITE_TCLAPI=__cdecl
# <>
+
!ELSE
!IFNDEF PLATFORM
CORE_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
SHELL_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
+
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
TEST_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall -DINCLUDE_SQLITE_TCL_H=1 -DSQLITE_TCLAPI=__cdecl
# <>
+
!ELSE
CORE_CCONV_OPTS =
SHELL_CCONV_OPTS =
@@ -777,18 +772,6 @@ SHELL_LINK_OPTS = $(SHELL_CORE_LIB)
TCC = $(TCC) -FAcs
!ENDIF
-# When compiling the library for use in the WinRT environment,
-# the following compile-time options must be used as well to
-# disable use of Win32 APIs that are not available and to enable
-# use of Win32 APIs that are specific to Windows 8 and/or WinRT.
-#
-!IF $(FOR_WINRT)!=0
-TCC = $(TCC) -DSQLITE_OS_WINRT=1
-RCC = $(RCC) -DSQLITE_OS_WINRT=1
-TCC = $(TCC) -DWINAPI_FAMILY=WINAPI_FAMILY_APP
-RCC = $(RCC) -DWINAPI_FAMILY=WINAPI_FAMILY_APP
-!ENDIF
-
# C compiler options for the Windows 10 platform (needs MSVC 2015).
#
!IF $(FOR_WIN10)!=0
@@ -801,7 +784,7 @@ BCC = $(BCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
# USE_CRT_DLL option is set to force dynamically linking to the
# MSVC runtime library.
#
-!IF $(FOR_WINRT)!=0 || $(USE_CRT_DLL)!=0
+!IF $(USE_CRT_DLL)!=0
!IF $(DEBUG)>1
TCC = $(TCC) -MDd
BCC = $(BCC) -MDd
@@ -824,6 +807,8 @@ ZLIBCFLAGS = -nologo -MT -W3 -O2 -Oy- -Zi
!ENDIF
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
# The mksqlite3c.tcl and mksqlite3h.tcl scripts will pull in
# any extension header files by default. For non-amalgamation
# builds, we need to make sure the compiler can find these.
@@ -874,16 +859,6 @@ MKSQLITE3H_ARGS =
!ENDIF
# <>
-# Define -DNDEBUG to compile without debugging (i.e., for production usage)
-# Omitting the define will cause extra debugging code to be inserted and
-# includes extra comments when "EXPLAIN stmt" is used.
-#
-!IF $(DEBUG)==0
-TCC = $(TCC) -DNDEBUG
-BCC = $(BCC) -DNDEBUG
-RCC = $(RCC) -DNDEBUG
-!ENDIF
-
!IF $(DEBUG)>0 || $(API_ARMOR)!=0 || $(FOR_WIN10)!=0
TCC = $(TCC) -DSQLITE_ENABLE_API_ARMOR=1
RCC = $(RCC) -DSQLITE_ENABLE_API_ARMOR=1
@@ -949,6 +924,8 @@ TCC = $(TCC) /fsanitize=address
!ENDIF
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
# The locations of the Tcl header and library files. Also, the library that
# non-stubs enabled programs using Tcl must link against. These variables
# (TCLINCDIR, TCLLIBDIR, and LIBTCL) may be overridden via the environment
@@ -1188,6 +1165,8 @@ BCC = $(BCC) -Zi
!ENDIF
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
# If zlib support is enabled, add the compiler options for it.
#
!IF $(USE_ZLIB)!=0
@@ -1240,56 +1219,6 @@ LTLINKOPTS = /NOLOGO
LTLIBOPTS = /NOLOGO
!ENDIF
-# When compiling for use in the WinRT environment, the following
-# linker option must be used to mark the executable as runnable
-# only in the context of an application container.
-#
-!IF $(FOR_WINRT)!=0
-LTLINKOPTS = $(LTLINKOPTS) /APPCONTAINER
-!IF "$(VISUALSTUDIOVERSION)"=="12.0" || "$(VISUALSTUDIOVERSION)"=="14.0"
-!IFNDEF STORELIBPATH
-!IF "$(PLATFORM)"=="x86"
-STORELIBPATH = $(CRTLIBPATH)\store
-!ELSEIF "$(PLATFORM)"=="x64"
-STORELIBPATH = $(CRTLIBPATH)\store\amd64
-!ELSEIF "$(PLATFORM)"=="ARM"
-STORELIBPATH = $(CRTLIBPATH)\store\arm
-!ELSE
-STORELIBPATH = $(CRTLIBPATH)\store
-!ENDIF
-!ENDIF
-STORELIBPATH = $(STORELIBPATH:\\=\)
-LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(STORELIBPATH)"
-!ENDIF
-!ENDIF
-
-# When compiling for Windows Phone 8.1, an extra library path is
-# required.
-#
-!IF $(USE_WP81_OPTS)!=0
-!IFNDEF WP81LIBPATH
-!IF "$(PLATFORM)"=="x86"
-WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\x86
-!ELSEIF "$(PLATFORM)"=="ARM"
-WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\ARM
-!ELSE
-WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\x86
-!ENDIF
-!ENDIF
-!ENDIF
-
-# When compiling for Windows Phone 8.1, some extra linker options
-# are also required.
-#
-!IF $(USE_WP81_OPTS)!=0
-!IFDEF WP81LIBPATH
-LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(WP81LIBPATH)"
-!ENDIF
-LTLINKOPTS = $(LTLINKOPTS) /DYNAMICBASE
-LTLINKOPTS = $(LTLINKOPTS) WindowsPhoneCore.lib RuntimeObject.lib PhoneAppModelHost.lib
-LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:kernel32.lib /NODEFAULTLIB:ole32.lib
-!ENDIF
-
# When compiling for UWP or the Windows 10 platform, some extra linker
# options are also required.
#
@@ -1313,12 +1242,14 @@ LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib
# If either debugging or symbols are enabled, enable PDBs.
#
!IF $(DEBUG)>1 || $(SYMBOLS)!=0
-LDFLAGS = /DEBUG $(LDOPTS)
+LDFLAGS = /NODEFAULTLIB:msvcrt /DEBUG $(LDOPTS)
!ELSE
-LDFLAGS = $(LDOPTS)
+LDFLAGS = /NODEFAULTLIB:msvcrt $(LDOPTS)
!ENDIF
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
# Start with the Tcl related linker options.
#
!IF $(NO_TCL)==0
@@ -1345,6 +1276,8 @@ LTLIBS = $(LTLIBS) $(LIBICU)
###############################################################################
# <>
+# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc
+#
# Object files for the SQLite library (non-amalgamation).
#
LIBOBJS0 = vdbe.lo parse.lo alter.lo analyze.lo attach.lo auth.lo \
@@ -1463,7 +1396,7 @@ SRC01 = \
$(TOP)\src\status.c \
$(TOP)\src\table.c \
$(TOP)\src\threads.c \
- $(TOP)\src\tclsqlite.c \
+ tclsqlite-ex.c \
$(TOP)\src\tokenize.c \
$(TOP)\src\treeview.c \
$(TOP)\src\trigger.c \
@@ -1774,6 +1707,7 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_PERCENTILE=1
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1
+SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB=1
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_STRICT_SUBTYPE=1
!ENDIF
@@ -1932,8 +1866,8 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT
/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
# <>
-sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
- $(LTLINK) $(NO_WARN) -I$(TOP)\ext\misc $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS)
+sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) $(TOP)\tool\winmain.c
+ $(LTLINK) $(NO_WARN) -I$(TOP)\ext\misc $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.c $(TOP)\tool\winmain.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)
@@ -2023,8 +1957,18 @@ sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) src-v
sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl $(JIM_TCLSH)
$(JIM_TCLSH) $(TOP)\tool\split-sqlite3c.tcl
+
+TCLSQLITEEX = \
+ $(TOP)\ext\qrf\qrf.h \
+ $(TOP)\ext\qrf\qrf.c \
+ $(TOP)\src\tclsqlite.c
+
+tclsqlite-ex.c: $(TCLSQLITEEX) $(TOP)\tool\mkcombo.tcl $(JIM_TCLSH)
+ $(JIM_TCLSH) $(TOP)\tool\mkcombo.tcl $(TCLSQLITEEX) -o $@
# <>
+tclsqlite-ex.c:
+
# Rule to build the amalgamation
#
sqlite3.lo: $(SQLITE3C)
@@ -2324,11 +2268,11 @@ whereexpr.lo: $(TOP)\src\whereexpr.c $(HDR)
window.lo: $(TOP)\src\window.c $(HDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\window.c
-tclsqlite.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP)
- $(LTCOMPILE) $(NO_WARN) -DUSE_TCL_STUBS=1 -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c
+tclsqlite.lo: tclsqlite-ex.c $(HDR) $(SQLITE_TCL_DEP)
+ $(LTCOMPILE) $(NO_WARN) -DUSE_TCL_STUBS=1 -DBUILD_sqlite -I$(TCLINCDIR) -c tclsqlite-ex.c /OUT:tclsqlite.lo
-tclsqlite-shell.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP)
- $(LTCOMPILE) $(NO_WARN) -DTCLSH -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c
+tclsqlite-shell.lo: tclsqlite-ex.c $(HDR) $(SQLITE_TCL_DEP)
+ $(LTCOMPILE) $(NO_WARN) -DTCLSH -DBUILD_sqlite -I$(TCLINCDIR) -c tclsqlite-ex.c
tclsqlite3.exe: tclsqlite-shell.lo $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
$(LTLINK) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) /OUT:$@ tclsqlite-shell.lo $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS)
@@ -2381,6 +2325,8 @@ keywordhash.h: $(TOP)\tool\mkkeywordhash.c mkkeywordhash.exe
# Source and header files that shell.c depends on
SHELL_DEP = \
$(TOP)\src\shell.c.in \
+ $(TOP)\ext\qrf\qrf.c \
+ $(TOP)\ext\qrf\qrf.h \
$(TOP)\ext\expert\sqlite3expert.c \
$(TOP)\ext\expert\sqlite3expert.h \
$(TOP)\ext\intck\sqlite3intck.c \
@@ -2544,9 +2490,9 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_STRICT_SUBTYPE=1
TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2)
TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C)
!IF $(USE_AMALGAMATION)==0
-TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC0)
+TESTFIXTURE_SRC = $(TESTSRC) tclsqlite-ex.c $(TESTFIXTURE_SRC0)
!ELSE
-TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC1)
+TESTFIXTURE_SRC = $(TESTSRC) tclsqlite-ex.c $(TESTFIXTURE_SRC1)
!ENDIF
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
@@ -2655,6 +2601,22 @@ devtest: srctree-check sourcetest
mdevtest:
$(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest
+# Rerun test cases that failed on the previous testrunner invocation
+#
+retest:
+ $(TCLSH_CMD) $(TOP)\test\testrunner.tcl retest
+
+# Show all errors from the most reason testrunner invocation
+#
+errors:
+ $(TCLSH_CMD) $(TOP)\test\testrunner.tcl errors
+
+# Show the status of the current testrunner invocation,
+# updated every couple of seconds
+#
+status:
+ $(TCLSH_CMD) $(TOP)\test\testrunner.tcl status -d 2
+
# Validate that various generated files in the source tree
# are up-to-date.
#
@@ -2678,17 +2640,17 @@ smoketest: $(TESTPROGS)
@set PATH=$(LIBTCLPATH);$(PATH)
.\testfixture.exe $(TOP)\test\main.test $(TESTOPTS)
-shelltest: $(TESTPROGS)
- .\testfixture.exe $(TOP)\test\permutations.test shell
+shelltest:
+ $(TCLSH_CMD) $(TOP)\test\testrunner.tcl release shell
-sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE_TCL_DEP)
+sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) tclsqlite-ex.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE_TCL_DEP)
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl -DINCLUDE_SQLITE3_C $(TOP)\tool\sqlite3_analyzer.c.in > $@
sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS)
$(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_analyzer.c \
/link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(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 tclsqlite-ex.c $(TOP)\tool\sqltclsh.tcl $(TOP)\ext\misc\appendvfs.c $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in >sqltclsh.c
sqltclsh.exe: sqltclsh.c $(SHELL_CORE_DEP) $(LIBRESOBJS)
@@ -2698,23 +2660,6 @@ sqltclsh.exe: sqltclsh.c $(SHELL_CORE_DEP) $(LIBRESOBJS)
sqlite3_expert.exe: $(SQLITE3C) $(TOP)\ext\expert\sqlite3expert.h $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c
$(LTLINK) $(NO_WARN) $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c $(SQLITE3C) $(TLIBS)
-CHECKER_DEPS =\
- $(TOP)\tool\mkccode.tcl \
- sqlite3.c \
- $(TOP)\src\tclsqlite.c \
- $(TOP)\ext\repair\sqlite3_checker.tcl \
- $(TOP)\ext\repair\checkindex.c \
- $(TOP)\ext\repair\checkfreelist.c \
- $(TOP)\ext\misc\btreeinfo.c \
- $(TOP)\ext\repair\sqlite3_checker.c.in
-
-sqlite3_checker.c: $(CHECKER_DEPS)
- $(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\ext\repair\sqlite3_checker.c.in > $@
-
-sqlite3_checker.exe: sqlite3_checker.c $(LIBRESOBJS)
- $(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_checker.c \
- /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS)
-
dbdump.exe: $(TOP)\ext\misc\dbdump.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
$(LTLINK) $(NO_WARN) -DDBDUMP_STANDALONE $(TOP)\ext\misc\dbdump.c $(SQLITE3C) \
/link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS)
@@ -2747,6 +2692,9 @@ showwal.exe: $(TOP)\tool\showwal.c $(SQLITE3C) $(SQLITE3H)
showshm.exe: $(TOP)\tool\showshm.c
$(LTLINK) $(NO_WARN) $(TOP)\tool\showshm.c /link $(LDFLAGS) $(LTLINKOPTS)
+showtmlog.exe: $(TOP)\tool\showtmlog.c
+ $(LTLINK) $(NO_WARN) $(TOP)\tool\showtmlog.c /link $(LDFLAGS) $(LTLINKOPTS)
+
index_usage.exe: $(TOP)\tool\index_usage.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
$(TOP)\tool\index_usage.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
@@ -2938,6 +2886,7 @@ env:
@echo USE_STDCALL = $(USE_STDCALL)
@echo USE_ZLIB = $(USE_ZLIB)
@echo XCOMPILE = $(XCOMPILE)
+ @echo ZLIBCFLAGS = $(ZLIBCFLAGS)
@echo ZLIBDIR = $(ZLIBDIR)
@echo ZLIBINCDIR = $(ZLIBINCDIR)
@echo ZLIBLIBDIR = $(ZLIBLIBDIR)
diff --git a/README.md b/README.md
index d44bf5cb7..7ec5042e5 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
SQLite Source Repository
This repository contains the complete source code for the
-[SQLite database engine](https://sqlite.org/), including
-many tests. Additional tests and most documentation
+[SQLite database engine](https://sqlite.org/) going back
+to 2000-05-29. The tree includes many tests and some
+documentation, though additional tests and most documentation
are managed separately.
See the [on-line documentation](https://sqlite.org/) for more information
@@ -99,16 +100,16 @@ script found at the root of the source tree. Then run "make".
For example:
- apt install gcc make tcl-dev ;# Make sure you have all the necessary build tools
+ apt install gcc make tcl-dev ;# Install the necessary build tools
tar xzf sqlite.tar.gz ;# Unpack the source tree into "sqlite"
- mkdir bld ;# Build will occur in a sibling directory
+ mkdir bld ;# Build happens in a sibling directory
cd bld ;# Change to the build directory
../sqlite/configure ;# Run the configure script
- make sqlite3 ;# Builds the "sqlite3" command-line tool
- make sqlite3.c ;# Build the "amalgamation" source file
- make sqldiff ;# Builds the "sqldiff" command-line tool
- # Makefile targets below this point require tcl-dev
- make tclextension-install ;# Build and install the SQLite TCL extension
+ make sqlite3 ;# The "sqlite3" command-line tool
+ make sqlite3.c ;# The "amalgamation" source file
+ make sqldiff ;# The "sqldiff" command-line tool
+ #### Targets below require tcl-dev ####
+ make tclextension-install ;# Install the SQLite TCL extension
make devtest ;# Run development tests
make releasetest ;# Run full release tests
make sqlite3_analyzer ;# Builds the "sqlite3_analyzer" tool
@@ -116,14 +117,15 @@ For example:
See the makefile for additional targets. For debugging builds, the
core developers typically run "configure" with options like this:
- ../sqlite/configure --enable-all --enable-debug CFLAGS='-O0 -g'
+ ../sqlite/configure --all --debug CFLAGS='-O0 -g'
For release builds, the core developers usually do:
- ../sqlite/configure --enable-all
+ ../sqlite/configure --all
-Almost all makefile targets require a "tclsh" TCL interpreter version 8.6 or
-later. The "tclextension-install" target and the test targets that follow
+Core deliverables (sqlite3.c, sqlite3) can be built without a TCL, but
+many makefile targets require a "tclsh" TCL interpreter version 8.6
+or later. The "tclextension-install" target and the test targets that follow
all require TCL development libraries too. ("apt install tcl-dev"). It is
helpful, but is not required, to install the SQLite TCL extension (the
"tclextension-install" target) prior to running tests. The "releasetest"
@@ -133,20 +135,20 @@ On "make" command-lines, one can add "OPTIONS=..." to specify additional
compile-time options over and above those set by ./configure. For example,
to compile with the SQLITE_OMIT_DEPRECATED compile-time option, one could say:
- ./configure --enable-all
+ ./configure --all
make OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3
-The configure script uses autoconf 2.61 and libtool. If the configure
-script does not work out for you, there is a generic makefile named
-"Makefile.linux-gcc" in the top directory of the source tree that you
-can copy and edit to suit your needs. Comments on the generic makefile
-show what changes are needed.
+The configure script uses [autosetup](https://msteveb.github.io/autosetup/).
+If the configure script does not work out for you, there is a generic
+makefile named "Makefile.linux-gcc" in the top directory of the source tree
+that you can copy and edit to suit your needs. Comments on the generic
+makefile show what changes are needed.
## Compiling for Windows Using MSVC
On Windows, everything can be compiled with MSVC.
-You will also need a working installation of TCL if you want to run tests.
-TCL is not required if you just want to build SQLite itself.
+You will also need a working installation of TCL if you want to run tests,
+though TCL is not required if you just want to build SQLite itself.
See the [compile-for-windows.md](doc/compile-for-windows.md) document for
additional information about how to install MSVC and TCL and configure your
build environment.
@@ -156,24 +158,25 @@ TCL library, using a command like this:
set TCLDIR=c:\Tcl
-SQLite uses "tclsh.exe" as part of the build process, and so that
-program will need to be somewhere on your %PATH%. SQLite itself
-does not contain any TCL code, but it does use TCL to run tests.
-You may need to install TCL development
-libraries in order to successfully complete some makefile targets.
-It is helpful, but is not required, to install the SQLite TCL extension
-(the "tclextension-install" target) prior to running tests.
-
-Build using Makefile.msc. Example:
-
- nmake /f Makefile.msc sqlite3.exe
- nmake /f Makefile.msc sqlite3.c
- nmake /f Makefile.msc sqldiff.exe
- # Makefile targets below this point require TCL development libraries
- nmake /f Makefile.msc tclextension-install
- nmake /f Makefile.msc devtest
- nmake /f Makefile.msc releasetest
- nmake /f Makefile.msc sqlite3_analyzer.exe
+SQLite itself does not contain any TCL code, but it does use TCL to run
+tests. You may need to install TCL development libraries in order to
+successfully complete some makefile targets. It is helpful, but is not
+required, to install the SQLite TCL extension (the "tclextension-install"
+target) prior to running tests.
+
+The source tree contains a "make.bat" file that allows the same "make"
+commands of Unix to work on Windows. In the following, you can substitute
+"nmake /f Makefile.msc" in place of "make", if you prefer to avoid this BAT
+file:
+
+ make sqlite3.exe
+ make sqlite3.c
+ make sqldiff.exe
+ #### Targets below require TCL development libraries ####
+ make tclextension-install
+ make devtest
+ make releasetest
+ make sqlite3_analyzer.exe
There are many other makefile targets. See comments in Makefile.msc for
details.
@@ -181,7 +184,7 @@ details.
As with the unix Makefile, the OPTIONS=... argument can be passed on the nmake
command-line to enable new compile-time options. For example:
- nmake /f Makefile.msc OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3.exe
+ make OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3.exe
## Source Tree Map
@@ -194,8 +197,7 @@ command-line to enable new compile-time options. For example:
* **test/** - This directory and its subdirectories contains code used
for testing. Files that end in "`.test`" are TCL scripts that run
tests using an augmented TCL interpreter named "testfixture". Use
- a command like "`make testfixture`" (unix) or
- "`nmake /f Makefile.msc testfixture.exe`" (windows) to build that
+ a command like "`make testfixture`" to build that
augmented TCL interpreter, then run individual tests using commands like
"`testfixture test/main.test`". This test/ subdirectory also contains
additional C code modules and scripts for other kinds of testing.
@@ -377,10 +379,11 @@ implementation. It will not be the easiest library in the world to hack.
(and some other test programs too) is built and run when you type
"make test".
- * **VERSION**, **manifest**, and **manifest.uuid** - These files define
- the current SQLite version number. The "VERSION" file is human generated,
- but the "manifest" and "manifest.uuid" files are automatically generated
- by the [Fossil version control system](https://fossil-scm.org/).
+ * **VERSION**, **manifest**, **manifest.tags**, and **manifest.uuid** -
+ These files define the current SQLite version number. The "VERSION" file
+ is human generated, but the "manifest", "manifest.tags", and
+ "manifest.uuid" files are automatically generated by the
+ [Fossil version control system](https://fossil-scm.org/).
There are many other source files. Each has a succinct header comment that
describes its purpose and role within the larger system.
@@ -406,10 +409,6 @@ makefile:
> make verify-source
-Or on windows:
-
-> nmake /f Makefile.msc verify-source
-
Using the makefile to verify source integrity is good for detecting
accidental changes to the source tree, but malicious changes could be
hidden by also modifying the makefiles.
diff --git a/VERSION b/VERSION
index 0306a564d..7ac0b0a68 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.51.2
+3.52.0
diff --git a/autoconf/Makefile.in b/autoconf/Makefile.in
index c938ffe1b..8e6b358be 100644
--- a/autoconf/Makefile.in
+++ b/autoconf/Makefile.in
@@ -117,19 +117,6 @@ sqlite_cfg.h: $(AS_AUTO_DEF)
# CFLAGS for sqlite3$(T.exe)
#
SHELL_OPT ?= @OPT_SHELL@
-SHELL_OPT += -DSQLITE_DQS=0
-SHELL_OPT += -DSQLITE_ENABLE_FTS4
-#SHELL_OPT += -DSQLITE_ENABLE_FTS5
-SHELL_OPT += -DSQLITE_ENABLE_RTREE
-SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS
-SHELL_OPT += -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
-SHELL_OPT += -DSQLITE_ENABLE_STMTVTAB
-SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB
-SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
-SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
-SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC
-SHELL_OPT += -DSQLITE_ENABLE_PERCENTILE
-SHELL_OPT += -DSQLITE_STRICT_SUBTYPE=1
#
# Library-level feature flags
@@ -295,7 +282,7 @@ DIST_FILES := \
README.txt VERSION \
auto.def autosetup configure tea \
sqlite3.h sqlite3.c shell.c sqlite3ext.h \
- Makefile.in Makefile.msc Makefile.fallback \
+ Makefile.in Makefile.msc Makefile.fallback make.bat \
sqlite3.rc sqlite3rc.h Replace.cs \
sqlite3.pc.in sqlite3.1
diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc
index 365e1f0fb..34e41d8aa 100644
--- a/autoconf/Makefile.msc
+++ b/autoconf/Makefile.msc
@@ -101,13 +101,6 @@ NO_WARN = $(NO_WARN) -wd4210 -wd4232 -wd4244 -wd4305 -wd4306 -wd4702 -wd4706
!ENDIF
!ENDIF
-# Set this non-0 to use the library paths and other options necessary for
-# Windows Phone 8.1.
-#
-!IFNDEF USE_WP81_OPTS
-USE_WP81_OPTS = 0
-!ENDIF
-
# Set this non-0 to split the SQLite amalgamation file into chunks to
# be used for debugging with Visual Studio.
#
@@ -156,14 +149,6 @@ USE_NATIVE_LIBPATHS = 0
USE_RC = 1
!ENDIF
-# Set this non-0 to compile binaries suitable for the WinRT environment.
-# This setting does not apply to any binaries that require Tcl to operate
-# properly (i.e. the text fixture, etc).
-#
-!IFNDEF FOR_WINRT
-FOR_WINRT = 0
-!ENDIF
-
# Set this non-0 to compile binaries suitable for the UWP environment.
# This setting does not apply to any binaries that require Tcl to operate
# properly (i.e. the text fixture, etc).
@@ -563,10 +548,14 @@ RCC = $(RC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) $(RCOPTS) $(RCCOPTS)
!IF "$(PLATFORM)"=="x86"
CORE_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
SHELL_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
+
+
!ELSE
!IFNDEF PLATFORM
CORE_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
SHELL_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
+
+
!ELSE
CORE_CCONV_OPTS =
SHELL_CCONV_OPTS =
@@ -667,18 +656,6 @@ SHELL_LINK_OPTS = $(SHELL_CORE_LIB)
TCC = $(TCC) -FAcs
!ENDIF
-# When compiling the library for use in the WinRT environment,
-# the following compile-time options must be used as well to
-# disable use of Win32 APIs that are not available and to enable
-# use of Win32 APIs that are specific to Windows 8 and/or WinRT.
-#
-!IF $(FOR_WINRT)!=0
-TCC = $(TCC) -DSQLITE_OS_WINRT=1
-RCC = $(RCC) -DSQLITE_OS_WINRT=1
-TCC = $(TCC) -DWINAPI_FAMILY=WINAPI_FAMILY_APP
-RCC = $(RCC) -DWINAPI_FAMILY=WINAPI_FAMILY_APP
-!ENDIF
-
# C compiler options for the Windows 10 platform (needs MSVC 2015).
#
!IF $(FOR_WIN10)!=0
@@ -691,7 +668,7 @@ BCC = $(BCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
# USE_CRT_DLL option is set to force dynamically linking to the
# MSVC runtime library.
#
-!IF $(FOR_WINRT)!=0 || $(USE_CRT_DLL)!=0
+!IF $(USE_CRT_DLL)!=0
!IF $(DEBUG)>1
TCC = $(TCC) -MDd
BCC = $(BCC) -MDd
@@ -714,16 +691,6 @@ ZLIBCFLAGS = -nologo -MT -W3 -O2 -Oy- -Zi
!ENDIF
-# Define -DNDEBUG to compile without debugging (i.e., for production usage)
-# Omitting the define will cause extra debugging code to be inserted and
-# includes extra comments when "EXPLAIN stmt" is used.
-#
-!IF $(DEBUG)==0
-TCC = $(TCC) -DNDEBUG
-BCC = $(BCC) -DNDEBUG
-RCC = $(RCC) -DNDEBUG
-!ENDIF
-
!IF $(DEBUG)>0 || $(API_ARMOR)!=0 || $(FOR_WIN10)!=0
TCC = $(TCC) -DSQLITE_ENABLE_API_ARMOR=1
RCC = $(RCC) -DSQLITE_ENABLE_API_ARMOR=1
@@ -913,56 +880,6 @@ LTLINKOPTS = /NOLOGO
LTLIBOPTS = /NOLOGO
!ENDIF
-# When compiling for use in the WinRT environment, the following
-# linker option must be used to mark the executable as runnable
-# only in the context of an application container.
-#
-!IF $(FOR_WINRT)!=0
-LTLINKOPTS = $(LTLINKOPTS) /APPCONTAINER
-!IF "$(VISUALSTUDIOVERSION)"=="12.0" || "$(VISUALSTUDIOVERSION)"=="14.0"
-!IFNDEF STORELIBPATH
-!IF "$(PLATFORM)"=="x86"
-STORELIBPATH = $(CRTLIBPATH)\store
-!ELSEIF "$(PLATFORM)"=="x64"
-STORELIBPATH = $(CRTLIBPATH)\store\amd64
-!ELSEIF "$(PLATFORM)"=="ARM"
-STORELIBPATH = $(CRTLIBPATH)\store\arm
-!ELSE
-STORELIBPATH = $(CRTLIBPATH)\store
-!ENDIF
-!ENDIF
-STORELIBPATH = $(STORELIBPATH:\\=\)
-LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(STORELIBPATH)"
-!ENDIF
-!ENDIF
-
-# When compiling for Windows Phone 8.1, an extra library path is
-# required.
-#
-!IF $(USE_WP81_OPTS)!=0
-!IFNDEF WP81LIBPATH
-!IF "$(PLATFORM)"=="x86"
-WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\x86
-!ELSEIF "$(PLATFORM)"=="ARM"
-WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\ARM
-!ELSE
-WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\x86
-!ENDIF
-!ENDIF
-!ENDIF
-
-# When compiling for Windows Phone 8.1, some extra linker options
-# are also required.
-#
-!IF $(USE_WP81_OPTS)!=0
-!IFDEF WP81LIBPATH
-LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(WP81LIBPATH)"
-!ENDIF
-LTLINKOPTS = $(LTLINKOPTS) /DYNAMICBASE
-LTLINKOPTS = $(LTLINKOPTS) WindowsPhoneCore.lib RuntimeObject.lib PhoneAppModelHost.lib
-LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:kernel32.lib /NODEFAULTLIB:ole32.lib
-!ENDIF
-
# When compiling for UWP or the Windows 10 platform, some extra linker
# options are also required.
#
@@ -986,9 +903,9 @@ LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib
# If either debugging or symbols are enabled, enable PDBs.
#
!IF $(DEBUG)>1 || $(SYMBOLS)!=0
-LDFLAGS = /DEBUG $(LDOPTS)
+LDFLAGS = /NODEFAULTLIB:msvcrt /DEBUG $(LDOPTS)
!ELSE
-LDFLAGS = $(LDOPTS)
+LDFLAGS = /NODEFAULTLIB:msvcrt $(LDOPTS)
!ENDIF
@@ -1024,6 +941,7 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_PERCENTILE=1
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1
+SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB=1
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_STRICT_SUBTYPE=1
!ENDIF
@@ -1072,6 +990,8 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT
/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
+tclsqlite-ex.c:
+
# Rule to build the amalgamation
#
sqlite3.lo: $(SQLITE3C)
diff --git a/autosetup/README.md b/autosetup/README.md
index c8da5c643..2560bfab9 100644
--- a/autosetup/README.md
+++ b/autosetup/README.md
@@ -375,6 +375,11 @@ configure process, and check it in.
Patching Autosetup for Project-local Changes
------------------------------------------------------------------------
+The autosetup files require the following patches after updating
+from their upstream sources:
+
+### `--debug` flag
+
Autosetup reserves the flag name **`--debug`** for its own purposes,
and its own special handling of `--enable-...` flags makes `--debug`
an alias for `--enable-debug`. As this project has a long history of
@@ -388,6 +393,11 @@ If autosetup is upgraded and this patch is _not_ applied the invoking
`./configure` will fail loudly because of the declaration of the
`debug` flag in `auto.def` - duplicated flags are not permitted.
+### Fail on `malloc()` error
+
+See [check-in 72c8a5b94cdf5d](/info/72c8a5b94cdf5d).
+
+
Branch-specific Customization
========================================================================
diff --git a/autosetup/jimsh0.c b/autosetup/jimsh0.c
index 1a6453d0c..0f0a89088 100644
--- a/autosetup/jimsh0.c
+++ b/autosetup/jimsh0.c
@@ -7409,11 +7409,13 @@ void *JimDefaultAllocator(void *ptr, size_t size)
free(ptr);
return NULL;
}
- else if (ptr) {
- return realloc(ptr, size);
- }
else {
- return malloc(size);
+ void *p = realloc(ptr, size);
+ if( p==0 ){
+ fprintf(stderr,"Out of memory\n");
+ exit(1);
+ }
+ return p;
}
}
diff --git a/autosetup/proj.tcl b/autosetup/proj.tcl
index 86f4df44e..caa679ad6 100644
--- a/autosetup/proj.tcl
+++ b/autosetup/proj.tcl
@@ -1842,7 +1842,7 @@ proc proj-setup-autoreconfig {defName} {
}
#
-# @prop-append-to defineName args...
+# @prop-define-append defineName args...
#
# A proxy for Autosetup's [define-append]. Appends all non-empty $args
# to [define-append $defineName].
@@ -1873,7 +1873,7 @@ proc proj-define-append {defineName args} {
# but it is technically correct and still relevant on some
# environments.
#
-# See: proj-append-to
+# See: proj-define-append
#
proc proj-define-amend {args} {
set defName ""
diff --git a/autosetup/sqlite-config.tcl b/autosetup/sqlite-config.tcl
index 7c798b31a..fe1b355ab 100644
--- a/autosetup/sqlite-config.tcl
+++ b/autosetup/sqlite-config.tcl
@@ -557,7 +557,25 @@ proc proc-debug {msg} {
}
define OPT_FEATURE_FLAGS {} ; # -DSQLITE_OMIT/ENABLE flags.
-define OPT_SHELL {} ; # Feature-related CFLAGS for the sqlite3 CLI app
+#
+# OPT_SHELL = feature-related CFLAGS for the sqlite3 CLI app. The
+# list's initial values are defaults which are always applied and not
+# affected by --feature-flags. The list is appended to by various
+# --feature-flags.
+define OPT_SHELL {
+ -DSQLITE_DQS=0
+ -DSQLITE_ENABLE_FTS4
+ -DSQLITE_ENABLE_RTREE
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS
+ -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+ -DSQLITE_ENABLE_STMTVTAB
+ -DSQLITE_ENABLE_DBPAGE_VTAB
+ -DSQLITE_ENABLE_DBSTAT_VTAB
+ -DSQLITE_ENABLE_BYTECODE_VTAB
+ -DSQLITE_ENABLE_OFFSET_SQL_FUNC
+ -DSQLITE_ENABLE_PERCENTILE
+ -DSQLITE_STRICT_SUBTYPE=1
+}
########################################################################
# Adds $args, if not empty, to OPT_FEATURE_FLAGS. If the first arg is
# -shell then it strips that arg and passes the remaining args the
@@ -661,6 +679,7 @@ proc sqlite-check-common-system-deps {} {
define HAVE_ZLIB 1
define LDFLAGS_ZLIB -lz
sqlite-add-shell-opt -DSQLITE_HAVE_ZLIB=1
+ sqlite-add-feature-flag -DSQLITE_HAVE_ZLIB=1
} else {
define HAVE_ZLIB 0
define LDFLAGS_ZLIB ""
@@ -787,7 +806,8 @@ proc sqlite-handle-common-feature-flags {} {
sqlite-add-feature-flag -DSQLITE_ENABLE_MEMSYS3
}
}
- scanstatus -DSQLITE_ENABLE_STMT_SCANSTATUS {}
+ bytecode-vtab -DSQLITE_ENABLE_BYTECODE_VTAB {}
+ scanstatus {-DSQLITE_ENABLE_STMT_SCANSTATUS -DSQLITE_ENABLE_BYTECODE_VTAB} {}
column-metadata -DSQLITE_ENABLE_COLUMN_METADATA {}
dbpage -DSQLITE_ENABLE_DBPAGE_VTAB {}
dbstat -DSQLITE_ENABLE_DBSTAT_VTAB {}
diff --git a/doc/compile-for-unix.md b/doc/compile-for-unix.md
index ce76b97ba..659bdb038 100644
--- a/doc/compile-for-unix.md
+++ b/doc/compile-for-unix.md
@@ -22,9 +22,10 @@ guidance on building for Windows.
or .
Untar the source archive. CD into the "unix/" subfolder
of the source tree.
-
As of 2024-10-25, TCL is not longer required for many
@@ -41,6 +42,12 @@ guidance on building for Windows.
You do not need to use --with-tclsh if the tclsh you want to use is the
first one on your PATH or if you are building without TCL.
+ The SQLite developers typically add the
+ `--with-linenoise=$HOME/linenoise` option
+ to provide command-line editing. "$HOME/linenoise" is a directory
+ that contains [linenoise](https://github.com/antirez/linenoise) source
+ code files, `linenoise.c` and `linenoise.h`.
+
6. Run the "`Makefile`" makefile with an appropriate target.
Examples:
diff --git a/doc/compile-for-windows.md b/doc/compile-for-windows.md
index 0e59c83fe..30536d5fd 100644
--- a/doc/compile-for-windows.md
+++ b/doc/compile-for-windows.md
@@ -180,3 +180,39 @@ statically linked so that it does not depend on separate DLL:
6. After your executable is built, you can verify that it does not
depend on the TCL DLL by running:
dumpbin /dependents sqlite3_analyzer.exe
+
+## Linking Against ZLIB
+
+Some feature (such as zip-file support in the CLI) require the ZLIB
+compression library. That library is more or less universally available
+on unix platforms, but is seldom provided on Windows. You will probably
+need to provide it yourself. Here the the steps needed:
+
+ 1. Download the zlib-1.3.1.tar.gz tarball (or a similar version).
+ Unpack the tarball sources. You can put them wherever you like.
+ For the purposes of this document, let's assume you put the source
+ tree in c:\\zlib-64. Note: If you are building for both x64 and
+ x86, you will need separate builds of ZLIB for each, thus separate
+ build directories.
+
+ 2. Before building SQLite (as described above) first make these
+ environment changes. The lead-programmer for SQLite (who writes
+ these words) has BAT files named "env-x64.bat" and "env-x32.bat"
+ and "env-arm64.bat" that make these changes, and he runs those
+ BAT file whenever he starts a new shell. These are the settings
+ needed:
+
+ set USE_ZLIB=1
+ set BUILD_ZLIB=0
+ set ZLIBDIR=c:\\zlib-64
+
+
+ 3. Because the settings in step 2 specify "BUILD_ZLIB=0", you will need
+ to build the library at least once. I recommand:
+
+ make clean sqlite3.exe BUILD_ZLIB=1
+
+
+ 4. After making the environment changes specified in steps 1 through 3
+ above, you then build and test SQLite as you normally would. The
+ environment variable changes will cause ZLIB to be linked automatically.
diff --git a/doc/lemon.html b/doc/lemon.html
index 965f305c0..a994b396b 100644
--- a/doc/lemon.html
+++ b/doc/lemon.html
@@ -696,6 +696,7 @@
The wildcard token is only matched if there are no alternatives.
-
4.4.26 The %realloc and %free directives
+
4.4.26 The %realloc, %free, and
+%stack_size_limit directives
The %realloc and %free directives defines function
-that allocate and free heap memory. The signatures of these functions
-should be the same as the realloc() and free() functions from the standard
-C library.
-
-
If both of these functions are defined
-then these functions are used to allocate and free
-memory for supplemental parser stack space, if the initial
-parse stack space is exceeded. The initial parser stack size
+that allocate and free heap memory. The signatures and semantics of
+these functions are similar to the realloc() and free() functions from
+the standard C library, except that these functions take an extra
+parameter at the end that is determined by %extra_context. If
+%extra_context is not defined, then the extra argument is 0. The
+extra parameter provides the capability to do better error reporting
+in the event of a memory allocation error, and/or to use an alternative
+private application heap.
+
+
If both of these functions are defined then they are used to
+allocate and free memory for supplemental parser stack space, if
+the initial parse stack space is exceeded. The initial parser stack size
is specified by either %stack_size or the
-DYYSTACKDEPTH compile-time flag.
+
The %stack_size_limit directive defines a function that returns
+the maximum allowed parser stack size. If this diretive does not exist,
+no size limit is enforced. This function takes a single argument which
+is the %extra_context value or "0" if %extra_context is not defined.
+The function should return an integer that is the maximum
+number of parser stack entries. If more stack space
+than this is needed, the %stack_overflow code is invoked.
+
2.1. Organization of Tcl Tests
@@ -26,17 +32,40 @@
The testrunner.tcl program is a Tcl script used to run multiple SQLite
tests in parallel, thus reducing testing time on multi-core machines.
-It supports the following types of tests:
+The testrunner.tcl supports running tests that based on `testfixture`,
+`sqlite3`, and `fuzzcheck`.
+
+
+## 1.1 Running testrunner.tcl
+
+The testrunner.tcl script is located in the "test" subdirectory of the
+SQLite source tree. So if your shell is current positioned at the top
+of the source tree, you would normally run the script using the command:
+"test/testrunner.tcl". On Windows, you have to specify the
+`tclsh` interpreter command first, like this:
+"tclsh test/testrunner.tcl".
+
+In this document, we will assume that you are on a unix-like OS
+(not on Windows) and that your current directory is the root
+of the SQLite source tree, and so all invocations of the testrunner.tcl
+script will be of the form "test/testrunner.tcl". If you
+are in a different directory, then make appropriate adjustments to
+the path. On Windows, add the "tclsh" interpreter command
+up front.
- * Tcl test scripts.
+
+## 1.2 Run using make
- * Fuzzcheck tests, including using an external fuzzcheck database.
+The standard Makefiles for SQLite include targets that invoke
+testrunner.tcl. So the following commands also run testrunner.tcl:
- * Tests run with `make` commands. Examples:
- - `make devtest`
- - `make releasetest`
- - `make sdevtest`
- - `make testrunner`
+ * `make devtest`
+ * `make releasetest`
+ * `make sdevtest`
+ * `make testrunner`
+
+
+## 1.3 Outputs from testrunner.tcl
The testrunner.tcl program stores output of all tests and builds run in
log file **testrunner.log**, created in the current working directory.
@@ -54,17 +83,19 @@ A useful query might be:
```
You can get a summary of errors in a prior run by invoking commands like
-these:
+those shown below. Note that the testrunner.tcl script can be run directly
+on unix systems (including Macs) but you will need to add tclsh
+to the front on Windows.
```
- tclsh $(TESTDIR)/testrunner.tcl errors
- tclsh $(TESTDIR)/testrunner.tcl errors -v
+ test/testrunner.tcl errors
+ test/testrunner.tcl errors -v
```
Running the command:
```
- tclsh $(TESTDIR)/testrunner.tcl status
+ test/testrunner.tcl status
```
in the directory containing the testrunner.db database runs various queries
@@ -73,32 +104,43 @@ A good way to keep and eye on test progress is to run either of the two
following commands:
```
- watch tclsh $(TESTDIR)/testrunner.tcl status
- tclsh $(TESTDIR)/testrunner.tcl status -d 2
+ watch test/testrunner.tcl status
+ test/testrunner.tcl status -d 2
```
Both of the commands above accomplish about the same thing, but the second
one has the advantage of not requiring "watch" to be installed on your
system.
-Sometimes testrunner.tcl uses the `testfixture` binary that it is run with
-to run tests (see "Binary Tests" below). Sometimes it builds testfixture and
+Sometimes testrunner.tcl uses the `testfixture` and `sqlite3` binaries that
+are in the directory from which testrunner.tcl is run.
+(see "Binary Tests" below). Sometimes it builds testfixture and
other binaries in specific configurations to test (see "Source Tests").
+
+## 1.4 Built-in help
+
+Run this command:
+
+```
+ test/testrunner.tcl help
+```
+
+To get a summary of all of the various command-line options available
+with testrunner.tcl
+
+
# 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.
+test scripts using whatever `testfixture` binary (and maybe also the `sqlite3`
+binary, depending on the test) that is found in the directory from which
+testrunner.tcl is launched. So typically, one must first run something
+like "`make testfixture sqlite3`" before launching binary tests. In other
+words, testrunner.tcl does not automatically build the binaries under test
+for binary tests. The testrunner.tcl expects the binaries to be available
+already.
The following sub-sections describe the various options that can be
passed to testrunner.tcl to test binary testfixture builds.
@@ -140,22 +182,22 @@ are defined in file *testrunner_data.tcl*.
To run the "veryquick" test set, use either of the following:
```
- ./testfixture $TESTDIR/testrunner.tcl
- ./testfixture $TESTDIR/testrunner.tcl veryquick
+ test/testrunner.tcl
+ test/testrunner.tcl veryquick
```
To run the "full" test suite:
```
- ./testfixture $TESTDIR/testrunner.tcl full
+ test/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*'
+ test/testrunner.tcl fts5%
+ test/testrunner.tcl 'fts5*'
```
Strictly speaking, for a test to be run the pattern must match the script
@@ -167,7 +209,7 @@ characters specified as part of the pattern are transformed to "\*".
To run "all" tests (full + permutations):
```
- ./testfixture $TESTDIR/testrunner.tcl all
+ test/testrunner.tcl all
```
@@ -186,10 +228,15 @@ If there is no permutation, the individual test script may be run with:
Or, if the failure occured as part of a permutation:
```
- ./testfixture $TESTDIR/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT
+ test/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT
```
-TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT?
+One can also rerun all tests that failed or did not complete
+in the previous invocation by typing:
+
+```
+ test/testrunner.tcl retest
+```
# 3. Source Code Tests
@@ -204,11 +251,9 @@ other tests. The advantages of this are that:
* 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.
+The testrunner.tcl commands described in this section do not require that
+the testfixture and/or sqlite3 binaries be built ahead of time. Those
+binaries will be constructed automatically.
## 3.1. Commands to Run SQLite Tests
@@ -218,7 +263,7 @@ the `make fuzztest` target once for each of two --enable-all builds - one
with debugging enabled and one without:
```
- tclsh $TESTDIR/testrunner.tcl mdevtest
+ test/testrunner.tcl mdevtest
```
In other words, it is equivalent to running:
@@ -227,13 +272,13 @@ In other words, it is equivalent to running:
$TOP/configure --enable-all --enable-debug
make fuzztest
make testfixture
- ./testfixture $TOP/test/testrunner.tcl veryquick
+ $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
+ $TOP/test/testrunner.tcl veryquick
```
The **sdevtest** command is identical to the mdevtest command, except that the
@@ -241,7 +286,7 @@ 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
+ test/testrunner.tcl sdevtest
```
The **release** command runs lots of tests under lots of builds. It runs
@@ -250,7 +295,7 @@ on Linux, Windows or OSX. Refer to *testrunner\_data.tcl* for the details
of the specific tests run.
```
- tclsh $TESTDIR/testrunner.tcl release
+ test/testrunner.tcl release
```
As with source code tests, one or more patterns
@@ -258,7 +303,7 @@ may be appended to any of the above commands (mdevtest, sdevtest or release).
Pattern matching is used for both Tcl tests and fuzz tests.
```
- tclsh $TESTDIR/testrunner.tcl release rtree%
+ test/testrunner.tcl release rtree%
```
@@ -268,14 +313,14 @@ 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
+ test/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
+ test/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest
```
@@ -295,10 +340,10 @@ 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
+ test/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
+ test/testrunner.tcl script Have-Not > make.bat
```
The generated bash or \*.bat file script accepts a single argument - a makefile
@@ -321,7 +366,7 @@ Thus, for example, to run a full releasetest including an external
dbsqlfuzz database, run a command like one of these:
```
- tclsh test/testrunner.tcl releasetest --fuzzdb ../fuzz/20250415.db
+ test/testrunner.tcl releasetest --fuzzdb ../fuzz/20250415.db
FUZZDB=../fuzz/20250415.db make releasetest
nmake /f Makefile.msc FUZZDB=../fuzz/20250415.db releasetest
```
@@ -331,7 +376,7 @@ databases. So if you want to run *only* tests involving the external
database, you can use a command something like this:
```
- tclsh test/testrunner.tcl releasetest 20250415 --fuzzdb ../fuzz/20250415.db
+ test/testrunner.tcl releasetest 20250415 --fuzzdb ../fuzz/20250415.db
```
@@ -345,7 +390,7 @@ required by a test, not to run any actual tests. For example:
```
# Build binaries required by release test.
- tclsh $TESTDIR/testrunner.tcl --buildonly release"
+ test/testrunner.tcl --buildonly release"
```
The **--dryrun** option prevents testrunner.tcl from building any binaries
@@ -354,7 +399,7 @@ would normally execute into the testrunner.log file. Example:
```
# Log the shell commmands that make up the mdevtest test.
- tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest"
+ test/testrunner.tcl --dryrun mdevtest"
```
The **--explain** option is similar to --dryrun in that it prevents
@@ -364,7 +409,7 @@ summary of all the builds and tests that would have been run.
```
# Show what builds and tests would have been run
- tclsh $TESTDIR/testrunner.tcl --explain mdevtest
+ test/testrunner.tcl --explain mdevtest
```
The **--status** option uses VT100 escape sequences to display the test
@@ -380,7 +425,7 @@ 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
+ $ test/testrunner.tcl
splitting work across 16 jobs
... more output ...
```
@@ -390,7 +435,7 @@ of real cores on the machine. This can be overridden using the "--jobs" (or -j)
switch:
```
- $ ./testfixture $TESTDIR/testrunner.tcl --jobs 8
+ $ test/testrunner.tcl --jobs 8
splitting work across 8 jobs
... more output ...
```
@@ -400,5 +445,5 @@ 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
+ $ test/testrunner.tcl njob $NEW_NUMBER_OF_JOBS
```
diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test
index 0c3b512af..aaea03711 100644
--- a/ext/expert/expert1.test
+++ b/ext/expert/expert1.test
@@ -90,7 +90,7 @@ foreach {tn setup} {
proc do_rec_test {tn sql res} {
set res [squish [string trim $res]]
set tst [subst -nocommands {
- squish [string trim [exec $::CLI test.db ".expert" {$sql;}]]
+ squish [string trim [exec $::CLI -noinit test.db ".expert" {$sql;}]]
}]
uplevel [list do_test $tn $tst $res]
}
diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c
index f178abafe..368e9b189 100644
--- a/ext/fts3/fts3.c
+++ b/ext/fts3/fts3.c
@@ -1816,9 +1816,7 @@ static int fts3CursorSeekStmt(Fts3Cursor *pCsr){
zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist);
if( !zSql ) return SQLITE_NOMEM;
p->bLock++;
- rc = sqlite3_prepare_v3(
- p->db, zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0
- );
+ rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt);
p->bLock--;
sqlite3_free(zSql);
}
@@ -3393,9 +3391,7 @@ static int fts3FilterMethod(
}
if( zSql ){
p->bLock++;
- rc = sqlite3_prepare_v3(
- p->db,zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0
- );
+ rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt);
p->bLock--;
sqlite3_free(zSql);
}else{
@@ -4018,6 +4014,7 @@ static int fts3IntegrityMethod(
UNUSED_PARAMETER(isQuick);
rc = sqlite3Fts3IntegrityCheck(p, &bOk);
+ assert( pVtab->zErrMsg==0 || rc!=SQLITE_OK );
assert( rc!=SQLITE_CORRUPT_VTAB );
if( rc==SQLITE_ERROR || (rc&0xFF)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h
index e98b90a75..556635def 100644
--- a/ext/fts3/fts3Int.h
+++ b/ext/fts3/fts3Int.h
@@ -601,6 +601,15 @@ int sqlite3Fts3Incrmerge(Fts3Table*,int,int);
(*(u8*)(p)&0x80) ? sqlite3Fts3GetVarint32(p, piVal) : (*piVal=*(u8*)(p), 1) \
)
+int sqlite3Fts3PrepareStmt(
+ Fts3Table *p, /* Prepare for this connection */
+ const char *zSql, /* SQL to prepare */
+ int bPersist, /* True to set SQLITE_PREPARE_PERSISTENT */
+ int bAllowVtab, /* True to omit SQLITE_PREPARE_NO_VTAB */
+ sqlite3_stmt **pp /* OUT: Prepared statement */
+);
+
+
/* fts3.c */
void sqlite3Fts3ErrMsg(char**,const char*,...);
int sqlite3Fts3PutVarint(char *, sqlite3_int64);
diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c
index 19dff31f0..1b8bca70f 100644
--- a/ext/fts3/fts3_write.c
+++ b/ext/fts3/fts3_write.c
@@ -98,9 +98,9 @@ typedef struct SegmentWriter SegmentWriter;
** incrementally. See function fts3PendingListAppend() for details.
*/
struct PendingList {
- int nData;
+ sqlite3_int64 nData;
char *aData;
- int nSpace;
+ sqlite3_int64 nSpace;
sqlite3_int64 iLastDocid;
sqlite3_int64 iLastCol;
sqlite3_int64 iLastPos;
@@ -273,6 +273,24 @@ struct SegmentNode {
#define SQL_UPDATE_LEVEL_IDX 38
#define SQL_UPDATE_LEVEL 39
+/*
+** Wrapper around sqlite3_prepare_v3() to ensure that SQLITE_PREPARE_FROM_DDL
+** is always set.
+*/
+int sqlite3Fts3PrepareStmt(
+ Fts3Table *p, /* Prepare for this connection */
+ const char *zSql, /* SQL to prepare */
+ int bPersist, /* True to set SQLITE_PREPARE_PERSISTENT */
+ int bAllowVtab, /* True to omit SQLITE_PREPARE_NO_VTAB */
+ sqlite3_stmt **pp /* OUT: Prepared statement */
+){
+ int f = SQLITE_PREPARE_FROM_DDL
+ |((bAllowVtab==0) ? SQLITE_PREPARE_NO_VTAB : 0)
+ |(bPersist ? SQLITE_PREPARE_PERSISTENT : 0);
+
+ return sqlite3_prepare_v3(p->db, zSql, -1, f, pp, NULL);
+}
+
/*
** This function is used to obtain an SQLite prepared statement handle
** for the statement identified by the second argument. If successful,
@@ -398,12 +416,12 @@ static int fts3SqlStmt(
pStmt = p->aStmt[eStmt];
if( !pStmt ){
- int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB;
+ int bAllowVtab = 0;
char *zSql;
if( eStmt==SQL_CONTENT_INSERT ){
zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist);
}else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){
- f &= ~SQLITE_PREPARE_NO_VTAB;
+ bAllowVtab = 1;
zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist);
}else{
zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName);
@@ -411,7 +429,7 @@ static int fts3SqlStmt(
if( !zSql ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare_v3(p->db, zSql, -1, f, &pStmt, NULL);
+ rc = sqlite3Fts3PrepareStmt(p, zSql, 1, bAllowVtab, &pStmt);
sqlite3_free(zSql);
assert( rc==SQLITE_OK || pStmt==0 );
p->aStmt[eStmt] = pStmt;
@@ -760,7 +778,9 @@ static int fts3PendingTermsAddOne(
pList = (PendingList *)fts3HashFind(pHash, zToken, nToken);
if( pList ){
- p->nPendingData -= (pList->nData + nToken + sizeof(Fts3HashElem));
+ assert( (i64)pList->nData+(i64)nToken+(i64)sizeof(Fts3HashElem)
+ <= (i64)p->nPendingData );
+ p->nPendingData -= (int)(pList->nData + nToken + sizeof(Fts3HashElem));
}
if( fts3PendingListAppend(&pList, p->iPrevDocid, iCol, iPos, &rc) ){
if( pList==fts3HashInsert(pHash, zToken, nToken, pList) ){
@@ -773,7 +793,9 @@ static int fts3PendingTermsAddOne(
}
}
if( rc==SQLITE_OK ){
- p->nPendingData += (pList->nData + nToken + sizeof(Fts3HashElem));
+ assert( (i64)p->nPendingData + pList->nData + nToken
+ + sizeof(Fts3HashElem) <= 0x3fffffff );
+ p->nPendingData += (int)(pList->nData + nToken + sizeof(Fts3HashElem));
}
return rc;
}
@@ -3574,7 +3596,7 @@ static int fts3DoRebuild(Fts3Table *p){
if( !zSql ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt);
sqlite3_free(zSql);
}
@@ -5327,7 +5349,7 @@ int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){
if( !zSql ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt);
sqlite3_free(zSql);
}
@@ -5457,7 +5479,7 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){
v = atoi(&zVal[9]);
if( v>=24 && v<=p->nPgsz-35 ) p->nNodeSize = v;
rc = SQLITE_OK;
- }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){
+ }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 11) ){
v = atoi(&zVal[11]);
if( v>=64 && v<=FTS3_MAX_PENDING_DATA ) p->nMaxPendingData = v;
rc = SQLITE_OK;
diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c
index 95b33ea31..ee43ca6cc 100644
--- a/ext/fts5/fts5_aux.c
+++ b/ext/fts5/fts5_aux.c
@@ -455,7 +455,7 @@ static void fts5SnippetFunction(
iBestCol = (iCol>=0 ? iCol : 0);
nPhrase = pApi->xPhraseCount(pFts);
- aSeen = sqlite3_malloc(nPhrase);
+ aSeen = sqlite3_malloc64(nPhrase);
if( aSeen==0 ){
rc = SQLITE_NOMEM;
}
diff --git a/ext/fts5/fts5_buffer.c b/ext/fts5/fts5_buffer.c
index afcd83b6b..d799e34cb 100644
--- a/ext/fts5/fts5_buffer.c
+++ b/ext/fts5/fts5_buffer.c
@@ -288,7 +288,7 @@ char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){
if( nIn<0 ){
nIn = (int)strlen(pIn);
}
- zRet = (char*)sqlite3_malloc(nIn+1);
+ zRet = (char*)sqlite3_malloc64((i64)nIn+1);
if( zRet ){
memcpy(zRet, pIn, nIn);
zRet[nIn] = '\0';
diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c
index eea82b046..cea14b500 100644
--- a/ext/fts5/fts5_config.c
+++ b/ext/fts5/fts5_config.c
@@ -576,7 +576,7 @@ int sqlite3Fts5ConfigParse(
sqlite3_int64 nByte;
int bUnindexed = 0; /* True if there are one or more UNINDEXED */
- *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config));
+ *ppOut = pRet = (Fts5Config*)sqlite3_malloc64(sizeof(Fts5Config));
if( pRet==0 ) return SQLITE_NOMEM;
memset(pRet, 0, sizeof(Fts5Config));
pRet->pGlobal = pGlobal;
@@ -1123,5 +1123,3 @@ void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...){
va_end(ap);
}
-
-
diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c
index 352df81f4..8ecaca34f 100644
--- a/ext/fts5/fts5_expr.c
+++ b/ext/fts5/fts5_expr.c
@@ -314,7 +314,7 @@ int sqlite3Fts5ExprNew(
assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 );
if( sParse.rc==SQLITE_OK ){
- *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr));
+ *ppNew = pNew = sqlite3_malloc64(sizeof(Fts5Expr));
if( pNew==0 ){
sParse.rc = SQLITE_NOMEM;
sqlite3Fts5ParseNodeFree(sParse.pExpr);
@@ -466,7 +466,7 @@ int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){
p2->pRoot = 0;
if( sParse.rc==SQLITE_OK ){
- Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc(
+ Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc64(
p1->apExprPhrase, nPhrase * sizeof(Fts5ExprPhrase*)
);
if( ap==0 ){
diff --git a/ext/fts5/fts5_hash.c b/ext/fts5/fts5_hash.c
index a33dec9a9..ba4a030b7 100644
--- a/ext/fts5/fts5_hash.c
+++ b/ext/fts5/fts5_hash.c
@@ -91,7 +91,7 @@ int sqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte){
int rc = SQLITE_OK;
Fts5Hash *pNew;
- *ppNew = pNew = (Fts5Hash*)sqlite3_malloc(sizeof(Fts5Hash));
+ *ppNew = pNew = (Fts5Hash*)sqlite3_malloc64(sizeof(Fts5Hash));
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c
index acd0570a5..164d61388 100644
--- a/ext/fts5/fts5_index.c
+++ b/ext/fts5/fts5_index.c
@@ -2093,7 +2093,7 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){
/* If necessary, grow the pIter->aRowidOffset[] array. */
if( iRowidOffset>=pIter->nRowidOffset ){
- int nNew = pIter->nRowidOffset + 8;
+ i64 nNew = pIter->nRowidOffset + 8;
int *aNew = (int*)sqlite3_realloc64(pIter->aRowidOffset,nNew*sizeof(int));
if( aNew==0 ){
p->rc = SQLITE_NOMEM;
@@ -5240,7 +5240,7 @@ static void fts5DoSecureDelete(
int iSegid = pSeg->pSeg->iSegid;
u8 *aPg = pSeg->pLeaf->p;
int nPg = pSeg->pLeaf->nn;
- int iPgIdx = pSeg->pLeaf->szLeaf;
+ int iPgIdx = pSeg->pLeaf->szLeaf; /* Offset of page footer */
u64 iDelta = 0;
int iNextOff = 0;
@@ -5319,7 +5319,7 @@ static void fts5DoSecureDelete(
iSOP += fts5GetVarint32(&aPg[iSOP], nPos);
}
assert_nc( iSOP==pSeg->iLeafOffset );
- iNextOff = pSeg->iLeafOffset + pSeg->nPos;
+ iNextOff = iSOP + pSeg->nPos;
}
}
@@ -5399,31 +5399,31 @@ static void fts5DoSecureDelete(
** 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;
- int nSuffix2 = 0;
+ u64 nPrefix = 0;
+ u64 nSuffix = 0;
+ u64 nPrefix2 = 0;
+ u64 nSuffix2 = 0;
iDelKeyOff = iNextOff;
- iNextOff += fts5GetVarint32(&aPg[iNextOff], nPrefix2);
- iNextOff += fts5GetVarint32(&aPg[iNextOff], nSuffix2);
+ iNextOff += fts5GetVarint(&aPg[iNextOff], &nPrefix2);
+ iNextOff += fts5GetVarint(&aPg[iNextOff], &nSuffix2);
if( iKey!=1 ){
- iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nPrefix);
+ iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nPrefix);
}
- iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nSuffix);
+ iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nSuffix);
nPrefix = MIN(nPrefix, nPrefix2);
nSuffix = (nPrefix2 + nSuffix2) - nPrefix;
- if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){
+ if( (iKeyOff+nSuffix)>(u64)iPgIdx || (iNextOff+nSuffix2)>(u64)iPgIdx ){
FTS5_CORRUPT_IDX(p);
}else{
if( iKey!=1 ){
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix);
}
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix);
- if( nPrefix2>pSeg->term.n ){
+ if( nPrefix2>(u64)pSeg->term.n ){
FTS5_CORRUPT_IDX(p);
}else if( nPrefix2>nPrefix ){
memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix);
@@ -5454,7 +5454,7 @@ static void fts5DoSecureDelete(
u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
int nTermIdx = pTerm->nn - pTerm->szLeaf;
int iTermIdx = 0;
- int iTermOff = 0;
+ i64 iTermOff = 0;
while( 1 ){
u32 iVal = 0;
@@ -5465,12 +5465,15 @@ static void fts5DoSecureDelete(
}
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);
+ if( iTermOff>pTerm->szLeaf ){
+ FTS5_CORRUPT_IDX(p);
+ }else{
+ 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);
@@ -5493,7 +5496,9 @@ static void fts5DoSecureDelete(
int iPrevKeyOut = 0;
int iKeyIn = 0;
- memmove(&aPg[iOff], &aPg[iNextOff], nMove);
+ if( nMove>0 ){
+ memmove(&aPg[iOff], &aPg[iNextOff], nMove);
+ }
iPgIdx -= nShift;
nPg = iPgIdx;
fts5PutU16(&aPg[2], iPgIdx);
@@ -6413,16 +6418,16 @@ struct Fts5TokenDataMap {
** aMap[] variables.
*/
struct Fts5TokenDataIter {
- int nMapAlloc; /* Allocated size of aMap[] in entries */
- int nMap; /* Number of valid entries in aMap[] */
+ i64 nMapAlloc; /* Allocated size of aMap[] in entries */
+ i64 nMap; /* Number of valid entries in aMap[] */
Fts5TokenDataMap *aMap; /* Array of (rowid+pos -> token) mappings */
/* The following are used for prefix-queries only. */
Fts5Buffer terms;
/* The following are used for other full-token tokendata queries only. */
- int nIter;
- int nIterAlloc;
+ i64 nIter;
+ i64 nIterAlloc;
Fts5PoslistReader *aPoslistReader;
int *aPoslistToIter;
Fts5Iter *apIter[FLEXARRAY];
@@ -6478,11 +6483,11 @@ static void fts5TokendataIterAppendMap(
){
if( p->rc==SQLITE_OK ){
if( pT->nMap==pT->nMapAlloc ){
- int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64;
- int nAlloc = nNew * sizeof(Fts5TokenDataMap);
+ i64 nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64;
+ i64 nAlloc = nNew * sizeof(Fts5TokenDataMap);
Fts5TokenDataMap *aNew;
- aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nAlloc);
+ aNew = (Fts5TokenDataMap*)sqlite3_realloc64(pT->aMap, nAlloc);
if( aNew==0 ){
p->rc = SQLITE_NOMEM;
return;
@@ -6508,7 +6513,7 @@ static void fts5TokendataIterAppendMap(
*/
static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){
Fts5TokenDataMap *aTmp = 0;
- int nByte = pT->nMap * sizeof(Fts5TokenDataMap);
+ i64 nByte = pT->nMap * sizeof(Fts5TokenDataMap);
aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte);
if( aTmp ){
@@ -7042,9 +7047,10 @@ static Fts5TokenDataIter *fts5AppendTokendataIter(
if( p->rc==SQLITE_OK ){
if( pIn==0 || pIn->nIter==pIn->nIterAlloc ){
- int nAlloc = pIn ? pIn->nIterAlloc*2 : 16;
- int nByte = SZ_FTS5TOKENDATAITER(nAlloc+1);
- Fts5TokenDataIter *pNew = (Fts5TokenDataIter*)sqlite3_realloc(pIn, nByte);
+ i64 nAlloc = pIn ? pIn->nIterAlloc*2 : 16;
+ i64 nByte = SZ_FTS5TOKENDATAITER(nAlloc+1);
+ Fts5TokenDataIter *pNew;
+ pNew = (Fts5TokenDataIter*)sqlite3_realloc64(pIn, nByte);
if( pNew==0 ){
p->rc = SQLITE_NOMEM;
@@ -7141,8 +7147,8 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){
/* Ensure the token-mapping is large enough */
if( eDetail==FTS5_DETAIL_FULL && pT->nMapAlloc<(pT->nMap + nByte) ){
- int nNew = (pT->nMapAlloc + nByte) * 2;
- Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc(
+ i64 nNew = (pT->nMapAlloc + nByte) * 2;
+ Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc64(
pT->aMap, nNew*sizeof(Fts5TokenDataMap)
);
if( aNew==0 ){
diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c
index f45b9ef90..cf033ab5d 100644
--- a/ext/fts5/fts5_main.c
+++ b/ext/fts5/fts5_main.c
@@ -631,7 +631,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
return SQLITE_ERROR;
}
- idxStr = (char*)sqlite3_malloc(pInfo->nConstraint * 8 + 1);
+ idxStr = (char*)sqlite3_malloc64((i64)pInfo->nConstraint * 8 + 1);
if( idxStr==0 ) return SQLITE_NOMEM;
pInfo->idxStr = idxStr;
pInfo->needToFreeIdxStr = 1;
@@ -2081,6 +2081,7 @@ static int fts5UpdateMethod(
}
update_out:
+ sqlite3Fts5IndexCloseReader(pTab->p.pIndex);
pTab->p.pConfig->pzErrmsg = 0;
return rc;
}
@@ -3762,7 +3763,7 @@ static int fts5Init(sqlite3 *db){
int rc;
Fts5Global *pGlobal = 0;
- pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global));
+ pGlobal = (Fts5Global*)sqlite3_malloc64(sizeof(Fts5Global));
if( pGlobal==0 ){
rc = SQLITE_NOMEM;
}else{
diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c
index 25cd5c063..f5d8705ff 100644
--- a/ext/fts5/fts5_tcl.c
+++ b/ext/fts5/fts5_tcl.c
@@ -391,7 +391,7 @@ static int SQLITE_TCLAPI xF5tApi(
break;
}
CASE(12, "xSetAuxdata") {
- F5tAuxData *pData = (F5tAuxData*)sqlite3_malloc(sizeof(F5tAuxData));
+ F5tAuxData *pData = (F5tAuxData*)sqlite3_malloc64(sizeof(F5tAuxData));
if( pData==0 ){
Tcl_AppendResult(interp, "out of memory", (char*)0);
return TCL_ERROR;
@@ -780,7 +780,7 @@ static int SQLITE_TCLAPI f5tTokenize(
}
if( nText>0 ){
- pCopy = sqlite3_malloc(nText);
+ pCopy = sqlite3_malloc64(nText);
if( pCopy==0 ){
tokenizer.xDelete(pTok);
Tcl_AppendResult(interp, "error in sqlite3_malloc()", (char*)0);
@@ -1420,7 +1420,7 @@ static int f5tOrigintextCreate(
void *pTokCtx = 0;
int rc = SQLITE_OK;
- pTok = (OriginTextTokenizer*)sqlite3_malloc(sizeof(OriginTextTokenizer));
+ pTok = (OriginTextTokenizer*)sqlite3_malloc64(sizeof(OriginTextTokenizer));
if( pTok==0 ){
rc = SQLITE_NOMEM;
}else if( nArg<1 ){
@@ -1480,7 +1480,7 @@ static int xOriginToken(
int nReq = nToken + 1 + (iEnd-iStart);
if( nReq>p->nBuf ){
sqlite3_free(p->aBuf);
- p->aBuf = sqlite3_malloc(nReq*2);
+ p->aBuf = sqlite3_malloc64(nReq*2);
if( p->aBuf==0 ) return SQLITE_NOMEM;
p->nBuf = nReq*2;
}
diff --git a/ext/fts5/fts5_test_tok.c b/ext/fts5/fts5_test_tok.c
index 994d304dc..c77c49de7 100644
--- a/ext/fts5/fts5_test_tok.c
+++ b/ext/fts5/fts5_test_tok.c
@@ -194,7 +194,7 @@ static int fts5tokConnectMethod(
}
if( rc==SQLITE_OK ){
- pTab = (Fts5tokTable*)sqlite3_malloc(sizeof(Fts5tokTable));
+ pTab = (Fts5tokTable*)sqlite3_malloc64(sizeof(Fts5tokTable));
if( pTab==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -275,7 +275,7 @@ static int fts5tokBestIndexMethod(
static int fts5tokOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
Fts5tokCursor *pCsr;
- pCsr = (Fts5tokCursor *)sqlite3_malloc(sizeof(Fts5tokCursor));
+ pCsr = (Fts5tokCursor *)sqlite3_malloc64(sizeof(Fts5tokCursor));
if( pCsr==0 ){
return SQLITE_NOMEM;
}
@@ -347,7 +347,7 @@ static int fts5tokCb(
if( pCsr->nRow ){
pRow->iPos = pRow[-1].iPos + ((tflags & FTS5_TOKEN_COLOCATED) ? 0 : 1);
}
- pRow->zToken = sqlite3_malloc(nToken+1);
+ pRow->zToken = sqlite3_malloc64((sqlite3_int64)nToken+1);
if( pRow->zToken==0 ) return SQLITE_NOMEM;
memcpy(pRow->zToken, pToken, nToken);
pRow->zToken[nToken] = 0;
@@ -373,8 +373,8 @@ static int fts5tokFilterMethod(
fts5tokResetCursor(pCsr);
if( idxNum==1 ){
const char *zByte = (const char *)sqlite3_value_text(apVal[0]);
- int nByte = sqlite3_value_bytes(apVal[0]);
- pCsr->zInput = sqlite3_malloc(nByte+1);
+ sqlite3_int64 nByte = sqlite3_value_bytes(apVal[0]);
+ pCsr->zInput = sqlite3_malloc64(nByte+1);
if( pCsr->zInput==0 ){
rc = SQLITE_NOMEM;
}else{
diff --git a/ext/fts5/fts5_tokenize.c b/ext/fts5/fts5_tokenize.c
index b8a113646..990810239 100644
--- a/ext/fts5/fts5_tokenize.c
+++ b/ext/fts5/fts5_tokenize.c
@@ -72,7 +72,7 @@ static int fts5AsciiCreate(
if( nArg%2 ){
rc = SQLITE_ERROR;
}else{
- p = sqlite3_malloc(sizeof(AsciiTokenizer));
+ p = sqlite3_malloc64(sizeof(AsciiTokenizer));
if( p==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -367,7 +367,7 @@ static int fts5UnicodeCreate(
if( nArg%2 ){
rc = SQLITE_ERROR;
}else{
- p = (Unicode61Tokenizer*)sqlite3_malloc(sizeof(Unicode61Tokenizer));
+ p = (Unicode61Tokenizer*)sqlite3_malloc64(sizeof(Unicode61Tokenizer));
if( p ){
const char *zCat = "L* N* Co";
int i;
@@ -590,7 +590,7 @@ static int fts5PorterCreate(
zBase = azArg[0];
}
- pRet = (PorterTokenizer*)sqlite3_malloc(sizeof(PorterTokenizer));
+ pRet = (PorterTokenizer*)sqlite3_malloc64(sizeof(PorterTokenizer));
if( pRet ){
memset(pRet, 0, sizeof(PorterTokenizer));
rc = pApi->xFindTokenizer_v2(pApi, zBase, &pUserdata, &pV2);
@@ -1297,7 +1297,7 @@ static int fts5TriCreate(
rc = SQLITE_ERROR;
}else{
int i;
- pNew = (TrigramTokenizer*)sqlite3_malloc(sizeof(*pNew));
+ pNew = (TrigramTokenizer*)sqlite3_malloc64(sizeof(*pNew));
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
diff --git a/ext/fts5/fts5_vocab.c b/ext/fts5/fts5_vocab.c
index 3a6a968f7..295ace6ba 100644
--- a/ext/fts5/fts5_vocab.c
+++ b/ext/fts5/fts5_vocab.c
@@ -666,7 +666,7 @@ static int fts5VocabFilterMethod(
const char *zCopy = (const char *)sqlite3_value_text(pLe);
if( zCopy==0 ) zCopy = "";
pCsr->nLeTerm = sqlite3_value_bytes(pLe);
- pCsr->zLeTerm = sqlite3_malloc(pCsr->nLeTerm+1);
+ pCsr->zLeTerm = sqlite3_malloc64((i64)pCsr->nLeTerm+1);
if( pCsr->zLeTerm==0 ){
rc = SQLITE_NOMEM;
}else{
diff --git a/ext/fts5/test/fts5corrupt9.test b/ext/fts5/test/fts5corrupt9.test
new file mode 100644
index 000000000..6cf06f836
--- /dev/null
+++ b/ext/fts5/test/fts5corrupt9.test
@@ -0,0 +1,129 @@
+# 2026 Jan 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]
+set testprefix fts5corrupt9
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+sqlite3_fts5_may_be_corrupt 1
+
+sqlite3 db test.db
+
+set nrows 50
+set repeat 500
+set text [string trim [string repeat "aaa " $repeat]]
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t USING fts5(content);
+ INSERT INTO t(t, rank) VALUES('secure-delete', 1);
+}
+do_test 1.1 {
+ for {set i 0} {$i < $nrows} {incr i} {
+ db eval "INSERT INTO t(content) VALUES('$text')"
+ }
+ db eval "INSERT INTO t(t) VALUES('optimize')"
+} {}
+
+do_test 1.2 {
+ db eval { SELECT segid, pgno FROM t_idx } {}
+ set rowid [expr {($segid << 37) + ($pgno >> 1)}]
+ db eval {
+ UPDATE t_data
+ SET block = X'00000009043061616104ffffffff07'
+ WHERE rowid=$rowid
+ }
+} {}
+
+# At one point this would segfault due to OOB write.
+#
+do_catchsql_test 1.3 {
+ DELETE FROM t WHERE rowid=3
+} {0 {}}
+
+#-------------------------------------------------------------------------
+reset_db
+
+set nRow 8000
+set zText aaa
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t USING fts5(content, detail=none);
+ INSERT INTO t(t, rank) VALUES('secure-delete', 1);
+ BEGIN;
+}
+do_test 2.1 {
+ for {set ii 0} {$ii<$nRow} {incr ii} {
+ execsql { INSERT INTO t(content) VALUES($zText); }
+ }
+ execsql {
+ COMMIT;
+ INSERT INTO t(t) VALUES('optimize');
+ }
+} {}
+
+set hex "00040f7d9f49[string repeat {01} 3958]80"
+
+do_execsql_test 2.1 "
+ UPDATE t_data SET block = X'$hex' WHERE rowid=137438953474;
+"
+
+do_execsql_test 2.3 {
+ DELETE FROM t WHERE rowid=7999
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t USING fts5(content, detail=none);
+ INSERT INTO t(t, rank) VALUES('secure-delete', 1);
+ INSERT INTO t(content) VALUES('aaa');
+ INSERT INTO t(content) VALUES('bbb');
+ INSERT INTO t(t) VALUES('optimize');
+}
+
+do_execsql_test 3.1 {
+ UPDATE t_data SET block = X'000000100430616161010187ffffff7f0406' WHERE rowid=412316860417;
+}
+
+do_catchsql_test 3.2 {
+ DELETE FROM t WHERE rowid=1;
+} {1 {fts5: corruption in table "t"}}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t USING fts5(content);
+ INSERT INTO t(t, rank) VALUES('secure-delete', 1);
+ INSERT INTO t(content) VALUES('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+ INSERT INTO t(t) VALUES('optimize');
+}
+
+do_execsql_test 4.1 {
+ UPDATE t_data SET block = X'00000fce9f4830616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161610487ffffff7f' WHERE rowid=137438953473;
+ UPDATE t_data SET block = X'0004000801040203' WHERE rowid=137438953474;
+}
+
+do_catchsql_test 4.2 {
+ DELETE FROM t WHERE rowid = 1;
+} {1 {fts5: corruption in table "t"}}
+
+sqlite3_fts5_may_be_corrupt 0
+
+finish_test
+
diff --git a/ext/fts5/test/fts5integrity.test b/ext/fts5/test/fts5integrity.test
index 4bf120c44..9b2720faf 100644
--- a/ext/fts5/test/fts5integrity.test
+++ b/ext/fts5/test/fts5integrity.test
@@ -379,9 +379,6 @@ do_execsql_test 12.2 {
db close
sqlite3 db test.db -readonly 1
-explain_i {
- PRAGMA integrity_check
- }
do_execsql_test 12.3 {
PRAGMA integrity_check
} {ok}
diff --git a/ext/fts5/test/fts5interrupt.test b/ext/fts5/test/fts5interrupt.test
index 67ef5f7e9..87b232b05 100644
--- a/ext/fts5/test/fts5interrupt.test
+++ b/ext/fts5/test/fts5interrupt.test
@@ -64,4 +64,21 @@ foreach {tn sql} {
}
}
+#-------------------------------------------------------------------------
+# Verify that https://sqlite.org/forum/forumpost/95413eb410 has been
+# fixed.
+#
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE f1 USING fts5(x);
+ BEGIN TRANSACTION;
+ INSERT INTO f1(x) VALUES('abc def ghi');
+}
+do_test 2.1 {
+ sqlite3_interrupt db
+} {}
+do_execsql_test 2.2 {
+ ROLLBACK
+}
+
finish_test
diff --git a/ext/intck/sqlite3intck.c b/ext/intck/sqlite3intck.c
index 5f645fae6..e3fef7763 100644
--- a/ext/intck/sqlite3intck.c
+++ b/ext/intck/sqlite3intck.c
@@ -319,7 +319,7 @@ static int intckGetToken(const char *z){
char c = z[0];
int iRet = 1;
if( c=='\'' || c=='"' || c=='`' ){
- while( 1 ){
+ while( z[iRet] ){
if( z[iRet]==c ){
iRet++;
if( z[iRet]!=c ) break;
diff --git a/ext/jni/README.md b/ext/jni/README.md
index 5ad79fce9..0bdbde91e 100644
--- a/ext/jni/README.md
+++ b/ext/jni/README.md
@@ -13,11 +13,14 @@ 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. The JNI
- bindings released with version 3.43 are a "tech preview." Once
- finalized, strong backward compatibility guarantees will apply.
+> **FOREWARNING:** the JNI subproject is experimental and subject to
+ any number of changes. This API is "feature-complete", with only a
+ few difficult-to-reach corners of the C API not represented here,
+ but it is not a supported deliverable of the project so does not
+ have same backward compatibility guarantees which the C APIs
+ do. That said: the [C-style API](#1to1ish) is especially resistent
+ to compatibility breakage because it's designed to be as close to
+ the C API as feasible.
Project goals/requirements:
@@ -162,11 +165,13 @@ 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
may cause a `NullPointerException`. The APIs clearly mark function
-parameters which should not be null, but does not generally 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`.
+parameters which should not be null, and it internally uses the
+`SQLITE_API_ARMOR` mechanism to help product 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`. There are no known cases where it
+will misuse memory if passed a `null` or out-of-range value from
+client code.
Client-defined callbacks _must never throw exceptions_ unless _very
explicitly documented_ as being throw-safe. Exceptions are generally
@@ -194,7 +199,8 @@ Some constructs, when modelled 1-to-1 from C to Java, are unduly
clumsy to work with in Java because they try to shoehorn C's way of
doing certain things into Java's wildly different ways. The following
subsections cover those, starting with a verbose explanation and
-demonstration of where such changes are "really necessary"...
+demonstration of where such changes are "really necessary" for
+usability's sake...
### Custom Collations
@@ -286,12 +292,9 @@ 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);
+ int flags, SQLFunction func);
```
-> 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:
@@ -313,4 +316,4 @@ in-flux nature of this API.
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.
+provide the same semantics as the C API documentation describes.
diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c
index f130eff04..8cdba9bcf 100644
--- a/ext/jni/src/c/sqlite3-jni.c
+++ b/ext/jni/src/c/sqlite3-jni.c
@@ -133,11 +133,9 @@
** 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 some internal 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.
+** to some internal details like SQLITE_MAX_... and friends, and keep
+** those consistent with this build. This increases the rebuild time
+** considerably, however.
*/
#ifndef SQLITE_C
# define SQLITE_C sqlite3.c
@@ -335,7 +333,7 @@ struct S3JniNphOp {
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.
+ ** klazz is a global ref to the class represented by zName.
**
** According to:
**
@@ -999,19 +997,20 @@ static S3JniEnv * S3JniEnv__get(JNIEnv * const env){
** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not
** from client code.
**
-** Returns err_code.
+** Returns err_code _unless_ err_code is 0 and sqlite3_set_errmsg()
+** fails with OOM, in which case it may return SQLITE_OOM or fail
+** fatally.
+**
+** This function predates sqlite3_set_errmsg(), which is why it has a
+** slightly different interface. Before that function was introduced,
+** this code used the SQLite-internal APIs to do this.
*/
-static int s3jni_db_error(sqlite3* const db, int err_code,
- const char * const zMsg){
+static int s3jni_db_error(JNIEnv * env, 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));
- }
+ int const rc = sqlite3_set_errmsg(db, err_code, zMsg);
+ s3jni_oom_fatal(0==rc);
+ if( rc && !err_code ) err_code=rc;
}
return err_code;
}
@@ -1235,11 +1234,11 @@ static int s3jni__db_exception(JNIEnv * const env, sqlite3 * const pDb,
char * zMsg;
S3JniExceptionClear;
zMsg = s3jni_exception_error_msg(env, ex);
- s3jni_db_error(pDb, errCode, zMsg ? zMsg : zDfltMsg);
+ s3jni_db_error(env, pDb, errCode, zMsg ? zMsg : zDfltMsg);
sqlite3_free(zMsg);
S3JniUnrefLocal(ex);
}else if( zDfltMsg ){
- s3jni_db_error(pDb, errCode, zDfltMsg);
+ s3jni_db_error(env, pDb, errCode, zDfltMsg);
}
return errCode;
}
@@ -1956,15 +1955,6 @@ static void S3JniUdf_finalizer(void * s){
S3JniUdf_free(s3jni_env(), (S3JniUdf*)s, 1);
}
-/*
-** Helper for processing args to UDF handlers with signature
-** (sqlite3_context*,int,sqlite3_value**).
-*/
-typedef struct {
- jobject jcx /* sqlite3_context */;
- jobjectArray jargv /* sqlite3_value[] */;
-} udf_jargs;
-
/*
** Converts the given (cx, argc, argv) into arguments for the given
** UDF, writing the result (Java wrappers for cx and argv) in the
@@ -2006,7 +1996,7 @@ static int udf_args(JNIEnv *env,
/*
** Requires that jCx and jArgv are sqlite3_context
** resp. array-of-sqlite3_value values initialized by udf_args(). The
-** latter will be 0-and-NULL for UDF types with no arguments. This
+** (argc,argv) are (0,NULL) for UDF types with no arguments. 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 its context or one
@@ -2099,19 +2089,19 @@ 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);
-
+ jobject jcx = 0 /* sqlite3_context */;
+ jobjectArray jargv = 0 /* sqlite3_value[] */;
+ int rc = udf_args(env, pCx, argc, argv, &jcx, &jargv);
if( 0 == rc ){
- (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
+ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx, jargv);
S3JniIfThrew{
rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx,
s->zFuncName, zFuncType);
}
- udf_unargs(env, args.jcx, argc, args.jargv);
+ udf_unargs(env, jcx, argc, jargv);
}
- S3JniUnrefLocal(args.jcx);
- S3JniUnrefLocal(args.jargv);
+ S3JniUnrefLocal(jcx);
+ S3JniUnrefLocal(jargv);
return rc;
}
@@ -3300,7 +3290,7 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,
S3JniDb_mutex_enter;
ps = S3JniDb_from_jlong(jpDb);
if( !ps ){
- s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0);
+ s3jni_db_error(env, ps->pDb, SQLITE_MISUSE, 0);
S3JniDb_mutex_leave;
return 0;
}
@@ -3320,13 +3310,14 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,
else sqlite3_rollback_hook(ps->pDb, 0, 0);
}else{
jclass const klazz = (*env)->GetObjectClass(env, jHook);
- jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call",
- isCommit ? "()I" : "()V");
+ jmethodID const xCallback =
+ (*env)->GetMethodID(env, klazz, "call",
+ isCommit ? "()I" : "()V");
S3JniUnrefLocal(klazz);
S3JniIfThrew {
S3JniExceptionReport;
S3JniExceptionClear;
- s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ s3jni_db_error(env, ps->pDb, SQLITE_ERROR,
"Cannot not find matching call() method in"
"hook object.");
}else{
@@ -3606,7 +3597,7 @@ S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(),
(*env)->GetMethodID(env, klazz, "call", "([B[B)I");
S3JniUnrefLocal(klazz);
S3JniIfThrew{
- rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ rc = s3jni_db_error(env, ps->pDb, SQLITE_ERROR,
"Could not get call() method from "
"CollationCallback object.");
}else{
@@ -3645,15 +3636,15 @@ S3JniApi(sqlite3_create_function() sqlite3_create_function_v2()
if( !pDb || !jFuncName ){
return SQLITE_MISUSE;
- }else if( !encodingTypeIsValid(eTextRep) ){
- return s3jni_db_error(pDb, SQLITE_FORMAT,
+ }else if( !encodingTypeIsValid(eTextRep & 0x0f) ){
+ return s3jni_db_error(env, pDb, SQLITE_FORMAT,
"Invalid function encoding option.");
}
s = S3JniUdf_alloc(env, jFunctor);
if( !s ) return SQLITE_NOMEM;
if( UDF_UNKNOWN_TYPE==s->type ){
- rc = s3jni_db_error(pDb, SQLITE_MISUSE,
+ rc = s3jni_db_error(env, pDb, SQLITE_MISUSE,
"Cannot unambiguously determine function type.");
S3JniUdf_free(env, s, 1);
goto error_cleanup;
@@ -4012,7 +4003,7 @@ S3JniApi(sqlite3_jni_db_error(), jint, 1jni_1db_1error)(
zStr = jStr
? s3jni_jstring_to_utf8( jStr, 0)
: NULL;
- rc = s3jni_db_error( ps->pDb, (int)jRc, zStr );
+ rc = s3jni_db_error(env, ps->pDb, (int)jRc, zStr );
sqlite3_free(zStr);
}
return rc;
@@ -4321,7 +4312,7 @@ static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId,
jTable = jDbName ? s3jni_utf8_to_jstring( zTable, -1) : 0;
S3JniIfThrew {
S3JniExceptionClear;
- s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
+ s3jni_db_error(env, ps->pDb, SQLITE_NOMEM, 0);
}else{
assert( hook.jObj );
assert( hook.midCallback );
@@ -4423,7 +4414,7 @@ static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jlong jpDb, jobject
S3JniUnrefLocal(klazz);
S3JniIfThrew {
S3JniExceptionClear;
- s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ s3jni_db_error(env, ps->pDb, SQLITE_ERROR,
"Cannot not find matching callback on "
"(pre)update hook object.");
}else{
@@ -4532,7 +4523,7 @@ S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)(
S3JniUnrefLocal(klazz);
S3JniIfThrew {
S3JniExceptionClear;
- s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ s3jni_db_error(env, ps->pDb, SQLITE_ERROR,
"Cannot not find matching xCallback() on "
"ProgressHandler object.");
}else{
@@ -4906,8 +4897,9 @@ S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)(
")I");
S3JniUnrefLocal(klazz);
S3JniIfThrew {
- rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
- "Error setting up Java parts of authorizer hook.");
+ rc = s3jni_db_error(env, ps->pDb, SQLITE_ERROR,
+ "Error setting up Java parts of "
+ "authorizer hook.");
}else{
rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps);
}
@@ -5191,7 +5183,7 @@ S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)(
S3JniUnrefLocal(klazz);
S3JniIfThrew {
S3JniExceptionClear;
- rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ rc = s3jni_db_error(env, ps->pDb, SQLITE_ERROR,
"Cannot not find matching call() on "
"TracerCallback object.");
}else{
diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h
index 81af5cbde..c326fa8ea 100644
--- a/ext/jni/src/c/sqlite3-jni.h
+++ b/ext/jni/src/c/sqlite3-jni.h
@@ -245,8 +245,10 @@ extern "C" {
#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_TEMPBUF_SPILL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_TEMPBUF_SPILL 13L
#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX
-#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX 12L
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX 13L
#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
diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java
index 0b840c362..1bdc5300d 100644
--- a/ext/jni/src/org/sqlite/jni/capi/CApi.java
+++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java
@@ -115,8 +115,9 @@ private static byte[] nulTerminateUtf8(String s){
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. Its return type and semantics are not considered
- stable and may change at any time.
+ decisions. The semantics of its return type and value are not
+ considered stable and may change at any time. i.e. act as if it
+ returns null.
*/
public static native boolean sqlite3_java_uncache_thread();
@@ -2585,7 +2586,8 @@ public static int sqlite3_value_type(@NotNull sqlite3_value v){
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;
+ public static final int SQLITE_DBSTATUS_TEMPBUF_SPILL = 13;
+ public static final int SQLITE_DBSTATUS_MAX = 13;
// encodings
public static final int SQLITE_UTF8 = 1;
diff --git a/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
index 9d14c954b..891bdea54 100644
--- a/ext/jni/src/org/sqlite/jni/capi/Tester1.java
+++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
@@ -815,7 +815,9 @@ public void xDestroy(){
};
// Register and use the function...
- int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
+ int rc = sqlite3_create_function(db, "myfunc", -1,
+ SQLITE_UTF8 | SQLITE_INNOCUOUS,
+ func);
affirm(0 == rc);
affirm(0 == xFuncAccum.value);
final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)");
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
index d259e0ce6..ba2ffd119 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
@@ -171,6 +171,7 @@ public final class Sqlite implements AutoCloseable {
public static final int DBSTATUS_DEFERRED_FKS = CApi.SQLITE_DBSTATUS_DEFERRED_FKS;
public static final int DBSTATUS_CACHE_USED_SHARED = CApi.SQLITE_DBSTATUS_CACHE_USED_SHARED;
public static final int DBSTATUS_CACHE_SPILL = CApi.SQLITE_DBSTATUS_CACHE_SPILL;
+ public static final int DBSTATUS_TEMPBUF_SPILL = CApi.SQLITE_DBSTATUS_TEMPBUF_SPILL;
// Limits
public static final int LIMIT_LENGTH = CApi.SQLITE_LIMIT_LENGTH;
diff --git a/ext/misc/btreeinfo.c b/ext/misc/btreeinfo.c
index 9c726f5f1..24645f226 100644
--- a/ext/misc/btreeinfo.c
+++ b/ext/misc/btreeinfo.c
@@ -306,6 +306,10 @@ static int binfoCompute(sqlite3 *db, int pgno, BinfoCursor *pCsr){
nEntry *= (nCell+1);
if( aData[0]==10 || aData[0]==13 ) break;
nPage *= (nCell+1);
+ if( 14+2*(nCell/2)>=pgsz ){
+ rc = SQLITE_CORRUPT;
+ break;
+ }
if( nCell<=1 ){
pgno = get_uint32(aData+8);
}else{
@@ -339,7 +343,7 @@ static int binfoColumn(
sqlite3 *db = sqlite3_context_db_handle(ctx);
int rc = binfoCompute(db, pgno, pCsr);
if( rc ){
- pCursor->pVtab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ pCursor->pVtab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errstr(rc));
return SQLITE_ERROR;
}
}
diff --git a/ext/misc/csv.c b/ext/misc/csv.c
index 1caaaec87..f44a30001 100644
--- a/ext/misc/csv.c
+++ b/ext/misc/csv.c
@@ -24,8 +24,8 @@
** schema= parameter, like this:
**
** CREATE VIRTUAL TABLE temp.csv2 USING csv(
-** filename = "../http.log",
-** schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
+** filename = '../http.log',
+** schema = 'CREATE TABLE x(date,ipaddr,url,referrer,userAgent)'
** );
**
** Instead of specifying a file, the text of the CSV can be loaded using
diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c
index f87699f96..be4321ca8 100644
--- a/ext/misc/decimal.c
+++ b/ext/misc/decimal.c
@@ -291,12 +291,36 @@ static void decimal_result(sqlite3_context *pCtx, Decimal *p){
sqlite3_result_text(pCtx, z, i, sqlite3_free);
}
+/*
+** Round a decimal value to N significant digits. N must be positive.
+*/
+static void decimal_round(Decimal *p, int N){
+ int i;
+ int nZero;
+ if( N<1 ) return;
+ for(nZero=0; nZeronDigit && p->a[nZero]==0; nZero++){}
+ N += nZero;
+ if( p->nDigit<=N ) return;
+ if( p->a[N]>4 ){
+ p->a[N-1]++;
+ for(i=N-1; i>0 && p->a[i]>9; i--){
+ p->a[i] = 0;
+ p->a[i-1]++;
+ }
+ if( p->a[0]>9 ){
+ p->a[0] = 1;
+ p->nFrac--;
+ }
+ }
+ memset(&p->a[N], 0, p->nDigit - N);
+}
+
/*
** Make the given Decimal the result in an format similar to '%+#e'.
** In other words, show exponential notation with leading and trailing
** zeros omitted.
*/
-static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p){
+static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p, int N){
char *z; /* The output buffer */
int i; /* Loop counter */
int nZero; /* Number of leading zeros */
@@ -314,7 +338,8 @@ static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p){
sqlite3_result_null(pCtx);
return;
}
- for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){}
+ if( N<1 ) N = 0;
+ for(nDigit=p->nDigit; nDigit>N && p->a[nDigit-1]==0; nDigit--){}
for(nZero=0; nZeroa[nZero]==0; nZero++){}
nFrac = p->nFrac + (nDigit - p->nDigit);
nDigit -= nZero;
@@ -677,10 +702,16 @@ static void decimalFunc(
sqlite3_value **argv
){
Decimal *p = decimal_new(context, argv[0], 0);
- UNUSED_PARAMETER(argc);
+ int N;
+ if( argc==2 ){
+ N = sqlite3_value_int(argv[1]);
+ if( N>0 ) decimal_round(p, N);
+ }else{
+ N = 0;
+ }
if( p ){
if( sqlite3_user_data(context)!=0 ){
- decimal_result_sci(context, p);
+ decimal_result_sci(context, p, N);
}else{
decimal_result(context, p);
}
@@ -850,7 +881,7 @@ static void decimalPow2Func(
UNUSED_PARAMETER(argc);
if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
Decimal *pA = decimalPow2(sqlite3_value_int(argv[0]));
- decimal_result_sci(context, pA);
+ decimal_result_sci(context, pA, 0);
decimal_free(pA);
}
}
@@ -871,7 +902,9 @@ int sqlite3_decimal_init(
void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
} aFunc[] = {
{ "decimal", 1, 0, decimalFunc },
+ { "decimal", 2, 0, decimalFunc },
{ "decimal_exp", 1, 1, decimalFunc },
+ { "decimal_exp", 2, 1, decimalFunc },
{ "decimal_cmp", 2, 0, decimalCmpFunc },
{ "decimal_add", 2, 0, decimalAddFunc },
{ "decimal_sub", 2, 0, decimalSubFunc },
diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c
index 6cc2ae008..51b748291 100644
--- a/ext/misc/fileio.c
+++ b/ext/misc/fileio.c
@@ -94,12 +94,16 @@ SQLITE_EXTENSION_INIT1
# include
# include
# define STRUCT_STAT struct stat
+# include
+# include
#else
# include "windirent.h"
# include
# define STRUCT_STAT struct _stat
# define chmod(path,mode) fileio_chmod(path,mode)
# define mkdir(path,mode) fileio_mkdir(path)
+ extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
+ extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR);
#endif
#include
#include
@@ -131,12 +135,9 @@ SQLITE_EXTENSION_INIT1
*/
#if defined(_WIN32) || defined(WIN32)
static int fileio_chmod(const char *zPath, int pmode){
- sqlite3_int64 sz = strlen(zPath);
- wchar_t *b1 = sqlite3_malloc64( (sz+1)*sizeof(b1[0]) );
int rc;
+ wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath);
if( b1==0 ) return -1;
- sz = MultiByteToWideChar(CP_UTF8, 0, zPath, sz, b1, sz);
- b1[sz] = 0;
rc = _wchmod(b1, pmode);
sqlite3_free(b1);
return rc;
@@ -148,12 +149,9 @@ static int fileio_chmod(const char *zPath, int pmode){
*/
#if defined(_WIN32) || defined(WIN32)
static int fileio_mkdir(const char *zPath){
- sqlite3_int64 sz = strlen(zPath);
- wchar_t *b1 = sqlite3_malloc64( (sz+1)*sizeof(b1[0]) );
int rc;
+ wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath);
if( b1==0 ) return -1;
- sz = MultiByteToWideChar(CP_UTF8, 0, zPath, sz, b1, sz);
- b1[sz] = 0;
rc = _wmkdir(b1);
sqlite3_free(b1);
return rc;
@@ -266,50 +264,7 @@ static sqlite3_uint64 fileTimeToUnixTime(
return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000;
}
-
-
-#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32))
-# /* To allow a standalone DLL, use this next replacement function: */
-# undef sqlite3_win32_utf8_to_unicode
-# define sqlite3_win32_utf8_to_unicode utf8_to_utf16
-#
-LPWSTR utf8_to_utf16(const char *z){
- int nAllot = MultiByteToWideChar(CP_UTF8, 0, z, -1, NULL, 0);
- LPWSTR rv = sqlite3_malloc(nAllot * sizeof(WCHAR));
- if( rv!=0 && 0 < MultiByteToWideChar(CP_UTF8, 0, z, -1, rv, nAllot) )
- return rv;
- sqlite3_free(rv);
- return 0;
-}
-#endif
-
-/*
-** This function attempts to normalize the time values found in the stat()
-** buffer to UTC. This is necessary on Win32, where the runtime library
-** appears to return these values as local times.
-*/
-static void statTimesToUtc(
- const char *zPath,
- STRUCT_STAT *pStatBuf
-){
- HANDLE hFindFile;
- WIN32_FIND_DATAW fd;
- LPWSTR zUnicodeName;
- extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
- zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath);
- if( zUnicodeName ){
- memset(&fd, 0, sizeof(WIN32_FIND_DATAW));
- hFindFile = FindFirstFileW(zUnicodeName, &fd);
- if( hFindFile!=NULL ){
- pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime);
- pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime);
- pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime);
- FindClose(hFindFile);
- }
- sqlite3_free(zUnicodeName);
- }
-}
-#endif
+#endif /* _WIN32 */
/*
** This function is used in place of stat(). On Windows, special handling
@@ -321,14 +276,22 @@ static int fileStat(
STRUCT_STAT *pStatBuf
){
#if defined(_WIN32)
- sqlite3_int64 sz = strlen(zPath);
- wchar_t *b1 = sqlite3_malloc64( (sz+1)*sizeof(b1[0]) );
int rc;
+ wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath);
if( b1==0 ) return 1;
- sz = MultiByteToWideChar(CP_UTF8, 0, zPath, sz, b1, sz);
- b1[sz] = 0;
rc = _wstat(b1, pStatBuf);
- if( rc==0 ) statTimesToUtc(zPath, pStatBuf);
+ if( rc==0 ){
+ HANDLE hFindFile;
+ WIN32_FIND_DATAW fd;
+ memset(&fd, 0, sizeof(WIN32_FIND_DATAW));
+ hFindFile = FindFirstFileW(b1, &fd);
+ if( hFindFile!=NULL ){
+ pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime);
+ pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime);
+ pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime);
+ FindClose(hFindFile);
+ }
+ }
sqlite3_free(b1);
return rc;
#else
@@ -460,7 +423,6 @@ static int writeFile(
if( mtime>=0 ){
#if defined(_WIN32)
-#if !SQLITE_OS_WINRT
/* Windows */
FILETIME lastAccess;
FILETIME lastWrite;
@@ -491,7 +453,6 @@ static int writeFile(
}else{
return 1;
}
-#endif
#elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */
/* Recent unix */
struct timespec times[2];
@@ -1095,6 +1056,154 @@ static int fsdirRegister(sqlite3 *db){
# define fsdirRegister(x) SQLITE_OK
#endif
+/*
+** This version of realpath() works on any system. The string
+** returned is held in memory allocated using sqlite3_malloc64().
+** The caller is responsible for calling sqlite3_free().
+*/
+static char *portable_realpath(const char *zPath){
+#if !defined(_WIN32) /* BEGIN unix */
+
+ char *zOut = 0; /* Result */
+ char *z; /* Temporary buffer */
+#if defined(PATH_MAX)
+ char zBuf[PATH_MAX+1]; /* Space for the temporary buffer */
+#endif
+
+ if( zPath==0 ) return 0;
+#if defined(PATH_MAX)
+ z = realpath(zPath, zBuf);
+ if( z ){
+ zOut = sqlite3_mprintf("%s", zBuf);
+ }
+#endif /* defined(PATH_MAX) */
+ if( zOut==0 ){
+ /* Try POSIX.1-2008 malloc behavior */
+ z = realpath(zPath, NULL);
+ if( z ){
+ zOut = sqlite3_mprintf("%s", z);
+ free(z);
+ }
+ }
+ return zOut;
+
+#else /* End UNIX, Begin WINDOWS */
+
+ wchar_t *zPath16; /* UTF16 translation of zPath */
+ char *zOut = 0; /* Result */
+ wchar_t *z = 0; /* Temporary buffer */
+
+ if( zPath==0 ) return 0;
+
+ zPath16 = sqlite3_win32_utf8_to_unicode(zPath);
+ if( zPath16==0 ) return 0;
+ z = _wfullpath(NULL, zPath16, 0);
+ sqlite3_free(zPath16);
+ if( z ){
+ zOut = sqlite3_win32_unicode_to_utf8(z);
+ free(z);
+ }
+ return zOut;
+
+#endif /* End WINDOWS, Begin common code */
+}
+
+/*
+** SQL function: realpath(X)
+**
+** Try to convert file or pathname X into its real, absolute pathname.
+** Return NULL if unable.
+**
+** The file or directory X is not required to exist. The answer is formed
+** by calling system realpath() on the prefix of X that does exist and
+** appending the tail of X that does not (yet) exist.
+*/
+static void realpathFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zPath; /* Original input path */
+ char *zCopy; /* An editable copy of zPath */
+ char *zOut; /* The result */
+ char cSep = 0; /* Separator turned into \000 */
+ size_t len; /* Prefix length before cSep */
+#ifdef _WIN32
+ const int isWin = 1;
+#else
+ const int isWin = 0;
+#endif
+
+ (void)argc;
+ zPath = (const char*)sqlite3_value_text(argv[0]);
+ if( zPath==0 ) return;
+ if( zPath[0]==0 ) zPath = ".";
+ zCopy = sqlite3_mprintf("%s",zPath);
+ len = strlen(zCopy);
+ while( len>1 && (zCopy[len-1]=='/' || (isWin && zCopy[len-1]=='\\')) ){
+ len--;
+ }
+ zCopy[len] = 0;
+ while( 1 /*exit-by-break*/ ){
+ zOut = portable_realpath(zCopy);
+ zCopy[len] = cSep;
+ if( zOut ){
+ if( cSep ){
+ zOut = sqlite3_mprintf("%z%s",zOut,&zCopy[len]);
+ }
+ break;
+ }else{
+ size_t i = len-1;
+ while( i>0 ){
+ if( zCopy[i]=='/' || (isWin && zCopy[i]=='\\') ) break;
+ i--;
+ }
+ if( i<=0 ){
+ if( zCopy[0]=='/' ){
+ zOut = zCopy;
+ zCopy = 0;
+ }else if( (zOut = portable_realpath("."))!=0 ){
+ zOut = sqlite3_mprintf("%z/%s", zOut, zCopy);
+ }
+ break;
+ }
+ cSep = zCopy[i];
+ zCopy[i] = 0;
+ len = i;
+ }
+ }
+ sqlite3_free(zCopy);
+ if( zOut ){
+ /* Simplify any "/./" or "/../" that might have snuck into the
+ ** pathname due to appending of zCopy. We only have to consider
+ ** unix "/" separators, because the _wfilepath() system call on
+ ** Windows will have already done this simplification for us. */
+ size_t i, j, n;
+ n = strlen(zOut);
+ for(i=j=0; i0 && zOut[j-1]!='/' ){ j--; }
+ if( j>0 ){ j--; }
+ i += 2;
+ continue;
+ }
+ }
+ zOut[j++] = zOut[i];
+ }
+ zOut[j] = 0;
+
+ /* Return the result */
+ sqlite3_result_text(context, zOut, -1, sqlite3_free);
+ }
+}
+
+
#ifdef _WIN32
__declspec(dllexport)
#endif
@@ -1121,13 +1230,10 @@ int sqlite3_fileio_init(
if( rc==SQLITE_OK ){
rc = fsdirRegister(db);
}
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(db, "realpath", 1,
+ SQLITE_UTF8, 0,
+ realpathFunc, 0, 0);
+ }
return rc;
}
-
-#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32))
-/* To allow a standalone DLL, make test_windirent.c use the same
- * redefined SQLite API calls as the above extension code does.
- * Just pull in this .c to accomplish this. As a beneficial side
- * effect, this extension becomes a single translation unit. */
-# include "test_windirent.c"
-#endif
diff --git a/ext/misc/fossildelta.c b/ext/misc/fossildelta.c
index d24a87700..2dc29b3c3 100644
--- a/ext/misc/fossildelta.c
+++ b/ext/misc/fossildelta.c
@@ -38,9 +38,11 @@ SQLITE_EXTENSION_INIT1
#ifndef SQLITE_AMALGAMATION
/*
-** The "u32" type must be an unsigned 32-bit integer. Adjust this
+** The "u32" type must be an unsigned 32-bit integer. "u64" is
+** an unsigned 64-bit integer.
*/
typedef unsigned int u32;
+typedef sqlite3_uint64 u64;
/*
** Must be a 16-bit value
@@ -541,8 +543,8 @@ static int delta_apply(
int lenDelta, /* Length of the delta */
char *zOut /* Write the output into this preallocated buffer */
){
- unsigned int limit;
- unsigned int total = 0;
+ sqlite3_uint64 limit;
+ sqlite3_uint64 total = 0;
#ifdef FOSSIL_ENABLE_DELTA_CKSUM_TEST
char *zOrigOut = zOut;
#endif
@@ -570,7 +572,7 @@ static int delta_apply(
/* ERROR: copy exceeds output file size */
return -1;
}
- if( ofst+cnt > lenSrc ){
+ if( (u64)ofst+(u64)cnt > (u64)lenSrc ){
/* ERROR: copy extends past end of input */
return -1;
}
diff --git a/ext/misc/fuzzer.c b/ext/misc/fuzzer.c
index e16d005d9..3dcf1d667 100644
--- a/ext/misc/fuzzer.c
+++ b/ext/misc/fuzzer.c
@@ -617,7 +617,7 @@ static int fuzzerRender(
int *pnBuf /* Size of the buffer */
){
const fuzzer_rule *pRule = pStem->pRule;
- int n; /* Size of output term without nul-term */
+ sqlite3_int64 n; /* Size of output term without nul-term */
char *z; /* Buffer to assemble output term in */
n = pStem->nBasis + pRule->nTo - pRule->nFrom;
diff --git a/ext/misc/ieee754.c b/ext/misc/ieee754.c
index 7f1c6d9e1..f551b2265 100644
--- a/ext/misc/ieee754.c
+++ b/ext/misc/ieee754.c
@@ -179,9 +179,9 @@ static void ieee754func(
}
if( m<0 ){
+ if( m<(-9223372036854775807LL) ) return;
isNeg = 1;
m = -m;
- if( m<0 ) return;
}else if( m==0 && e>-1000 && e<1000 ){
sqlite3_result_double(context, 0.0);
return;
@@ -259,6 +259,38 @@ static void ieee754func_to_blob(
}
}
+/*
+** Functions to convert between 64-bit integers and floats.
+**
+** The bit patterns are copied. The numeric values are different.
+*/
+static void ieee754func_from_int(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ UNUSED_PARAMETER(argc);
+ if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
+ double r;
+ sqlite3_int64 v = sqlite3_value_int64(argv[0]);
+ memcpy(&r, &v, sizeof(r));
+ sqlite3_result_double(context, r);
+ }
+}
+static void ieee754func_to_int(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ UNUSED_PARAMETER(argc);
+ if( sqlite3_value_type(argv[0])==SQLITE_FLOAT ){
+ double r = sqlite3_value_double(argv[0]);
+ sqlite3_uint64 v;
+ memcpy(&v, &r, sizeof(v));
+ sqlite3_result_int64(context, v);
+ }
+}
+
/*
** SQL Function: ieee754_inc(r,N)
**
@@ -311,6 +343,8 @@ int sqlite3_ieee_init(
{ "ieee754_exponent", 1, 2, ieee754func },
{ "ieee754_to_blob", 1, 0, ieee754func_to_blob },
{ "ieee754_from_blob", 1, 0, ieee754func_from_blob },
+ { "ieee754_to_int", 1, 0, ieee754func_to_int },
+ { "ieee754_from_int", 1, 0, ieee754func_from_int },
{ "ieee754_inc", 2, 0, ieee754inc },
};
unsigned int i;
diff --git a/ext/misc/regexp.c b/ext/misc/regexp.c
index f1babf4ab..e1826caf3 100644
--- a/ext/misc/regexp.c
+++ b/ext/misc/regexp.c
@@ -32,7 +32,7 @@
** ^X X occurring at the beginning of the string
** X$ X occurring at the end of the string
** . Match any single character
-** \c Character c where c is one of \{}()[]|*+?.
+** \c Character c where c is one of \{}()[]|*+?-.
** \c C-language escapes for c in afnrtv. ex: \t or \n
** \uXXXX Where XXXX is exactly 4 hex digits, unicode value XXXX
** \xXX Where XX is exactly 2 hex digits, unicode value XX
@@ -417,7 +417,7 @@ static int re_hex(int c, int *pV){
** return its interpretation.
*/
static unsigned re_esc_char(ReCompiled *p){
- static const char zEsc[] = "afnrtv\\()*.+?[$^{|}]";
+ static const char zEsc[] = "afnrtv\\()*.+?[$^{|}]-";
static const char zTrans[] = "\a\f\n\r\t\v";
int i, v = 0;
char c;
@@ -740,11 +740,18 @@ static const char *re_compile(
}
/*
-** Compute a reasonable limit on the length of the REGEXP NFA.
+** The value of LIMIT_MAX_PATTERN_LENGTH.
*/
static int re_maxlen(sqlite3_context *context){
sqlite3 *db = sqlite3_context_db_handle(context);
- return 75 + sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH,-1)/2;
+ return sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH,-1);
+}
+
+/*
+** Maximum NFA size given a maximum pattern length.
+*/
+static int re_maxnfa(int mxlen){
+ return 75+mxlen/2;
}
/*
@@ -770,10 +777,17 @@ static void re_sql_func(
(void)argc; /* Unused */
pRe = sqlite3_get_auxdata(context, 0);
if( pRe==0 ){
+ int mxLen = re_maxlen(context);
+ int nPattern;
zPattern = (const char*)sqlite3_value_text(argv[0]);
if( zPattern==0 ) return;
- zErr = re_compile(&pRe, zPattern, re_maxlen(context),
- sqlite3_user_data(context)!=0);
+ nPattern = sqlite3_value_bytes(argv[0]);
+ if( nPattern>mxLen ){
+ zErr = "REGEXP pattern too big";
+ }else{
+ zErr = re_compile(&pRe, zPattern, re_maxnfa(mxLen),
+ sqlite3_user_data(context)!=0);
+ }
if( zErr ){
re_free(pRe);
sqlite3_result_error(context, zErr, -1);
@@ -839,7 +853,7 @@ static void re_bytecode_func(
zPattern = (const char*)sqlite3_value_text(argv[0]);
if( zPattern==0 ) return;
- zErr = re_compile(&pRe, zPattern, re_maxlen(context),
+ zErr = re_compile(&pRe, zPattern, re_maxnfa(re_maxlen(context)),
sqlite3_user_data(context)!=0);
if( zErr ){
re_free(pRe);
diff --git a/ext/misc/sha1.c b/ext/misc/sha1.c
index 07d797060..02d864955 100644
--- a/ext/misc/sha1.c
+++ b/ext/misc/sha1.c
@@ -230,13 +230,16 @@ static void hash_finish(
*****************************************************************************/
/*
-** Implementation of the sha1(X) function.
+** Two SQL functions: sha1(X) and sha1b(X).
**
-** Return a lower-case hexadecimal rendering of the SHA1 hash of the
-** argument X. If X is a BLOB, it is hashed as is. For all other
+** sha1(X) returns a lower-case hexadecimal rendering of the SHA1 hash
+** of the argument X. If X is a BLOB, it is hashed as is. For all other
** types of input, X is converted into a UTF-8 string and the string
-** is hash without the trailing 0x00 terminator. The hash of a NULL
+** is hashed without the trailing 0x00 terminator. The hash of a NULL
** value is NULL.
+**
+** sha1b(X) is the same except that it returns a 20-byte BLOB containing
+** the binary hash instead of a hexadecimal string.
*/
static void sha1Func(
sqlite3_context *context,
@@ -257,11 +260,13 @@ static void sha1Func(
hash_step(&cx, sqlite3_value_text(argv[0]), nByte);
}
if( sqlite3_user_data(context)!=0 ){
+ /* sha1b() - binary result */
hash_finish(&cx, zOut, 1);
sqlite3_result_blob(context, zOut, 20, SQLITE_TRANSIENT);
}else{
+ /* sha1() - hexadecimal text result */
hash_finish(&cx, zOut, 0);
- sqlite3_result_blob(context, zOut, 40, SQLITE_TRANSIENT);
+ sqlite3_result_text(context, zOut, 40, SQLITE_TRANSIENT);
}
}
diff --git a/ext/misc/sqlite3_stdio.c b/ext/misc/sqlite3_stdio.c
index c9bceb194..d59757526 100644
--- a/ext/misc/sqlite3_stdio.c
+++ b/ext/misc/sqlite3_stdio.c
@@ -258,7 +258,7 @@ int sqlite3_fputs(const char *z, FILE *out){
/*
-** Work-alike for fprintf() from the standard C library.
+** Work-alikes for fprintf() and vfprintf() from the standard C library.
*/
int sqlite3_fprintf(FILE *out, const char *zFormat, ...){
int rc;
@@ -285,6 +285,24 @@ int sqlite3_fprintf(FILE *out, const char *zFormat, ...){
}
return rc;
}
+int sqlite3_vfprintf(FILE *out, const char *zFormat, va_list ap){
+ int rc;
+ if( UseWtextForOutput(out) ){
+ /* When writing to the command-prompt in Windows, it is necessary
+ ** to use _O_WTEXT input mode and write UTF-16 characters.
+ */
+ char *z;
+ z = sqlite3_vmprintf(zFormat, ap);
+ sqlite3_fputs(z, out);
+ rc = (int)strlen(z);
+ sqlite3_free(z);
+ }else{
+ /* Writing to a file or other destination, just write bytes without
+ ** any translation. */
+ rc = vfprintf(out, zFormat, ap);
+ }
+ return rc;
+}
/*
** Set the mode for an output stream. mode argument is typically _O_BINARY or
diff --git a/ext/misc/sqlite3_stdio.h b/ext/misc/sqlite3_stdio.h
index dd0eefad0..75368df9f 100644
--- a/ext/misc/sqlite3_stdio.h
+++ b/ext/misc/sqlite3_stdio.h
@@ -31,6 +31,7 @@
#ifdef _WIN32
/**** Definitions For Windows ****/
#include
+#include
#include
FILE *sqlite3_fopen(const char *zFilename, const char *zMode);
@@ -38,6 +39,7 @@ FILE *sqlite3_popen(const char *zCommand, const char *type);
char *sqlite3_fgets(char *s, int size, FILE *stream);
int sqlite3_fputs(const char *s, FILE *stream);
int sqlite3_fprintf(FILE *stream, const char *format, ...);
+int sqlite3_vfprintf(FILE *stream, const char *format, va_list);
void sqlite3_fsetmode(FILE *stream, int mode);
@@ -49,6 +51,7 @@ void sqlite3_fsetmode(FILE *stream, int mode);
#define sqlite3_fgets fgets
#define sqlite3_fputs fputs
#define sqlite3_fprintf fprintf
+#define sqlite3_vfprintf vfprintf
#define sqlite3_fsetmode(F,X) /*no-op*/
#endif
diff --git a/ext/misc/tmstmpvfs.c b/ext/misc/tmstmpvfs.c
new file mode 100644
index 000000000..6f1af36f7
--- /dev/null
+++ b/ext/misc/tmstmpvfs.c
@@ -0,0 +1,1042 @@
+/*
+** 2026-01-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 implements a VFS shim that writes a timestamp and other tracing
+** information into 16 byts of reserved space at the end of each page of the
+** database file.
+**
+** The VFS also tries to generate log-files with names of the form:
+**
+** $(DATABASE)-tmstmp/$(TIME)-$(PID)-$(ID)
+**
+** Log files are only generated if directory $(DATABASE)-tmstmp exists.
+** The name of each log file is the current ISO8601 time in milliseconds,
+** the process ID, and a random 32-bit value (to disambiguate multiple
+** connections from the same process) separated by dashes. The log file
+** contains 16-bytes records for various events, such as opening or close
+** of the database or WAL file, writes to the WAL file, checkpoints, and
+** similar. The logfile is only generated if the connection attempts to
+** modify the database. There is a separate log file for each open database
+** connection.
+**
+** COMPILING
+**
+** To build this extension as a separately loaded shared library or
+** DLL, use compiler command-lines similar to the following:
+**
+** (linux) gcc -fPIC -shared tmstmpvfs.c -o tmstmpvfs.so
+** (mac) clang -fPIC -dynamiclib tmstmpvfs.c -o tmstmpvfs.dylib
+** (windows) cl tmstmpvfs.c -link -dll -out:tmstmpvfs.dll
+**
+** You may want to add additional compiler options, of course,
+** according to the needs of your project.
+**
+** Another option is to statically link both SQLite and this extension
+** into your application. If both this file and "sqlite3.c" are statically
+** linked, and if "sqlite3.c" is compiled with an option like:
+**
+** -DSQLITE_EXTRA_INIT=sqlite3_register_tmstmpvfs
+**
+** Then SQLite will use the tmstmp VFS by default throughout your
+** application.
+**
+** LOADING
+**
+** To load this extension as a shared library, you first have to
+** bring up a dummy SQLite database connection to use as the argument
+** to the sqlite3_load_extension() API call. Then you invoke the
+** sqlite3_load_extension() API and shutdown the dummy database
+** connection. All subsequent database connections that are opened
+** will include this extension. For example:
+**
+** sqlite3 *db;
+** sqlite3_open(":memory:", &db);
+** sqlite3_load_extension(db, "./tmstmpvfs");
+** sqlite3_close(db);
+**
+** Tmstmpvfs is a VFS Shim. When loaded, "tmstmpvfs" becomes the new
+** default VFS and it uses the prior default VFS as the next VFS
+** down in the stack. This is normally what you want. However, in
+** complex situations where multiple VFS shims are being loaded,
+** it might be important to ensure that tmstmpvfs is loaded in the
+** correct order so that it sequences itself into the default VFS
+** Shim stack in the right order.
+**
+** When running the CLI, you can load this extension at invocation by
+** adding a command-line option like this: "--vfs ./tmstmpvfs.so".
+** The --vfs option usually specifies the symbolic name of a built-in VFS.
+** But if the argument to --vfs is not a built-in VFS but is instead the
+** name of a file, the CLI tries to load that file as an extension. Note
+** that the full name of the extension file must be provided, including
+** the ".so" or ".dylib" or ".dll" suffix.
+**
+** An application can see if the tmstmpvfs is being used by examining
+** the results from SQLITE_FCNTL_VFSNAME (or the .vfsname command in
+** the CLI). If the answer include "tmstmp", then this VFS is being
+** used.
+**
+** USING
+**
+** Open database connections using the sqlite3_open() or
+** sqlite3_open_v2() interfaces, as normal. Ordinary database files
+** (without a timestamp) will operate normally.
+**
+** Timestamping only works on databases that have a reserve-bytes
+** value of exactly 16. The default value for reserve-bytes is 0.
+** Hence, newly created database files will omit the timestamp by
+** default. To create a database that includes a timestamp, change
+** the reserve-bytes value to 16 by running:
+**
+** int n = 16;
+** sqlite3_file_control(db, 0, SQLITE_FCNTL_RESERVE_BYTES, &n);
+**
+** If you do this immediately after creating a new database file,
+** before anything else has been written into the file, then that
+** might be all that you need to do. Otherwise, the API call
+** above should be followed by:
+**
+** sqlite3_exec(db, "VACUUM", 0, 0, 0);
+**
+** It never hurts to run the VACUUM, even if you don't need it.
+**
+** From the CLI, use the ".filectrl reserve_bytes 16" command,
+** followed by "VACUUM;".
+**
+** SQLite allows the number of reserve-bytes to be increased, but
+** not decreased. If you want to restore the reserve-bytes to 0
+** (to disable tmstmpvfs), the easiest approach is to use VACUUM INTO
+** with a URI filename as the argument and include "reserve=0" query
+** parameter on the URI. Example:
+**
+** VACUUM INTO 'file:notimestamps.db?reserve=0';
+**
+** Then switch over to using the new database file. The reserve=0 query
+** parameter only works on SQLite 3.52.0 and later.
+**
+** IMPLEMENTATION NOTES
+**
+** The timestamp information is stored in the last 16 bytes of each page.
+** This module only operates if the "bytes of reserved space on each page"
+** value at offset 20 the SQLite database header is exactly 16. If
+** the reserved-space value is not 16, no timestamp information is added
+** to database pages. Some, but not all, logfile entries will be made
+** still, but the size of the logs will be greatly reduced.
+**
+** The timestamp layout is as follows:
+**
+** bytes 0,1 Zero. Reserved for future expansion
+** bytes 2-7 Milliseconds since the Unix Epoch
+** bytes 8-11 WAL frame number
+** bytes 12 0: WAL write 2: rollback write
+** bytes 13-15 Lower 24 bits of Salt-1
+**
+** For transactions that occur in rollback mode, only the timestamp
+** in bytes 2-7 and byte 12 are non-zero. Byte 12 is set to 2 for
+** rollback writes.
+**
+** The 16-byte tag is added to each database page when the content
+** is written into the database file itself. This shim does not make
+** any changes to the page as it is written to the WAL file, since
+** that would mess up the WAL checksum.
+**
+** LOGGING
+**
+** An open database connection that attempts to write to the database
+** will create a log file if a directory name $(DATABASE)-tmstmp exists.
+** The name of the log file is:
+**
+** $(TIME)-$(PID)-$(RANDOM)
+**
+** Where TIME is an ISO 8601 date in milliseconds with no punctuation,
+** PID is the process ID, and RANDOM is a 32-bit random number expressed
+** as hexadecimal.
+**
+** The log consists of 16-byte records. Each record consists of five
+** unsigned integers:
+**
+** 1 1 6 4 4 <--- bytes
+** op a1 ts a2 a3
+**
+** The meanings of the a1-a3 values depend on op. ts is the timestamp
+** in milliseconds since the unix epoch (1970-01-01 00:00:00).
+** Opcodes are defined by the ELOG_* #defines below.
+**
+** ELOG_OPEN_DB "Open a connection to the database file"
+** op = 0x01
+** a2 = process-ID
+**
+** ELOG_OPEN_WAL "Open a connection to the -wal file"
+** op = 0x02
+** a2 = process-ID
+**
+** ELOG_WAL_PAGE "New page added to the WAL file"
+** op = 0x03
+** a1 = 1 if last page of a txn. 0 otherwise.
+** a2 = page number in the DB file
+** a3 = frame number in the WAL file
+**
+** ELOG_DB_PAGE "Database page updated using rollback mode"
+** op = 0x04
+** a2 = page number in the DB file
+**
+** ELOG_CKPT_START "Start of a checkpoint operation"
+** op = 0x05
+**
+** ELOG_CKPT_PAGE "Page xfer from WAL to database"
+** op = 0x06
+** a2 = database page number
+** a3 = frame number in the WAL file
+**
+** ELOG_CKPT_END "Start of a checkpoint operation"
+** op = 0x07
+**
+** ELOG_WAL_RESET "WAL file header overwritten"
+** op = 0x08
+** a3 = Salt1 value
+**
+** ELOG_CLOSE_WAL "Close the WAL file connection"
+** op = 0x0e
+**
+** ELOG_CLOSE_DB "Close the DB connection"
+** op = 0x0f
+**
+** VIEWING TIMESTAMPS AND LOGS
+**
+** The command-line utility at tool/showtmlog.c will read and display
+** the content of one or more tmstmpvfs.c log files. If all of the
+** log files are stored in directory $(DATABASE)-tmstmp, then you can
+** view them all using a command like shown below (with an extra "?"
+** inserted on the wildcard to avoid closing the C-language comment
+** that contains this text):
+**
+** showtmlog $(DATABASE)-tmstmp/?*
+**
+** The command-line utility at tools/showdb.c can be used to show the
+** timestamps on pages of a database file, using a command like this:
+**
+** showdb --tmstmp $(DATABASE) pgidx
+*
+** The command above shows the timestamp and the intended use of every
+** pages in the database, in human-readable form. If you also add
+** the --csv option to the command above, then the command generates
+** a Comma-Separated-Value (CSV) file as output, which contains a
+** decoding of the complete timestamp tag on each page of the database.
+** This CVS file can be easily imported into another SQLite database
+** using a CLI command like the following:
+**
+** .import --csv '|showdb --tmstmp -csv orig.db pgidx' ts_table
+**
+** In the command above, the database containing the timestamps is
+** "orig.db" and the content is imported into a new table named "ts_table".
+** The "ts_table" is created automatically, using the column names found
+** in the first line of the CSV file. All columns of the automatically
+** created ts_table are of type TEXT. It might make more sense to
+** create the table yourself, using more sensible datatypes, like this:
+**
+** CREATE TABLE ts_table (
+** pgno INT, -- page number
+** tm REAL, -- seconds since 1970-01-01
+** frame INT, -- WAL frame number
+** flg INT, -- flag (tag byte 12)
+** salt INT, -- WAL salt (tag bytes 13-15)
+** parent INT, -- Parent page number
+** child INT, -- Index of this page in its parent
+** ovfl INT, -- Index of this page on the overflow chain
+** txt TEXT -- Description of this page
+** );
+**
+** Then import using:
+**
+** .import --csv --skip 1 '|showdb --tmstmp --csv orig.db pgidx' ts_table
+**
+** Note the addition of the "--skip 1" option on ".import" to bypass the
+** first line of the CSV file that contains the column names.
+**
+** Both programs "showdb" and "showtmlog" can be built by running
+** "make showtmlog showdb" from the top-level of a recent SQLite
+** source tree.
+*/
+#if defined(SQLITE_AMALGAMATION) && !defined(SQLITE_TMSTMPVFS_STATIC)
+# define SQLITE_TMSTMPVFS_STATIC
+#endif
+#ifdef SQLITE_TMSTMPVFS_STATIC
+# include "sqlite3.h"
+#else
+# include "sqlite3ext.h"
+ SQLITE_EXTENSION_INIT1
+#endif
+#include
+#include
+#include
+
+/*
+** Forward declaration of objects used by this utility
+*/
+typedef struct sqlite3_vfs TmstmpVfs;
+typedef struct TmstmpFile TmstmpFile;
+typedef struct TmstmpLog TmstmpLog;
+
+/*
+** Bytes of reserved space used by this extension
+*/
+#define TMSTMP_RESERVE 16
+
+/*
+** The magic number used to identify TmstmpFile objects
+*/
+#define TMSTMP_MAGIC 0x2a87b72d
+
+/*
+** Useful datatype abbreviations
+*/
+#if !defined(SQLITE_AMALGAMATION)
+ typedef unsigned char u8;
+ typedef unsigned int u32;
+#endif
+
+/*
+** Current process id
+*/
+#if defined(_WIN32)
+# include
+# define GETPID (u32)GetCurrentProcessId()
+#else
+# include
+# define GETPID (u32)getpid()
+#endif
+
+/* Access to a lower-level VFS that (might) implement dynamic loading,
+** access to randomness, etc.
+*/
+#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
+#define ORIGFILE(p) ((sqlite3_file*)(((TmstmpFile*)(p))+1))
+
+/* Information for the tmstmp log file. */
+struct TmstmpLog {
+ char *zLogname; /* Log filename */
+ FILE *log; /* Open log file */
+ int n; /* Bytes of a[] used */
+ unsigned char a[16*6]; /* Buffered header for the log */
+};
+
+/* An open WAL or DB file */
+struct TmstmpFile {
+ sqlite3_file base; /* IO methods */
+ u32 uMagic; /* Magic number for sanity checking */
+ u32 salt1; /* Last WAL salt-1 value */
+ u32 iFrame; /* Last WAL frame number */
+ u32 pgno; /* Current page number */
+ u32 pgsz; /* Size of each page, in bytes */
+ u8 isWal; /* True if this is a WAL file */
+ u8 isDb; /* True if this is a DB file */
+ u8 isCommit; /* Last WAL frame header was a transaction commit */
+ u8 hasCorrectReserve; /* File has the correct reserve size */
+ u8 inCkpt; /* True if in a checkpoint */
+ TmstmpLog *pLog; /* Log file */
+ TmstmpFile *pPartner; /* DB->WAL or WAL->DB mapping */
+ sqlite3_int64 iOfst; /* Offset of last WAL frame header */
+ sqlite3_vfs *pSubVfs; /* Underlying VFS */
+};
+
+/*
+** Event log opcodes
+*/
+#define ELOG_OPEN_DB 0x01
+#define ELOG_OPEN_WAL 0x02
+#define ELOG_WAL_PAGE 0x03
+#define ELOG_DB_PAGE 0x04
+#define ELOG_CKPT_START 0x05
+#define ELOG_CKPT_PAGE 0x06
+#define ELOG_CKPT_DONE 0x07
+#define ELOG_WAL_RESET 0x08
+#define ELOG_CLOSE_WAL 0x0e
+#define ELOG_CLOSE_DB 0x0f
+
+/*
+** Methods for TmstmpFile
+*/
+static int tmstmpClose(sqlite3_file*);
+static int tmstmpRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int tmstmpWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
+static int tmstmpTruncate(sqlite3_file*, sqlite3_int64 size);
+static int tmstmpSync(sqlite3_file*, int flags);
+static int tmstmpFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int tmstmpLock(sqlite3_file*, int);
+static int tmstmpUnlock(sqlite3_file*, int);
+static int tmstmpCheckReservedLock(sqlite3_file*, int *pResOut);
+static int tmstmpFileControl(sqlite3_file*, int op, void *pArg);
+static int tmstmpSectorSize(sqlite3_file*);
+static int tmstmpDeviceCharacteristics(sqlite3_file*);
+static int tmstmpShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
+static int tmstmpShmLock(sqlite3_file*, int offset, int n, int flags);
+static void tmstmpShmBarrier(sqlite3_file*);
+static int tmstmpShmUnmap(sqlite3_file*, int deleteFlag);
+static int tmstmpFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
+static int tmstmpUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
+
+/*
+** Methods for TmstmpVfs
+*/
+static int tmstmpOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int tmstmpDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int tmstmpAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int tmstmpFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
+static void *tmstmpDlOpen(sqlite3_vfs*, const char *zFilename);
+static void tmstmpDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*tmstmpDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
+static void tmstmpDlClose(sqlite3_vfs*, void*);
+static int tmstmpRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int tmstmpSleep(sqlite3_vfs*, int microseconds);
+static int tmstmpCurrentTime(sqlite3_vfs*, double*);
+static int tmstmpGetLastError(sqlite3_vfs*, int, char *);
+static int tmstmpCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
+static int tmstmpSetSystemCall(sqlite3_vfs*, const char*,sqlite3_syscall_ptr);
+static sqlite3_syscall_ptr tmstmpGetSystemCall(sqlite3_vfs*, const char *z);
+static const char *tmstmpNextSystemCall(sqlite3_vfs*, const char *zName);
+
+static sqlite3_vfs tmstmp_vfs = {
+ 3, /* iVersion (set when registered) */
+ 0, /* szOsFile (set when registered) */
+ 1024, /* mxPathname */
+ 0, /* pNext */
+ "tmstmpvfs", /* zName */
+ 0, /* pAppData (set when registered) */
+ tmstmpOpen, /* xOpen */
+ tmstmpDelete, /* xDelete */
+ tmstmpAccess, /* xAccess */
+ tmstmpFullPathname, /* xFullPathname */
+ tmstmpDlOpen, /* xDlOpen */
+ tmstmpDlError, /* xDlError */
+ tmstmpDlSym, /* xDlSym */
+ tmstmpDlClose, /* xDlClose */
+ tmstmpRandomness, /* xRandomness */
+ tmstmpSleep, /* xSleep */
+ tmstmpCurrentTime, /* xCurrentTime */
+ tmstmpGetLastError, /* xGetLastError */
+ tmstmpCurrentTimeInt64, /* xCurrentTimeInt64 */
+ tmstmpSetSystemCall, /* xSetSystemCall */
+ tmstmpGetSystemCall, /* xGetSystemCall */
+ tmstmpNextSystemCall /* xNextSystemCall */
+};
+
+static const sqlite3_io_methods tmstmp_io_methods = {
+ 3, /* iVersion */
+ tmstmpClose, /* xClose */
+ tmstmpRead, /* xRead */
+ tmstmpWrite, /* xWrite */
+ tmstmpTruncate, /* xTruncate */
+ tmstmpSync, /* xSync */
+ tmstmpFileSize, /* xFileSize */
+ tmstmpLock, /* xLock */
+ tmstmpUnlock, /* xUnlock */
+ tmstmpCheckReservedLock, /* xCheckReservedLock */
+ tmstmpFileControl, /* xFileControl */
+ tmstmpSectorSize, /* xSectorSize */
+ tmstmpDeviceCharacteristics, /* xDeviceCharacteristics */
+ tmstmpShmMap, /* xShmMap */
+ tmstmpShmLock, /* xShmLock */
+ tmstmpShmBarrier, /* xShmBarrier */
+ tmstmpShmUnmap, /* xShmUnmap */
+ tmstmpFetch, /* xFetch */
+ tmstmpUnfetch /* xUnfetch */
+};
+
+/*
+** Write a 6-byte millisecond timestamp into aOut[]
+*/
+static void tmstmpPutTS(TmstmpFile *p, unsigned char *aOut){
+ sqlite3_uint64 tm = 0;
+ p->pSubVfs->xCurrentTimeInt64(p->pSubVfs, (sqlite3_int64*)&tm);
+ tm -= 210866760000000LL;
+ aOut[0] = (tm>>40)&0xff;
+ aOut[1] = (tm>>32)&0xff;
+ aOut[2] = (tm>>24)&0xff;
+ aOut[3] = (tm>>16)&0xff;
+ aOut[4] = (tm>>8)&0xff;
+ aOut[5] = tm&0xff;
+}
+
+/*
+** Read a 32-bit big-endian unsigned integer and return it.
+*/
+static u32 tmstmpGetU32(const unsigned char *a){
+ return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
+}
+
+/* Write a 32-bit integer as big-ending into a[]
+*/
+static void tmstmpPutU32(u32 v, unsigned char *a){
+ a[0] = (v>>24) & 0xff;
+ a[1] = (v>>16) & 0xff;
+ a[2] = (v>>8) & 0xff;
+ a[3] = v & 0xff;
+}
+
+/* Free a TmstmpLog object */
+static void tmstmpLogFree(TmstmpLog *pLog){
+ if( pLog==0 ) return;
+ if( pLog->log ) fclose(pLog->log);
+ sqlite3_free(pLog->zLogname);
+ sqlite3_free(pLog);
+}
+
+/* Flush log content. Open the file if necessary. Return the
+** number of errors. */
+static int tmstmpLogFlush(TmstmpFile *p){
+ TmstmpLog *pLog = p->pLog;
+ assert( pLog!=0 );
+ if( pLog->log==0 ){
+ pLog->log = fopen(pLog->zLogname, "wb");
+ if( pLog->log==0 ){
+ tmstmpLogFree(pLog);
+ p->pLog = 0;
+ return 1;
+ }
+ }
+ (void)fwrite(pLog->a, pLog->n, 1, pLog->log);
+ fflush(pLog->log);
+ pLog->n = 0;
+ return 0;
+}
+
+/*
+** Write a record onto the event log
+*/
+static void tmstmpEvent(
+ TmstmpFile *p,
+ u8 op,
+ u8 a1,
+ u32 a2,
+ u32 a3,
+ u8 *pTS
+){
+ unsigned char *a;
+ TmstmpLog *pLog;
+ if( p->isWal ){
+ p = p->pPartner;
+ assert( p!=0 );
+ assert( p->isDb );
+ }
+ pLog = p->pLog;
+ if( pLog==0 ) return;
+ if( pLog->n >= (int)sizeof(pLog->a) ){
+ if( tmstmpLogFlush(p) ) return;
+ }
+ a = pLog->a + pLog->n;
+ a[0] = op;
+ a[1] = a1;
+ if( pTS ){
+ memcpy(a+2, pTS, 6);
+ }else{
+ tmstmpPutTS(p, a+2);
+ }
+ tmstmpPutU32(a2, a+8);
+ tmstmpPutU32(a3, a+12);
+ pLog->n += 16;
+ if( pLog->log || (op>=ELOG_WAL_PAGE && op<=ELOG_WAL_RESET) ){
+ (void)tmstmpLogFlush(p);
+ }
+}
+
+/*
+** Close a connection
+*/
+static int tmstmpClose(sqlite3_file *pFile){
+ TmstmpFile *p = (TmstmpFile *)pFile;
+ if( p->hasCorrectReserve ){
+ tmstmpEvent(p, p->isDb ? ELOG_CLOSE_DB : ELOG_CLOSE_WAL, 0, 0, 0, 0);
+ }
+ tmstmpLogFree(p->pLog);
+ if( p->pPartner ){
+ assert( p->pPartner->pPartner==p );
+ p->pPartner->pPartner = 0;
+ p->pPartner = 0;
+ }
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xClose(pFile);
+}
+
+/*
+** Read bytes from a file
+*/
+static int tmstmpRead(
+ sqlite3_file *pFile,
+ void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ int rc;
+ TmstmpFile *p = (TmstmpFile*)pFile;
+ pFile = ORIGFILE(pFile);
+ rc = pFile->pMethods->xRead(pFile, zBuf, iAmt, iOfst);
+ if( rc!=SQLITE_OK ) return rc;
+ if( p->isDb
+ && iOfst==0
+ && iAmt>=100
+ ){
+ const unsigned char *a = (unsigned char*)zBuf;
+ p->hasCorrectReserve = (a[20]==TMSTMP_RESERVE);
+ p->pgsz = (a[16]<<8) + a[17];
+ if( p->pgsz==1 ) p->pgsz = 65536;
+ if( p->pPartner ){
+ p->pPartner->hasCorrectReserve = p->hasCorrectReserve;
+ p->pPartner->pgsz = p->pgsz;
+ }
+ }
+ if( p->isWal
+ && p->inCkpt
+ && iAmt>=512 && iAmt<=65535 && (iAmt&(iAmt-1))==0
+ ){
+ p->pPartner->iFrame = (iOfst-56)/(p->pgsz+24) + 1;
+ }
+ return rc;
+}
+
+/*
+** Write data to a tmstmp-file.
+*/
+static int tmstmpWrite(
+ sqlite3_file *pFile,
+ const void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ TmstmpFile *p = (TmstmpFile*)pFile;
+ sqlite3_file *pSub = ORIGFILE(pFile);
+ if( !p->hasCorrectReserve ){
+ /* The database does not have the correct reserve size. No-op */
+ }else if( p->isWal ){
+ /* Writing into a WAL file */
+ if( iAmt==24 ){
+ /* A frame header */
+ u32 x = 0;
+ p->iFrame = (iOfst - 32)/(p->pgsz+24)+1;
+ p->pgno = tmstmpGetU32((const u8*)zBuf);
+ p->salt1 = tmstmpGetU32(((const u8*)zBuf)+8);
+ memcpy(&x, ((const u8*)zBuf)+4, 4);
+ p->isCommit = (x!=0);
+ p->iOfst = iOfst;
+ }else if( iAmt>=512 && iOfst==p->iOfst+24 ){
+ unsigned char s[TMSTMP_RESERVE];
+ memset(s, 0, TMSTMP_RESERVE);
+ tmstmpPutTS(p, s+2);
+ tmstmpEvent(p, ELOG_WAL_PAGE, p->isCommit, p->pgno, p->iFrame, s+2);
+ }else if( iAmt==32 && iOfst==0 ){
+ p->salt1 = tmstmpGetU32(((const u8*)zBuf)+16);
+ tmstmpEvent(p, ELOG_WAL_RESET, 0, 0, p->salt1, 0);
+ }
+ }else if( p->inCkpt ){
+ unsigned char *s = (unsigned char*)zBuf+iAmt-TMSTMP_RESERVE;
+ memset(s, 0, TMSTMP_RESERVE);
+ tmstmpPutTS(p, s+2);
+ tmstmpPutU32(p->iFrame, s+8);
+ tmstmpPutU32(p->pPartner->salt1 & 0xffffff, s+12);
+ assert( p->pgsz>0 );
+ tmstmpEvent(p, ELOG_CKPT_PAGE, 0, (iOfst/p->pgsz)+1, p->iFrame, 0);
+ }else if( p->pPartner==0 ){
+ /* Writing into a database in rollback mode */
+ unsigned char *s = (unsigned char*)zBuf+iAmt-TMSTMP_RESERVE;
+ memset(s, 0, TMSTMP_RESERVE);
+ tmstmpPutTS(p, s+2);
+ s[12] = 2;
+ assert( p->pgsz>0 );
+ tmstmpEvent(p, ELOG_DB_PAGE, 0, (u32)(iOfst/p->pgsz)+1, 0, s+2);
+ }
+ return pSub->pMethods->xWrite(pSub,zBuf,iAmt,iOfst);
+}
+
+/*
+** Truncate a tmstmp-file.
+*/
+static int tmstmpTruncate(sqlite3_file *pFile, sqlite_int64 size){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xTruncate(pFile, size);
+}
+
+/*
+** Sync a tmstmp-file.
+*/
+static int tmstmpSync(sqlite3_file *pFile, int flags){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xSync(pFile, flags);
+}
+
+/*
+** Return the current file-size of a tmstmp-file.
+*/
+static int tmstmpFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+ TmstmpFile *p = (TmstmpFile *)pFile;
+ pFile = ORIGFILE(p);
+ return pFile->pMethods->xFileSize(pFile, pSize);
+}
+
+/*
+** Lock a tmstmp-file.
+*/
+static int tmstmpLock(sqlite3_file *pFile, int eLock){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xLock(pFile, eLock);
+}
+
+/*
+** Unlock a tmstmp-file.
+*/
+static int tmstmpUnlock(sqlite3_file *pFile, int eLock){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xUnlock(pFile, eLock);
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on a tmstmp-file.
+*/
+static int tmstmpCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xCheckReservedLock(pFile, pResOut);
+}
+
+/*
+** File control method. For custom operations on a tmstmp-file.
+*/
+static int tmstmpFileControl(sqlite3_file *pFile, int op, void *pArg){
+ int rc;
+ TmstmpFile *p = (TmstmpFile*)pFile;
+ pFile = ORIGFILE(pFile);
+ rc = pFile->pMethods->xFileControl(pFile, op, pArg);
+ switch( op ){
+ case SQLITE_FCNTL_VFSNAME: {
+ if( p->hasCorrectReserve && rc==SQLITE_OK ){
+ *(char**)pArg = sqlite3_mprintf("tmstmp/%z", *(char**)pArg);
+ }
+ break;
+ }
+ case SQLITE_FCNTL_CKPT_START: {
+ p->inCkpt = 1;
+ assert( p->isDb );
+ assert( p->pPartner!=0 );
+ p->pPartner->inCkpt = 1;
+ if( p->hasCorrectReserve ){
+ tmstmpEvent(p, ELOG_CKPT_START, 0, 0, 0, 0);
+ }
+ rc = SQLITE_OK;
+ break;
+ }
+ case SQLITE_FCNTL_CKPT_DONE: {
+ p->inCkpt = 0;
+ assert( p->isDb );
+ assert( p->pPartner!=0 );
+ p->pPartner->inCkpt = 0;
+ if( p->hasCorrectReserve ){
+ tmstmpEvent(p, ELOG_CKPT_DONE, 0, 0, 0, 0);
+ }
+ rc = SQLITE_OK;
+ break;
+ }
+ }
+ return rc;
+}
+
+/*
+** Return the sector-size in bytes for a tmstmp-file.
+*/
+static int tmstmpSectorSize(sqlite3_file *pFile){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xSectorSize(pFile);
+}
+
+/*
+** Return the device characteristic flags supported by a tmstmp-file.
+*/
+static int tmstmpDeviceCharacteristics(sqlite3_file *pFile){
+ int devchar = 0;
+ pFile = ORIGFILE(pFile);
+ devchar = pFile->pMethods->xDeviceCharacteristics(pFile);
+ return (devchar & ~SQLITE_IOCAP_SUBPAGE_READ);
+}
+
+/* Create a shared memory file mapping */
+static int tmstmpShmMap(
+ sqlite3_file *pFile,
+ int iPg,
+ int pgsz,
+ int bExtend,
+ void volatile **pp
+){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xShmMap(pFile,iPg,pgsz,bExtend,pp);
+}
+
+/* Perform locking on a shared-memory segment */
+static int tmstmpShmLock(sqlite3_file *pFile, int offset, int n, int flags){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xShmLock(pFile,offset,n,flags);
+}
+
+/* Memory barrier operation on shared memory */
+static void tmstmpShmBarrier(sqlite3_file *pFile){
+ pFile = ORIGFILE(pFile);
+ pFile->pMethods->xShmBarrier(pFile);
+}
+
+/* Unmap a shared memory segment */
+static int tmstmpShmUnmap(sqlite3_file *pFile, int deleteFlag){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xShmUnmap(pFile,deleteFlag);
+}
+
+/* Fetch a page of a memory-mapped file */
+static int tmstmpFetch(
+ sqlite3_file *pFile,
+ sqlite3_int64 iOfst,
+ int iAmt,
+ void **pp
+){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xFetch(pFile, iOfst, iAmt, pp);
+}
+
+/* Release a memory-mapped page */
+static int tmstmpUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xUnfetch(pFile, iOfst, pPage);
+}
+
+
+/*
+** Open a tmstmp file handle.
+*/
+static int tmstmpOpen(
+ sqlite3_vfs *pVfs,
+ const char *zName,
+ sqlite3_file *pFile,
+ int flags,
+ int *pOutFlags
+){
+ TmstmpFile *p, *pDb;
+ sqlite3_file *pSubFile;
+ sqlite3_vfs *pSubVfs;
+ int rc;
+
+ pSubVfs = ORIGVFS(pVfs);
+ if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){
+ /* If the file is not a persistent database or a WAL file, then
+ ** bypass the timestamp logic all together */
+ return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags);
+ }
+ if( (flags & SQLITE_OPEN_WAL)!=0 ){
+ pDb = (TmstmpFile*)sqlite3_database_file_object(zName);
+ if( pDb==0
+ || pDb->uMagic!=TMSTMP_MAGIC
+ || !pDb->isDb
+ || pDb->pPartner!=0
+ ){
+ return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags);
+ }
+ }else{
+ pDb = 0;
+ }
+ p = (TmstmpFile*)pFile;
+ memset(p, 0, sizeof(*p));
+ pSubFile = ORIGFILE(pFile);
+ pFile->pMethods = &tmstmp_io_methods;
+ p->pSubVfs = pSubVfs;
+ p->uMagic = TMSTMP_MAGIC;
+ rc = pSubVfs->xOpen(pSubVfs, zName, pSubFile, flags, pOutFlags);
+ if( rc ) goto tmstmp_open_done;
+ if( pDb!=0 ){
+ p->isWal = 1;
+ p->pPartner = pDb;
+ pDb->pPartner = p;
+ }else{
+ u32 r2;
+ u32 pid;
+ TmstmpLog *pLog;
+ sqlite3_uint64 r1; /* Milliseconds since 1970-01-01 */
+ sqlite3_uint64 days; /* Days since 1970-01-01 */
+ sqlite3_uint64 sod; /* Start of date specified by r1 */
+ sqlite3_uint64 z; /* Days since 0000-03-01 */
+ sqlite3_uint64 era; /* 400-year era */
+ int h; /* hour */
+ int m; /* minute */
+ int s; /* second */
+ int f; /* millisecond */
+ int Y; /* year */
+ int M; /* month */
+ int D; /* day */
+ int y; /* year assuming March is first month */
+ unsigned int doe; /* day of 400-year era */
+ unsigned int yoe; /* year of 400-year era */
+ unsigned int doy; /* day of year */
+ unsigned int mp; /* month with March==0 */
+
+ p->isDb = 1;
+ r1 = 0;
+ pLog = sqlite3_malloc64( sizeof(TmstmpLog) );
+ if( pLog==0 ){
+ pSubFile->pMethods->xClose(pSubFile);
+ rc = SQLITE_NOMEM;
+ goto tmstmp_open_done;
+ }
+ memset(pLog, 0, sizeof(pLog[0]));
+ p->pLog = pLog;
+ p->pSubVfs->xCurrentTimeInt64(p->pSubVfs, (sqlite3_int64*)&r1);
+ r1 -= 210866760000000LL;
+ days = r1/86400000;
+ sod = (r1%86400000)/1000;
+ f = (int)(r1%1000);
+
+ h = sod/3600;
+ m = (sod%3600)/60;
+ s = sod%60;
+ z = days + 719468;
+ era = z/146097;
+ doe = (unsigned)(z - era*146097);
+ yoe = (doe - doe/1460 + doe/36524 - doe/146096)/365;
+ y = (int)yoe + era*400;
+ doy = doe - (365*yoe + yoe/4 - yoe/100);
+ mp = (5*doy + 2)/153;
+ D = doy - (153*mp + 2)/5 + 1;
+ M = mp + (mp<10 ? 3 : -9);
+ Y = y + (M <=2);
+ sqlite3_randomness(sizeof(r2), &r2);
+ pid = GETPID;
+ pLog->zLogname = sqlite3_mprintf(
+ "%s-tmstmp/%04d%02d%02dT%02d%02d%02d%03d-%08d-%08x",
+ zName, Y, M, D, h, m, s, f, pid, r2);
+ }
+ tmstmpEvent(p, p->isWal ? ELOG_OPEN_WAL : ELOG_OPEN_DB, 0, GETPID, 0, 0);
+
+tmstmp_open_done:
+ if( rc ) pFile->pMethods = 0;
+ return rc;
+}
+
+/*
+** All VFS interfaces other than xOpen are passed down into the Sub-VFS.
+*/
+static int tmstmpDelete(sqlite3_vfs *p, const char *zName, int syncDir){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xDelete(pSub,zName,syncDir);
+}
+static int tmstmpAccess(sqlite3_vfs *p, const char *zName, int flags, int *pR){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xAccess(pSub,zName,flags,pR);
+}
+static int tmstmpFullPathname(sqlite3_vfs*p,const char *zName,int n,char *zOut){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xFullPathname(pSub,zName,n,zOut);
+}
+static void *tmstmpDlOpen(sqlite3_vfs *p, const char *zFilename){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xDlOpen(pSub,zFilename);
+}
+static void tmstmpDlError(sqlite3_vfs *p, int nByte, char *zErrMsg){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xDlError(pSub,nByte,zErrMsg);
+}
+static void(*tmstmpDlSym(sqlite3_vfs *p, void *pDl, const char *zSym))(void){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xDlSym(pSub,pDl,zSym);
+}
+static void tmstmpDlClose(sqlite3_vfs *p, void *pDl){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xDlClose(pSub,pDl);
+}
+static int tmstmpRandomness(sqlite3_vfs *p, int nByte, char *zOut){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xRandomness(pSub,nByte,zOut);
+}
+static int tmstmpSleep(sqlite3_vfs *p, int microseconds){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xSleep(pSub,microseconds);
+}
+static int tmstmpCurrentTime(sqlite3_vfs *p, double *prNow){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xCurrentTime(pSub,prNow);
+}
+static int tmstmpGetLastError(sqlite3_vfs *p, int a, char *b){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xGetLastError(pSub,a,b);
+}
+static int tmstmpCurrentTimeInt64(sqlite3_vfs *p, sqlite3_int64 *piNow){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xCurrentTimeInt64(pSub,piNow);
+}
+static int tmstmpSetSystemCall(sqlite3_vfs *p, const char *zName,
+ sqlite3_syscall_ptr x){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xSetSystemCall(pSub,zName,x);
+}
+static sqlite3_syscall_ptr tmstmpGetSystemCall(sqlite3_vfs *p, const char *z){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xGetSystemCall(pSub,z);
+}
+static const char *tmstmpNextSystemCall(sqlite3_vfs *p, const char *zName){
+ sqlite3_vfs *pSub = ORIGVFS(p);
+ return pSub->xNextSystemCall(pSub,zName);
+}
+
+/*
+** Register the tmstmp VFS as the default VFS for the system.
+*/
+static int tmstmpRegisterVfs(void){
+ int rc = SQLITE_OK;
+ sqlite3_vfs *pOrig = sqlite3_vfs_find(0);
+ if( pOrig==0 ) return SQLITE_ERROR;
+ if( pOrig==&tmstmp_vfs ) return SQLITE_OK;
+ tmstmp_vfs.iVersion = pOrig->iVersion;
+ tmstmp_vfs.pAppData = pOrig;
+ tmstmp_vfs.szOsFile = pOrig->szOsFile + sizeof(TmstmpFile);
+ rc = sqlite3_vfs_register(&tmstmp_vfs, 1);
+ return rc;
+}
+
+#if defined(SQLITE_TMSTMPVFS_STATIC)
+/* This variant of the initializer runs when the extension is
+** statically linked.
+*/
+int sqlite3_register_tmstmpvfs(const char *NotUsed){
+ (void)NotUsed;
+ return tmstmpRegisterVfs();
+}
+int sqlite3_unregister_tmstmpvfs(void){
+ if( sqlite3_vfs_find("tmstmpvfs") ){
+ sqlite3_vfs_unregister(&tmstmp_vfs);
+ }
+ return SQLITE_OK;
+}
+#endif /* defined(SQLITE_TMSTMPVFS_STATIC */
+
+#if !defined(SQLITE_TMSTMPVFS_STATIC)
+/* This variant of the initializer function is used when the
+** extension is shared library to be loaded at run-time.
+*/
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+/*
+** This routine is called by sqlite3_load_extension() when the
+** extension is first loaded.
+***/
+int sqlite3_tmstmpvfs_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ int rc;
+ SQLITE_EXTENSION_INIT2(pApi);
+ (void)pzErrMsg; /* not used */
+ (void)db; /* not used */
+ rc = tmstmpRegisterVfs();
+ if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
+ return rc;
+}
+#endif /* !defined(SQLITE_TMSTMPVFS_STATIC) */
diff --git a/ext/misc/vtablog.c b/ext/misc/vtablog.c
index 2b3e30355..1e28495de 100644
--- a/ext/misc/vtablog.c
+++ b/ext/misc/vtablog.c
@@ -14,6 +14,8 @@
** on stdout when its key interfaces are called. This is intended for
** interactive analysis and debugging of virtual table interfaces.
**
+** HOW TO COMPILE:
+**
** To build this extension as a separately loaded shared library or
** DLL, use compiler command-lines similar to the following:
**
@@ -21,7 +23,7 @@
** (mac) clang -fPIC -dynamiclib vtablog.c -o vtablog.dylib
** (windows) cl vtablog.c -link -dll -out:vtablog.dll
**
-** Usage example:
+** USAGE EXAMPLE:
**
** .load ./vtablog
** CREATE VIRTUAL TABLE temp.log USING vtablog(
@@ -29,6 +31,23 @@
** rows=25
** );
** SELECT * FROM log;
+**
+** ARGUMENTS TO CREATE VIRTUAL TABLE:
+**
+** In "CREATE VIRTUAL TABLE temp.log AS vtablog(ARGS....)" statement, the
+** ARGS argument is a list of key-value pairs that can be any of the
+** following.
+**
+** schema=TEXT Text is a CREATE TABLE statement that defines
+** the schema of the new virtual table.
+**
+** rows=N The table as N rows.
+**
+** consume_order_by=N If the left-most ORDER BY terms is ASC and
+** against column N (where the leftmost column
+** is #1) then set the orderByConsumed=1 flag in
+** xBestIndex. Or if the left-most ORDER BY is
+** DESC and against column -N, do likewise.
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
@@ -49,6 +68,8 @@ struct vtablog_vtab {
char *zName; /* Table name. argv[2] of xConnect/xCreate */
int nRow; /* Number of rows in the table */
int nCursor; /* Number of cursors created */
+ int iConsumeOB; /* Consume the ORDER BY clause if on column N-th
+ ** and consumeOB=N or consumeOB=(-N) and DESC */
};
/* vtablog_cursor is a subclass of sqlite3_vtab_cursor which will
@@ -180,6 +201,7 @@ static int vtablogConnectCreate(
int rc;
char *zSchema = 0;
char *zNRow = 0;
+ char *zConsumeOB = 0;
printf("%s.%s.%s():\n", argv[1], argv[2],
isCreate ? "xCreate" : "xConnect");
@@ -203,6 +225,10 @@ static int vtablogConnectCreate(
rc = SQLITE_ERROR;
goto vtablog_end_connect;
}
+ if( vtablog_string_parameter(pzErr, "consume_order_by", z, &zConsumeOB) ){
+ rc = SQLITE_ERROR;
+ goto vtablog_end_connect;
+ }
}
if( zSchema==0 ){
zSchema = sqlite3_mprintf("%s","CREATE TABLE x(a,b);");
@@ -221,6 +247,10 @@ static int vtablogConnectCreate(
pNew->nRow = 10;
if( zNRow ) pNew->nRow = atoi(zNRow);
printf(" nrow = %d\n", pNew->nRow);
+ if( zConsumeOB ) pNew->iConsumeOB = atoi(zConsumeOB);
+ if( pNew->iConsumeOB ){
+ printf(" consume_order_by = %d\n", pNew->iConsumeOB);
+ }
pNew->zDb = sqlite3_mprintf("%s", argv[1]);
pNew->zName = sqlite3_mprintf("%s", argv[2]);
}
@@ -228,6 +258,7 @@ static int vtablogConnectCreate(
vtablog_end_connect:
sqlite3_free(zSchema);
sqlite3_free(zNRow);
+ sqlite3_free(zConsumeOB);
return rc;
}
static int vtablogCreate(
@@ -514,16 +545,27 @@ static int vtablogBestIndex(
}
}
printf(" nOrderBy: %d\n", p->nOrderBy);
- for(i=0; inOrderBy; i++){
- printf(" orderby[%d]: col=%d desc=%d\n",
- i,
- p->aOrderBy[i].iColumn,
- p->aOrderBy[i].desc);
+ if( p->nOrderBy ){
+ for(i=0; inOrderBy; i++){
+ printf(" orderby[%d]: col=%d desc=%d\n",
+ i,
+ p->aOrderBy[i].iColumn,
+ p->aOrderBy[i].desc);
+ }
+ if( pTab->iConsumeOB ){
+ int N = p->aOrderBy[0].iColumn+1;
+ if( (p->aOrderBy[0].desc && N==-pTab->iConsumeOB)
+ || (!p->aOrderBy[0].desc && N==pTab->iConsumeOB)
+ ){
+ p->orderByConsumed = 1;
+ }
+ }
}
p->estimatedCost = (double)500;
p->estimatedRows = 500;
printf(" idxNum=%d\n", p->idxNum);
printf(" idxStr=NULL\n");
+ printf(" sqlite3_vtab_distinct()=%d\n", sqlite3_vtab_distinct(p));
printf(" orderByConsumed=%d\n", p->orderByConsumed);
printf(" estimatedCost=%g\n", p->estimatedCost);
printf(" estimatedRows=%lld\n", p->estimatedRows);
diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c
index 2f74906d9..c4862650b 100644
--- a/ext/misc/zipfile.c
+++ b/ext/misc/zipfile.c
@@ -393,7 +393,7 @@ static int zipfileConnect(
rc = sqlite3_declare_vtab(db, ZIPFILE_SCHEMA);
if( rc==SQLITE_OK ){
- pNew = (ZipfileTab*)sqlite3_malloc64((sqlite3_int64)nByte+nFile);
+ pNew = (ZipfileTab*)sqlite3_malloc64((i64)nByte+nFile);
if( pNew==0 ) return SQLITE_NOMEM;
memset(pNew, 0, nByte+nFile);
pNew->db = db;
@@ -539,14 +539,15 @@ static void zipfileCursorErr(ZipfileCsr *pCsr, const char *zFmt, ...){
static int zipfileReadData(
FILE *pFile, /* Read from this file */
u8 *aRead, /* Read into this buffer */
- int nRead, /* Number of bytes to read */
+ i64 nRead, /* Number of bytes to read */
i64 iOff, /* Offset to read from */
char **pzErrmsg /* OUT: Error message (from sqlite3_malloc) */
){
size_t n;
fseek(pFile, (long)iOff, SEEK_SET);
- n = fread(aRead, 1, nRead, pFile);
- if( (int)n!=nRead ){
+ n = fread(aRead, 1, (long)nRead, pFile);
+ if( n!=(size_t)nRead ){
+ sqlite3_free(*pzErrmsg);
*pzErrmsg = sqlite3_mprintf("error in fread()");
return SQLITE_ERROR;
}
@@ -563,7 +564,7 @@ static int zipfileAppendData(
fseek(pTab->pWriteFd, (long)pTab->szCurrent, SEEK_SET);
n = fwrite(aWrite, 1, nWrite, pTab->pWriteFd);
if( (int)n!=nWrite ){
- pTab->base.zErrMsg = sqlite3_mprintf("error in fwrite()");
+ zipfileTableErr(pTab,"error in fwrite()");
return SQLITE_ERROR;
}
pTab->szCurrent += nWrite;
@@ -704,7 +705,12 @@ static int zipfileScanExtra(u8 *aExtra, int nExtra, u32 *pmTime){
u8 *p = aExtra;
u8 *pEnd = &aExtra[nExtra];
- while( pcds);
if( rc!=SQLITE_OK ){
- *pzErr = sqlite3_mprintf("failed to read CDS at offset %lld", iOff);
+ zipfileTableErr(pTab, "failed to read CDS at offset %lld", iOff);
}else if( aBlob==0 ){
rc = zipfileReadData(
pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr
@@ -900,14 +907,15 @@ static int zipfileGetEntry(
rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr);
}else{
aRead = (u8*)&aBlob[pNew->cds.iOffset];
- if( (pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ)>nBlob ){
+ if( ((i64)pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ)>nBlob ){
rc = zipfileCorrupt(pzErr);
}
}
+ memset(&lfh, 0, sizeof(lfh));
if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh);
if( rc==SQLITE_OK ){
- pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
+ pNew->iDataOff = (i64)pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
pNew->iDataOff += lfh.nFile + lfh.nExtra;
if( aBlob && pNew->cds.szCompressed ){
if( pNew->iDataOff + pNew->cds.szCompressed > nBlob ){
@@ -918,7 +926,7 @@ static int zipfileGetEntry(
}
}
}else{
- *pzErr = sqlite3_mprintf("failed to read LFH at offset %d",
+ zipfileTableErr(pTab, "failed to read LFH at offset %d",
(int)pNew->cds.iOffset
);
}
@@ -942,7 +950,7 @@ static int zipfileNext(sqlite3_vtab_cursor *cur){
int rc = SQLITE_OK;
if( pCsr->pFile ){
- i64 iEof = pCsr->eocd.iOffset + pCsr->eocd.nSize;
+ i64 iEof = (i64)pCsr->eocd.iOffset + (i64)pCsr->eocd.nSize;
zipfileEntryFree(pCsr->pCurrent);
pCsr->pCurrent = 0;
if( pCsr->iNextOff>=iEof ){
@@ -1008,7 +1016,7 @@ static void zipfileInflate(
if( err!=Z_STREAM_END ){
zipfileCtxErrorMsg(pCtx, "inflate() failed (%d)", err);
}else{
- sqlite3_result_blob(pCtx, aRes, nOut, zipfileFree);
+ sqlite3_result_blob(pCtx, aRes, (int)str.total_out, zipfileFree);
aRes = 0;
}
}
@@ -1180,12 +1188,12 @@ static int zipfileEof(sqlite3_vtab_cursor *cur){
static int zipfileReadEOCD(
ZipfileTab *pTab, /* Return errors here */
const u8 *aBlob, /* Pointer to in-memory file image */
- int nBlob, /* Size of aBlob[] in bytes */
+ i64 nBlob, /* Size of aBlob[] in bytes */
FILE *pFile, /* Read from this file if aBlob==0 */
ZipfileEOCD *pEOCD /* Object to populate */
){
u8 *aRead = pTab->aBuffer; /* Temporary buffer */
- int nRead; /* Bytes to read from file */
+ i64 nRead; /* Bytes to read from file */
int rc = SQLITE_OK;
memset(pEOCD, 0, sizeof(ZipfileEOCD));
@@ -1206,7 +1214,7 @@ static int zipfileReadEOCD(
}
if( rc==SQLITE_OK ){
- int i;
+ i64 i;
/* Scan backwards looking for the signature bytes */
for(i=nRead-20; i>=0; i--){
@@ -1217,9 +1225,7 @@ static int zipfileReadEOCD(
}
}
if( i<0 ){
- pTab->base.zErrMsg = sqlite3_mprintf(
- "cannot find end of central directory record"
- );
+ zipfileTableErr(pTab, "cannot find end of central directory record");
return SQLITE_ERROR;
}
@@ -1264,7 +1270,7 @@ static void zipfileAddEntry(
}
}
-static int zipfileLoadDirectory(ZipfileTab *pTab, const u8 *aBlob, int nBlob){
+static int zipfileLoadDirectory(ZipfileTab *pTab, const u8 *aBlob, i64 nBlob){
ZipfileEOCD eocd;
int rc;
int i;
@@ -1312,7 +1318,7 @@ static int zipfileFilter(
}else if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){
static const u8 aEmptyBlob = 0;
const u8 *aBlob = (const u8*)sqlite3_value_blob(argv[0]);
- int nBlob = sqlite3_value_bytes(argv[0]);
+ i64 nBlob = sqlite3_value_bytes(argv[0]);
assert( pTab->pFirstEntry==0 );
if( aBlob==0 ){
aBlob = &aEmptyBlob;
@@ -1510,7 +1516,7 @@ static int zipfileBegin(sqlite3_vtab *pVtab){
assert( pTab->pWriteFd==0 );
if( pTab->zFile==0 || pTab->zFile[0]==0 ){
- pTab->base.zErrMsg = sqlite3_mprintf("zipfile: missing filename");
+ zipfileTableErr(pTab, "zipfile: missing filename");
return SQLITE_ERROR;
}
@@ -1520,9 +1526,9 @@ static int zipfileBegin(sqlite3_vtab *pVtab){
** in main-memory until the transaction is committed. */
pTab->pWriteFd = sqlite3_fopen(pTab->zFile, "ab+");
if( pTab->pWriteFd==0 ){
- pTab->base.zErrMsg = sqlite3_mprintf(
- "zipfile: failed to open file %s for writing", pTab->zFile
- );
+ zipfileTableErr(pTab,
+ "zipfile: failed to open file %s for writing", pTab->zFile
+ );
rc = SQLITE_ERROR;
}else{
fseek(pTab->pWriteFd, 0, SEEK_END);
@@ -1987,7 +1993,7 @@ struct ZipfileCtx {
ZipfileBuffer cds;
};
-static int zipfileBufferGrow(ZipfileBuffer *pBuf, int nByte){
+static int zipfileBufferGrow(ZipfileBuffer *pBuf, i64 nByte){
if( pBuf->n+nByte>pBuf->nAlloc ){
u8 *aNew;
sqlite3_int64 nNew = pBuf->n ? pBuf->n*2 : 512;
@@ -2036,7 +2042,7 @@ static void zipfileStep(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal){
char *zName = 0; /* Path (name) of new entry */
int nName = 0; /* Size of zName in bytes */
char *zFree = 0; /* Free this before returning */
- int nByte;
+ i64 nByte;
memset(&e, 0, sizeof(e));
p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx));
diff --git a/ext/qrf/README.md b/ext/qrf/README.md
new file mode 100644
index 000000000..4bb1790a4
--- /dev/null
+++ b/ext/qrf/README.md
@@ -0,0 +1,762 @@
+# SQLite Query Result Formatting Subsystem
+
+The "Query Result Formatter" or "QRF" subsystem is a C-language
+subroutine that formats the output from an SQLite query for display using
+a fix-width font, for example on a terminal window over an SSH connection.
+The output format is configurable. The application can request various
+table formats, with flexible column widths and alignments, row-oriented
+formats, such as CSV and similar, as well as various special purpose formats
+like JSON.
+
+For the first 25 years of SQLite's existance, the
+[command-line interface](https://sqlite.org/cli.html) (CLI)
+formatted query results using a hodge-podge of routines
+that had grown slowly by accretion. The QRF was created
+in fall of 2025 to refactor and reorganize this code into
+a more usable form. The idea behind QRF is to implement all the
+query result formatting capabilities of the CLI in a subroutine
+that can be incorporated and reused by other applications.
+
+## 1.0 Overview Of Operation
+
+Suppose variable `sqlite3_stmt *pStmt` is a pointer to an SQLite
+prepared statement that has been reset and bound and is ready to run.
+Then to format the output from this prepared statement, use code
+similar to the following:
+
+> ~~~
+sqlite3_qrf_spec spec; /* Format specification */
+char *zErrMsg; /* Text error message (optional) */
+char *zResult = 0; /* Formatted output written here */
+int rc; /* Result code */
+
+memset(&spec, 0, sizeof(spec)); /* Initialize the spec */
+spec.iVersion = 1; /* Version number must be 1 */
+spec.pzOutput = &zResult; /* Write results in variable zResult */
+/* Optionally fill in other settings in spec here, as needed */
+zErrMsg = 0; /* Not required; just being pedantic */
+rc = sqlite3_format_query_result(pStmt, &spec, &zErrMsg); /* Format results */
+if( rc ){
+ printf("Error (%d): %s\n", rc, zErrMsg); /* Report an error */
+ sqlite3_free(zErrMsg); /* Free the error message text */
+}else{
+ printf("%s", zResult); /* Report the results */
+}
+sqlite3_free(zResult); /* Free memory used to hold results */
+~~~
+
+The `sqlite3_qrf_spec` object describes the desired output format
+and where to send the generated output. Most of the work in using
+the QRF involves filling out the sqlite3_qrf_spec.
+
+### 1.1 Using QRF with SQL text
+
+If you start with SQL text instead of an sqlite3_stmt pointer, and
+especially if the SQL text might comprise two or more statements, then
+the SQL text needs to be converted into sqlite3_stmt objects separately.
+If the original SQL text is in a variable `const char *zSql` and the
+database connection is in variable `sqlite3 *db`, then code
+similar to the following should work:
+
+> ~~~
+sqlite3_qrf_spec spec; /* Format specification */
+char *zErrMsg; /* Text error message (optional) */
+char *zResult = 0; /* Formatted output written here */
+sqlite3_stmt *pStmt; /* Next prepared statement */
+int rc; /* Result code */
+
+memset(&spec, 0, sizeof(spec)); /* Initialize the spec */
+spec.iVersion = 1; /* Version number must be 1 */
+spec.pzOutput = &zResult; /* Write results in variable zResult */
+/* Optionally fill in other settings in spec here, as needed */
+zErrMsg = 0; /* Not required; just being pedantic */
+while( zSql && zSql[0] ){
+ pStmt = 0; /* Not required; just being pedantic */
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zSql);
+ if( rc!=SQLITE_OK ){
+ printf("Error: %s\n", sqlite3_errmsg(db));
+ }else{
+ rc = sqlite3_format_query_result(pStmt, &spec, &zErrMsg); /* Get results */
+ if( rc ){
+ printf("Error (%d): %s\n", rc, zErrMsg); /* Report an error */
+ sqlite3_free(zErrMsg); /* Free the error message text */
+ }else{
+ printf("%s", zResult); /* Report the results */
+ sqlite3_free(zResult); /* Free memory used to hold results */
+ zResult = 0;
+ }
+ }
+ sqlite3_finalize(pStmt);
+}
+~~~
+
+
+## 2.0 The `sqlite3_qrf_spec` object
+
+The `sqlite3_qrf_spec` looks like this:
+
+> ~~~
+typedef struct sqlite3_qrf_spec sqlite3_qrf_spec;
+struct sqlite3_qrf_spec {
+ unsigned char iVersion; /* Version number of this structure */
+ unsigned char eStyle; /* Formatting style. "box", "csv", etc... */
+ unsigned char eEsc; /* How to escape control characters in text */
+ unsigned char eText; /* Quoting style for text */
+ unsigned char eTitle; /* Quating style for the text of column names */
+ unsigned char eBlob; /* Quoting style for BLOBs */
+ unsigned char bTitles; /* True to show column names */
+ unsigned char bWordWrap; /* Try to wrap on word boundaries */
+ unsigned char bTextJsonb; /* Render JSONB blobs as JSON text */
+ unsigned char eDfltAlign; /* Default alignment, no covered by aAlignment */
+ unsigned char eTitleAlign; /* Alignment for column headers */
+ unsigned char bSplitColumn; /* Wrap single-column output into many columns */
+ unsigned char bBorder; /* Show outer border in Box and Table styles */
+ short int nWrap; /* Wrap columns wider than this */
+ short int nScreenWidth; /* Maximum overall table width */
+ short int nLineLimit; /* Maximum number of lines for any row */
+ short int nTitleLimit; /* Maximum number of characters in a title */
+ int nCharLimit; /* Maximum number of characters in a cell */
+ int nWidth; /* Number of entries in aWidth[] */
+ int nAlign; /* Number of entries in aAlignment[] */
+ short int *aWidth; /* Column widths */
+ unsigned char *aAlign; /* Column alignments */
+ char *zColumnSep; /* Alternative column separator */
+ char *zRowSep; /* Alternative row separator */
+ char *zTableName; /* Output table name */
+ char *zNull; /* Rendering of NULL */
+ char *(*xRender)(void*,sqlite3_value*); /* Render a value */
+ int (*xWrite)(void*,const char*,sqlite3_int64); /* Write output */
+ void *pRenderArg; /* First argument to the xRender callback */
+ void *pWriteArg; /* First argument to the xWrite callback */
+ char **pzOutput; /* Storage location for output string */
+ /* Additional fields may be added in the future */
+};
+~~~
+
+Do not be alarmed by the complexity of this structure. Everything can
+be zeroed except for:
+
+ * `.iVersion`
+ * One of `.pzOutput` or `.xWrite`.
+
+You do not need to understand and configure every field of this object
+in order to use QRF effectively. Start by zeroing out the whole structure,
+then initializing iVersion and one of pzOutput or xWrite. Then maybe
+tweak one or two other settings to get the output you want.
+
+Further detail on the meanings of each of the fields in the
+`sqlite3_qrf_spec` object is in the subsequent sections.
+
+### 2.1 Structure Version Number
+
+The sqlite3_qrf_spec.iVersion field must be 1. Future enhancements to
+the QRF might add new fields to the bottom of the sqlite3_qrf_spec
+object. Those new fields will only be accessible if the iVersion is greater
+than 1. Thus the iVersion field is used to support upgradability.
+
+### 2.2 Output Deposition (xWrite and pzOutput)
+
+The formatted output can either be sent to a callback function
+or accumulated into an output buffer in memory obtained
+from sqlite3_malloc(). If the sqlite3_qrf_spec.xWrite column is not NULL,
+then that function is invoked (using sqlite3_qrf_spec.xWriteArg as its
+first argument) to transmit the formatted output. Or, if
+sqlite3_qrf_spec.pzOutput points to a pointer to a character, then that
+pointer is made to point to memory obtained from sqlite3_malloc() that
+contains the complete text of the formatted output. If spec.pzOutput\[0\]
+is initially non-NULL, then it is assumed to already point to memory obtained
+from sqlite3_malloc(). In that case, the buffer is resized using
+sqlite3_realloc() and the new text is appended.
+
+One of either sqlite3_qrf_spec.xWrite and sqlite3_qrf_spec.pzOutput must be
+non-NULL and the other must be NULL.
+
+The return value from xWrite is an SQLITE result code. The usual return
+should be SQLITE_OK. But if for some reason the write fails, a different
+value might be returned.
+
+### 2.3 Output Format
+
+The sqlite3_qrf_spec.eStyle field is an integer code that defines the
+specific output format that will be generated. See [section 4.0](#style)
+below for details on the meaning of the various style options.
+
+Other fields in sqlite3_qrf_spec might be used or might be
+ignored, depending on the value of eStyle.
+
+### 2.4 Show Column Names (bTitles)
+
+The sqlite3_qrf_spec.bTitles field can be either QRF_SW_Auto,
+QRF_SW_On, or QRF_SW_Off. Those three constants also have shorter
+alternative spellings: QRF_Auto, QRF_No, and
+QRF_Yes.
+
+> ~~~
+#define QRF_SW_Auto 0 /* Let QRF choose the best value */
+#define QRF_SW_Off 1 /* This setting is forced off */
+#define QRF_SW_On 2 /* This setting is forced on */
+#define QRF_Auto 0 /* Alternate spelling for QRF_SW_Auto and others */
+#define QRF_No 1 /* Alternate spelling for QRF_SW_Off */
+#define QRF_Yes 2 /* Alternate spelling for QRF_SW_On */
+~~~
+
+If the value is QRF_Yes, then column names appear in the output.
+If the value is QRF_No, column names are omitted. If the
+value is QRF_Auto, then an appropriate default is chosen.
+
+### 2.5 Control Character Escapes (eEsc)
+
+The sqlite3_qrf_spec.eEsc determines how ASCII control characters are
+formatted when displaying TEXT values in the result. These are the allowed
+values:
+
+> ~~~
+#define QRF_ESC_Auto 0 /* Choose the ctrl-char escape automatically */
+#define QRF_ESC_Off 1 /* Do not escape control characters */
+#define QRF_ESC_Ascii 2 /* Unix-style escapes. Ex: U+0007 shows ^G */
+#define QRF_ESC_Symbol 3 /* Unicode escapes. Ex: U+0007 shows U+2407 */
+~~~
+
+If the value of eEsc is QRF_ESC_Ascii, then the control character
+with value X is displayed as ^Y where Y is X+0x40. Hence, a
+backspace character (U+0008) is shown as "^H".
+
+If eEsc is QRF_ESC_Symbol, then control characters in the range of U+0001
+through U+001f are mapped into U+2401 through U+241f, respectively.
+
+If the value of eEsc is QRF_ESC_Off, then no translation occurs
+and control characters that appear in TEXT strings are transmitted
+to the formatted output as-is. This can be dangerous in applications,
+since an adversary who can control TEXT values might be able to
+inject ANSI cursor movement sequences to hide nefarious values.
+
+The QRF_ESC_Auto value for eEsc means that the query result formatter
+gets to pick whichever control-character encoding it thinks is best for
+the situation. This will usually be QRF_ESC_Ascii.
+
+The TAB (U+0009), LF (U+000a) and CR-LF (U+000d,U+000a) character
+sequence are always output literally and are not mapped to alternative
+display values, regardless of this setting.
+
+### 2.6 Display of TEXT values (eText, eTitle)
+
+The sqlite3_qrf_spec.eText controls how text values are rendered in the
+display. sqlite3_qrf_spec.eTitle controls how column names are rendered.
+Both fields can have one of the following values:
+
+> ~~~
+#define QRF_TEXT_Auto 0 /* Choose text encoding automatically */
+#define QRF_TEXT_Plain 1 /* Literal text */
+#define QRF_TEXT_Sql 2 /* Quote as an SQL literal */
+#define QRF_TEXT_Csv 3 /* CSV-style quoting */
+#define QRF_TEXT_Html 4 /* HTML-style quoting */
+#define QRF_TEXT_Tcl 5 /* C/Tcl quoting */
+#define QRF_TEXT_Json 6 /* JSON quoting */
+#define QRF_TEXT_Relaxed 7 /* Relaxed SQL quoting */
+~~~
+
+A value of QRF_TEXT_Auto means that the query result formatter will choose
+what it thinks will be the best text encoding.
+
+A value of QRF_TEXT_Plain means that text values appear in the output exactly
+as they are found in the database file, with no translation.
+
+A value of QRF_TEXT_Sql means that text values are escaped so that they
+look like SQL literals. That means the value will be surrounded by
+single-quotes (U+0027) and any single-quotes contained within the text
+will be doubled.
+
+QRF_TEXT_Relaxed is similar to QRF_TEXT_Sql, except that it automatically
+reverts to QRF_TEXT_Plain if the value to be displayed does not contain
+special characters and is not easily confused with a NULL or a numeric
+value. QRF_TEXT_Relaxed strives to minimize the amount of quoting syntax
+while keeping the result unambiguous and easy for humans to read. The
+precise rules for when quoting is omitted in QRF_TEXT_Relaxed, and when
+it is applied, might be adjusted in future releases.
+
+A value of QRF_TEXT_Csv means that text values are escaped in accordance
+with RFC 4180, which defines Comma-Separated-Value or CSV files.
+Text strings that contain no special values appears as-is. Text strings
+that contain special values are contained in double-quotes (U+0022) and
+any double-quotes within the value are doubled.
+
+A value of QRF_TEXT_Html means that text values are escaped for use in
+HTML. Special characters "<", "&", ">", """, and "'"
+are displayed as "<", "&", ">", """,
+and "'", respectively.
+
+A value of QRF_TEXT_Tcl means that text values are displayed inside of
+double-quotes and special characters within the string are escaped using
+backslash escape, as in ANSI-C or TCL or Perl or other popular programming
+languages.
+
+A value of QRF_TEXT_Json gives similar results as QRF_TEXT_Tcl except that the
+rules are adjusted so that the displayed string is strictly conforming
+the JSON specification.
+
+### 2.7 How to display BLOB values (eBlob and bTextJsonb)
+
+If the sqlite3_qrf_spec.bTextJsonb flag is QRF_SW_On and if the value to be
+displayed is JSONB, then the JSONB is translated into text JSON and the
+text is shown according to the sqlite3_qrf_spec.eText setting as
+described in the previous section.
+
+If the bTextJsonb flag is QRF_SW_Off (the usual case) or if the BLOB value to
+be displayed is not JSONB, then the sqlite3_qrf_spec.eBlob field determines
+how the BLOB value is formatted. The following options are available;
+
+> ~~~
+#define QRF_BLOB_Auto 0 /* Determine BLOB quoting using eText */
+#define QRF_BLOB_Text 1 /* Display content exactly as it is */
+#define QRF_BLOB_Sql 2 /* Quote as an SQL literal */
+#define QRF_BLOB_Hex 3 /* Hexadecimal representation */
+#define QRF_BLOB_Tcl 4 /* "\000" notation */
+#define QRF_BLOB_Json 5 /* A JSON string */
+#define QRF_BLOB_Size 6 /* Display the blob size only */
+~~~
+
+A value of QRF_BLOB_Auto means that display format is selected automatically
+by sqlite3_format_query_result() based on eStyle and eText.
+
+A value of QRF_BLOB_Text means that BLOB values are interpreted as UTF8
+text and are displayed using formatting results set by eEsc and
+eText.
+
+A value of QRF_BLOB_Sql means that BLOB values are shown as SQL BLOB
+literals: a prefix "`x'`" following by hexadecimal and ending with a
+final "`'`".
+
+A value of QRF_BLOB_Hex means that BLOB values are shown as
+hexadecimal text with no delimiters.
+
+A value of QRF_BLOB_Tcl means that BLOB values are shown as a
+C/Tcl/Perl string literal where every byte is an octal backslash
+escape. So a BLOB of `x'052881f3'` would be displayed as
+`"\005\050\201\363"`.
+
+A value of QRF_BLOB_Json is similar to QRF_BLOB_Tcl except that is
+uses unicode backslash escapes, since JSON does not understand
+the C/Tcl/Perl octal backslash escapes. So the string from the
+previous paragraph would be shown as
+`"\u0005\u0028\u0081\u00f3"`.
+
+A value of QRF_BLOB_Size does not show any BLOB content at all.
+Instead, it substitutes a text string that says how many bytes
+the BLOB contains.
+
+### 2.8 Maximum size of displayed content (nLineLimit, nCharLimit, nTitleLimit)
+
+If the sqlite3_qrf_spec.nCharLimit setting is non-zero, then the formatter
+will display only the first nCharLimit characters of each value.
+Only characters that take up space are counted when enforcing this
+limit. Zero-width characters and VT100 escape sequences do not count
+toward this limit. The count is in characters, not bytes. When
+imposing this limit, the formatter adds the three characters "..."
+to the end of the value. Those added characters are not counted
+as part of the limit. Very small limits still result in truncation,
+but might render a few more characters than the limit.
+
+If the sqlite3_qrf_spec.nLineLimit setting is non-zero, then the
+formatter will only display the first nLineLimit lines of each value.
+It does not matter if the value is split because it contains a newline
+character, or if it split by wrapping. This setting merely limits
+the number of displayed lines. The nLineLimit setting currently only
+works for **Box**, **Column**, **Line**, **Markdown**, and **Table**
+styles, though that limitation might change in future releases.
+
+The idea behind both of these settings is to prevent large renderings
+when doing a query that (unexpectedly) contains very large text or
+blob values: perhaps megabyes of text.
+
+If the sqlite3_qrf_spec.nTitleLimit is non-zero, then the formatter
+attempts to limits the size of column titles to at most nTitleLimit
+display characters in width and a single line of text. The nTitleLimit
+is useful for queries that have result columns that are scalar
+subqueries or complex expressions. If those columns lack an AS
+clause, then the name of the column will be a copy of the expression
+that defines the column, which in some queries can be hundreds of
+characters and multiple lines in length, which can reduce the readability
+of tabular displays. An nTitleLimit somewhere in the range of 10 to 20.
+can improve readability. The nTitleLimit setting currently only
+works for **Box**, **Column**, **Line**, **Markdown**, and **Table**
+styles, though that limitation might change in future releases.
+
+### 2.9 Word Wrapping In Columnar Styles (nWrap, bWordWrap)
+
+When using columnar formatting modes (QRF_STYLE_Box, QRF_STYLE_Column,
+QRF_STYLE_Markdown, or QRF_STYLE_Table), the formatter attempts to limit
+the width of any individual column to sqlite3_qrf_spec.nWrap characters
+if nWrap is non-zero. A zero value for nWrap means "unlimited".
+The nWrap limit might be exceeded if the limit is very small.
+
+In order to keep individual columns within requested width limits,
+it is sometimes necessary to wrap the content for a single row of
+a single column across multiple lines. When this
+becomes necessary and if the bWordWrap setting is QRF_Yes, then the
+formatter attempts to split the content on whitespace or at a word boundary.
+If bWordWrap is QRF_No, then the formatter is free to split content
+anywhere, including in the middle of a word.
+
+For narrow columns and wide words, it might sometimes be necessary to split
+a column in the middle of a word, even when bWordWrap is QRF_Yes.
+
+### 2.10 Helping The Output To Fit On The Terminal (nScreenWidth)
+
+The sqlite3_qrf_spec.nScreenWidth field can be set the number of
+characters that will fit on one line on the viewer output device.
+This is typically a number like 80 or 132. The formatter will attempt
+to reduce the length of output lines, depending on the style, so
+that all output fits on that screen.
+
+A value of zero for nScreenWidth means "unknown" or "no width limit".
+When the value is zero, the formatter makes no attempt to keep the
+lines of output short.
+
+The nScreenWidth is a hint to the formatter, not a requirement.
+The formatter trieds to keep lines below the nScreenWidth limit,
+but it does not guarantee that it will.
+
+The nScreenWidth field currently only makes a difference in
+columnar styles (**Box**, **Column**, **Markdown**, and **Table**)
+and in the **Line** style.
+
+### 2.11 Individual Column Width (nWidth and aWidth)
+
+The sqlite3_qrf_spec.aWidth field is a pointer to an array of
+signed 16-bit integers that control the width of individual columns
+in columnar output modes (QRF_STYLE_Box, QRF_STYLE_Column,
+QRF_STYLE_Markdown, or QRF_STYLE_Table). The sqlite3_qrf_spec.nWidth
+field is the number of integers in the aWidth array.
+
+If aWidth is a NULL pointer or if nWidth is zero, then the array is
+assumed to be all zeros. If nWidth is less then the number of
+columns in the output, then zero is used for the width
+for all columns past then end of the aWidth array.
+
+The aWidth array is deliberately an array of 16-bit signed integers.
+Only 16 bits are used because no good comes for having very large
+column widths. The range if further restricted as follows:
+
+> ~~~
+#define QRF_MAX_WIDTH 10000 /* Maximum column width */
+#define QRF_MIN_WIDTH 0 /* Minimum column width */
+~~~
+
+A width greater than then QRF_MAX_WIDTH is interpreted as QRF_MAX_WIDTH.
+
+Any aWidth\[\] value of zero means the formatter should use a flexible
+width column (limited only by sqlite_qrf_spec.mxWidth) that is just
+big enough to hold the largest row.
+
+For historical compatibility, aWidth\[\] can contain negative values,
+down to -QRF_MAX_WIDTH. The column width used is the absolute value
+of the number in aWidth\[\]. The only difference is that negative
+values cause the default horizontal alignment to be QRF_ALIGN_Right.
+The sign of the aWidth\[\] values only affects alignment if the
+alignment is not otherwise specified by aAlign\[\] or eDfltAlign.
+Again, negative values for aWidth\[\] entries are supported for
+backwards compatibility only, and are not recommended for new
+applications.
+
+### 2.12 Alignment (nAlignment, aAlignment, eDfltAlign, eTitleAlign)
+
+Some cells in a display table might contain a lot of text and thus
+be wide, or they might contain newline characters or be wrapped by
+width constraints so that they span many rows of text. Other cells
+might be narrower and shorter. In columnar formats, the display width
+of a cell is the maximum of the widest value in the same column, and the
+display height is the height of the tallest value in the same row.
+So some cells might be much taller and wider than necessary to hold
+their values.
+
+Alignment determines where smaller values are placed within larger cells.
+
+The sqlite3_qrf_spec.aAlign field points to an array of unsigned characters
+that specifies alignment (both vertical and horizontal) of individual
+columns within the table. The sqlite3_qrf_spec.nAlign fields holds
+the number of entries in the aAlign\[\] array.
+
+If sqlite3_qrf_spec.aAlign is a NULL pointer or if sqlite3_qrf_spec.nAlign
+is zero, or for columns to the right of what are specified by
+sqlite3_qrf_spec.nAlign, the sqlite3_qrf_spec.eDfltAlign value is used
+for the alignment. Column names can be (and often are) aligned
+differently, as specified by sqlite3_qrf_spec.eTitleAlign.
+
+Each alignment value specifies both vertical and horizontal alignment.
+Horizontal alignment can be left, center, right, or no preference.
+Vertical alignment can be top, middle, bottom, or no preference.
+Thus there are 16 possible alignment values, as follows:
+
+> ~~~
+/*
+** Horizontal Vertial
+** ---------- -------- */
+#define QRF_ALIGN_Auto 0 /* auto auto */
+#define QRF_ALIGN_Left 1 /* left auto */
+#define QRF_ALIGN_Center 2 /* center auto */
+#define QRF_ALIGN_Right 3 /* right auto */
+#define QRF_ALIGN_Top 4 /* auto top */
+#define QRF_ALIGN_NW 5 /* left top */
+#define QRF_ALIGN_N 6 /* center top */
+#define QRF_ALIGN_NE 7 /* right top */
+#define QRF_ALIGN_Middle 8 /* auto middle */
+#define QRF_ALIGN_W 9 /* left middle */
+#define QRF_ALIGN_C 10 /* center middle */
+#define QRF_ALIGN_E 11 /* right middle */
+#define QRF_ALIGN_Bottom 12 /* auto bottom */
+#define QRF_ALIGN_SW 13 /* left bottom */
+#define QRF_ALIGN_S 14 /* center bottom */
+#define QRF_ALIGN_SE 15 /* right bottom */
+~~~
+
+Notice how alignment values with an unspecified horizontal
+or vertical component can be added to another alignment value
+for which that component is specified, to get a fully
+specified alignment. For eample:
+
+> QRF_ALIGN_Center + QRF_ALIGN_Bottom == QRF_ALIGN_S.
+
+The alignment for column names is always determined by the
+eTitleAlign setting. If eTitleAlign is QRF_Auto, then column
+names use center-bottom alignment, QRF_ALIGN_W, value 14.
+The aAlign\[\] and eDfltAlign settings have no affect on
+column names.
+
+For data in the first nAlign columns, the aAlign\[\] array
+entry for that column takes precedence. If either the horizontal
+or vertical alignment has an "auto" value for that column or if
+a column is beyond the first nAlign entries, then eDfltAlign
+is used as a backup. If neither aAlign\[\] nor eDfltAlign
+specify a horizontal alignment, then values are right-aligned
+(QRF_ALIGN_Right) if they are numeric and left-aligned
+(QRF_ALIGN_Left) otherwise. If neither aAlign\[\] nor eDfltAlign
+specify a vertical alignment, then values are top-aligned
+(QRF_ALIGN_Top).
+
+*As of 2025-11-08, only horizontal alignment is implemented.
+The vertical alignment settings are currently ignored and
+the vertical alignment is always QRF_ALIGN_Top.*
+
+### 2.13 Row and Column Separator Strings
+
+The sqlite3_qrf_spec.zColumnSep and sqlite3_qrf_spec.zRowSep strings
+are alternative column and row separator character sequences. If not
+specified (if these pointers are left as NULL) then appropriate defaults
+are used. Some output styles have hard-coded column and row separators
+and these settings are ignored for those styles.
+
+### 2.14 The Output Table Name
+
+The sqlite3_qrf_spec.zTableName value is the name of the output table
+when eStyle is QRF_STYLE_Insert.
+
+### 2.15 The Rendering Of NULL (zNull)
+
+If a value is NULL then show the NULL using the string
+found in sqlite3_qrf_spec.zNull. If zNull is itself a NULL pointer
+then NULL values are rendered as an empty string.
+
+### 2.16 Optional Value Rendering Callback
+
+If the sqlite3_qrf_spec.xRender field is not NULL, then each
+sqlite3_value coming out of the query is first passed to the
+xRender function, giving that function an opportunity to render
+the results itself, using whatever custom format is desired.
+If xRender chooses to render, it should write the rendering
+into memory obtained from sqlite3_malloc() and return a pointer
+to that memory. The xRender function can decline
+to render (for example, based on the sqlite3_value_type() or other
+characteristics of the value) in which case it can simply return a
+NULL pointer and the usual default rendering will be used instead.
+
+The sqlite3_format_query_result() function (which calls xRender)
+will take responsibility for freeing the string returned by xRender
+after it has finished using it.
+
+The eText, eBlob, and eEsc settings above become no-ops if the xRender
+routine returns non-NULL. In other words, the application-supplied
+xRender routine is expected to do all of its own quoting and formatting.
+
+The xRender routine is expected to do character length limiting itself.
+So the nCharLimit setting becomes a no-op if xRender is used. However
+the nLineLimit setting is still applied. The nTitleLimit setting is
+not applicable to xRender because title values come from the
+sqlite3_column_name() interface not from sqlite3_column_value(),
+and so that names of columns are never processed by xRender.
+
+## 3.0 The `sqlite3_format_query_result()` Interface
+
+Invoke the `sqlite3_format_query_result(P,S,E)` interface to run
+the prepared statement P and format its results according to the
+specification found in S. The sqlite3_format_query_result() function
+will return an SQLite result code, usually SQLITE_OK, but perhaps
+SQLITE_NOMEM or SQLITE_ERROR or similar. If an error occurs and if
+the E parameter is not NULL, then error message text might be written
+into *E. Any error message text will be stored in memory obtained
+from sqlite3_malloc() and it is the responsibility of the caller to
+free that memory by a subsequent call to sqlite3_free().
+
+
+## 4.0 Output Styles
+
+The result formatter supports a variety of output styles. The
+output style (sometimes called "output mode") is determined by
+the eStyle field of the sqlite3_qrf_spec object. The set of
+supported output modes might increase in future versions.
+The following output modes are currently defined:
+
+> ~~~
+#define QRF_STYLE_Auto 0 /* Choose a style automatically */
+#define QRF_STYLE_Box 1 /* Unicode box-drawing characters */
+#define QRF_STYLE_Column 2 /* One record per line in neat columns */
+#define QRF_STYLE_Count 3 /* Output only a count of the rows of output */
+#define QRF_STYLE_Csv 4 /* Comma-separated-value */
+#define QRF_STYLE_Eqp 5 /* Format EXPLAIN QUERY PLAN output */
+#define QRF_STYLE_Explain 6 /* EXPLAIN output */
+#define QRF_STYLE_Html 7 /* Generate an XHTML table */
+#define QRF_STYLE_Insert 8 /* Generate SQL "insert" statements */
+#define QRF_STYLE_Json 9 /* Output is a list of JSON objects */
+#define QRF_STYLE_JObject 10 /* Independent JSON objects for each row */
+#define QRF_STYLE_Line 11 /* One column per line. */
+#define QRF_STYLE_List 12 /* One record per line with a separator */
+#define QRF_STYLE_Markdown 13 /* Markdown formatting */
+#define QRF_STYLE_Off 14 /* No query output shown */
+#define QRF_STYLE_Quote 15 /* SQL-quoted, comma-separated */
+#define QRF_STYLE_Stats 16 /* EQP-like output but with performance stats */
+#define QRF_STYLE_StatsEst 17 /* EQP-like output with planner estimates */
+#define QRF_STYLE_StatsVm 18 /* EXPLAIN-like output with performance stats */
+#define QRF_STYLE_Table 19 /* MySQL-style table formatting */
+~~~
+
+In the following subsections, these styles will often be referred
+to without the "QRF_STYLE_" prefix.
+
+### 4.1 Default Style (Auto)
+
+The **Auto** style means QRF gets to choose an appropriate output
+style. It will usually choose **Box**, but might also pick one of
+**Explain** or **Eqp** if the `sqlite3_stmt_explain()` function
+returns 1 or 2, respectively.
+
+### 4.2 Columnar Styles (Box, Column, Markdown, Table)
+
+The **Box**, **Column**, **Markdown**, and **Table**
+modes are columnar. This means the output is arranged into neat,
+uniform-width columns. These styles can use more memory, especially when
+the query result has many rows, because they need to load the entire output
+into memory first in order to determine how wide to make each column.
+
+The nWidth, aWidth, and mxWidth fields of the `sqlite3_qrf_spec` object
+are used by these styles only, and are ignored by all other styles.
+The zRowSep and zColumnSep settings are ignored by these styles. The
+bTitles setting is honored by these styles; it defaults to QRF_SW_On.
+
+The **Box** style uses Unicode box-drawing character to draw a grid
+of columns and rows to show the result. The **Table** is the same,
+except that it uses ASCII-art rather than Unicode box-drawing characters
+to draw the grid. The **Column** arranges the results in neat columns
+but does not draw in column or row separator, except that it does draw
+lines horizontal lines using "`-`" characters to separate the column names
+from the data below. This is very similar to default output styling in
+psql. The **Markdown** renders its result in the Markdown table format.
+
+The **Box** and **Table** styles normally have a border that surrounds
+the entire result. However, if sqlite3_qrf_spec.bBorder is QRF_No, then
+that border is omitted, saving a little space both horizontally and
+vertically.
+
+#### 4.2.1 Split Column Mode
+
+If the bSplitColumn field is QRF_Yes, and eStyle is QRF_STYLE_Column,
+and bTitles is QRF_No, and nScreenWidth is greater than zero, and if
+the query only returns a single column, then a special rendering known
+as "Split Column Mode" will be used. In split column mode, instead
+of showing all results in one tall column, the content wraps vertically
+so that it appears on the screen as multiple columns, as many as will
+fit in the available screen width.
+
+### 4.3 Line-oriented Styles
+
+The line-oriented styles output each row of result as it is received from
+the prepared statement.
+
+The **List** style is the most familiar line-oriented output format.
+The **List** style shows output columns for each row on the
+same line, each separated by a single "`|`" character and with lines
+terminated by a single newline (\\u000a or \\n). These column
+and row separator choices can be overridden using the zColumnSep
+and zRowSep fields of the `sqlite3_qrf_spec` structure. The text
+formatting is QRF_TEXT_Plain, and BLOB encoding is QRF_BLOB_Text. So
+characters appear in the output exactly as they appear in the database.
+Except the eEsp mode defaults to `QRF_ESC_On`, so that control
+characters are escaped, for safety.
+
+The **Csv** and **Quote** styles are simply variations on **List**
+with hard-coded values for some of the sqlite3_qrf_spec settings:
+
+
+
Quote
Csv
+
zColumnSep
","
","
+
zRowSep
"\\n"
"\\r\\n"
+
zNull
"NULL"
""
+
eText
QRF_TEXT_Sql
QRF_TEXT_Csv
+
eBlob
QRF_BLOB_Sql
QRF_BLOB_Text
+
+
+The **Html** style generates HTML table content, just without
+the `
..
` around the outside.
+
+The **Insert** style generates a series of SQL "INSERT" statements
+that will inserts the data that is output into a table whose name is defined
+by the zTableName field of `sqlite3_qrf_spec`. If zTableName is NULL,
+then a substitute name is used.
+
+The **Json** and **JObject** styles generates JSON text for the query result.
+The **Json** style produces a JSON array of structures with one
+structure per row. **JObject** outputs independent JSON objects, one per
+row, with each structure on a separate line all by itself, and not
+part of a larger array. In both cases, the labels on the elements of the
+JSON objects are taken from the column names of the SQL query. So if
+you have an SQL query that has two or more output columns with the same
+name, you will end up with JSON structures that have duplicate elements.
+
+Finally, the **Line** style paints each column of a row on a
+separate line with the column name on the left and a "`=`" separating the
+column name from its value. A single blank line appears between rows.
+
+### 4.4 EXPLAIN Styles (Eqp, Explain)
+
+The **Eqp** and **Explain** styles format output for
+EXPLAIN QUERY PLAN and EXPLAIN statements, respectively. If the input
+statement is not already an EXPLAIN QUERY PLAN or EXPLAIN statement is
+is temporarily converted for the duration of the rendering, but
+is converted back before `sqlite3_format_query_result()` returns.
+
+### 4.5 ScanStatus Styles (Stats, StatsEst, StatsVm)
+
+The **Stats**, **StatsEst**, and **StatsVm** styles are similar to **Eqp**
+and **Explain** except that they include profiling information
+from prior executions of the input prepared statement.
+These modes only work if SQLite has been compiled with
+-DSQLITE_ENABLE_STMT_SCANSTATUS and if the SQLITE_DBCONFIG_STMT_SCANSTATUS
+is enabled for the database connection. The **StatsVm** style
+also requires the bytecode() virtual table which is enabled using
+the -DSQLITE_ENABLE_BYTECODE_VTAB compile-time option.
+
+### 4.6 Other Styles (Count, Off)
+
+The **Count** style discards all query results and returns
+a count of the number of rows of output at the end. The **Off**
+style is completely silent; it generates no output. These corner-case
+modes are sometimes useful for debugging.
+
+### 5.0 Source Code Files
+
+The SQLite Query Result Formatter is implemented in three source code files:
+
+ * `qrf.c` → The implementation, written in portable C99
+ * `qrf.h` → A header file defining interfaces
+ * `README.md` → This documentation
+
+To use the SQLite result formatter, include the "`qrf.h`" header file
+and link the application against the "`qrf.c`" source file.
diff --git a/ext/qrf/dev-notes.md b/ext/qrf/dev-notes.md
new file mode 100644
index 000000000..a46aada83
--- /dev/null
+++ b/ext/qrf/dev-notes.md
@@ -0,0 +1,14 @@
+# Developer Notes
+
+## Measuring Test Coverage On Linux
+
+On Mint Linux, as of 2025-12-02:
+
+> ~~~
+./configure --dev CFLAGS='-O0 -g -fprofile-arcs -ftest-coverage'
+make clean testfixture
+./testfixture test/qrf*.test
+gcov -b -c testfixture-tclsqlite-ex.c
+~~~
+
+View results in tclsqlite-ex.c.gcov
diff --git a/ext/qrf/qrf.c b/ext/qrf/qrf.c
new file mode 100644
index 000000000..cacfa1526
--- /dev/null
+++ b/ext/qrf/qrf.c
@@ -0,0 +1,2983 @@
+/*
+** 2025-10-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.
+**
+*************************************************************************
+** Implementation of the Result-Format or "qrf" utility library for SQLite.
+** See the qrf.md documentation for additional information.
+*/
+#ifndef SQLITE_QRF_H
+#include "qrf.h"
+#endif
+#include
+#include
+#include
+
+typedef sqlite3_int64 i64;
+
+/* A single line in the EQP output */
+typedef struct qrfEQPGraphRow qrfEQPGraphRow;
+struct qrfEQPGraphRow {
+ int iEqpId; /* ID for this row */
+ int iParentId; /* ID of the parent row */
+ qrfEQPGraphRow *pNext; /* Next row in sequence */
+ char zText[1]; /* Text to display for this row */
+};
+
+/* All EQP output is collected into an instance of the following */
+typedef struct qrfEQPGraph qrfEQPGraph;
+struct qrfEQPGraph {
+ qrfEQPGraphRow *pRow; /* Linked list of all rows of the EQP output */
+ qrfEQPGraphRow *pLast; /* Last element of the pRow list */
+ int nWidth; /* Width of the graph */
+ char zPrefix[400]; /* Graph prefix */
+};
+
+/*
+** Private state information. Subject to change from one release to the
+** next.
+*/
+typedef struct Qrf Qrf;
+struct Qrf {
+ sqlite3_stmt *pStmt; /* The statement whose output is to be rendered */
+ sqlite3 *db; /* The corresponding database connection */
+ sqlite3_stmt *pJTrans; /* JSONB to JSON translator statement */
+ char **pzErr; /* Write error message here, if not NULL */
+ sqlite3_str *pOut; /* Accumulated output */
+ int iErr; /* Error code */
+ int nCol; /* Number of output columns */
+ int expMode; /* Original sqlite3_stmt_isexplain() plus 1 */
+ int mxWidth; /* Screen width */
+ int mxHeight; /* nLineLimit */
+ union {
+ struct { /* Content for QRF_STYLE_Line */
+ int mxColWth; /* Maximum display width of any column */
+ char **azCol; /* Names of output columns (MODE_Line) */
+ } sLine;
+ qrfEQPGraph *pGraph; /* EQP graph (Eqp, Stats, and StatsEst) */
+ struct { /* Content for QRF_STYLE_Explain */
+ int nIndent; /* Slots allocated for aiIndent */
+ int iIndent; /* Current slot */
+ int *aiIndent; /* Indentation for each opcode */
+ } sExpln;
+ } u;
+ sqlite3_int64 nRow; /* Number of rows handled so far */
+ int *actualWidth; /* Actual width of each column */
+ sqlite3_qrf_spec spec; /* Copy of the original spec */
+};
+
+/*
+** Data for substitute ctype.h functions. Used for x-platform
+** consistency and so that '_' is counted as an alphabetic
+** character.
+**
+** 0x01 - space
+** 0x02 - digit
+** 0x04 - alphabetic, including '_'
+*/
+static const char qrfCType[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
+ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 4,
+ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+#define qrfSpace(x) ((qrfCType[(unsigned char)x]&1)!=0)
+#define qrfDigit(x) ((qrfCType[(unsigned char)x]&2)!=0)
+#define qrfAlpha(x) ((qrfCType[(unsigned char)x]&4)!=0)
+#define qrfAlnum(x) ((qrfCType[(unsigned char)x]&6)!=0)
+
+#ifndef deliberate_fall_through
+/* Quiet some compilers about some of our intentional code. */
+# if defined(GCC_VERSION) && GCC_VERSION>=7000000
+# define deliberate_fall_through __attribute__((fallthrough));
+# else
+# define deliberate_fall_through
+# endif
+#endif
+
+/*
+** Set an error code and error message.
+*/
+static void qrfError(
+ Qrf *p, /* Query result state */
+ int iCode, /* Error code */
+ const char *zFormat, /* Message format (or NULL) */
+ ...
+){
+ p->iErr = iCode;
+ if( p->pzErr!=0 ){
+ sqlite3_free(*p->pzErr);
+ *p->pzErr = 0;
+ if( zFormat ){
+ va_list ap;
+ va_start(ap, zFormat);
+ *p->pzErr = sqlite3_vmprintf(zFormat, ap);
+ va_end(ap);
+ }
+ }
+}
+
+/*
+** Out-of-memory error.
+*/
+static void qrfOom(Qrf *p){
+ qrfError(p, SQLITE_NOMEM, "out of memory");
+}
+
+/*
+** Transfer any error in pStr over into p.
+*/
+static void qrfStrErr(Qrf *p, sqlite3_str *pStr){
+ int rc = pStr ? sqlite3_str_errcode(pStr) : 0;
+ if( rc ){
+ qrfError(p, rc, sqlite3_errstr(rc));
+ }
+}
+
+
+/*
+** Add a new entry to the EXPLAIN QUERY PLAN data
+*/
+static void qrfEqpAppend(Qrf *p, int iEqpId, int p2, const char *zText){
+ qrfEQPGraphRow *pNew;
+ sqlite3_int64 nText;
+ if( zText==0 ) return;
+ if( p->u.pGraph==0 ){
+ p->u.pGraph = sqlite3_malloc64( sizeof(qrfEQPGraph) );
+ if( p->u.pGraph==0 ){
+ qrfOom(p);
+ return;
+ }
+ memset(p->u.pGraph, 0, sizeof(qrfEQPGraph) );
+ }
+ nText = strlen(zText);
+ pNew = sqlite3_malloc64( sizeof(*pNew) + nText );
+ if( pNew==0 ){
+ qrfOom(p);
+ return;
+ }
+ pNew->iEqpId = iEqpId;
+ pNew->iParentId = p2;
+ memcpy(pNew->zText, zText, nText+1);
+ pNew->pNext = 0;
+ if( p->u.pGraph->pLast ){
+ p->u.pGraph->pLast->pNext = pNew;
+ }else{
+ p->u.pGraph->pRow = pNew;
+ }
+ p->u.pGraph->pLast = pNew;
+}
+
+/*
+** Free and reset the EXPLAIN QUERY PLAN data that has been collected
+** in p->u.pGraph.
+*/
+static void qrfEqpReset(Qrf *p){
+ qrfEQPGraphRow *pRow, *pNext;
+ if( p->u.pGraph ){
+ for(pRow = p->u.pGraph->pRow; pRow; pRow = pNext){
+ pNext = pRow->pNext;
+ sqlite3_free(pRow);
+ }
+ sqlite3_free(p->u.pGraph);
+ p->u.pGraph = 0;
+ }
+}
+
+/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after
+** pOld, or return the first such line if pOld is NULL
+*/
+static qrfEQPGraphRow *qrfEqpNextRow(Qrf *p, int iEqpId, qrfEQPGraphRow *pOld){
+ qrfEQPGraphRow *pRow = pOld ? pOld->pNext : p->u.pGraph->pRow;
+ while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext;
+ return pRow;
+}
+
+/* Render a single level of the graph that has iEqpId as its parent. Called
+** recursively to render sublevels.
+*/
+static void qrfEqpRenderLevel(Qrf *p, int iEqpId){
+ qrfEQPGraphRow *pRow, *pNext;
+ i64 n = strlen(p->u.pGraph->zPrefix);
+ char *z;
+ for(pRow = qrfEqpNextRow(p, iEqpId, 0); pRow; pRow = pNext){
+ pNext = qrfEqpNextRow(p, iEqpId, pRow);
+ z = pRow->zText;
+ sqlite3_str_appendf(p->pOut, "%s%s%s\n", p->u.pGraph->zPrefix,
+ pNext ? "|--" : "`--", z);
+ if( n<(i64)sizeof(p->u.pGraph->zPrefix)-7 ){
+ memcpy(&p->u.pGraph->zPrefix[n], pNext ? "| " : " ", 4);
+ qrfEqpRenderLevel(p, pRow->iEqpId);
+ p->u.pGraph->zPrefix[n] = 0;
+ }
+ }
+}
+
+/*
+** Render the 64-bit value N in a more human-readable format into
+** pOut.
+**
+** + Only show the first three significant digits.
+** + Append suffixes K, M, G, T, P, and E for 1e3, 1e6, ... 1e18
+*/
+static void qrfApproxInt64(sqlite3_str *pOut, i64 N){
+ static const char aSuffix[] = { 'K', 'M', 'G', 'T', 'P', 'E' };
+ int i;
+ if( N<0 ){
+ N = N==INT64_MIN ? INT64_MAX : -N;
+ sqlite3_str_append(pOut, "-", 1);
+ }
+ if( N<10000 ){
+ sqlite3_str_appendf(pOut, "%4lld ", N);
+ return;
+ }
+ for(i=1; i<=18; i++){
+ N = (N+5)/10;
+ if( N<10000 ){
+ int n = (int)N;
+ switch( i%3 ){
+ case 0:
+ sqlite3_str_appendf(pOut, "%d.%02d", n/1000, (n%1000)/10);
+ break;
+ case 1:
+ sqlite3_str_appendf(pOut, "%2d.%d", n/100, (n%100)/10);
+ break;
+ case 2:
+ sqlite3_str_appendf(pOut, "%4d", n/10);
+ break;
+ }
+ sqlite3_str_append(pOut, &aSuffix[i/3], 1);
+ break;
+ }
+ }
+}
+
+/*
+** Display and reset the EXPLAIN QUERY PLAN data
+*/
+static void qrfEqpRender(Qrf *p, i64 nCycle){
+ qrfEQPGraphRow *pRow;
+ if( p->u.pGraph!=0 && (pRow = p->u.pGraph->pRow)!=0 ){
+ if( pRow->zText[0]=='-' ){
+ if( pRow->pNext==0 ){
+ qrfEqpReset(p);
+ return;
+ }
+ sqlite3_str_appendf(p->pOut, "%s\n", pRow->zText+3);
+ p->u.pGraph->pRow = pRow->pNext;
+ sqlite3_free(pRow);
+ }else if( nCycle>0 ){
+ int nSp = p->u.pGraph->nWidth - 2;
+ if( p->spec.eStyle==QRF_STYLE_StatsEst ){
+ sqlite3_str_appendchar(p->pOut, nSp, ' ');
+ sqlite3_str_appendall(p->pOut,
+ "Cycles Loops (est) Rows (est)\n");
+ sqlite3_str_appendchar(p->pOut, nSp, ' ');
+ sqlite3_str_appendall(p->pOut,
+ "---------- ------------ ------------\n");
+ }else{
+ sqlite3_str_appendchar(p->pOut, nSp, ' ');
+ sqlite3_str_appendall(p->pOut,
+ "Cycles Loops Rows \n");
+ sqlite3_str_appendchar(p->pOut, nSp, ' ');
+ sqlite3_str_appendall(p->pOut,
+ "---------- ----- -----\n");
+ }
+ sqlite3_str_appendall(p->pOut, "QUERY PLAN");
+ sqlite3_str_appendchar(p->pOut, nSp - 10, ' ');
+ qrfApproxInt64(p->pOut, nCycle);
+ sqlite3_str_appendall(p->pOut, " 100%\n");
+ }else{
+ sqlite3_str_appendall(p->pOut, "QUERY PLAN\n");
+ }
+ p->u.pGraph->zPrefix[0] = 0;
+ qrfEqpRenderLevel(p, 0);
+ qrfEqpReset(p);
+ }
+}
+
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+/*
+** Helper function for qrfExpStats().
+**
+*/
+static int qrfStatsHeight(sqlite3_stmt *p, int iEntry){
+ int iPid = 0;
+ int ret = 1;
+ sqlite3_stmt_scanstatus_v2(p, iEntry,
+ SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
+ );
+ while( iPid!=0 ){
+ int ii;
+ for(ii=0; 1; ii++){
+ int iId;
+ int res;
+ res = sqlite3_stmt_scanstatus_v2(p, ii,
+ SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iId
+ );
+ if( res ) break;
+ if( iId==iPid ){
+ sqlite3_stmt_scanstatus_v2(p, ii,
+ SQLITE_SCANSTAT_PARENTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
+ );
+ }
+ }
+ ret++;
+ }
+ return ret;
+}
+#endif /* SQLITE_ENABLE_STMT_SCANSTATUS */
+
+
+/*
+** Generate ".scanstatus est" style of EQP output.
+*/
+static void qrfEqpStats(Qrf *p){
+#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
+ qrfError(p, SQLITE_ERROR, "not available in this build");
+#else
+ static const int f = SQLITE_SCANSTAT_COMPLEX;
+ sqlite3_stmt *pS = p->pStmt;
+ int i = 0;
+ i64 nTotal = 0;
+ int nWidth = 0;
+ int prevPid = -1; /* Previous iPid */
+ double rEstCum = 1.0; /* Cumulative row estimate */
+ sqlite3_str *pLine = sqlite3_str_new(p->db);
+ sqlite3_str *pStats = sqlite3_str_new(p->db);
+ qrfEqpReset(p);
+
+ for(i=0; 1; i++){
+ const char *z = 0;
+ int n = 0;
+ if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
+ break;
+ }
+ n = (int)strlen(z) + qrfStatsHeight(pS,i)*3;
+ if( n>nWidth ) nWidth = n;
+ }
+ nWidth += 2;
+
+ sqlite3_stmt_scanstatus_v2(pS,-1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal);
+ for(i=0; 1; i++){
+ i64 nLoop = 0;
+ i64 nRow = 0;
+ i64 nCycle = 0;
+ int iId = 0;
+ int iPid = 0;
+ const char *zo = 0;
+ const char *zName = 0;
+ double rEst = 0.0;
+
+ if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){
+ break;
+ }
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
+ if( iPid!=prevPid ){
+ prevPid = iPid;
+ rEstCum = 1.0;
+ }
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_EST,f,(void*)&rEst);
+ rEstCum *= rEst;
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NAME,f,(void*)&zName);
+
+ if( nCycle>=0 || nLoop>=0 || nRow>=0 ){
+ int nSp = 0;
+ sqlite3_str_reset(pStats);
+ if( nCycle>=0 && nTotal>0 ){
+ qrfApproxInt64(pStats, nCycle);
+ sqlite3_str_appendf(pStats, " %3d%%",
+ ((nCycle*100)+nTotal/2) / nTotal
+ );
+ nSp = 2;
+ }
+ if( nLoop>=0 ){
+ if( nSp ) sqlite3_str_appendchar(pStats, nSp, ' ');
+ qrfApproxInt64(pStats, nLoop);
+ nSp = 2;
+ if( p->spec.eStyle==QRF_STYLE_StatsEst ){
+ sqlite3_str_appendf(pStats, " ");
+ qrfApproxInt64(pStats, (i64)(rEstCum/rEst));
+ }
+ }
+ if( nRow>=0 ){
+ if( nSp ) sqlite3_str_appendchar(pStats, nSp, ' ');
+ qrfApproxInt64(pStats, nRow);
+ nSp = 2;
+ if( p->spec.eStyle==QRF_STYLE_StatsEst ){
+ sqlite3_str_appendf(pStats, " ");
+ qrfApproxInt64(pStats, (i64)rEstCum);
+ }
+ }
+ sqlite3_str_appendf(pLine,
+ "% *s %s", -1*(nWidth-qrfStatsHeight(pS,i)*3), zo,
+ sqlite3_str_value(pStats)
+ );
+ sqlite3_str_reset(pStats);
+ qrfEqpAppend(p, iId, iPid, sqlite3_str_value(pLine));
+ sqlite3_str_reset(pLine);
+ }else{
+ qrfEqpAppend(p, iId, iPid, zo);
+ }
+ }
+ if( p->u.pGraph ) p->u.pGraph->nWidth = nWidth;
+ qrfStrErr(p, pLine);
+ sqlite3_free(sqlite3_str_finish(pLine));
+ qrfStrErr(p, pStats);
+ sqlite3_free(sqlite3_str_finish(pStats));
+#endif
+}
+
+
+/*
+** Reset the prepared statement.
+*/
+static void qrfResetStmt(Qrf *p){
+ int rc = sqlite3_reset(p->pStmt);
+ if( rc!=SQLITE_OK && p->iErr==SQLITE_OK ){
+ qrfError(p, rc, "%s", sqlite3_errmsg(p->db));
+ }
+}
+
+/*
+** If xWrite is defined, send all content of pOut to xWrite and
+** reset pOut.
+*/
+static void qrfWrite(Qrf *p){
+ int n;
+ if( p->spec.xWrite && (n = sqlite3_str_length(p->pOut))>0 ){
+ int rc = p->spec.xWrite(p->spec.pWriteArg,
+ sqlite3_str_value(p->pOut),
+ (sqlite3_int64)n);
+ sqlite3_str_reset(p->pOut);
+ if( rc ){
+ qrfError(p, rc, "Failed to write %d bytes of output", n);
+ }
+ }
+}
+
+/* Lookup table to estimate the number of columns consumed by a Unicode
+** character.
+*/
+static const struct {
+ unsigned char w; /* Width of the character in columns */
+ int iFirst; /* First character in a span having this width */
+} aQrfUWidth[] = {
+ /* {1, 0x00000}, */
+ {0, 0x00300}, {1, 0x00370}, {0, 0x00483}, {1, 0x00487}, {0, 0x00488},
+ {1, 0x0048a}, {0, 0x00591}, {1, 0x005be}, {0, 0x005bf}, {1, 0x005c0},
+ {0, 0x005c1}, {1, 0x005c3}, {0, 0x005c4}, {1, 0x005c6}, {0, 0x005c7},
+ {1, 0x005c8}, {0, 0x00600}, {1, 0x00604}, {0, 0x00610}, {1, 0x00616},
+ {0, 0x0064b}, {1, 0x0065f}, {0, 0x00670}, {1, 0x00671}, {0, 0x006d6},
+ {1, 0x006e5}, {0, 0x006e7}, {1, 0x006e9}, {0, 0x006ea}, {1, 0x006ee},
+ {0, 0x0070f}, {1, 0x00710}, {0, 0x00711}, {1, 0x00712}, {0, 0x00730},
+ {1, 0x0074b}, {0, 0x007a6}, {1, 0x007b1}, {0, 0x007eb}, {1, 0x007f4},
+ {0, 0x00901}, {1, 0x00903}, {0, 0x0093c}, {1, 0x0093d}, {0, 0x00941},
+ {1, 0x00949}, {0, 0x0094d}, {1, 0x0094e}, {0, 0x00951}, {1, 0x00955},
+ {0, 0x00962}, {1, 0x00964}, {0, 0x00981}, {1, 0x00982}, {0, 0x009bc},
+ {1, 0x009bd}, {0, 0x009c1}, {1, 0x009c5}, {0, 0x009cd}, {1, 0x009ce},
+ {0, 0x009e2}, {1, 0x009e4}, {0, 0x00a01}, {1, 0x00a03}, {0, 0x00a3c},
+ {1, 0x00a3d}, {0, 0x00a41}, {1, 0x00a43}, {0, 0x00a47}, {1, 0x00a49},
+ {0, 0x00a4b}, {1, 0x00a4e}, {0, 0x00a70}, {1, 0x00a72}, {0, 0x00a81},
+ {1, 0x00a83}, {0, 0x00abc}, {1, 0x00abd}, {0, 0x00ac1}, {1, 0x00ac6},
+ {0, 0x00ac7}, {1, 0x00ac9}, {0, 0x00acd}, {1, 0x00ace}, {0, 0x00ae2},
+ {1, 0x00ae4}, {0, 0x00b01}, {1, 0x00b02}, {0, 0x00b3c}, {1, 0x00b3d},
+ {0, 0x00b3f}, {1, 0x00b40}, {0, 0x00b41}, {1, 0x00b44}, {0, 0x00b4d},
+ {1, 0x00b4e}, {0, 0x00b56}, {1, 0x00b57}, {0, 0x00b82}, {1, 0x00b83},
+ {0, 0x00bc0}, {1, 0x00bc1}, {0, 0x00bcd}, {1, 0x00bce}, {0, 0x00c3e},
+ {1, 0x00c41}, {0, 0x00c46}, {1, 0x00c49}, {0, 0x00c4a}, {1, 0x00c4e},
+ {0, 0x00c55}, {1, 0x00c57}, {0, 0x00cbc}, {1, 0x00cbd}, {0, 0x00cbf},
+ {1, 0x00cc0}, {0, 0x00cc6}, {1, 0x00cc7}, {0, 0x00ccc}, {1, 0x00cce},
+ {0, 0x00ce2}, {1, 0x00ce4}, {0, 0x00d41}, {1, 0x00d44}, {0, 0x00d4d},
+ {1, 0x00d4e}, {0, 0x00dca}, {1, 0x00dcb}, {0, 0x00dd2}, {1, 0x00dd5},
+ {0, 0x00dd6}, {1, 0x00dd7}, {0, 0x00e31}, {1, 0x00e32}, {0, 0x00e34},
+ {1, 0x00e3b}, {0, 0x00e47}, {1, 0x00e4f}, {0, 0x00eb1}, {1, 0x00eb2},
+ {0, 0x00eb4}, {1, 0x00eba}, {0, 0x00ebb}, {1, 0x00ebd}, {0, 0x00ec8},
+ {1, 0x00ece}, {0, 0x00f18}, {1, 0x00f1a}, {0, 0x00f35}, {1, 0x00f36},
+ {0, 0x00f37}, {1, 0x00f38}, {0, 0x00f39}, {1, 0x00f3a}, {0, 0x00f71},
+ {1, 0x00f7f}, {0, 0x00f80}, {1, 0x00f85}, {0, 0x00f86}, {1, 0x00f88},
+ {0, 0x00f90}, {1, 0x00f98}, {0, 0x00f99}, {1, 0x00fbd}, {0, 0x00fc6},
+ {1, 0x00fc7}, {0, 0x0102d}, {1, 0x01031}, {0, 0x01032}, {1, 0x01033},
+ {0, 0x01036}, {1, 0x0103b}, {0, 0x01058},
+ {1, 0x0105a}, {2, 0x01100}, {0, 0x01160}, {1, 0x01200}, {0, 0x0135f},
+ {1, 0x01360}, {0, 0x01712}, {1, 0x01715}, {0, 0x01732}, {1, 0x01735},
+ {0, 0x01752}, {1, 0x01754}, {0, 0x01772}, {1, 0x01774}, {0, 0x017b4},
+ {1, 0x017b6}, {0, 0x017b7}, {1, 0x017be}, {0, 0x017c6}, {1, 0x017c7},
+ {0, 0x017c9}, {1, 0x017d4}, {0, 0x017dd}, {1, 0x017de}, {0, 0x0180b},
+ {1, 0x0180e}, {0, 0x018a9}, {1, 0x018aa}, {0, 0x01920}, {1, 0x01923},
+ {0, 0x01927}, {1, 0x01929}, {0, 0x01932}, {1, 0x01933}, {0, 0x01939},
+ {1, 0x0193c}, {0, 0x01a17}, {1, 0x01a19}, {0, 0x01b00}, {1, 0x01b04},
+ {0, 0x01b34}, {1, 0x01b35}, {0, 0x01b36}, {1, 0x01b3b}, {0, 0x01b3c},
+ {1, 0x01b3d}, {0, 0x01b42}, {1, 0x01b43}, {0, 0x01b6b}, {1, 0x01b74},
+ {0, 0x01dc0}, {1, 0x01dcb}, {0, 0x01dfe}, {1, 0x01e00}, {0, 0x0200b},
+ {1, 0x02010}, {0, 0x0202a}, {1, 0x0202f}, {0, 0x02060}, {1, 0x02064},
+ {0, 0x0206a}, {1, 0x02070}, {0, 0x020d0}, {1, 0x020f0}, {2, 0x02329},
+ {1, 0x0232b}, {2, 0x02e80}, {0, 0x0302a}, {2, 0x03030}, {1, 0x0303f},
+ {2, 0x03040}, {0, 0x03099}, {2, 0x0309b}, {1, 0x0a4d0}, {0, 0x0a806},
+ {1, 0x0a807}, {0, 0x0a80b}, {1, 0x0a80c}, {0, 0x0a825}, {1, 0x0a827},
+ {2, 0x0ac00}, {1, 0x0d7a4}, {2, 0x0f900}, {1, 0x0fb00}, {0, 0x0fb1e},
+ {1, 0x0fb1f}, {0, 0x0fe00}, {2, 0x0fe10}, {1, 0x0fe1a}, {0, 0x0fe20},
+ {1, 0x0fe24}, {2, 0x0fe30}, {1, 0x0fe70}, {0, 0x0feff}, {2, 0x0ff00},
+ {1, 0x0ff61}, {2, 0x0ffe0}, {1, 0x0ffe7}, {0, 0x0fff9}, {1, 0x0fffc},
+ {0, 0x10a01}, {1, 0x10a04}, {0, 0x10a05}, {1, 0x10a07}, {0, 0x10a0c},
+ {1, 0x10a10}, {0, 0x10a38}, {1, 0x10a3b}, {0, 0x10a3f}, {1, 0x10a40},
+ {0, 0x1d167}, {1, 0x1d16a}, {0, 0x1d173}, {1, 0x1d183}, {0, 0x1d185},
+ {1, 0x1d18c}, {0, 0x1d1aa}, {1, 0x1d1ae}, {0, 0x1d242}, {1, 0x1d245},
+ {2, 0x20000}, {1, 0x2fffe}, {2, 0x30000}, {1, 0x3fffe}, {0, 0xe0001},
+ {1, 0xe0002}, {0, 0xe0020}, {1, 0xe0080}, {0, 0xe0100}, {1, 0xe01f0}
+};
+
+/*
+** Return an estimate of the width, in columns, for the single Unicode
+** character c. For normal characters, the answer is always 1. But the
+** estimate might be 0 or 2 for zero-width and double-width characters.
+**
+** Different display devices display unicode using different widths. So
+** it is impossible to know that true display width with 100% accuracy.
+** Inaccuracies in the width estimates might cause columns to be misaligned.
+** Unfortunately, there is nothing we can do about that.
+*/
+int sqlite3_qrf_wcwidth(int c){
+ int iFirst, iLast;
+
+ /* Fast path for common characters */
+ if( c<0x300 ) return 1;
+
+ /* The general case */
+ iFirst = 0;
+ iLast = sizeof(aQrfUWidth)/sizeof(aQrfUWidth[0]) - 1;
+ while( iFirst c ){
+ iLast = iMid - 1;
+ }else{
+ return aQrfUWidth[iMid].w;
+ }
+ }
+ if( aQrfUWidth[iLast].iFirst > c ) return aQrfUWidth[iFirst].w;
+ return aQrfUWidth[iLast].w;
+}
+
+/*
+** Compute the value and length of a multi-byte UTF-8 character that
+** begins at z[0]. Return the length. Write the Unicode value into *pU.
+**
+** This routine only works for *multi-byte* UTF-8 characters. It does
+** not attempt to detect illegal characters.
+*/
+int sqlite3_qrf_decode_utf8(const unsigned char *z, int *pU){
+ if( (z[0] & 0xe0)==0xc0 && (z[1] & 0xc0)==0x80 ){
+ *pU = ((z[0] & 0x1f)<<6) | (z[1] & 0x3f);
+ return 2;
+ }
+ if( (z[0] & 0xf0)==0xe0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80 ){
+ *pU = ((z[0] & 0x0f)<<12) | ((z[1] & 0x3f)<<6) | (z[2] & 0x3f);
+ return 3;
+ }
+ if( (z[0] & 0xf8)==0xf0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80
+ && (z[3] & 0xc0)==0x80
+ ){
+ *pU = ((z[0] & 0x0f)<<18) | ((z[1] & 0x3f)<<12) | ((z[2] & 0x3f))<<6
+ | (z[3] & 0x3f);
+ return 4;
+ }
+ *pU = 0;
+ return 1;
+}
+
+/*
+** Check to see if z[] is a valid VT100 escape. If it is, then
+** return the number of bytes in the escape sequence. Return 0 if
+** z[] is not a VT100 escape.
+**
+** This routine assumes that z[0] is \033 (ESC).
+*/
+static int qrfIsVt100(const unsigned char *z){
+ int i;
+ if( z[1]!='[' ) return 0;
+ i = 2;
+ while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
+ while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
+ if( z[i]<0x40 || z[i]>0x7e ) return 0;
+ return i+1;
+}
+
+/*
+** Return the length of a string in display characters.
+**
+** Most characters of the input string count as 1, including
+** multi-byte UTF8 characters. However, zero-width unicode
+** characters and VT100 escape sequences count as zero, and
+** double-width characters count as two.
+**
+** The definition of "zero-width" and "double-width" characters
+** is not precise. It depends on the output device, to some extent,
+** and it varies according to the Unicode version. This routine
+** makes the best guess that it can.
+*/
+size_t sqlite3_qrf_wcswidth(const char *zIn){
+ const unsigned char *z = (const unsigned char*)zIn;
+ size_t n = 0;
+ while( *z ){
+ if( z[0]<' ' ){
+ int k;
+ if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){
+ z += k;
+ }else{
+ z++;
+ }
+ }else if( (0x80&z[0])==0 ){
+ n++;
+ z++;
+ }else{
+ int u = 0;
+ int len = sqlite3_qrf_decode_utf8(z, &u);
+ z += len;
+ n += sqlite3_qrf_wcwidth(u);
+ }
+ }
+ return n;
+}
+
+/*
+** Return the display width of the longest line of text
+** in the (possibly) multi-line input string zIn[0..nByte].
+** zIn[] is not necessarily zero-terminated. Take
+** into account tab characters, zero- and double-width
+** characters, CR and NL, and VT100 escape codes.
+**
+** Write the number of newlines into *pnNL. So, *pnNL will
+** return 0 if everything fits on one line, or positive it
+** it will need to be split.
+*/
+static int qrfDisplayWidth(const char *zIn, sqlite3_int64 nByte, int *pnNL){
+ const unsigned char *z;
+ const unsigned char *zEnd;
+ int mx = 0;
+ int n = 0;
+ int nNL = 0;
+ if( zIn==0 ) zIn = "";
+ z = (const unsigned char*)zIn;
+ zEnd = &z[nByte];
+ while( z0 ){
+ z += k;
+ }else{
+ if( z[0]=='\t' ){
+ n = (n+8)&~7;
+ }else if( z[0]=='\n' || z[0]=='\r' ){
+ nNL++;
+ if( n>mx ) mx = n;
+ n = 0;
+ }
+ z++;
+ }
+ }else if( (0x80&z[0])==0 ){
+ n++;
+ z++;
+ }else{
+ int u = 0;
+ int len = sqlite3_qrf_decode_utf8(z, &u);
+ z += len;
+ n += sqlite3_qrf_wcwidth(u);
+ }
+ }
+ if( mx>n ) n = mx;
+ if( pnNL ) *pnNL = nNL;
+ return n;
+}
+
+/*
+** Escape the input string if it is needed and in accordance with
+** eEsc, which is either QRF_ESC_Ascii or QRF_ESC_Symbol.
+**
+** Escaping is needed if the string contains any control characters
+** other than \t, \n, and \r\n
+**
+** If no escaping is needed (the common case) then set *ppOut to NULL
+** and return 0. If escaping is needed, write the escaped string into
+** memory obtained from sqlite3_malloc64() and make *ppOut point to that
+** memory and return 0. If an error occurs, return non-zero.
+**
+** The caller is responsible for freeing *ppFree if it is non-NULL in order
+** to reclaim memory.
+*/
+static void qrfEscape(
+ int eEsc, /* QRF_ESC_Ascii or QRF_ESC_Symbol */
+ sqlite3_str *pStr, /* String to be escaped */
+ int iStart /* Begin escapding on this byte of pStr */
+){
+ sqlite3_int64 i, j; /* Loop counters */
+ sqlite3_int64 sz; /* Size of the string prior to escaping */
+ sqlite3_int64 nCtrl = 0;/* Number of control characters to escape */
+ unsigned char *zIn; /* Text to be escaped */
+ unsigned char c; /* A single character of the text */
+ unsigned char *zOut; /* Where to write the results */
+
+ /* Find the text to be escaped */
+ zIn = (unsigned char*)sqlite3_str_value(pStr);
+ if( zIn==0 ) return;
+ zIn += iStart;
+
+ /* Count the control characters */
+ for(i=0; (c = zIn[i])!=0; i++){
+ if( c<=0x1f
+ && c!='\t'
+ && c!='\n'
+ && (c!='\r' || zIn[i+1]!='\n')
+ ){
+ nCtrl++;
+ }
+ }
+ if( nCtrl==0 ) return; /* Early out if no control characters */
+
+ /* Make space to hold the escapes. Copy the original text to the end
+ ** of the available space. */
+ sz = sqlite3_str_length(pStr) - iStart;
+ if( eEsc==QRF_ESC_Symbol ) nCtrl *= 2;
+ sqlite3_str_appendchar(pStr, nCtrl, ' ');
+ zOut = (unsigned char*)sqlite3_str_value(pStr);
+ if( zOut==0 ) return;
+ zOut += iStart;
+ zIn = zOut + nCtrl;
+ memmove(zIn,zOut,sz);
+
+ /* Convert the control characters */
+ for(i=j=0; (c = zIn[i])!=0; i++){
+ if( c>0x1f
+ || c=='\t'
+ || c=='\n'
+ || (c=='\r' && zIn[i+1]=='\n')
+ ){
+ continue;
+ }
+ if( i>0 ){
+ memmove(&zOut[j], zIn, i);
+ j += i;
+ }
+ zIn += i+1;
+ i = -1;
+ if( eEsc==QRF_ESC_Symbol ){
+ zOut[j++] = 0xe2;
+ zOut[j++] = 0x90;
+ zOut[j++] = 0x80+c;
+ }else{
+ zOut[j++] = '^';
+ zOut[j++] = 0x40+c;
+ }
+ }
+}
+
+/*
+** Determine if the string z[] can be shown as plain text. Return true
+** if z[] is unambiguously text. Return false if z[] needs to be
+** quoted.
+**
+** All of the following must be true in order for z[] to be relaxable:
+**
+** (1) z[] does not begin or end with ' or whitespace
+** (2) z[] is not the same as the NULL rendering
+** (3) z[] does not looks like a numeric literal
+*/
+static int qrfRelaxable(Qrf *p, const char *z){
+ size_t i, n;
+ if( z[0]=='\'' || qrfSpace(z[0]) ) return 0;
+ if( z[0]==0 ){
+ return (p->spec.zNull!=0 && p->spec.zNull[0]!=0);
+ }
+ n = strlen(z);
+ if( n==0 || z[n-1]=='\'' || qrfSpace(z[n-1]) ) return 0;
+ if( p->spec.zNull && strcmp(p->spec.zNull,z)==0 ) return 0;
+ i = (z[0]=='-' || z[0]=='+');
+ if( strcmp(z+i,"Inf")==0 ) return 0;
+ if( !qrfDigit(z[i]) ) return 1;
+ i++;
+ while( qrfDigit(z[i]) ){ i++; }
+ if( z[i]==0 ) return 0;
+ if( z[i]=='.' ){
+ i++;
+ while( qrfDigit(z[i]) ){ i++; }
+ if( z[i]==0 ) return 0;
+ }
+ if( z[i]=='e' || z[i]=='E' ){
+ i++;
+ if( z[i]=='+' || z[i]=='-' ){ i++; }
+ if( !qrfDigit(z[i]) ) return 1;
+ i++;
+ while( qrfDigit(z[i]) ){ i++; }
+ }
+ return z[i]!=0;
+}
+
+/*
+** If a field contains any character identified by a 1 in the following
+** array, then the string must be quoted for CSV.
+*/
+static const char qrfCsvQuote[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+/*
+** Encode text appropriately and append it to pOut.
+*/
+static void qrfEncodeText(Qrf *p, sqlite3_str *pOut, const char *zTxt){
+ int iStart = sqlite3_str_length(pOut);
+ switch( p->spec.eText ){
+ case QRF_TEXT_Relaxed:
+ if( qrfRelaxable(p, zTxt) ){
+ sqlite3_str_appendall(pOut, zTxt);
+ break;
+ }
+ deliberate_fall_through; /* FALLTHRU */
+ case QRF_TEXT_Sql: {
+ if( p->spec.eEsc==QRF_ESC_Off ){
+ sqlite3_str_appendf(pOut, "%Q", zTxt);
+ }else{
+ sqlite3_str_appendf(pOut, "%#Q", zTxt);
+ }
+ break;
+ }
+ case QRF_TEXT_Csv: {
+ unsigned int i;
+ for(i=0; zTxt[i]; i++){
+ if( qrfCsvQuote[((const unsigned char*)zTxt)[i]] ){
+ i = 0;
+ break;
+ }
+ }
+ if( i==0 || strstr(zTxt, p->spec.zColumnSep)!=0 ){
+ sqlite3_str_appendf(pOut, "\"%w\"", zTxt);
+ }else{
+ sqlite3_str_appendall(pOut, zTxt);
+ }
+ break;
+ }
+ case QRF_TEXT_Html: {
+ const unsigned char *z = (const unsigned char*)zTxt;
+ while( *z ){
+ unsigned int i = 0;
+ unsigned char c;
+ while( (c=z[i])>'>'
+ || (c && c!='<' && c!='>' && c!='&' && c!='\"' && c!='\'')
+ ){
+ i++;
+ }
+ if( i>0 ){
+ sqlite3_str_append(pOut, (const char*)z, i);
+ }
+ switch( z[i] ){
+ case '>': sqlite3_str_append(pOut, "<", 4); break;
+ case '&': sqlite3_str_append(pOut, "&", 5); break;
+ case '<': sqlite3_str_append(pOut, "<", 4); break;
+ case '"': sqlite3_str_append(pOut, """, 6); break;
+ case '\'': sqlite3_str_append(pOut, "'", 5); break;
+ default: i--;
+ }
+ z += i + 1;
+ }
+ break;
+ }
+ case QRF_TEXT_Tcl:
+ case QRF_TEXT_Json: {
+ const unsigned char *z = (const unsigned char*)zTxt;
+ sqlite3_str_append(pOut, "\"", 1);
+ while( *z ){
+ unsigned int i;
+ for(i=0; z[i]>=0x20 && z[i]!='\\' && z[i]!='"'; i++){}
+ if( i>0 ){
+ sqlite3_str_append(pOut, (const char*)z, i);
+ }
+ if( z[i]==0 ) break;
+ switch( z[i] ){
+ case '"': sqlite3_str_append(pOut, "\\\"", 2); break;
+ case '\\': sqlite3_str_append(pOut, "\\\\", 2); break;
+ case '\b': sqlite3_str_append(pOut, "\\b", 2); break;
+ case '\f': sqlite3_str_append(pOut, "\\f", 2); break;
+ case '\n': sqlite3_str_append(pOut, "\\n", 2); break;
+ case '\r': sqlite3_str_append(pOut, "\\r", 2); break;
+ case '\t': sqlite3_str_append(pOut, "\\t", 2); break;
+ default: {
+ if( p->spec.eText==QRF_TEXT_Json ){
+ sqlite3_str_appendf(pOut, "\\u%04x", z[i]);
+ }else{
+ sqlite3_str_appendf(pOut, "\\%03o", z[i]);
+ }
+ break;
+ }
+ }
+ z += i + 1;
+ }
+ sqlite3_str_append(pOut, "\"", 1);
+ break;
+ }
+ default: {
+ sqlite3_str_appendall(pOut, zTxt);
+ break;
+ }
+ }
+ if( p->spec.eEsc!=QRF_ESC_Off ){
+ qrfEscape(p->spec.eEsc, pOut, iStart);
+ }
+}
+
+/*
+** Do a quick sanity check to see aBlob[0..nBlob-1] is valid JSONB
+** return true if it is and false if it is not.
+**
+** False positives are possible, but not false negatives.
+*/
+static int qrfJsonbQuickCheck(unsigned char *aBlob, int nBlob){
+ unsigned char x; /* Payload size half-byte */
+ int i; /* Loop counter */
+ int n; /* Bytes in the payload size integer */
+ sqlite3_uint64 sz; /* value of the payload size integer */
+
+ if( nBlob==0 ) return 0;
+ x = aBlob[0]>>4;
+ if( x<=11 ) return nBlob==(1+x);
+ n = x<14 ? x-11 : 4*(x-13);
+ if( nBlob<1+n ) return 0;
+ sz = aBlob[1];
+ for(i=1; ipStmt is known to be a BLOB. Check
+** to see if that BLOB is really a JSONB blob. If it is, then translate
+** it into a text JSON representation and return a pointer to that text JSON.
+** If the BLOB is not JSONB, then return a NULL pointer.
+**
+** The memory used to hold the JSON text is managed internally by the
+** "p" object and is overwritten and/or deallocated upon the next call
+** to this routine (with the same p argument) or when the p object is
+** finailized.
+*/
+static const char *qrfJsonbToJson(Qrf *p, int iCol){
+ int nByte;
+ const void *pBlob;
+ int rc;
+ nByte = sqlite3_column_bytes(p->pStmt, iCol);
+ pBlob = sqlite3_column_blob(p->pStmt, iCol);
+ if( qrfJsonbQuickCheck((unsigned char*)pBlob, nByte)==0 ){
+ return 0;
+ }
+ if( p->pJTrans==0 ){
+ sqlite3 *db;
+ rc = sqlite3_open(":memory:",&db);
+ if( rc ){
+ sqlite3_close(db);
+ return 0;
+ }
+ rc = sqlite3_prepare_v2(db, "SELECT json(?1)", -1, &p->pJTrans, 0);
+ if( rc ){
+ sqlite3_finalize(p->pJTrans);
+ p->pJTrans = 0;
+ sqlite3_close(db);
+ return 0;
+ }
+ }else{
+ sqlite3_reset(p->pJTrans);
+ }
+ sqlite3_bind_blob(p->pJTrans, 1, (void*)pBlob, nByte, SQLITE_STATIC);
+ rc = sqlite3_step(p->pJTrans);
+ if( rc==SQLITE_ROW ){
+ return (const char*)sqlite3_column_text(p->pJTrans, 0);
+ }else{
+ return 0;
+ }
+}
+
+/*
+** Adjust the input string zIn[] such that it is no more than N display
+** characters wide. If it is wider than that, then truncate and add
+** ellipsis. Or if zIn[] contains a \r or \n, truncate at that point,
+** adding ellipsis. Embedded tabs in zIn[] are converted into ordinary
+** spaces.
+**
+** Return this display width of the modified title string.
+*/
+static int qrfTitleLimit(char *zIn, int N){
+ unsigned char *z = (unsigned char*)zIn;
+ int n = 0;
+ unsigned char *zEllipsis = 0;
+ while( 1 /*exit-by-break*/ ){
+ if( z[0]<' ' ){
+ int k;
+ if( z[0]==0 ){
+ zEllipsis = 0;
+ break;
+ }else if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){
+ z += k;
+ }else if( z[0]=='\t' ){
+ z[0] = ' ';
+ }else if( z[0]=='\n' || z[0]=='\r' ){
+ z[0] = ' ';
+ }else{
+ z++;
+ }
+ }else if( (0x80&z[0])==0 ){
+ if( n>=(N-3) && zEllipsis==0 ) zEllipsis = z;
+ if( n==N ){ z[0] = 0; break; }
+ n++;
+ z++;
+ }else{
+ int u = 0;
+ int len = sqlite3_qrf_decode_utf8(z, &u);
+ if( n+len>(N-3) && zEllipsis==0 ) zEllipsis = z;
+ if( n+len>N ){ z[0] = 0; break; }
+ z += len;
+ n += sqlite3_qrf_wcwidth(u);
+ }
+ }
+ if( zEllipsis && N>=3 ) memcpy(zEllipsis,"...",4);
+ return n;
+}
+
+
+/*
+** Render value pVal into pOut
+*/
+static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){
+#if SQLITE_VERSION_NUMBER>=3052000
+ int iStartLen = sqlite3_str_length(pOut);
+#endif
+ if( p->spec.xRender ){
+ sqlite3_value *pVal;
+ char *z;
+ pVal = sqlite3_value_dup(sqlite3_column_value(p->pStmt,iCol));
+ z = p->spec.xRender(p->spec.pRenderArg, pVal);
+ sqlite3_value_free(pVal);
+ if( z ){
+ sqlite3_str_appendall(pOut, z);
+ sqlite3_free(z);
+ return;
+ }
+ }
+ switch( sqlite3_column_type(p->pStmt,iCol) ){
+ case SQLITE_INTEGER: {
+ sqlite3_str_appendf(pOut, "%lld", sqlite3_column_int64(p->pStmt,iCol));
+ break;
+ }
+ case SQLITE_FLOAT: {
+ const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
+ sqlite3_str_appendall(pOut, zTxt);
+ break;
+ }
+ case SQLITE_BLOB: {
+ if( p->spec.bTextJsonb==QRF_Yes ){
+ const char *zJson = qrfJsonbToJson(p, iCol);
+ if( zJson ){
+ if( p->spec.eText==QRF_TEXT_Sql ){
+ sqlite3_str_append(pOut,"jsonb(",6);
+ qrfEncodeText(p, pOut, zJson);
+ sqlite3_str_append(pOut,")",1);
+ }else{
+ qrfEncodeText(p, pOut, zJson);
+ }
+ break;
+ }
+ }
+ switch( p->spec.eBlob ){
+ case QRF_BLOB_Hex:
+ case QRF_BLOB_Sql: {
+ int iStart;
+ int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
+ int i, j;
+ char *zVal;
+ const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol);
+ if( p->spec.eBlob==QRF_BLOB_Sql ){
+ sqlite3_str_append(pOut, "x'", 2);
+ }
+ iStart = sqlite3_str_length(pOut);
+ sqlite3_str_appendchar(pOut, nBlob, ' ');
+ sqlite3_str_appendchar(pOut, nBlob, ' ');
+ if( p->spec.eBlob==QRF_BLOB_Sql ){
+ sqlite3_str_appendchar(pOut, 1, '\'');
+ }
+ if( sqlite3_str_errcode(pOut) ) return;
+ zVal = sqlite3_str_value(pOut);
+ for(i=0, j=iStart; i>4)&0xf];
+ zVal[j+1] = "0123456789abcdef"[(c)&0xf];
+ }
+ break;
+ }
+ case QRF_BLOB_Tcl:
+ case QRF_BLOB_Json: {
+ int iStart;
+ int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
+ int i, j;
+ char *zVal;
+ const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol);
+ int szC = p->spec.eBlob==QRF_BLOB_Json ? 6 : 4;
+ sqlite3_str_append(pOut, "\"", 1);
+ iStart = sqlite3_str_length(pOut);
+ for(i=szC; i>0; i--){
+ sqlite3_str_appendchar(pOut, nBlob, ' ');
+ }
+ sqlite3_str_appendchar(pOut, 1, '"');
+ if( sqlite3_str_errcode(pOut) ) return;
+ zVal = sqlite3_str_value(pOut);
+ for(i=0, j=iStart; i>6)&3);
+ zVal[j+2] = '0' + ((c>>3)&7);
+ zVal[j+3] = '0' + (c&7);
+ }else{
+ zVal[j+1] = 'u';
+ zVal[j+2] = '0';
+ zVal[j+3] = '0';
+ zVal[j+4] = "0123456789abcdef"[(c>>4)&0xf];
+ zVal[j+5] = "0123456789abcdef"[(c)&0xf];
+ }
+ }
+ break;
+ }
+ case QRF_BLOB_Size: {
+ int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
+ sqlite3_str_appendf(pOut, "(%d-byte blob)", nBlob);
+ break;
+ }
+ default: {
+ const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
+ qrfEncodeText(p, pOut, zTxt);
+ }
+ }
+ break;
+ }
+ case SQLITE_NULL: {
+ sqlite3_str_appendall(pOut, p->spec.zNull);
+ break;
+ }
+ case SQLITE_TEXT: {
+ const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
+ qrfEncodeText(p, pOut, zTxt);
+ break;
+ }
+ }
+#if SQLITE_VERSION_NUMBER>=3052000
+ if( p->spec.nCharLimit>0
+ && (sqlite3_str_length(pOut) - iStartLen) > p->spec.nCharLimit
+ ){
+ const unsigned char *z;
+ int ii = 0, w = 0, limit = p->spec.nCharLimit;
+ z = (const unsigned char*)sqlite3_str_value(pOut) + iStartLen;
+ if( limit<4 ) limit = 4;
+ while( 1 ){
+ if( z[ii]<' ' ){
+ int k;
+ if( z[ii]=='\033' && (k = qrfIsVt100(z+ii))>0 ){
+ ii += k;
+ }else if( z[ii]==0 ){
+ break;
+ }else{
+ ii++;
+ }
+ }else if( (0x80&z[ii])==0 ){
+ w++;
+ if( w>limit ) break;
+ ii++;
+ }else{
+ int u = 0;
+ int len = sqlite3_qrf_decode_utf8(&z[ii], &u);
+ w += sqlite3_qrf_wcwidth(u);
+ if( w>limit ) break;
+ ii += len;
+ }
+ }
+ if( w>limit ){
+ sqlite3_str_truncate(pOut, iStartLen+ii);
+ sqlite3_str_append(pOut, "...", 3);
+ }
+ }
+#endif
+}
+
+/* Trim spaces of the end if pOut
+*/
+static void qrfRTrim(sqlite3_str *pOut){
+#if SQLITE_VERSION_NUMBER>=3052000
+ int nByte = sqlite3_str_length(pOut);
+ const char *zOut = sqlite3_str_value(pOut);
+ while( nByte>0 && zOut[nByte-1]==' ' ){ nByte--; }
+ sqlite3_str_truncate(pOut, nByte);
+#endif
+}
+
+/*
+** Store string zUtf to pOut as w characters. If w is negative,
+** then right-justify the text. W is the width in display characters, not
+** in bytes. Double-width unicode characters count as two characters.
+** VT100 escape sequences count as zero. And so forth.
+*/
+static void qrfWidthPrint(Qrf *p, sqlite3_str *pOut, int w, const char *zUtf){
+ const unsigned char *a = (const unsigned char*)zUtf;
+ static const int mxW = 10000000;
+ unsigned char c;
+ int i = 0;
+ int n = 0;
+ int k;
+ int aw;
+ (void)p;
+ if( w<-mxW ){
+ w = -mxW;
+ }else if( w>mxW ){
+ w= mxW;
+ }
+ aw = w<0 ? -w : w;
+ if( a==0 ) a = (const unsigned char*)"";
+ while( (c = a[i])!=0 ){
+ if( (c&0xc0)==0xc0 ){
+ int u;
+ int len = sqlite3_qrf_decode_utf8(a+i, &u);
+ int x = sqlite3_qrf_wcwidth(u);
+ if( x+n>aw ){
+ break;
+ }
+ i += len;
+ n += x;
+ }else if( c==0x1b && (k = qrfIsVt100(&a[i]))>0 ){
+ i += k;
+ }else if( n>=aw ){
+ break;
+ }else{
+ n++;
+ i++;
+ }
+ }
+ if( n>=aw ){
+ sqlite3_str_append(pOut, zUtf, i);
+ }else if( w<0 ){
+ if( aw>n ) sqlite3_str_appendchar(pOut, aw-n, ' ');
+ sqlite3_str_append(pOut, zUtf, i);
+ }else{
+ sqlite3_str_append(pOut, zUtf, i);
+ if( aw>n ) sqlite3_str_appendchar(pOut, aw-n, ' ');
+ }
+}
+
+/*
+** (*pz)[] is a line of text that is to be displayed the box or table or
+** similar tabular formats. z[] contain newlines or might be too wide
+** to fit in the columns so will need to be split into multiple line.
+**
+** This routine determines:
+**
+** * How many bytes of z[] should be shown on the current line.
+** * How many character positions those bytes will cover.
+** * The byte offset to the start of the next line.
+*/
+static void qrfWrapLine(
+ const char *zIn, /* Input text to be displayed */
+ int w, /* Column width in characters (not bytes) */
+ int bWrap, /* True if we should do word-wrapping */
+ int *pnThis, /* OUT: How many bytes of z[] for the current line */
+ int *pnWide, /* OUT: How wide is the text of this line */
+ int *piNext /* OUT: Offset into z[] to start of the next line */
+){
+ int i; /* Input bytes consumed */
+ int k; /* Bytes in a VT100 code */
+ int n; /* Output column number */
+ const unsigned char *z = (const unsigned char*)zIn;
+ unsigned char c = 0;
+
+ if( z[0]==0 ){
+ *pnThis = 0;
+ *pnWide = 0;
+ *piNext = 0;
+ return;
+ }
+ n = 0;
+ for(i=0; n<=w; i++){
+ c = z[i];
+ if( c>=0xc0 ){
+ int u;
+ int len = sqlite3_qrf_decode_utf8(&z[i], &u);
+ int wcw = sqlite3_qrf_wcwidth(u);
+ if( wcw+n>w ) break;
+ i += len-1;
+ n += wcw;
+ continue;
+ }
+ if( c>=' ' ){
+ if( n==w ) break;
+ n++;
+ continue;
+ }
+ if( c==0 || c=='\n' ) break;
+ if( c=='\r' && z[i+1]=='\n' ){ c = z[++i]; break; }
+ if( c=='\t' ){
+ int wcw = 8 - (n&7);
+ if( n+wcw>w ) break;
+ n += wcw;
+ continue;
+ }
+ if( c==0x1b && (k = qrfIsVt100(&z[i]))>0 ){
+ i += k-1;
+ }else if( n==w ){
+ break;
+ }else{
+ n++;
+ }
+ }
+ if( c==0 ){
+ *pnThis = i;
+ *pnWide = n;
+ *piNext = i;
+ return;
+ }
+ if( c=='\n' ){
+ *pnThis = i;
+ *pnWide = n;
+ *piNext = i+1;
+ return;
+ }
+
+ /* If we get this far, that means the current line will end at some
+ ** point that is neither a "\n" or a 0x00. Figure out where that
+ ** split should occur
+ */
+ if( bWrap && z[i]!=0 && !qrfSpace(z[i]) && qrfAlnum(c)==qrfAlnum(z[i]) ){
+ /* Perhaps try to back up to a better place to break the line */
+ for(k=i-1; k>=i/2; k--){
+ if( qrfSpace(z[k]) ) break;
+ }
+ if( k=i/2; k--){
+ if( qrfAlnum(z[k-1])!=qrfAlnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
+ }
+ }
+ if( k>=i/2 ){
+ i = k;
+ n = qrfDisplayWidth((const char*)z, k, 0);
+ }
+ }
+ *pnThis = i;
+ *pnWide = n;
+ while( zIn[i]==' ' || zIn[i]=='\t' || zIn[i]=='\r' ){ i++; }
+ *piNext = i;
+}
+
+/*
+** Append nVal bytes of text from zVal onto the end of pOut.
+** Convert tab characters in zVal to the appropriate number of
+** spaces.
+*/
+static void qrfAppendWithTabs(
+ sqlite3_str *pOut, /* Append text here */
+ const char *zVal, /* Text to append */
+ int nVal /* Use only the first nVal bytes of zVal[] */
+){
+ int i = 0;
+ unsigned int col = 0;
+ unsigned char *z = (unsigned char *)zVal;
+ while( i0 ){
+ sqlite3_str_append(pOut, (const char*)z, k);
+ z += k;
+ nVal -= k;
+ }else if( c=='\t' ){
+ k = 8 - (col&7);
+ sqlite3_str_appendchar(pOut, k, ' ');
+ col += k;
+ z++;
+ nVal--;
+ }else if( c=='\r' && nVal==1 ){
+ z++;
+ nVal--;
+ }else{
+ char zCtrlPik[4];
+ col++;
+ zCtrlPik[0] = 0xe2;
+ zCtrlPik[1] = 0x90;
+ zCtrlPik[2] = 0x80+c;
+ sqlite3_str_append(pOut, zCtrlPik, 3);
+ z++;
+ nVal--;
+ }
+ }else if( (0x80&c)==0 ){
+ i++;
+ col++;
+ }else{
+ int u = 0;
+ int len = sqlite3_qrf_decode_utf8(&z[i], &u);
+ i += len;
+ col += sqlite3_qrf_wcwidth(u);
+ }
+ }
+ sqlite3_str_append(pOut, (const char*)z, i);
+}
+
+/*
+** GCC does not define the offsetof() macro so we'll have to do it
+** ourselves.
+*/
+#ifndef offsetof
+# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0))
+#endif
+
+/*
+** Data for columnar layout, collected into a single object so
+** that it can be more easily passed into subroutines.
+*/
+typedef struct qrfColData qrfColData;
+struct qrfColData {
+ Qrf *p; /* The QRF instance */
+ int nCol; /* Number of columns in the table */
+ unsigned char bMultiRow; /* One or more cells will span multiple lines */
+ unsigned char nMargin; /* Width of column margins */
+ sqlite3_int64 nRow; /* Number of rows */
+ sqlite3_int64 nAlloc; /* Number of cells allocated */
+ sqlite3_int64 n; /* Number of cells. nCol*nRow */
+ char **az; /* Content of all cells */
+ int *aiWth; /* Width of each cell */
+ unsigned char *abNum; /* True for each numeric cell */
+ struct qrfPerCol { /* Per-column data */
+ char *z; /* Cache of text for current row */
+ int w; /* Computed width of this column */
+ int mxW; /* Maximum natural (unwrapped) width */
+ unsigned char e; /* Alignment */
+ unsigned char fx; /* Width is fixed */
+ unsigned char bNum; /* True if is numeric */
+ } *a; /* One per column */
+};
+
+/*
+** Output horizontally justified text into pOut. The text is the
+** first nVal bytes of zVal. Include nWS bytes of whitespace, either
+** split between both sides, or on the left, or on the right, depending
+** on eAlign.
+*/
+static void qrfPrintAligned(
+ sqlite3_str *pOut, /* Append text here */
+ struct qrfPerCol *pCol, /* Information about the text to print */
+ int nVal, /* Use only the first nVal bytes of zVal[] */
+ int nWS /* Whitespace for horizonal alignment */
+){
+ unsigned char eAlign = pCol->e & QRF_ALIGN_HMASK;
+ if( eAlign==QRF_Auto && pCol->bNum ) eAlign = QRF_ALIGN_Right;
+ if( eAlign==QRF_ALIGN_Center ){
+ /* Center the text */
+ sqlite3_str_appendchar(pOut, nWS/2, ' ');
+ qrfAppendWithTabs(pOut, pCol->z, nVal);
+ sqlite3_str_appendchar(pOut, nWS - nWS/2, ' ');
+ }else if( eAlign==QRF_ALIGN_Right ){
+ /* Right justify the text */
+ sqlite3_str_appendchar(pOut, nWS, ' ');
+ qrfAppendWithTabs(pOut, pCol->z, nVal);
+ }else{
+ /* Left justify the text */
+ qrfAppendWithTabs(pOut, pCol->z, nVal);
+ sqlite3_str_appendchar(pOut, nWS, ' ');
+ }
+}
+
+/*
+** Free all the memory allocates in the qrfColData object
+*/
+static void qrfColDataFree(qrfColData *p){
+ sqlite3_int64 i;
+ for(i=0; in; i++) sqlite3_free(p->az[i]);
+ sqlite3_free(p->az);
+ sqlite3_free(p->aiWth);
+ sqlite3_free(p->abNum);
+ sqlite3_free(p->a);
+ memset(p, 0, sizeof(*p));
+}
+
+/*
+** Allocate space for more cells in the qrfColData object.
+** Return non-zero if a memory allocation fails.
+*/
+static int qrfColDataEnlarge(qrfColData *p){
+ char **azData;
+ int *aiWth;
+ unsigned char *abNum;
+ p->nAlloc = 2*p->nAlloc + 10*p->nCol;
+ azData = sqlite3_realloc64(p->az, p->nAlloc*sizeof(char*));
+ if( azData==0 ){
+ qrfOom(p->p);
+ qrfColDataFree(p);
+ return 1;
+ }
+ p->az = azData;
+ aiWth = sqlite3_realloc64(p->aiWth, p->nAlloc*sizeof(int));
+ if( aiWth==0 ){
+ qrfOom(p->p);
+ qrfColDataFree(p);
+ return 1;
+ }
+ p->aiWth = aiWth;
+ abNum = sqlite3_realloc64(p->abNum, p->nAlloc);
+ if( abNum==0 ){
+ qrfOom(p->p);
+ qrfColDataFree(p);
+ return 1;
+ }
+ p->abNum = abNum;
+ return 0;
+}
+
+/*
+** Print a markdown or table-style row separator using ascii-art
+*/
+static void qrfRowSeparator(sqlite3_str *pOut, qrfColData *p, char cSep){
+ int i;
+ if( p->nCol>0 ){
+ int useBorder = p->p->spec.bBorder!=QRF_No;
+ if( useBorder ){
+ sqlite3_str_append(pOut, &cSep, 1);
+ }
+ sqlite3_str_appendchar(pOut, p->a[0].w+p->nMargin, '-');
+ for(i=1; inCol; i++){
+ sqlite3_str_append(pOut, &cSep, 1);
+ sqlite3_str_appendchar(pOut, p->a[i].w+p->nMargin, '-');
+ }
+ if( useBorder ){
+ sqlite3_str_append(pOut, &cSep, 1);
+ }
+ }
+ sqlite3_str_append(pOut, "\n", 1);
+}
+
+/*
+** UTF8 box-drawing characters. Imagine box lines like this:
+**
+** 1
+** |
+** 4 --+-- 2
+** |
+** 3
+**
+** Each box characters has between 2 and 4 of the lines leading from
+** the center. The characters are here identified by the numbers of
+** their corresponding lines.
+*/
+#define BOX_24 "\342\224\200" /* U+2500 --- */
+#define BOX_13 "\342\224\202" /* U+2502 | */
+#define BOX_23 "\342\224\214" /* U+250c ,- */
+#define BOX_34 "\342\224\220" /* U+2510 -, */
+#define BOX_12 "\342\224\224" /* U+2514 '- */
+#define BOX_14 "\342\224\230" /* U+2518 -' */
+#define BOX_123 "\342\224\234" /* U+251c |- */
+#define BOX_134 "\342\224\244" /* U+2524 -| */
+#define BOX_234 "\342\224\254" /* U+252c -,- */
+#define BOX_124 "\342\224\264" /* U+2534 -'- */
+#define BOX_1234 "\342\224\274" /* U+253c -|- */
+
+/* Rounded corners: */
+#define BOX_R12 "\342\225\260" /* U+2570 '- */
+#define BOX_R23 "\342\225\255" /* U+256d ,- */
+#define BOX_R34 "\342\225\256" /* U+256e -, */
+#define BOX_R14 "\342\225\257" /* U+256f -' */
+
+/* Doubled horizontal lines: */
+#define DBL_24 "\342\225\220" /* U+2550 === */
+#define DBL_123 "\342\225\236" /* U+255e |= */
+#define DBL_134 "\342\225\241" /* U+2561 =| */
+#define DBL_1234 "\342\225\252" /* U+256a =|= */
+
+/* Draw horizontal line N characters long using unicode box
+** characters
+*/
+static void qrfBoxLine(sqlite3_str *pOut, int N, int bDbl){
+ const char *azDash[2] = {
+ BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24,
+ DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24
+ };/* 0 1 2 3 4 5 6 7 8 9 */
+ const int nDash = 30;
+ N *= 3;
+ while( N>nDash ){
+ sqlite3_str_append(pOut, azDash[bDbl], nDash);
+ N -= nDash;
+ }
+ sqlite3_str_append(pOut, azDash[bDbl], N);
+}
+
+/*
+** Draw a horizontal separator for a QRF_STYLE_Box table.
+*/
+static void qrfBoxSeparator(
+ sqlite3_str *pOut,
+ qrfColData *p,
+ const char *zSep1,
+ const char *zSep2,
+ const char *zSep3,
+ int bDbl
+){
+ int i;
+ if( p->nCol>0 ){
+ int useBorder = p->p->spec.bBorder!=QRF_No;
+ if( useBorder ){
+ sqlite3_str_appendall(pOut, zSep1);
+ }
+ qrfBoxLine(pOut, p->a[0].w+p->nMargin, bDbl);
+ for(i=1; inCol; i++){
+ sqlite3_str_appendall(pOut, zSep2);
+ qrfBoxLine(pOut, p->a[i].w+p->nMargin, bDbl);
+ }
+ if( useBorder ){
+ sqlite3_str_appendall(pOut, zSep3);
+ }
+ }
+ sqlite3_str_append(pOut, "\n", 1);
+}
+
+/*
+** Load into pData the default alignment for the body of a table.
+*/
+static void qrfLoadAlignment(qrfColData *pData, Qrf *p){
+ sqlite3_int64 i;
+ for(i=0; inCol; i++){
+ pData->a[i].e = p->spec.eDfltAlign;
+ if( ispec.nAlign ){
+ unsigned char ax = p->spec.aAlign[i];
+ if( (ax & QRF_ALIGN_HMASK)!=0 ){
+ pData->a[i].e = (ax & QRF_ALIGN_HMASK) |
+ (pData->a[i].e & QRF_ALIGN_VMASK);
+ }
+ }else if( ispec.nWidth ){
+ if( p->spec.aWidth[i]<0 ){
+ pData->a[i].e = QRF_ALIGN_Right |
+ (pData->a[i].e & QRF_ALIGN_VMASK);
+ }
+ }
+ }
+}
+
+/*
+** If the single column in pData->a[] with pData->n entries can be
+** laid out as nCol columns with a 2-space gap between each such
+** that all columns fit within nSW, then return a pointer to an array
+** of integers which is the width of each column from left to right.
+**
+** If the layout is not possible, return a NULL pointer.
+**
+** Space to hold the returned array is from sqlite_malloc64().
+*/
+static int *qrfValidLayout(
+ qrfColData *pData, /* Collected query results */
+ Qrf *p, /* On which to report an OOM */
+ int nCol, /* Attempt this many columns */
+ int nSW /* Screen width */
+){
+ int i; /* Loop counter */
+ int nr; /* Number of rows */
+ int w = 0; /* Width of the current column */
+ int t; /* Total width of all columns */
+ int *aw; /* Array of individual column widths */
+
+ aw = sqlite3_malloc64( sizeof(int)*nCol );
+ if( aw==0 ){
+ qrfOom(p);
+ return 0;
+ }
+ nr = (pData->n + nCol - 1)/nCol;
+ for(i=0; in; i++){
+ if( (i%nr)==0 ){
+ if( i>0 ) aw[i/nr-1] = w;
+ w = pData->aiWth[i];
+ }else if( pData->aiWth[i]>w ){
+ w = pData->aiWth[i];
+ }
+ }
+ aw[nCol-1] = w;
+ for(t=i=0; inSW ){
+ sqlite3_free(aw);
+ return 0;
+ }
+ return aw;
+}
+
+/*
+** The output is single-column and the bSplitColumn flag is set.
+** Check to see if the single-column output can be split into multiple
+** columns that appear side-by-side. Adjust pData appropriately.
+*/
+static void qrfSplitColumn(qrfColData *pData, Qrf *p){
+ int nCol = 1;
+ int *aw = 0;
+ char **az = 0;
+ int *aiWth = 0;
+ unsigned char *abNum = 0;
+ int nColNext = 2;
+ int w;
+ struct qrfPerCol *a = 0;
+ sqlite3_int64 nRow = 1;
+ sqlite3_int64 i;
+ while( 1/*exit-by-break*/ ){
+ int *awNew = qrfValidLayout(pData, p, nColNext, p->spec.nScreenWidth);
+ if( awNew==0 ) break;
+ sqlite3_free(aw);
+ aw = awNew;
+ nCol = nColNext;
+ nRow = (pData->n + nCol - 1)/nCol;
+ if( nRow==1 ) break;
+ nColNext++;
+ while( (pData->n + nColNext - 1)/nColNext == nRow ) nColNext++;
+ }
+ if( nCol==1 ){
+ sqlite3_free(aw);
+ return; /* Cannot do better than 1 column */
+ }
+ az = sqlite3_malloc64( nRow*nCol*sizeof(char*) );
+ if( az==0 ){
+ qrfOom(p);
+ return;
+ }
+ aiWth = sqlite3_malloc64( nRow*nCol*sizeof(int) );
+ if( aiWth==0 ){
+ sqlite3_free(az);
+ qrfOom(p);
+ return;
+ }
+ a = sqlite3_malloc64( nCol*sizeof(struct qrfPerCol) );
+ if( a==0 ){
+ sqlite3_free(az);
+ sqlite3_free(aiWth);
+ qrfOom(p);
+ return;
+ }
+ abNum = sqlite3_malloc64( nRow*nCol );
+ if( abNum==0 ){
+ sqlite3_free(az);
+ sqlite3_free(aiWth);
+ sqlite3_free(a);
+ qrfOom(p);
+ return;
+ }
+ for(i=0; in; i++){
+ sqlite3_int64 j = (i%nRow)*nCol + (i/nRow);
+ az[j] = pData->az[i];
+ abNum[j]= pData->abNum[i];
+ pData->az[i] = 0;
+ aiWth[j] = pData->aiWth[i];
+ }
+ while( ia[0].e;
+ }
+ sqlite3_free(pData->az);
+ sqlite3_free(pData->aiWth);
+ sqlite3_free(pData->a);
+ sqlite3_free(pData->abNum);
+ sqlite3_free(aw);
+ pData->az = az;
+ pData->aiWth = aiWth;
+ pData->a = a;
+ pData->abNum = abNum;
+ pData->nCol = nCol;
+ pData->n = pData->nAlloc = nRow*nCol;
+ for(i=w=0; inMargin = (p->spec.nScreenWidth - w)/(nCol - 1);
+ if( pData->nMargin>5 ) pData->nMargin = 5;
+}
+
+/*
+** Adjust the layout for the screen width restriction
+*/
+static void qrfRestrictScreenWidth(qrfColData *pData, Qrf *p){
+ int sepW; /* Width of all box separators and margins */
+ int sumW; /* Total width of data area over all columns */
+ int targetW; /* Desired total data area */
+ int i; /* Loop counters */
+ int nCol; /* Number of columns */
+
+ pData->nMargin = 2; /* Default to normal margins */
+ if( p->spec.nScreenWidth==0 ) return;
+ if( p->spec.eStyle==QRF_STYLE_Column ){
+ sepW = pData->nCol*2 - 2;
+ }else{
+ sepW = pData->nCol*3 + 1;
+ if( p->spec.bBorder==QRF_No ) sepW -= 2;
+ }
+ nCol = pData->nCol;
+ for(i=sumW=0; ia[i].w;
+ if( p->spec.nScreenWidth >= sumW+sepW ) return;
+
+ /* First thing to do is reduce the separation between columns */
+ pData->nMargin = 0;
+ if( p->spec.eStyle==QRF_STYLE_Column ){
+ sepW = pData->nCol - 1;
+ }else{
+ sepW = pData->nCol + 1;
+ if( p->spec.bBorder==QRF_No ) sepW -= 2;
+ }
+ targetW = p->spec.nScreenWidth - sepW;
+
+#define MIN_SQUOZE 8
+#define MIN_EX_SQUOZE 16
+ /* Reduce the width of the widest eligible column. A column is
+ ** eligible for narrowing if:
+ **
+ ** * It is not a fixed-width column (a[0].fx is false)
+ ** * The current width is more than MIN_SQUOZE
+ ** * Either:
+ ** + The current width is more then MIN_EX_SQUOZE, or
+ ** + The current width is more than half the max width (a[].mxW)
+ **
+ ** Keep making reductions until either no more reductions are
+ ** possible or until the size target is reached.
+ */
+ while( sumW > targetW ){
+ int gain, w;
+ int ix = -1;
+ int mx = 0;
+ for(i=0; ia[i].fx==0
+ && (w = pData->a[i].w)>mx
+ && w>MIN_SQUOZE
+ && (w>MIN_EX_SQUOZE || w*2>pData->a[i].mxW)
+ ){
+ ix = i;
+ mx = w;
+ }
+ }
+ if( ix<0 ) break;
+ if( mx>=MIN_SQUOZE*2 ){
+ gain = mx/2;
+ }else{
+ gain = mx - MIN_SQUOZE;
+ }
+ if( sumW - gain < targetW ){
+ gain = sumW - targetW;
+ }
+ sumW -= gain;
+ pData->a[ix].w -= gain;
+ pData->bMultiRow = 1;
+ }
+}
+
+/*
+** Columnar modes require that the entire query be evaluated first, with
+** results written into memory, so that we can compute appropriate column
+** widths.
+*/
+static void qrfColumnar(Qrf *p){
+ sqlite3_int64 i, j; /* Loop counters */
+ const char *colSep = 0; /* Column separator text */
+ const char *rowSep = 0; /* Row terminator text */
+ const char *rowStart = 0; /* Row start text */
+ int szColSep, szRowSep, szRowStart; /* Size in bytes of previous 3 */
+ int rc; /* Result code */
+ int nColumn = p->nCol; /* Number of columns */
+ int bWW; /* True to do word-wrap */
+ sqlite3_str *pStr; /* Temporary rendering */
+ qrfColData data; /* Columnar layout data */
+ int bRTrim; /* Trim trailing space */
+
+ rc = sqlite3_step(p->pStmt);
+ if( rc!=SQLITE_ROW || nColumn==0 ){
+ return; /* No output */
+ }
+
+ /* Initialize the data container */
+ memset(&data, 0, sizeof(data));
+ data.nCol = p->nCol;
+ data.p = p;
+ data.a = sqlite3_malloc64( nColumn*sizeof(struct qrfPerCol) );
+ if( data.a==0 ){
+ qrfOom(p);
+ return;
+ }
+ memset(data.a, 0, nColumn*sizeof(struct qrfPerCol) );
+ if( qrfColDataEnlarge(&data) ) return;
+ assert( data.az!=0 );
+
+ /* Load the column header names and all cell content into data */
+ if( p->spec.bTitles==QRF_Yes ){
+ unsigned char saved_eText = p->spec.eText;
+ p->spec.eText = p->spec.eTitle;
+ memset(data.abNum, 0, nColumn);
+ for(i=0; ipStmt,i);
+ int nNL = 0;
+ int n, w;
+ pStr = sqlite3_str_new(p->db);
+ qrfEncodeText(p, pStr, z ? z : "");
+ n = sqlite3_str_length(pStr);
+ qrfStrErr(p, pStr);
+ z = data.az[data.n] = sqlite3_str_finish(pStr);
+ if( p->spec.nTitleLimit ){
+ nNL = 0;
+ data.aiWth[data.n] = w = qrfTitleLimit(data.az[data.n],
+ p->spec.nTitleLimit );
+ }else{
+ data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL);
+ }
+ data.n++;
+ if( w>data.a[i].mxW ) data.a[i].mxW = w;
+ if( nNL ) data.bMultiRow = 1;
+ }
+ p->spec.eText = saved_eText;
+ p->nRow++;
+ }
+ do{
+ if( data.n+nColumn > data.nAlloc ){
+ if( qrfColDataEnlarge(&data) ) return;
+ }
+ for(i=0; ipStmt,i);
+ pStr = sqlite3_str_new(p->db);
+ qrfRenderValue(p, pStr, i);
+ n = sqlite3_str_length(pStr);
+ qrfStrErr(p, pStr);
+ z = data.az[data.n] = sqlite3_str_finish(pStr);
+ data.abNum[data.n] = eType==SQLITE_INTEGER || eType==SQLITE_FLOAT;
+ data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL);
+ data.n++;
+ if( w>data.a[i].mxW ) data.a[i].mxW = w;
+ if( nNL ) data.bMultiRow = 1;
+ }
+ p->nRow++;
+ }while( sqlite3_step(p->pStmt)==SQLITE_ROW && p->iErr==SQLITE_OK );
+ if( p->iErr ){
+ qrfColDataFree(&data);
+ return;
+ }
+
+ /* Compute the width and alignment of every column */
+ if( p->spec.bTitles==QRF_No ){
+ qrfLoadAlignment(&data, p);
+ }else{
+ unsigned char e;
+ if( p->spec.eTitleAlign==QRF_Auto ){
+ e = QRF_ALIGN_Center;
+ }else{
+ e = p->spec.eTitleAlign;
+ }
+ for(i=0; ispec.nWidth ){
+ w = p->spec.aWidth[i];
+ if( w==(-32768) ){
+ w = 0;
+ if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){
+ data.a[i].e |= QRF_ALIGN_Right;
+ }
+ }else if( w<0 ){
+ w = -w;
+ if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){
+ data.a[i].e |= QRF_ALIGN_Right;
+ }
+ }
+ if( w ) data.a[i].fx = 1;
+ }
+ if( w==0 ){
+ w = data.a[i].mxW;
+ if( p->spec.nWrap>0 && w>p->spec.nWrap ){
+ w = p->spec.nWrap;
+ data.bMultiRow = 1;
+ }
+ }else if( (data.bMultiRow==0 || w==1) && data.a[i].mxW>w ){
+ data.bMultiRow = 1;
+ if( w==1 ){
+ /* If aiWth[j] is 2 or more, then there might be a double-wide
+ ** character somewhere. So make the column width at least 2. */
+ w = 2;
+ }
+ }
+ data.a[i].w = w;
+ }
+
+ if( nColumn==1
+ && data.n>1
+ && p->spec.bSplitColumn==QRF_Yes
+ && p->spec.eStyle==QRF_STYLE_Column
+ && p->spec.bTitles==QRF_No
+ && p->spec.nScreenWidth>data.a[0].w+3
+ ){
+ /* Attempt to convert single-column tables into multi-column by
+ ** verticle wrapping, if the screen is wide enough and if the
+ ** bSplitColumn flag is set. */
+ qrfSplitColumn(&data, p);
+ nColumn = data.nCol;
+ }else{
+ /* Adjust the column widths due to screen width restrictions */
+ qrfRestrictScreenWidth(&data, p);
+ }
+
+ /* Draw the line across the top of the table. Also initialize
+ ** the row boundary and column separator texts. */
+ switch( p->spec.eStyle ){
+ case QRF_STYLE_Box:
+ if( data.nMargin ){
+ rowStart = BOX_13 " ";
+ colSep = " " BOX_13 " ";
+ rowSep = " " BOX_13 "\n";
+ }else{
+ rowStart = BOX_13;
+ colSep = BOX_13;
+ rowSep = BOX_13 "\n";
+ }
+ if( p->spec.bBorder==QRF_No){
+ rowStart += 3;
+ rowSep = "\n";
+ }else{
+ qrfBoxSeparator(p->pOut, &data, BOX_R23, BOX_234, BOX_R34, 0);
+ }
+ break;
+ case QRF_STYLE_Table:
+ if( data.nMargin ){
+ rowStart = "| ";
+ colSep = " | ";
+ rowSep = " |\n";
+ }else{
+ rowStart = "|";
+ colSep = "|";
+ rowSep = "|\n";
+ }
+ if( p->spec.bBorder==QRF_No ){
+ rowStart += 1;
+ rowSep = "\n";
+ }else{
+ qrfRowSeparator(p->pOut, &data, '+');
+ }
+ break;
+ case QRF_STYLE_Column: {
+ static const char zSpace[] = " ";
+ rowStart = "";
+ if( data.nMargin<2 ){
+ colSep = " ";
+ }else if( data.nMargin<=5 ){
+ colSep = &zSpace[5-data.nMargin];
+ }else{
+ colSep = zSpace;
+ }
+ rowSep = "\n";
+ break;
+ }
+ default: /*case QRF_STYLE_Markdown:*/
+ if( data.nMargin ){
+ rowStart = "| ";
+ colSep = " | ";
+ rowSep = " |\n";
+ }else{
+ rowStart = "|";
+ colSep = "|";
+ rowSep = "|\n";
+ }
+ break;
+ }
+ szRowStart = (int)strlen(rowStart);
+ szRowSep = (int)strlen(rowSep);
+ szColSep = (int)strlen(colSep);
+
+ bWW = (p->spec.bWordWrap==QRF_Yes && data.bMultiRow);
+ if( p->spec.eStyle==QRF_STYLE_Column
+ || (p->spec.bBorder==QRF_No
+ && (p->spec.eStyle==QRF_STYLE_Box || p->spec.eStyle==QRF_STYLE_Table)
+ )
+ ){
+ bRTrim = 1;
+ }else{
+ bRTrim = 0;
+ }
+ for(i=0; ipOut)==SQLITE_OK; i+=nColumn){
+ int bMore;
+ int nRow = 0;
+
+ /* Draw a single row of the table. This might be the title line
+ ** (if there is a title line) or a row in the body of the table.
+ ** The column number will be j. The row number is i/nColumn.
+ */
+ for(j=0; jpOut, rowStart, szRowStart);
+ bMore = 0;
+ for(j=0; jpOut, &data.a[j], nThis, nWS);
+ data.a[j].z += iNext;
+ if( data.a[j].z[0]!=0 ){
+ bMore = 1;
+ }
+ if( jpOut, colSep, szColSep);
+ }else{
+ if( bRTrim ) qrfRTrim(p->pOut);
+ sqlite3_str_append(p->pOut, rowSep, szRowSep);
+ }
+ }
+ }while( bMore && ++nRow < p->mxHeight );
+ if( bMore ){
+ /* This row was terminated by nLineLimit. Show ellipsis. */
+ sqlite3_str_append(p->pOut, rowStart, szRowStart);
+ for(j=0; jpOut, data.a[j].w, ' ');
+ }else{
+ int nE = 3;
+ if( nE>data.a[j].w ) nE = data.a[j].w;
+ data.a[j].z = "...";
+ qrfPrintAligned(p->pOut, &data.a[j], nE, data.a[j].w-nE);
+ }
+ if( jpOut, colSep, szColSep);
+ }else{
+ if( bRTrim ) qrfRTrim(p->pOut);
+ sqlite3_str_append(p->pOut, rowSep, szRowSep);
+ }
+ }
+ }
+
+ /* Draw either (1) the separator between the title line and the body
+ ** of the table, or (2) separators between individual rows of the table
+ ** body. isTitleDataSeparator will be true if we are doing (1).
+ */
+ if( (i==0 || data.bMultiRow) && i+nColumnspec.bTitles==QRF_Yes);
+ if( isTitleDataSeparator ){
+ qrfLoadAlignment(&data, p);
+ }
+ switch( p->spec.eStyle ){
+ case QRF_STYLE_Table: {
+ if( isTitleDataSeparator || data.bMultiRow ){
+ qrfRowSeparator(p->pOut, &data, '+');
+ }
+ break;
+ }
+ case QRF_STYLE_Box: {
+ if( isTitleDataSeparator ){
+ qrfBoxSeparator(p->pOut, &data, DBL_123, DBL_1234, DBL_134, 1);
+ }else if( data.bMultiRow ){
+ qrfBoxSeparator(p->pOut, &data, BOX_123, BOX_1234, BOX_134, 0);
+ }
+ break;
+ }
+ case QRF_STYLE_Markdown: {
+ if( isTitleDataSeparator ){
+ qrfRowSeparator(p->pOut, &data, '|');
+ }
+ break;
+ }
+ case QRF_STYLE_Column: {
+ if( isTitleDataSeparator ){
+ for(j=0; jpOut, data.a[j].w, '-');
+ if( jpOut, colSep, szColSep);
+ }else{
+ qrfRTrim(p->pOut);
+ sqlite3_str_append(p->pOut, rowSep, szRowSep);
+ }
+ }
+ }else if( data.bMultiRow ){
+ qrfRTrim(p->pOut);
+ sqlite3_str_append(p->pOut, "\n", 1);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /* Draw the line across the bottom of the table */
+ if( p->spec.bBorder!=QRF_No ){
+ switch( p->spec.eStyle ){
+ case QRF_STYLE_Box:
+ qrfBoxSeparator(p->pOut, &data, BOX_R12, BOX_124, BOX_R14, 0);
+ break;
+ case QRF_STYLE_Table:
+ qrfRowSeparator(p->pOut, &data, '+');
+ break;
+ }
+ }
+ qrfWrite(p);
+
+ qrfColDataFree(&data);
+ return;
+}
+
+/*
+** Parameter azArray points to a zero-terminated array of strings. zStr
+** points to a single nul-terminated string. Return non-zero if zStr
+** is equal, according to strcmp(), to any of the strings in the array.
+** Otherwise, return zero.
+*/
+static int qrfStringInArray(const char *zStr, const char **azArray){
+ int i;
+ if( zStr==0 ) return 0;
+ for(i=0; azArray[i]; i++){
+ if( 0==strcmp(zStr, azArray[i]) ) return 1;
+ }
+ return 0;
+}
+
+/*
+** Print out an EXPLAIN with indentation. This is a two-pass algorithm.
+**
+** On the first pass, we compute aiIndent[iOp] which is the amount of
+** indentation to apply to the iOp-th opcode. The output actually occurs
+** on the second pass.
+**
+** The indenting rules are:
+**
+** * For each "Next", "Prev", "VNext" or "VPrev" instruction, indent
+** all opcodes that occur between the p2 jump destination and the opcode
+** itself by 2 spaces.
+**
+** * Do the previous for "Return" instructions for when P2 is positive.
+** See tag-20220407a in wherecode.c and vdbe.c.
+**
+** * For each "Goto", if the jump destination is earlier in the program
+** and ends on one of:
+** Yield SeekGt SeekLt RowSetRead Rewind
+** or if the P1 parameter is one instead of zero,
+** then indent all opcodes between the earlier instruction
+** and "Goto" by 2 spaces.
+*/
+static void qrfExplain(Qrf *p){
+ int *abYield = 0; /* abYield[iOp] is rue if opcode iOp is an OP_Yield */
+ int *aiIndent = 0; /* Indent the iOp-th opcode by aiIndent[iOp] */
+ i64 nAlloc = 0; /* Allocated size of aiIndent[], abYield */
+ int nIndent = 0; /* Number of entries in aiIndent[] */
+ int iOp; /* Opcode number */
+ int i; /* Column loop counter */
+
+ const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext",
+ "Return", 0 };
+ const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead",
+ "Rewind", 0 };
+ const char *azGoto[] = { "Goto", 0 };
+
+ /* The caller guarantees that the leftmost 4 columns of the statement
+ ** passed to this function are equivalent to the leftmost 4 columns
+ ** of EXPLAIN statement output. In practice the statement may be
+ ** an EXPLAIN, or it may be a query on the bytecode() virtual table. */
+ assert( sqlite3_column_count(p->pStmt)>=4 );
+ assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 0), "addr" ) );
+ assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 1), "opcode" ) );
+ assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 2), "p1" ) );
+ assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 3), "p2" ) );
+
+ for(iOp=0; SQLITE_ROW==sqlite3_step(p->pStmt) && !p->iErr; iOp++){
+ int iAddr = sqlite3_column_int(p->pStmt, 0);
+ const char *zOp = (const char*)sqlite3_column_text(p->pStmt, 1);
+ int p1 = sqlite3_column_int(p->pStmt, 2);
+ int p2 = sqlite3_column_int(p->pStmt, 3);
+
+ /* Assuming that p2 is an instruction address, set variable p2op to the
+ ** index of that instruction in the aiIndent[] array. p2 and p2op may be
+ ** different if the current instruction is part of a sub-program generated
+ ** by an SQL trigger or foreign key. */
+ int p2op = (p2 + (iOp-iAddr));
+
+ /* Grow the aiIndent array as required */
+ if( iOp>=nAlloc ){
+ nAlloc += 100;
+ aiIndent = (int*)sqlite3_realloc64(aiIndent, nAlloc*sizeof(int));
+ abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int));
+ if( aiIndent==0 || abYield==0 ){
+ qrfOom(p);
+ sqlite3_free(aiIndent);
+ sqlite3_free(abYield);
+ return;
+ }
+ }
+
+ abYield[iOp] = qrfStringInArray(zOp, azYield);
+ aiIndent[iOp] = 0;
+ nIndent = iOp+1;
+ if( qrfStringInArray(zOp, azNext) && p2op>0 ){
+ for(i=p2op; ipStmt);
+ if( p->iErr==SQLITE_OK ){
+ static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13};
+ static const int aExplainMap[] = {0, 1, 2, 3, 4, 5, 6, 7 };
+ static const int aScanExpWidth[] = {4,15, 6, 13, 4, 4, 4, 13, 2, 13};
+ static const int aScanExpMap[] = {0, 9, 8, 1, 2, 3, 4, 5, 6, 7 };
+ const int *aWidth = aExplainWidth;
+ const int *aMap = aExplainMap;
+ int nWidth = sizeof(aExplainWidth)/sizeof(int);
+ int iIndent = 1;
+ int nArg = p->nCol;
+ if( p->spec.eStyle==QRF_STYLE_StatsVm ){
+ aWidth = aScanExpWidth;
+ aMap = aScanExpMap;
+ nWidth = sizeof(aScanExpWidth)/sizeof(int);
+ iIndent = 3;
+ }
+ if( nArg>nWidth ) nArg = nWidth;
+
+ for(iOp=0; sqlite3_step(p->pStmt)==SQLITE_ROW && !p->iErr; iOp++){
+ /* If this is the first row seen, print out the headers */
+ if( iOp==0 ){
+ for(i=0; ipStmt, aMap[i]);
+ qrfWidthPrint(p,p->pOut, aWidth[i], zCol);
+ if( i==nArg-1 ){
+ sqlite3_str_append(p->pOut, "\n", 1);
+ }else{
+ sqlite3_str_append(p->pOut, " ", 2);
+ }
+ }
+ for(i=0; ipOut, "%.*c", aWidth[i], '-');
+ if( i==nArg-1 ){
+ sqlite3_str_append(p->pOut, "\n", 1);
+ }else{
+ sqlite3_str_append(p->pOut, " ", 2);
+ }
+ }
+ }
+
+ for(i=0; ipStmt, aMap[i]);
+ int len;
+ if( i==nArg-1 ) w = 0;
+ if( zVal==0 ) zVal = "";
+ len = (int)sqlite3_qrf_wcswidth(zVal);
+ if( len>w ){
+ w = len;
+ zSep = " ";
+ }
+ if( i==iIndent && aiIndent && iOppOut, aiIndent[iOp], ' ');
+ }
+ qrfWidthPrint(p, p->pOut, w, zVal);
+ if( i==nArg-1 ){
+ sqlite3_str_append(p->pOut, "\n", 1);
+ }else{
+ sqlite3_str_appendall(p->pOut, zSep);
+ }
+ }
+ p->nRow++;
+ }
+ qrfWrite(p);
+ }
+ sqlite3_free(aiIndent);
+}
+
+/*
+** Do a "scanstatus vm" style EXPLAIN listing on p->pStmt.
+**
+** p->pStmt is probably not an EXPLAIN query. Instead, construct a
+** new query that is a bytecode() rendering of p->pStmt with extra
+** columns for the "scanstatus vm" outputs, and run the results of
+** that new query through the normal EXPLAIN formatting.
+*/
+static void qrfScanStatusVm(Qrf *p){
+ sqlite3_stmt *pOrigStmt = p->pStmt;
+ sqlite3_stmt *pExplain;
+ int rc;
+ static const char *zSql =
+ " SELECT addr, opcode, p1, p2, p3, p4, p5, comment, nexec,"
+ " format('% 6s (%.2f%%)',"
+ " CASE WHEN ncycle<100_000 THEN ncycle || ' '"
+ " WHEN ncycle<100_000_000 THEN (ncycle/1_000) || 'K'"
+ " WHEN ncycle<100_000_000_000 THEN (ncycle/1_000_000) || 'M'"
+ " ELSE (ncycle/1000_000_000) || 'G' END,"
+ " ncycle*100.0/(sum(ncycle) OVER ())"
+ " ) AS cycles"
+ " FROM bytecode(?1)";
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pExplain, 0);
+ if( rc ){
+ qrfError(p, rc, "%s", sqlite3_errmsg(p->db));
+ sqlite3_finalize(pExplain);
+ return;
+ }
+ sqlite3_bind_pointer(pExplain, 1, pOrigStmt, "stmt-pointer", 0);
+ p->pStmt = pExplain;
+ p->nCol = 10;
+ qrfExplain(p);
+ sqlite3_finalize(pExplain);
+ p->pStmt = pOrigStmt;
+}
+
+/*
+** Attempt to determine if identifier zName needs to be quoted, either
+** because it contains non-alphanumeric characters, or because it is an
+** SQLite keyword. Be conservative in this estimate: When in doubt assume
+** that quoting is required.
+**
+** Return 1 if quoting is required. Return 0 if no quoting is required.
+*/
+
+static int qrf_need_quote(const char *zName){
+ int i;
+ const unsigned char *z = (const unsigned char*)zName;
+ if( z==0 ) return 1;
+ if( !qrfAlpha(z[0]) ) return 1;
+ for(i=0; z[i]; i++){
+ if( !qrfAlnum(z[i]) ) return 1;
+ }
+ return sqlite3_keyword_check(zName, i)!=0;
+}
+
+/*
+** Helper function for QRF_STYLE_Json and QRF_STYLE_JObject.
+** The initial "{" for a JSON object that will contain row content
+** has been output. Now output all the content.
+*/
+static void qrfOneJsonRow(Qrf *p){
+ int i, nItem;
+ for(nItem=i=0; inCol; i++){
+ const char *zCName;
+ zCName = sqlite3_column_name(p->pStmt, i);
+ if( nItem>0 ) sqlite3_str_append(p->pOut, ",", 1);
+ nItem++;
+ qrfEncodeText(p, p->pOut, zCName);
+ sqlite3_str_append(p->pOut, ":", 1);
+ qrfRenderValue(p, p->pOut, i);
+ }
+ qrfWrite(p);
+}
+
+/*
+** Render a single row of output for non-columnar styles - any
+** style that lets us render row by row as the content is received
+** from the query.
+*/
+static void qrfOneSimpleRow(Qrf *p){
+ int i;
+ switch( p->spec.eStyle ){
+ case QRF_STYLE_Off:
+ case QRF_STYLE_Count: {
+ /* No-op */
+ break;
+ }
+ case QRF_STYLE_Json: {
+ if( p->nRow==0 ){
+ sqlite3_str_append(p->pOut, "[{", 2);
+ }else{
+ sqlite3_str_append(p->pOut, "},\n{", 4);
+ }
+ qrfOneJsonRow(p);
+ break;
+ }
+ case QRF_STYLE_JObject: {
+ if( p->nRow==0 ){
+ sqlite3_str_append(p->pOut, "{", 1);
+ }else{
+ sqlite3_str_append(p->pOut, "}\n{", 3);
+ }
+ qrfOneJsonRow(p);
+ break;
+ }
+ case QRF_STYLE_Html: {
+ if( p->nRow==0 && p->spec.bTitles==QRF_Yes ){
+ sqlite3_str_append(p->pOut, "
\n", 7);
+ qrfWrite(p);
+ break;
+ }
+ case QRF_STYLE_Insert: {
+ if( qrf_need_quote(p->spec.zTableName) ){
+ sqlite3_str_appendf(p->pOut,"INSERT INTO \"%w\"",p->spec.zTableName);
+ }else{
+ sqlite3_str_appendf(p->pOut,"INSERT INTO %s",p->spec.zTableName);
+ }
+ if( p->spec.bTitles==QRF_Yes ){
+ for(i=0; inCol; i++){
+ const char *zCName = sqlite3_column_name(p->pStmt, i);
+ if( qrf_need_quote(zCName) ){
+ sqlite3_str_appendf(p->pOut, "%c\"%w\"",
+ i==0 ? '(' : ',', zCName);
+ }else{
+ sqlite3_str_appendf(p->pOut, "%c%s",
+ i==0 ? '(' : ',', zCName);
+ }
+ }
+ sqlite3_str_append(p->pOut, ")", 1);
+ }
+ sqlite3_str_append(p->pOut," VALUES(", 8);
+ for(i=0; inCol; i++){
+ if( i>0 ) sqlite3_str_append(p->pOut, ",", 1);
+ qrfRenderValue(p, p->pOut, i);
+ }
+ sqlite3_str_append(p->pOut, ");\n", 3);
+ qrfWrite(p);
+ break;
+ }
+ case QRF_STYLE_Line: {
+ sqlite3_str *pVal;
+ int mxW;
+ int bWW;
+ int nSep;
+ if( p->u.sLine.azCol==0 ){
+ p->u.sLine.azCol = sqlite3_malloc64( p->nCol*sizeof(char*) );
+ if( p->u.sLine.azCol==0 ){
+ qrfOom(p);
+ break;
+ }
+ p->u.sLine.mxColWth = 0;
+ for(i=0; inCol; i++){
+ int sz;
+ const char *zCName = sqlite3_column_name(p->pStmt, i);
+ if( zCName==0 ) zCName = "unknown";
+ p->u.sLine.azCol[i] = sqlite3_mprintf("%s", zCName);
+ if( p->spec.nTitleLimit>0 ){
+ (void)qrfTitleLimit(p->u.sLine.azCol[i], p->spec.nTitleLimit);
+ }
+ sz = (int)sqlite3_qrf_wcswidth(p->u.sLine.azCol[i]);
+ if( sz > p->u.sLine.mxColWth ) p->u.sLine.mxColWth = sz;
+ }
+ }
+ if( p->nRow ) sqlite3_str_append(p->pOut, "\n", 1);
+ pVal = sqlite3_str_new(p->db);
+ nSep = (int)strlen(p->spec.zColumnSep);
+ mxW = p->mxWidth - (nSep + p->u.sLine.mxColWth);
+ bWW = p->spec.bWordWrap==QRF_Yes;
+ for(i=0; inCol; i++){
+ const char *zVal;
+ int cnt = 0;
+ qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth, p->u.sLine.azCol[i]);
+ sqlite3_str_append(p->pOut, p->spec.zColumnSep, nSep);
+ qrfRenderValue(p, pVal, i);
+ zVal = sqlite3_str_value(pVal);
+ if( zVal==0 ) zVal = "";
+ do{
+ int nThis, nWide, iNext;
+ qrfWrapLine(zVal, mxW, bWW, &nThis, &nWide, &iNext);
+ if( cnt ) sqlite3_str_appendchar(p->pOut,p->u.sLine.mxColWth+2,' ');
+ cnt++;
+ if( cnt>p->mxHeight ){
+ zVal = "...";
+ nThis = iNext = 3;
+ }
+ sqlite3_str_append(p->pOut, zVal, nThis);
+ sqlite3_str_append(p->pOut, "\n", 1);
+ zVal += iNext;
+ }while( zVal[0] );
+ sqlite3_str_reset(pVal);
+ }
+ qrfStrErr(p, pVal);
+ sqlite3_free(sqlite3_str_finish(pVal));
+ qrfWrite(p);
+ break;
+ }
+ case QRF_STYLE_Eqp: {
+ const char *zEqpLine = (const char*)sqlite3_column_text(p->pStmt,3);
+ int iEqpId = sqlite3_column_int(p->pStmt, 0);
+ int iParentId = sqlite3_column_int(p->pStmt, 1);
+ if( zEqpLine==0 ) zEqpLine = "";
+ if( zEqpLine[0]=='-' ) qrfEqpRender(p, 0);
+ qrfEqpAppend(p, iEqpId, iParentId, zEqpLine);
+ break;
+ }
+ default: { /* QRF_STYLE_List */
+ if( p->nRow==0 && p->spec.bTitles==QRF_Yes ){
+ int saved_eText = p->spec.eText;
+ p->spec.eText = p->spec.eTitle;
+ for(i=0; inCol; i++){
+ const char *zCName = sqlite3_column_name(p->pStmt, i);
+ if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
+ qrfEncodeText(p, p->pOut, zCName);
+ }
+ sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
+ qrfWrite(p);
+ p->spec.eText = saved_eText;
+ }
+ for(i=0; inCol; i++){
+ if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
+ qrfRenderValue(p, p->pOut, i);
+ }
+ sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
+ qrfWrite(p);
+ break;
+ }
+ }
+ p->nRow++;
+}
+
+/*
+** Initialize the internal Qrf object.
+*/
+static void qrfInitialize(
+ Qrf *p, /* State object to be initialized */
+ sqlite3_stmt *pStmt, /* Query whose output to be formatted */
+ const sqlite3_qrf_spec *pSpec, /* Format specification */
+ char **pzErr /* Write errors here */
+){
+ size_t sz; /* Size of pSpec[], based on pSpec->iVersion */
+ memset(p, 0, sizeof(*p));
+ p->pzErr = pzErr;
+ if( pSpec->iVersion!=1 ){
+ qrfError(p, SQLITE_ERROR,
+ "unusable sqlite3_qrf_spec.iVersion (%d)",
+ pSpec->iVersion);
+ return;
+ }
+ p->pStmt = pStmt;
+ p->db = sqlite3_db_handle(pStmt);
+ p->pOut = sqlite3_str_new(p->db);
+ if( p->pOut==0 ){
+ qrfOom(p);
+ return;
+ }
+ p->iErr = SQLITE_OK;
+ p->nCol = sqlite3_column_count(p->pStmt);
+ p->nRow = 0;
+ sz = sizeof(sqlite3_qrf_spec);
+ memcpy(&p->spec, pSpec, sz);
+ if( p->spec.zNull==0 ) p->spec.zNull = "";
+ p->mxWidth = p->spec.nScreenWidth;
+ if( p->mxWidth<=0 ) p->mxWidth = QRF_MAX_WIDTH;
+ p->mxHeight = p->spec.nLineLimit;
+ if( p->mxHeight<=0 ) p->mxHeight = 2147483647;
+ if( p->spec.eStyle>QRF_STYLE_Table ) p->spec.eStyle = QRF_Auto;
+ if( p->spec.eEsc>QRF_ESC_Symbol ) p->spec.eEsc = QRF_Auto;
+ if( p->spec.eText>QRF_TEXT_Relaxed ) p->spec.eText = QRF_Auto;
+ if( p->spec.eTitle>QRF_TEXT_Relaxed ) p->spec.eTitle = QRF_Auto;
+ if( p->spec.eBlob>QRF_BLOB_Size ) p->spec.eBlob = QRF_Auto;
+qrf_reinit:
+ switch( p->spec.eStyle ){
+ case QRF_Auto: {
+ switch( sqlite3_stmt_isexplain(pStmt) ){
+ case 0: p->spec.eStyle = QRF_STYLE_Box; break;
+ case 1: p->spec.eStyle = QRF_STYLE_Explain; break;
+ default: p->spec.eStyle = QRF_STYLE_Eqp; break;
+ }
+ goto qrf_reinit;
+ }
+ case QRF_STYLE_List: {
+ if( p->spec.zColumnSep==0 ) p->spec.zColumnSep = "|";
+ if( p->spec.zRowSep==0 ) p->spec.zRowSep = "\n";
+ break;
+ }
+ case QRF_STYLE_JObject:
+ case QRF_STYLE_Json: {
+ p->spec.eText = QRF_TEXT_Json;
+ p->spec.zNull = "null";
+ break;
+ }
+ case QRF_STYLE_Html: {
+ p->spec.eText = QRF_TEXT_Html;
+ p->spec.zNull = "null";
+ break;
+ }
+ case QRF_STYLE_Insert: {
+ p->spec.eText = QRF_TEXT_Sql;
+ p->spec.zNull = "NULL";
+ if( p->spec.zTableName==0 || p->spec.zTableName[0]==0 ){
+ p->spec.zTableName = "tab";
+ }
+ break;
+ }
+ case QRF_STYLE_Line: {
+ if( p->spec.zColumnSep==0 ){
+ p->spec.zColumnSep = ": ";
+ }
+ break;
+ }
+ case QRF_STYLE_Csv: {
+ p->spec.eStyle = QRF_STYLE_List;
+ p->spec.eText = QRF_TEXT_Csv;
+ p->spec.zColumnSep = ",";
+ p->spec.zRowSep = "\r\n";
+ p->spec.zNull = "";
+ break;
+ }
+ case QRF_STYLE_Quote: {
+ p->spec.eText = QRF_TEXT_Sql;
+ p->spec.zNull = "NULL";
+ p->spec.zColumnSep = ",";
+ p->spec.zRowSep = "\n";
+ break;
+ }
+ case QRF_STYLE_Eqp: {
+ int expMode = sqlite3_stmt_isexplain(p->pStmt);
+ if( expMode!=2 ){
+ sqlite3_stmt_explain(p->pStmt, 2);
+ p->expMode = expMode+1;
+ }
+ break;
+ }
+ case QRF_STYLE_Explain: {
+ int expMode = sqlite3_stmt_isexplain(p->pStmt);
+ if( expMode!=1 ){
+ sqlite3_stmt_explain(p->pStmt, 1);
+ p->expMode = expMode+1;
+ }
+ break;
+ }
+ }
+ if( p->spec.eEsc==QRF_Auto ){
+ p->spec.eEsc = QRF_ESC_Ascii;
+ }
+ if( p->spec.eText==QRF_Auto ){
+ p->spec.eText = QRF_TEXT_Plain;
+ }
+ if( p->spec.eTitle==QRF_Auto ){
+ switch( p->spec.eStyle ){
+ case QRF_STYLE_Box:
+ case QRF_STYLE_Column:
+ case QRF_STYLE_Table:
+ p->spec.eTitle = QRF_TEXT_Plain;
+ break;
+ default:
+ p->spec.eTitle = p->spec.eText;
+ break;
+ }
+ }
+ if( p->spec.eBlob==QRF_Auto ){
+ switch( p->spec.eText ){
+ case QRF_TEXT_Sql: p->spec.eBlob = QRF_BLOB_Sql; break;
+ case QRF_TEXT_Csv: p->spec.eBlob = QRF_BLOB_Tcl; break;
+ case QRF_TEXT_Tcl: p->spec.eBlob = QRF_BLOB_Tcl; break;
+ case QRF_TEXT_Json: p->spec.eBlob = QRF_BLOB_Json; break;
+ default: p->spec.eBlob = QRF_BLOB_Text; break;
+ }
+ }
+ if( p->spec.bTitles==QRF_Auto ){
+ switch( p->spec.eStyle ){
+ case QRF_STYLE_Box:
+ case QRF_STYLE_Csv:
+ case QRF_STYLE_Column:
+ case QRF_STYLE_Table:
+ case QRF_STYLE_Markdown:
+ p->spec.bTitles = QRF_Yes;
+ break;
+ default:
+ p->spec.bTitles = QRF_No;
+ break;
+ }
+ }
+ if( p->spec.bWordWrap==QRF_Auto ){
+ p->spec.bWordWrap = QRF_Yes;
+ }
+ if( p->spec.bTextJsonb==QRF_Auto ){
+ p->spec.bTextJsonb = QRF_No;
+ }
+ if( p->spec.zColumnSep==0 ) p->spec.zColumnSep = ",";
+ if( p->spec.zRowSep==0 ) p->spec.zRowSep = "\n";
+}
+
+/*
+** Finish rendering the results
+*/
+static void qrfFinalize(Qrf *p){
+ switch( p->spec.eStyle ){
+ case QRF_STYLE_Count: {
+ sqlite3_str_appendf(p->pOut, "%lld\n", p->nRow);
+ qrfWrite(p);
+ break;
+ }
+ case QRF_STYLE_Json: {
+ if( p->nRow>0 ){
+ sqlite3_str_append(p->pOut, "}]\n", 3);
+ qrfWrite(p);
+ }
+ break;
+ }
+ case QRF_STYLE_JObject: {
+ if( p->nRow>0 ){
+ sqlite3_str_append(p->pOut, "}\n", 2);
+ qrfWrite(p);
+ }
+ break;
+ }
+ case QRF_STYLE_Line: {
+ if( p->u.sLine.azCol ){
+ int i;
+ for(i=0; inCol; i++) sqlite3_free(p->u.sLine.azCol[i]);
+ sqlite3_free(p->u.sLine.azCol);
+ }
+ break;
+ }
+ case QRF_STYLE_Stats:
+ case QRF_STYLE_StatsEst: {
+ i64 nCycle = 0;
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ sqlite3_stmt_scanstatus_v2(p->pStmt, -1, SQLITE_SCANSTAT_NCYCLE,
+ SQLITE_SCANSTAT_COMPLEX, (void*)&nCycle);
+#endif
+ qrfEqpRender(p, nCycle);
+ qrfWrite(p);
+ break;
+ }
+ case QRF_STYLE_Eqp: {
+ qrfEqpRender(p, 0);
+ qrfWrite(p);
+ break;
+ }
+ }
+ qrfStrErr(p, p->pOut);
+ if( p->spec.pzOutput ){
+ if( p->spec.pzOutput[0] ){
+ sqlite3_int64 n, sz;
+ char *zCombined;
+ sz = strlen(p->spec.pzOutput[0]);
+ n = sqlite3_str_length(p->pOut);
+ zCombined = sqlite3_realloc64(p->spec.pzOutput[0], sz+n+1);
+ if( zCombined==0 ){
+ sqlite3_free(p->spec.pzOutput[0]);
+ p->spec.pzOutput[0] = 0;
+ qrfOom(p);
+ }else{
+ p->spec.pzOutput[0] = zCombined;
+ memcpy(zCombined+sz, sqlite3_str_value(p->pOut), n+1);
+ }
+ sqlite3_free(sqlite3_str_finish(p->pOut));
+ }else{
+ p->spec.pzOutput[0] = sqlite3_str_finish(p->pOut);
+ }
+ }else if( p->pOut ){
+ sqlite3_free(sqlite3_str_finish(p->pOut));
+ }
+ if( p->expMode>0 ){
+ sqlite3_stmt_explain(p->pStmt, p->expMode-1);
+ }
+ if( p->actualWidth ){
+ sqlite3_free(p->actualWidth);
+ }
+ if( p->pJTrans ){
+ sqlite3 *db = sqlite3_db_handle(p->pJTrans);
+ sqlite3_finalize(p->pJTrans);
+ sqlite3_close(db);
+ }
+}
+
+/*
+** Run the prepared statement pStmt and format the results according
+** to the specification provided in pSpec. Return an error code.
+** If pzErr is not NULL and if an error occurs, write an error message
+** into *pzErr.
+*/
+int sqlite3_format_query_result(
+ sqlite3_stmt *pStmt, /* Statement to evaluate */
+ const sqlite3_qrf_spec *pSpec, /* Format specification */
+ char **pzErr /* Write error message here */
+){
+ Qrf qrf; /* The new Qrf being created */
+
+ if( pStmt==0 ) return SQLITE_OK; /* No-op */
+ if( pSpec==0 ) return SQLITE_MISUSE;
+ qrfInitialize(&qrf, pStmt, pSpec, pzErr);
+ switch( qrf.spec.eStyle ){
+ case QRF_STYLE_Box:
+ case QRF_STYLE_Column:
+ case QRF_STYLE_Markdown:
+ case QRF_STYLE_Table: {
+ /* Columnar modes require that the entire query be evaluated and the
+ ** results stored in memory, so that we can compute column widths */
+ qrfColumnar(&qrf);
+ break;
+ }
+ case QRF_STYLE_Explain: {
+ qrfExplain(&qrf);
+ break;
+ }
+ case QRF_STYLE_StatsVm: {
+ qrfScanStatusVm(&qrf);
+ break;
+ }
+ case QRF_STYLE_Stats:
+ case QRF_STYLE_StatsEst: {
+ qrfEqpStats(&qrf);
+ break;
+ }
+ default: {
+ /* Non-columnar modes where the output can occur after each row
+ ** of result is received */
+ while( qrf.iErr==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
+ qrfOneSimpleRow(&qrf);
+ }
+ break;
+ }
+ }
+ qrfResetStmt(&qrf);
+ qrfFinalize(&qrf);
+ return qrf.iErr;
+}
diff --git a/ext/qrf/qrf.h b/ext/qrf/qrf.h
new file mode 100644
index 000000000..c23ec772f
--- /dev/null
+++ b/ext/qrf/qrf.h
@@ -0,0 +1,200 @@
+/*
+** 2025-10-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.
+**
+*************************************************************************
+** Header file for the Result-Format or "resfmt" utility library for SQLite.
+** See the resfmt.md documentation for additional information.
+*/
+#ifndef SQLITE_QRF_H
+#define SQLITE_QRF_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include
+#include "sqlite3.h"
+
+/*
+** Specification used by clients to define the output format they want
+*/
+typedef struct sqlite3_qrf_spec sqlite3_qrf_spec;
+struct sqlite3_qrf_spec {
+ unsigned char iVersion; /* Version number of this structure */
+ unsigned char eStyle; /* Formatting style. "box", "csv", etc... */
+ unsigned char eEsc; /* How to escape control characters in text */
+ unsigned char eText; /* Quoting style for text */
+ unsigned char eTitle; /* Quating style for the text of column names */
+ unsigned char eBlob; /* Quoting style for BLOBs */
+ unsigned char bTitles; /* True to show column names */
+ unsigned char bWordWrap; /* Try to wrap on word boundaries */
+ unsigned char bTextJsonb; /* Render JSONB blobs as JSON text */
+ unsigned char eDfltAlign; /* Default alignment, no covered by aAlignment */
+ unsigned char eTitleAlign; /* Alignment for column headers */
+ unsigned char bSplitColumn; /* Wrap single-column output into many columns */
+ unsigned char bBorder; /* Show outer border in Box and Table styles */
+ short int nWrap; /* Wrap columns wider than this */
+ short int nScreenWidth; /* Maximum overall table width */
+ short int nLineLimit; /* Maximum number of lines for any row */
+ short int nTitleLimit; /* Maximum number of characters in a title */
+ int nCharLimit; /* Maximum number of characters in a cell */
+ int nWidth; /* Number of entries in aWidth[] */
+ int nAlign; /* Number of entries in aAlignment[] */
+ short int *aWidth; /* Column widths */
+ unsigned char *aAlign; /* Column alignments */
+ char *zColumnSep; /* Alternative column separator */
+ char *zRowSep; /* Alternative row separator */
+ char *zTableName; /* Output table name */
+ char *zNull; /* Rendering of NULL */
+ char *(*xRender)(void*,sqlite3_value*); /* Render a value */
+ int (*xWrite)(void*,const char*,sqlite3_int64); /* Write output */
+ void *pRenderArg; /* First argument to the xRender callback */
+ void *pWriteArg; /* First argument to the xWrite callback */
+ char **pzOutput; /* Storage location for output string */
+ /* Additional fields may be added in the future */
+};
+
+/*
+** Interfaces
+*/
+int sqlite3_format_query_result(
+ sqlite3_stmt *pStmt, /* SQL statement to run */
+ const sqlite3_qrf_spec *pSpec, /* Result format specification */
+ char **pzErr /* OUT: Write error message here */
+);
+
+/*
+** Range of values for sqlite3_qrf_spec.aWidth[] entries and for
+** sqlite3_qrf_spec.mxColWidth and .nScreenWidth
+*/
+#define QRF_MAX_WIDTH 10000
+#define QRF_MIN_WIDTH 0
+
+/*
+** Output styles:
+*/
+#define QRF_STYLE_Auto 0 /* Choose a style automatically */
+#define QRF_STYLE_Box 1 /* Unicode box-drawing characters */
+#define QRF_STYLE_Column 2 /* One record per line in neat columns */
+#define QRF_STYLE_Count 3 /* Output only a count of the rows of output */
+#define QRF_STYLE_Csv 4 /* Comma-separated-value */
+#define QRF_STYLE_Eqp 5 /* Format EXPLAIN QUERY PLAN output */
+#define QRF_STYLE_Explain 6 /* EXPLAIN output */
+#define QRF_STYLE_Html 7 /* Generate an XHTML table */
+#define QRF_STYLE_Insert 8 /* Generate SQL "insert" statements */
+#define QRF_STYLE_Json 9 /* Output is a list of JSON objects */
+#define QRF_STYLE_JObject 10 /* Independent JSON objects for each row */
+#define QRF_STYLE_Line 11 /* One column per line. */
+#define QRF_STYLE_List 12 /* One record per line with a separator */
+#define QRF_STYLE_Markdown 13 /* Markdown formatting */
+#define QRF_STYLE_Off 14 /* No query output shown */
+#define QRF_STYLE_Quote 15 /* SQL-quoted, comma-separated */
+#define QRF_STYLE_Stats 16 /* EQP-like output but with performance stats */
+#define QRF_STYLE_StatsEst 17 /* EQP-like output with planner estimates */
+#define QRF_STYLE_StatsVm 18 /* EXPLAIN-like output with performance stats */
+#define QRF_STYLE_Table 19 /* MySQL-style table formatting */
+
+/*
+** Quoting styles for text.
+** Allowed values for sqlite3_qrf_spec.eText
+*/
+#define QRF_TEXT_Auto 0 /* Choose text encoding automatically */
+#define QRF_TEXT_Plain 1 /* Literal text */
+#define QRF_TEXT_Sql 2 /* Quote as an SQL literal */
+#define QRF_TEXT_Csv 3 /* CSV-style quoting */
+#define QRF_TEXT_Html 4 /* HTML-style quoting */
+#define QRF_TEXT_Tcl 5 /* C/Tcl quoting */
+#define QRF_TEXT_Json 6 /* JSON quoting */
+#define QRF_TEXT_Relaxed 7 /* Relaxed SQL quoting */
+
+/*
+** Quoting styles for BLOBs
+** Allowed values for sqlite3_qrf_spec.eBlob
+*/
+#define QRF_BLOB_Auto 0 /* Determine BLOB quoting using eText */
+#define QRF_BLOB_Text 1 /* Display content exactly as it is */
+#define QRF_BLOB_Sql 2 /* Quote as an SQL literal */
+#define QRF_BLOB_Hex 3 /* Hexadecimal representation */
+#define QRF_BLOB_Tcl 4 /* "\000" notation */
+#define QRF_BLOB_Json 5 /* A JSON string */
+#define QRF_BLOB_Size 6 /* Display the blob size only */
+
+/*
+** Control-character escape modes.
+** Allowed values for sqlite3_qrf_spec.eEsc
+*/
+#define QRF_ESC_Auto 0 /* Choose the ctrl-char escape automatically */
+#define QRF_ESC_Off 1 /* Do not escape control characters */
+#define QRF_ESC_Ascii 2 /* Unix-style escapes. Ex: U+0007 shows ^G */
+#define QRF_ESC_Symbol 3 /* Unicode escapes. Ex: U+0007 shows U+2407 */
+
+/*
+** Allowed values for "boolean" fields, such as "bColumnNames", "bWordWrap",
+** and "bTextJsonb". There is an extra "auto" variants so these are actually
+** tri-state settings, not booleans.
+*/
+#define QRF_SW_Auto 0 /* Let QRF choose the best value */
+#define QRF_SW_Off 1 /* This setting is forced off */
+#define QRF_SW_On 2 /* This setting is forced on */
+#define QRF_Auto 0 /* Alternate spelling for QRF_*_Auto */
+#define QRF_No 1 /* Alternate spelling for QRF_SW_Off */
+#define QRF_Yes 2 /* Alternate spelling for QRF_SW_On */
+
+/*
+** Possible alignment values alignment settings
+**
+** Horizontal Vertial
+** ---------- -------- */
+#define QRF_ALIGN_Auto 0 /* auto auto */
+#define QRF_ALIGN_Left 1 /* left auto */
+#define QRF_ALIGN_Center 2 /* center auto */
+#define QRF_ALIGN_Right 3 /* right auto */
+#define QRF_ALIGN_Top 4 /* auto top */
+#define QRF_ALIGN_NW 5 /* left top */
+#define QRF_ALIGN_N 6 /* center top */
+#define QRF_ALIGN_NE 7 /* right top */
+#define QRF_ALIGN_Middle 8 /* auto middle */
+#define QRF_ALIGN_W 9 /* left middle */
+#define QRF_ALIGN_C 10 /* center middle */
+#define QRF_ALIGN_E 11 /* right middle */
+#define QRF_ALIGN_Bottom 12 /* auto bottom */
+#define QRF_ALIGN_SW 13 /* left bottom */
+#define QRF_ALIGN_S 14 /* center bottom */
+#define QRF_ALIGN_SE 15 /* right bottom */
+#define QRF_ALIGN_HMASK 3 /* Horizontal alignment mask */
+#define QRF_ALIGN_VMASK 12 /* Vertical alignment mask */
+
+/*
+** Auxiliary routines contined within this module that might be useful
+** in other contexts, and which are therefore exported.
+*/
+/*
+** Return an estimate of the width, in columns, for the single Unicode
+** character c. For normal characters, the answer is always 1. But the
+** estimate might be 0 or 2 for zero-width and double-width characters.
+**
+** Different devices display unicode using different widths. So
+** it is impossible to know that true display width with 100% accuracy.
+** Inaccuracies in the width estimates might cause columns to be misaligned.
+** Unfortunately, there is nothing we can do about that.
+*/
+int sqlite3_qrf_wcwidth(int c);
+
+/*
+** Return an estimate of the number of display columns used by the
+** string in the argument. The width of individual characters is
+** determined as for sqlite3_qrf_wcwidth(). VT100 escape code sequences
+** are assigned a width of zero.
+*/
+size_t sqlite3_qrf_wcswidth(const char*);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !defined(SQLITE_QRF_H) */
diff --git a/ext/rbu/rbu11.test b/ext/rbu/rbu11.test
index a42163cce..513ab29f6 100644
--- a/ext/rbu/rbu11.test
+++ b/ext/rbu/rbu11.test
@@ -192,4 +192,32 @@ do_test 4.7.2 {
list [catch {rbu close} msg] $msg
} {1 {SQLITE_ERROR - rbu_state mismatch error}}
+#-------------------------------------------------------------------------
+# https://sqlite.org/forum/info/6d0a31e22a435877
+#
+reset_db
+forcedelete rbu.db
+
+do_execsql_test 5.0 {
+ CREATE TABLE t1(a BLOB);
+ INSERT INTO t1 VALUES(x'41');
+}
+
+sqlite3 dbRbu rbu.db
+dbRbu eval {
+ CREATE TABLE data_t1(a, rbu_control, rbu_rowid);
+ INSERT INTO data_t1 VALUES(X'310a313a5a337e7e7e7e7e40302c303b','f',1);
+}
+dbRbu close
+
+do_test 5.1 {
+ sqlite3rbu rbu test.db rbu.db
+ rbu step
+} {SQLITE_ERROR}
+
+do_test 5.2 {
+ list [catch {rbu close} msg] $msg
+} {1 {SQLITE_ERROR - corrupt fossil delta}}
+
+
finish_test
diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c
index 4509986ee..f377d5c30 100644
--- a/ext/rbu/sqlite3rbu.c
+++ b/ext/rbu/sqlite3rbu.c
@@ -623,7 +623,7 @@ static int rbuDeltaApply(
/* ERROR: copy exceeds output file size */
return -1;
}
- if( (int)(ofst+cnt) > lenSrc ){
+ if( (u64)ofst+(u64)cnt > (u64)lenSrc ){
/* ERROR: copy extends past end of input */
return -1;
}
@@ -2269,8 +2269,8 @@ static char *rbuObjIterGetIndexWhere(sqlite3rbu *p, RbuObjIter *pIter){
/* If necessary, grow the pIter->aIdxCol[] array */
if( iIdxCol==nIdxAlloc ){
- RbuSpan *aIdxCol = (RbuSpan*)sqlite3_realloc(
- pIter->aIdxCol, (nIdxAlloc+16)*sizeof(RbuSpan)
+ RbuSpan *aIdxCol = (RbuSpan*)sqlite3_realloc64(
+ pIter->aIdxCol, nIdxAlloc*sizeof(RbuSpan) + 16*sizeof(RbuSpan)
);
if( aIdxCol==0 ){
rc = SQLITE_NOMEM;
diff --git a/ext/repair/README.md b/ext/repair/README.md
deleted file mode 100644
index 927ceb7c4..000000000
--- a/ext/repair/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-This folder contains extensions and utility programs intended to analyze
-live database files, detect problems, and possibly fix them.
-
-As SQLite is being used on larger and larger databases, database sizes
-are growing into the terabyte range. At that size, hardware malfunctions
-and/or cosmic rays will occasionally corrupt a database file. Detecting
-problems and fixing errors a terabyte-sized databases can take hours or days,
-and it is undesirable to take applications that depend on the databases
-off-line for such a long time.
-The utilities in the folder are intended to provide mechanisms for
-detecting and fixing problems in large databases while those databases
-are in active use.
-
-The utilities and extensions in this folder are experimental and under
-active development at the time of this writing (2017-10-12). If and when
-they stabilize, this README will be updated to reflect that fact.
diff --git a/ext/repair/checkfreelist.c b/ext/repair/checkfreelist.c
deleted file mode 100644
index d1d3d5407..000000000
--- a/ext/repair/checkfreelist.c
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
-** 2017 October 11
-**
-** 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 module exports a single C function:
-**
-** int sqlite3_check_freelist(sqlite3 *db, const char *zDb);
-**
-** This function checks the free-list in database zDb (one of "main",
-** "temp", etc.) and reports any errors by invoking the sqlite3_log()
-** function. It returns SQLITE_OK if successful, or an SQLite error
-** code otherwise. It is not an error if the free-list is corrupted but
-** no IO or OOM errors occur.
-**
-** If this file is compiled and loaded as an SQLite loadable extension,
-** it adds an SQL function "checkfreelist" to the database handle, to
-** be invoked as follows:
-**
-** SELECT checkfreelist();
-**
-** This function performs the same checks as sqlite3_check_freelist(),
-** except that it returns all error messages as a single text value,
-** separated by newline characters. If the freelist is not corrupted
-** in any way, an empty string is returned.
-**
-** To compile this module for use as an SQLite loadable extension:
-**
-** gcc -Os -fPIC -shared checkfreelist.c -o checkfreelist.so
-*/
-
-#include "sqlite3ext.h"
-SQLITE_EXTENSION_INIT1
-
-#ifndef SQLITE_AMALGAMATION
-# include
-# include
-# include
-# include
-# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST)
-# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1
-# endif
-# if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS)
-# define ALWAYS(X) (1)
-# define NEVER(X) (0)
-# elif !defined(NDEBUG)
-# define ALWAYS(X) ((X)?1:(assert(0),0))
-# define NEVER(X) ((X)?(assert(0),1):0)
-# else
-# define ALWAYS(X) (X)
-# define NEVER(X) (X)
-# endif
- typedef unsigned char u8;
- typedef unsigned short u16;
- typedef unsigned int u32;
-#define get4byte(x) ( \
- ((u32)((x)[0])<<24) + \
- ((u32)((x)[1])<<16) + \
- ((u32)((x)[2])<<8) + \
- ((u32)((x)[3])) \
-)
-#endif
-
-/*
-** Execute a single PRAGMA statement and return the integer value returned
-** via output parameter (*pnOut).
-**
-** The SQL statement passed as the third argument should be a printf-style
-** format string containing a single "%s" which will be replace by the
-** value passed as the second argument. e.g.
-**
-** sqlGetInteger(db, "main", "PRAGMA %s.page_count", pnOut)
-**
-** executes "PRAGMA main.page_count" and stores the results in (*pnOut).
-*/
-static int sqlGetInteger(
- sqlite3 *db, /* Database handle */
- const char *zDb, /* Database name ("main", "temp" etc.) */
- const char *zFmt, /* SQL statement format */
- u32 *pnOut /* OUT: Integer value */
-){
- int rc, rc2;
- char *zSql;
- sqlite3_stmt *pStmt = 0;
- int bOk = 0;
-
- zSql = sqlite3_mprintf(zFmt, zDb);
- if( zSql==0 ){
- rc = SQLITE_NOMEM;
- }else{
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- }
-
- if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
- *pnOut = (u32)sqlite3_column_int(pStmt, 0);
- bOk = 1;
- }
-
- rc2 = sqlite3_finalize(pStmt);
- if( rc==SQLITE_OK ) rc = rc2;
- if( rc==SQLITE_OK && bOk==0 ) rc = SQLITE_ERROR;
- return rc;
-}
-
-/*
-** Argument zFmt must be a printf-style format string and must be
-** followed by its required arguments. If argument pzOut is NULL,
-** then the results of printf()ing the format string are passed to
-** sqlite3_log(). Otherwise, they are appended to the string
-** at (*pzOut).
-*/
-static int checkFreelistError(char **pzOut, const char *zFmt, ...){
- int rc = SQLITE_OK;
- char *zErr = 0;
- va_list ap;
-
- va_start(ap, zFmt);
- zErr = sqlite3_vmprintf(zFmt, ap);
- if( zErr==0 ){
- rc = SQLITE_NOMEM;
- }else{
- if( pzOut ){
- *pzOut = sqlite3_mprintf("%s%z%s", *pzOut?"\n":"", *pzOut, zErr);
- if( *pzOut==0 ) rc = SQLITE_NOMEM;
- }else{
- sqlite3_log(SQLITE_ERROR, "checkfreelist: %s", zErr);
- }
- sqlite3_free(zErr);
- }
- va_end(ap);
- return rc;
-}
-
-static int checkFreelist(
- sqlite3 *db,
- const char *zDb,
- char **pzOut
-){
- /* This query returns one row for each page on the free list. Each row has
- ** two columns - the page number and page content. */
- const char *zTrunk =
- "WITH freelist_trunk(i, d, n) AS ("
- "SELECT 1, NULL, sqlite_readint32(data, 32) "
- "FROM sqlite_dbpage(:1) WHERE pgno=1 "
- "UNION ALL "
- "SELECT n, data, sqlite_readint32(data) "
- "FROM freelist_trunk, sqlite_dbpage(:1) WHERE pgno=n "
- ")"
- "SELECT i, d FROM freelist_trunk WHERE i!=1;";
-
- int rc, rc2; /* Return code */
- sqlite3_stmt *pTrunk = 0; /* Compilation of zTrunk */
- u32 nPage = 0; /* Number of pages in db */
- u32 nExpected = 0; /* Expected number of free pages */
- u32 nFree = 0; /* Number of pages on free list */
-
- if( zDb==0 ) zDb = "main";
-
- if( (rc = sqlGetInteger(db, zDb, "PRAGMA %s.page_count", &nPage))
- || (rc = sqlGetInteger(db, zDb, "PRAGMA %s.freelist_count", &nExpected))
- ){
- return rc;
- }
-
- rc = sqlite3_prepare_v2(db, zTrunk, -1, &pTrunk, 0);
- if( rc!=SQLITE_OK ) return rc;
- sqlite3_bind_text(pTrunk, 1, zDb, -1, SQLITE_STATIC);
- while( rc==SQLITE_OK && sqlite3_step(pTrunk)==SQLITE_ROW ){
- u32 i;
- u32 iTrunk = (u32)sqlite3_column_int(pTrunk, 0);
- const u8 *aData = (const u8*)sqlite3_column_blob(pTrunk, 1);
- u32 nData = (u32)sqlite3_column_bytes(pTrunk, 1);
- u32 iNext = get4byte(&aData[0]);
- u32 nLeaf = get4byte(&aData[4]);
-
- if( nLeaf>((nData/4)-2-6) ){
- rc = checkFreelistError(pzOut,
- "leaf count out of range (%d) on trunk page %d",
- (int)nLeaf, (int)iTrunk
- );
- nLeaf = (nData/4) - 2 - 6;
- }
-
- nFree += 1+nLeaf;
- if( iNext>nPage ){
- rc = checkFreelistError(pzOut,
- "trunk page %d is out of range", (int)iNext
- );
- }
-
- for(i=0; rc==SQLITE_OK && inPage ){
- rc = checkFreelistError(pzOut,
- "leaf page %d is out of range (child %d of trunk page %d)",
- (int)iLeaf, (int)i, (int)iTrunk
- );
- }
- }
- }
-
- if( rc==SQLITE_OK && nFree!=nExpected ){
- rc = checkFreelistError(pzOut,
- "free-list count mismatch: actual=%d header=%d",
- (int)nFree, (int)nExpected
- );
- }
-
- rc2 = sqlite3_finalize(pTrunk);
- if( rc==SQLITE_OK ) rc = rc2;
- return rc;
-}
-
-int sqlite3_check_freelist(sqlite3 *db, const char *zDb){
- return checkFreelist(db, zDb, 0);
-}
-
-static void checkfreelist_function(
- sqlite3_context *pCtx,
- int nArg,
- sqlite3_value **apArg
-){
- const char *zDb;
- int rc;
- char *zOut = 0;
- sqlite3 *db = sqlite3_context_db_handle(pCtx);
-
- assert( nArg==1 );
- zDb = (const char*)sqlite3_value_text(apArg[0]);
- rc = checkFreelist(db, zDb, &zOut);
- if( rc==SQLITE_OK ){
- sqlite3_result_text(pCtx, zOut?zOut:"ok", -1, SQLITE_TRANSIENT);
- }else{
- sqlite3_result_error_code(pCtx, rc);
- }
-
- sqlite3_free(zOut);
-}
-
-/*
-** An SQL function invoked as follows:
-**
-** sqlite_readint32(BLOB) -- Decode 32-bit integer from start of blob
-*/
-static void readint_function(
- sqlite3_context *pCtx,
- int nArg,
- sqlite3_value **apArg
-){
- const u8 *zBlob;
- int nBlob;
- int iOff = 0;
- u32 iRet = 0;
-
- if( nArg!=1 && nArg!=2 ){
- sqlite3_result_error(
- pCtx, "wrong number of arguments to function sqlite_readint32()", -1
- );
- return;
- }
- if( nArg==2 ){
- iOff = sqlite3_value_int(apArg[1]);
- }
-
- zBlob = sqlite3_value_blob(apArg[0]);
- nBlob = sqlite3_value_bytes(apArg[0]);
-
- if( nBlob>=(iOff+4) ){
- iRet = get4byte(&zBlob[iOff]);
- }
-
- sqlite3_result_int64(pCtx, (sqlite3_int64)iRet);
-}
-
-/*
-** Register the SQL functions.
-*/
-static int cflRegister(sqlite3 *db){
- int rc = sqlite3_create_function(
- db, "sqlite_readint32", -1, SQLITE_UTF8, 0, readint_function, 0, 0
- );
- if( rc!=SQLITE_OK ) return rc;
- rc = sqlite3_create_function(
- db, "checkfreelist", 1, SQLITE_UTF8, 0, checkfreelist_function, 0, 0
- );
- return rc;
-}
-
-/*
-** Extension load function.
-*/
-#ifdef _WIN32
-__declspec(dllexport)
-#endif
-int sqlite3_checkfreelist_init(
- sqlite3 *db,
- char **pzErrMsg,
- const sqlite3_api_routines *pApi
-){
- SQLITE_EXTENSION_INIT2(pApi);
- return cflRegister(db);
-}
diff --git a/ext/repair/checkindex.c b/ext/repair/checkindex.c
deleted file mode 100644
index ed30357e5..000000000
--- a/ext/repair/checkindex.c
+++ /dev/null
@@ -1,929 +0,0 @@
-/*
-** 2017 October 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.
-**
-*************************************************************************
-*/
-
-#include "sqlite3ext.h"
-SQLITE_EXTENSION_INIT1
-
-/*
-** Stuff that is available inside the amalgamation, but which we need to
-** declare ourselves if this module is compiled separately.
-*/
-#ifndef SQLITE_AMALGAMATION
-# include
-# include
-# include
-# include
-typedef unsigned char u8;
-typedef unsigned short u16;
-typedef unsigned int u32;
-#define get4byte(x) ( \
- ((u32)((x)[0])<<24) + \
- ((u32)((x)[1])<<16) + \
- ((u32)((x)[2])<<8) + \
- ((u32)((x)[3])) \
-)
-#endif
-
-typedef struct CidxTable CidxTable;
-typedef struct CidxCursor CidxCursor;
-
-struct CidxTable {
- sqlite3_vtab base; /* Base class. Must be first */
- sqlite3 *db;
-};
-
-struct CidxCursor {
- sqlite3_vtab_cursor base; /* Base class. Must be first */
- sqlite3_int64 iRowid; /* Row number of the output */
- char *zIdxName; /* Copy of the index_name parameter */
- char *zAfterKey; /* Copy of the after_key parameter */
- sqlite3_stmt *pStmt; /* SQL statement that generates the output */
-};
-
-typedef struct CidxColumn CidxColumn;
-struct CidxColumn {
- char *zExpr; /* Text for indexed expression */
- int bDesc; /* True for DESC columns, otherwise false */
- int bKey; /* Part of index, not PK */
-};
-
-typedef struct CidxIndex CidxIndex;
-struct CidxIndex {
- char *zWhere; /* WHERE clause, if any */
- int nCol; /* Elements in aCol[] array */
- CidxColumn aCol[1]; /* Array of indexed columns */
-};
-
-static void *cidxMalloc(int *pRc, int n){
- void *pRet = 0;
- assert( n!=0 );
- if( *pRc==SQLITE_OK ){
- pRet = sqlite3_malloc(n);
- if( pRet ){
- memset(pRet, 0, n);
- }else{
- *pRc = SQLITE_NOMEM;
- }
- }
- return pRet;
-}
-
-static void cidxCursorError(CidxCursor *pCsr, const char *zFmt, ...){
- va_list ap;
- va_start(ap, zFmt);
- assert( pCsr->base.pVtab->zErrMsg==0 );
- pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap);
- va_end(ap);
-}
-
-/*
-** Connect to the incremental_index_check virtual table.
-*/
-static int cidxConnect(
- sqlite3 *db,
- void *pAux,
- int argc, const char *const*argv,
- sqlite3_vtab **ppVtab,
- char **pzErr
-){
- int rc = SQLITE_OK;
- CidxTable *pRet;
-
-#define IIC_ERRMSG 0
-#define IIC_CURRENT_KEY 1
-#define IIC_INDEX_NAME 2
-#define IIC_AFTER_KEY 3
-#define IIC_SCANNER_SQL 4
- rc = sqlite3_declare_vtab(db,
- "CREATE TABLE xyz("
- " errmsg TEXT," /* Error message or NULL if everything is ok */
- " current_key TEXT," /* SQLite quote() text of key values */
- " index_name HIDDEN," /* IN: name of the index being scanned */
- " after_key HIDDEN," /* IN: Start scanning after this key */
- " scanner_sql HIDDEN" /* debugging info: SQL used for scanner */
- ")"
- );
- pRet = cidxMalloc(&rc, sizeof(CidxTable));
- if( pRet ){
- pRet->db = db;
- }
-
- *ppVtab = (sqlite3_vtab*)pRet;
- return rc;
-}
-
-/*
-** Disconnect from or destroy an incremental_index_check virtual table.
-*/
-static int cidxDisconnect(sqlite3_vtab *pVtab){
- CidxTable *pTab = (CidxTable*)pVtab;
- sqlite3_free(pTab);
- return SQLITE_OK;
-}
-
-/*
-** idxNum and idxStr are not used. There are only three possible plans,
-** which are all distinguished by the number of parameters.
-**
-** No parameters: A degenerate plan. The result is zero rows.
-** 1 Parameter: Scan all of the index starting with first entry
-** 2 parameters: Scan the index starting after the "after_key".
-**
-** Provide successively smaller costs for each of these plans to encourage
-** the query planner to select the one with the most parameters.
-*/
-static int cidxBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pInfo){
- int iIdxName = -1;
- int iAfterKey = -1;
- int i;
-
- for(i=0; inConstraint; i++){
- struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
- if( p->usable==0 ) continue;
- if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
-
- if( p->iColumn==IIC_INDEX_NAME ){
- iIdxName = i;
- }
- if( p->iColumn==IIC_AFTER_KEY ){
- iAfterKey = i;
- }
- }
-
- if( iIdxName<0 ){
- pInfo->estimatedCost = 1000000000.0;
- }else{
- pInfo->aConstraintUsage[iIdxName].argvIndex = 1;
- pInfo->aConstraintUsage[iIdxName].omit = 1;
- if( iAfterKey<0 ){
- pInfo->estimatedCost = 1000000.0;
- }else{
- pInfo->aConstraintUsage[iAfterKey].argvIndex = 2;
- pInfo->aConstraintUsage[iAfterKey].omit = 1;
- pInfo->estimatedCost = 1000.0;
- }
- }
-
- return SQLITE_OK;
-}
-
-/*
-** Open a new btreeinfo cursor.
-*/
-static int cidxOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
- CidxCursor *pRet;
- int rc = SQLITE_OK;
-
- pRet = cidxMalloc(&rc, sizeof(CidxCursor));
-
- *ppCursor = (sqlite3_vtab_cursor*)pRet;
- return rc;
-}
-
-/*
-** Close a btreeinfo cursor.
-*/
-static int cidxClose(sqlite3_vtab_cursor *pCursor){
- CidxCursor *pCsr = (CidxCursor*)pCursor;
- sqlite3_finalize(pCsr->pStmt);
- sqlite3_free(pCsr->zIdxName);
- sqlite3_free(pCsr->zAfterKey);
- sqlite3_free(pCsr);
- return SQLITE_OK;
-}
-
-/*
-** Move a btreeinfo cursor to the next entry in the file.
-*/
-static int cidxNext(sqlite3_vtab_cursor *pCursor){
- CidxCursor *pCsr = (CidxCursor*)pCursor;
- int rc = sqlite3_step(pCsr->pStmt);
- if( rc!=SQLITE_ROW ){
- rc = sqlite3_finalize(pCsr->pStmt);
- pCsr->pStmt = 0;
- if( rc!=SQLITE_OK ){
- sqlite3 *db = ((CidxTable*)pCsr->base.pVtab)->db;
- cidxCursorError(pCsr, "Cursor error: %s", sqlite3_errmsg(db));
- }
- }else{
- pCsr->iRowid++;
- rc = SQLITE_OK;
- }
- return rc;
-}
-
-/* We have reached EOF if previous sqlite3_step() returned
-** anything other than SQLITE_ROW;
-*/
-static int cidxEof(sqlite3_vtab_cursor *pCursor){
- CidxCursor *pCsr = (CidxCursor*)pCursor;
- return pCsr->pStmt==0;
-}
-
-static char *cidxMprintf(int *pRc, const char *zFmt, ...){
- char *zRet = 0;
- va_list ap;
- va_start(ap, zFmt);
- zRet = sqlite3_vmprintf(zFmt, ap);
- if( *pRc==SQLITE_OK ){
- if( zRet==0 ){
- *pRc = SQLITE_NOMEM;
- }
- }else{
- sqlite3_free(zRet);
- zRet = 0;
- }
- va_end(ap);
- return zRet;
-}
-
-static sqlite3_stmt *cidxPrepare(
- int *pRc, CidxCursor *pCsr, const char *zFmt, ...
-){
- sqlite3_stmt *pRet = 0;
- char *zSql;
- va_list ap; /* ... printf arguments */
- va_start(ap, zFmt);
-
- zSql = sqlite3_vmprintf(zFmt, ap);
- if( *pRc==SQLITE_OK ){
- if( zSql==0 ){
- *pRc = SQLITE_NOMEM;
- }else{
- sqlite3 *db = ((CidxTable*)pCsr->base.pVtab)->db;
- *pRc = sqlite3_prepare_v2(db, zSql, -1, &pRet, 0);
- if( *pRc!=SQLITE_OK ){
- cidxCursorError(pCsr, "SQL error: %s", sqlite3_errmsg(db));
- }
- }
- }
- sqlite3_free(zSql);
- va_end(ap);
-
- return pRet;
-}
-
-static void cidxFinalize(int *pRc, sqlite3_stmt *pStmt){
- int rc = sqlite3_finalize(pStmt);
- if( *pRc==SQLITE_OK ) *pRc = rc;
-}
-
-char *cidxStrdup(int *pRc, const char *zStr){
- char *zRet = 0;
- if( *pRc==SQLITE_OK ){
- int n = (int)strlen(zStr);
- zRet = cidxMalloc(pRc, n+1);
- if( zRet ) memcpy(zRet, zStr, n+1);
- }
- return zRet;
-}
-
-static void cidxFreeIndex(CidxIndex *pIdx){
- if( pIdx ){
- int i;
- for(i=0; inCol; i++){
- sqlite3_free(pIdx->aCol[i].zExpr);
- }
- sqlite3_free(pIdx->zWhere);
- sqlite3_free(pIdx);
- }
-}
-
-static int cidx_isspace(char c){
- return c==' ' || c=='\t' || c=='\r' || c=='\n';
-}
-
-static int cidx_isident(char c){
- return c<0
- || (c>='0' && c<='9') || (c>='a' && c<='z')
- || (c>='A' && c<='Z') || c=='_';
-}
-
-#define CIDX_PARSE_EOF 0
-#define CIDX_PARSE_COMMA 1 /* "," */
-#define CIDX_PARSE_OPEN 2 /* "(" */
-#define CIDX_PARSE_CLOSE 3 /* ")" */
-
-/*
-** Argument zIn points into the start, middle or end of a CREATE INDEX
-** statement. If argument pbDoNotTrim is non-NULL, then this function
-** scans the input until it finds EOF, a comma (",") or an open or
-** close parenthesis character. It then sets (*pzOut) to point to said
-** character and returns a CIDX_PARSE_XXX constant as appropriate. The
-** parser is smart enough that special characters inside SQL strings
-** or comments are not returned for.
-**
-** Or, if argument pbDoNotTrim is NULL, then this function sets *pzOut
-** to point to the first character of the string that is not whitespace
-** or part of an SQL comment and returns CIDX_PARSE_EOF.
-**
-** Additionally, if pbDoNotTrim is not NULL and the element immediately
-** before (*pzOut) is an SQL comment of the form "-- comment", then
-** (*pbDoNotTrim) is set before returning. In all other cases it is
-** cleared.
-*/
-static int cidxFindNext(
- const char *zIn,
- const char **pzOut,
- int *pbDoNotTrim /* OUT: True if prev is -- comment */
-){
- const char *z = zIn;
-
- while( 1 ){
- while( cidx_isspace(*z) ) z++;
- if( z[0]=='-' && z[1]=='-' ){
- z += 2;
- while( z[0]!='\n' ){
- if( z[0]=='\0' ) return CIDX_PARSE_EOF;
- z++;
- }
- while( cidx_isspace(*z) ) z++;
- if( pbDoNotTrim ) *pbDoNotTrim = 1;
- }else
- if( z[0]=='/' && z[1]=='*' ){
- z += 2;
- while( z[0]!='*' || z[1]!='/' ){
- if( z[1]=='\0' ) return CIDX_PARSE_EOF;
- z++;
- }
- z += 2;
- }else{
- *pzOut = z;
- if( pbDoNotTrim==0 ) return CIDX_PARSE_EOF;
- switch( *z ){
- case '\0':
- return CIDX_PARSE_EOF;
- case '(':
- return CIDX_PARSE_OPEN;
- case ')':
- return CIDX_PARSE_CLOSE;
- case ',':
- return CIDX_PARSE_COMMA;
-
- case '"':
- case '\'':
- case '`': {
- char q = *z;
- z++;
- while( *z ){
- if( *z==q ){
- z++;
- if( *z!=q ) break;
- }
- z++;
- }
- break;
- }
-
- case '[':
- while( *z++!=']' );
- break;
-
- default:
- z++;
- break;
- }
- *pbDoNotTrim = 0;
- }
- }
-
- assert( 0 );
- return -1;
-}
-
-static int cidxParseSQL(CidxCursor *pCsr, CidxIndex *pIdx, const char *zSql){
- const char *z = zSql;
- const char *z1;
- int e;
- int rc = SQLITE_OK;
- int nParen = 1;
- int bDoNotTrim = 0;
- CidxColumn *pCol = pIdx->aCol;
-
- e = cidxFindNext(z, &z, &bDoNotTrim);
- if( e!=CIDX_PARSE_OPEN ) goto parse_error;
- z1 = z+1;
- z++;
- while( nParen>0 ){
- e = cidxFindNext(z, &z, &bDoNotTrim);
- if( e==CIDX_PARSE_EOF ) goto parse_error;
- if( (e==CIDX_PARSE_COMMA || e==CIDX_PARSE_CLOSE) && nParen==1 ){
- const char *z2 = z;
- if( pCol->zExpr ) goto parse_error;
-
- if( bDoNotTrim==0 ){
- while( cidx_isspace(z[-1]) ) z--;
- if( !sqlite3_strnicmp(&z[-3], "asc", 3) && 0==cidx_isident(z[-4]) ){
- z -= 3;
- while( cidx_isspace(z[-1]) ) z--;
- }else
- if( !sqlite3_strnicmp(&z[-4], "desc", 4) && 0==cidx_isident(z[-5]) ){
- z -= 4;
- while( cidx_isspace(z[-1]) ) z--;
- }
- while( cidx_isspace(z1[0]) ) z1++;
- }
-
- pCol->zExpr = cidxMprintf(&rc, "%.*s", z-z1, z1);
- pCol++;
- z = z1 = z2+1;
- }
- if( e==CIDX_PARSE_OPEN ) nParen++;
- if( e==CIDX_PARSE_CLOSE ) nParen--;
- z++;
- }
-
- /* Search for a WHERE clause */
- cidxFindNext(z, &z, 0);
- if( 0==sqlite3_strnicmp(z, "where", 5) ){
- pIdx->zWhere = cidxMprintf(&rc, "%s\n", &z[5]);
- }else if( z[0]!='\0' ){
- goto parse_error;
- }
-
- return rc;
-
- parse_error:
- cidxCursorError(pCsr, "Parse error in: %s", zSql);
- return SQLITE_ERROR;
-}
-
-static int cidxLookupIndex(
- CidxCursor *pCsr, /* Cursor object */
- const char *zIdx, /* Name of index to look up */
- CidxIndex **ppIdx, /* OUT: Description of columns */
- char **pzTab /* OUT: Table name */
-){
- int rc = SQLITE_OK;
- char *zTab = 0;
- CidxIndex *pIdx = 0;
-
- sqlite3_stmt *pFindTab = 0;
- sqlite3_stmt *pInfo = 0;
-
- /* Find the table for this index. */
- pFindTab = cidxPrepare(&rc, pCsr,
- "SELECT tbl_name, sql FROM sqlite_schema WHERE name=%Q AND type='index'",
- zIdx
- );
- if( rc==SQLITE_OK && sqlite3_step(pFindTab)==SQLITE_ROW ){
- const char *zSql = (const char*)sqlite3_column_text(pFindTab, 1);
- zTab = cidxStrdup(&rc, (const char*)sqlite3_column_text(pFindTab, 0));
-
- pInfo = cidxPrepare(&rc, pCsr, "PRAGMA index_xinfo(%Q)", zIdx);
- if( rc==SQLITE_OK ){
- int nAlloc = 0;
- int iCol = 0;
-
- while( sqlite3_step(pInfo)==SQLITE_ROW ){
- const char *zName = (const char*)sqlite3_column_text(pInfo, 2);
- const char *zColl = (const char*)sqlite3_column_text(pInfo, 4);
- CidxColumn *p;
- if( zName==0 ) zName = "rowid";
- if( iCol==nAlloc ){
- int nByte = sizeof(CidxIndex) + sizeof(CidxColumn)*(nAlloc+8);
- pIdx = (CidxIndex*)sqlite3_realloc(pIdx, nByte);
- nAlloc += 8;
- }
- p = &pIdx->aCol[iCol++];
- p->bDesc = sqlite3_column_int(pInfo, 3);
- p->bKey = sqlite3_column_int(pInfo, 5);
- if( zSql==0 || p->bKey==0 ){
- p->zExpr = cidxMprintf(&rc, "\"%w\" COLLATE %s",zName,zColl);
- }else{
- p->zExpr = 0;
- }
- pIdx->nCol = iCol;
- pIdx->zWhere = 0;
- }
- cidxFinalize(&rc, pInfo);
- }
-
- if( rc==SQLITE_OK && zSql ){
- rc = cidxParseSQL(pCsr, pIdx, zSql);
- }
- }
-
- cidxFinalize(&rc, pFindTab);
- if( rc==SQLITE_OK && zTab==0 ){
- rc = SQLITE_ERROR;
- }
-
- if( rc!=SQLITE_OK ){
- sqlite3_free(zTab);
- cidxFreeIndex(pIdx);
- }else{
- *pzTab = zTab;
- *ppIdx = pIdx;
- }
-
- return rc;
-}
-
-static int cidxDecodeAfter(
- CidxCursor *pCsr,
- int nCol,
- const char *zAfterKey,
- char ***pazAfter
-){
- char **azAfter;
- int rc = SQLITE_OK;
- int nAfterKey = (int)strlen(zAfterKey);
-
- azAfter = cidxMalloc(&rc, sizeof(char*)*nCol + nAfterKey+1);
- if( rc==SQLITE_OK ){
- int i;
- char *zCopy = (char*)&azAfter[nCol];
- char *p = zCopy;
- memcpy(zCopy, zAfterKey, nAfterKey+1);
- for(i=0; i='0' && *p<='9')
- || *p=='.' || *p=='+' || *p=='-' || *p=='e' || *p=='E'
- ){
- p++;
- }
- }
-
- while( *p==' ' ) p++;
- if( *p!=(i==(nCol-1) ? '\0' : ',') ){
- goto parse_error;
- }
- *p++ = '\0';
- }
- }
-
- *pazAfter = azAfter;
- return rc;
-
- parse_error:
- sqlite3_free(azAfter);
- *pazAfter = 0;
- cidxCursorError(pCsr, "%s", "error parsing after value");
- return SQLITE_ERROR;
-}
-
-static char *cidxWhere(
- int *pRc, CidxColumn *aCol, char **azAfter, int iGt, int bLastIsNull
-){
- char *zRet = 0;
- const char *zSep = "";
- int i;
-
- for(i=0; i"),
- azAfter[iGt]
- );
- }else{
- zRet = cidxMprintf(pRc, "%z%s(%s) IS NOT NULL", zRet, zSep,aCol[iGt].zExpr);
- }
-
- return zRet;
-}
-
-#define CIDX_CLIST_ALL 0
-#define CIDX_CLIST_ORDERBY 1
-#define CIDX_CLIST_CURRENT_KEY 2
-#define CIDX_CLIST_SUBWHERE 3
-#define CIDX_CLIST_SUBEXPR 4
-
-/*
-** This function returns various strings based on the contents of the
-** CidxIndex structure and the eType parameter.
-*/
-static char *cidxColumnList(
- int *pRc, /* IN/OUT: Error code */
- const char *zIdx,
- CidxIndex *pIdx, /* Indexed columns */
- int eType /* True to include ASC/DESC */
-){
- char *zRet = 0;
- if( *pRc==SQLITE_OK ){
- const char *aDir[2] = {"", " DESC"};
- int i;
- const char *zSep = "";
-
- for(i=0; inCol; i++){
- CidxColumn *p = &pIdx->aCol[i];
- assert( pIdx->aCol[i].bDesc==0 || pIdx->aCol[i].bDesc==1 );
- switch( eType ){
-
- case CIDX_CLIST_ORDERBY:
- zRet = cidxMprintf(pRc, "%z%s%d%s", zRet, zSep, i+1, aDir[p->bDesc]);
- zSep = ",";
- break;
-
- case CIDX_CLIST_CURRENT_KEY:
- zRet = cidxMprintf(pRc, "%z%squote(i%d)", zRet, zSep, i);
- zSep = "||','||";
- break;
-
- case CIDX_CLIST_SUBWHERE:
- if( p->bKey==0 ){
- zRet = cidxMprintf(pRc, "%z%s%s IS i.i%d", zRet,
- zSep, p->zExpr, i
- );
- zSep = " AND ";
- }
- break;
-
- case CIDX_CLIST_SUBEXPR:
- if( p->bKey==1 ){
- zRet = cidxMprintf(pRc, "%z%s%s IS i.i%d", zRet,
- zSep, p->zExpr, i
- );
- zSep = " AND ";
- }
- break;
-
- default:
- assert( eType==CIDX_CLIST_ALL );
- zRet = cidxMprintf(pRc, "%z%s(%s) AS i%d", zRet, zSep, p->zExpr, i);
- zSep = ", ";
- break;
- }
- }
- }
-
- return zRet;
-}
-
-/*
-** Generate SQL (in memory obtained from sqlite3_malloc()) that will
-** continue the index scan for zIdxName starting after zAfterKey.
-*/
-int cidxGenerateScanSql(
- CidxCursor *pCsr, /* The cursor which needs the new statement */
- const char *zIdxName, /* index to be scanned */
- const char *zAfterKey, /* start after this key, if not NULL */
- char **pzSqlOut /* OUT: Write the generated SQL here */
-){
- int rc;
- char *zTab = 0;
- char *zCurrentKey = 0;
- char *zOrderBy = 0;
- char *zSubWhere = 0;
- char *zSubExpr = 0;
- char *zSrcList = 0;
- char **azAfter = 0;
- CidxIndex *pIdx = 0;
-
- *pzSqlOut = 0;
- rc = cidxLookupIndex(pCsr, zIdxName, &pIdx, &zTab);
-
- zOrderBy = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_ORDERBY);
- zCurrentKey = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_CURRENT_KEY);
- zSubWhere = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_SUBWHERE);
- zSubExpr = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_SUBEXPR);
- zSrcList = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_ALL);
-
- if( rc==SQLITE_OK && zAfterKey ){
- rc = cidxDecodeAfter(pCsr, pIdx->nCol, zAfterKey, &azAfter);
- }
-
- if( rc==SQLITE_OK ){
- if( zAfterKey==0 ){
- *pzSqlOut = cidxMprintf(&rc,
- "SELECT (SELECT %s FROM %Q AS t WHERE %s), %s "
- "FROM (SELECT %s FROM %Q INDEXED BY %Q %s%sORDER BY %s) AS i",
- zSubExpr, zTab, zSubWhere, zCurrentKey,
- zSrcList, zTab, zIdxName,
- (pIdx->zWhere ? "WHERE " : ""), (pIdx->zWhere ? pIdx->zWhere : ""),
- zOrderBy
- );
- }else{
- const char *zSep = "";
- char *zSql;
- int i;
-
- zSql = cidxMprintf(&rc,
- "SELECT (SELECT %s FROM %Q WHERE %s), %s FROM (",
- zSubExpr, zTab, zSubWhere, zCurrentKey
- );
- for(i=pIdx->nCol-1; i>=0; i--){
- int j;
- if( pIdx->aCol[i].bDesc && azAfter[i]==0 ) continue;
- for(j=0; j<2; j++){
- char *zWhere = cidxWhere(&rc, pIdx->aCol, azAfter, i, j);
- zSql = cidxMprintf(&rc, "%z"
- "%sSELECT * FROM ("
- "SELECT %s FROM %Q INDEXED BY %Q WHERE %s%s%z ORDER BY %s"
- ")",
- zSql, zSep, zSrcList, zTab, zIdxName,
- pIdx->zWhere ? pIdx->zWhere : "",
- pIdx->zWhere ? " AND " : "",
- zWhere, zOrderBy
- );
- zSep = " UNION ALL ";
- if( pIdx->aCol[i].bDesc==0 ) break;
- }
- }
- *pzSqlOut = cidxMprintf(&rc, "%z) AS i", zSql);
- }
- }
-
- sqlite3_free(zTab);
- sqlite3_free(zCurrentKey);
- sqlite3_free(zOrderBy);
- sqlite3_free(zSubWhere);
- sqlite3_free(zSubExpr);
- sqlite3_free(zSrcList);
- cidxFreeIndex(pIdx);
- sqlite3_free(azAfter);
- return rc;
-}
-
-
-/*
-** Position a cursor back to the beginning.
-*/
-static int cidxFilter(
- sqlite3_vtab_cursor *pCursor,
- int idxNum, const char *idxStr,
- int argc, sqlite3_value **argv
-){
- int rc = SQLITE_OK;
- CidxCursor *pCsr = (CidxCursor*)pCursor;
- const char *zIdxName = 0;
- const char *zAfterKey = 0;
-
- sqlite3_free(pCsr->zIdxName);
- pCsr->zIdxName = 0;
- sqlite3_free(pCsr->zAfterKey);
- pCsr->zAfterKey = 0;
- sqlite3_finalize(pCsr->pStmt);
- pCsr->pStmt = 0;
-
- if( argc>0 ){
- zIdxName = (const char*)sqlite3_value_text(argv[0]);
- if( argc>1 ){
- zAfterKey = (const char*)sqlite3_value_text(argv[1]);
- }
- }
-
- if( zIdxName ){
- char *zSql = 0;
- pCsr->zIdxName = sqlite3_mprintf("%s", zIdxName);
- pCsr->zAfterKey = zAfterKey ? sqlite3_mprintf("%s", zAfterKey) : 0;
- rc = cidxGenerateScanSql(pCsr, zIdxName, zAfterKey, &zSql);
- if( zSql ){
- pCsr->pStmt = cidxPrepare(&rc, pCsr, "%z", zSql);
- }
- }
-
- if( pCsr->pStmt ){
- assert( rc==SQLITE_OK );
- rc = cidxNext(pCursor);
- }
- pCsr->iRowid = 1;
- return rc;
-}
-
-/*
-** Return a column value.
-*/
-static int cidxColumn(
- sqlite3_vtab_cursor *pCursor,
- sqlite3_context *ctx,
- int iCol
-){
- CidxCursor *pCsr = (CidxCursor*)pCursor;
- assert( iCol>=IIC_ERRMSG && iCol<=IIC_SCANNER_SQL );
- switch( iCol ){
- case IIC_ERRMSG: {
- const char *zVal = 0;
- if( sqlite3_column_type(pCsr->pStmt, 0)==SQLITE_INTEGER ){
- if( sqlite3_column_int(pCsr->pStmt, 0)==0 ){
- zVal = "row data mismatch";
- }
- }else{
- zVal = "row missing";
- }
- sqlite3_result_text(ctx, zVal, -1, SQLITE_STATIC);
- break;
- }
- case IIC_CURRENT_KEY: {
- sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, 1));
- break;
- }
- case IIC_INDEX_NAME: {
- sqlite3_result_text(ctx, pCsr->zIdxName, -1, SQLITE_TRANSIENT);
- break;
- }
- case IIC_AFTER_KEY: {
- sqlite3_result_text(ctx, pCsr->zAfterKey, -1, SQLITE_TRANSIENT);
- break;
- }
- case IIC_SCANNER_SQL: {
- char *zSql = 0;
- cidxGenerateScanSql(pCsr, pCsr->zIdxName, pCsr->zAfterKey, &zSql);
- sqlite3_result_text(ctx, zSql, -1, sqlite3_free);
- break;
- }
- }
- return SQLITE_OK;
-}
-
-/* Return the ROWID for the sqlite_btreeinfo table */
-static int cidxRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
- CidxCursor *pCsr = (CidxCursor*)pCursor;
- *pRowid = pCsr->iRowid;
- return SQLITE_OK;
-}
-
-/*
-** Register the virtual table modules with the database handle passed
-** as the only argument.
-*/
-static int ciInit(sqlite3 *db){
- static sqlite3_module cidx_module = {
- 0, /* iVersion */
- 0, /* xCreate */
- cidxConnect, /* xConnect */
- cidxBestIndex, /* xBestIndex */
- cidxDisconnect, /* xDisconnect */
- 0, /* xDestroy */
- cidxOpen, /* xOpen - open a cursor */
- cidxClose, /* xClose - close a cursor */
- cidxFilter, /* xFilter - configure scan constraints */
- cidxNext, /* xNext - advance a cursor */
- cidxEof, /* xEof - check for end of scan */
- cidxColumn, /* xColumn - read data */
- cidxRowid, /* xRowid - read data */
- 0, /* xUpdate */
- 0, /* xBegin */
- 0, /* xSync */
- 0, /* xCommit */
- 0, /* xRollback */
- 0, /* xFindMethod */
- 0, /* xRename */
- 0, /* xSavepoint */
- 0, /* xRelease */
- 0, /* xRollbackTo */
- 0, /* xShadowName */
- 0 /* xIntegrity */
- };
- return sqlite3_create_module(db, "incremental_index_check", &cidx_module, 0);
-}
-
-/*
-** Extension load function.
-*/
-#ifdef _WIN32
-__declspec(dllexport)
-#endif
-int sqlite3_checkindex_init(
- sqlite3 *db,
- char **pzErrMsg,
- const sqlite3_api_routines *pApi
-){
- SQLITE_EXTENSION_INIT2(pApi);
- return ciInit(db);
-}
diff --git a/ext/repair/sqlite3_checker.c.in b/ext/repair/sqlite3_checker.c.in
deleted file mode 100644
index 96b15f271..000000000
--- a/ext/repair/sqlite3_checker.c.in
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
-** Read an SQLite database file and analyze its space utilization. Generate
-** text on standard output.
-*/
-#define TCLSH_INIT_PROC sqlite3_checker_init_proc
-#define SQLITE_ENABLE_DBPAGE_VTAB 1
-#undef SQLITE_THREADSAFE
-#define SQLITE_THREADSAFE 0
-#undef SQLITE_ENABLE_COLUMN_METADATA
-#define SQLITE_OMIT_DECLTYPE 1
-#define SQLITE_OMIT_DEPRECATED 1
-#define SQLITE_OMIT_PROGRESS_CALLBACK 1
-#define SQLITE_OMIT_SHARED_CACHE 1
-#define SQLITE_DEFAULT_MEMSTATUS 0
-#define SQLITE_MAX_EXPR_DEPTH 0
-INCLUDE sqlite3.c
-INCLUDE $ROOT/src/tclsqlite.c
-INCLUDE $ROOT/ext/misc/btreeinfo.c
-INCLUDE $ROOT/ext/repair/checkindex.c
-INCLUDE $ROOT/ext/repair/checkfreelist.c
-
-/*
-** Decode a pointer to an sqlite3 object.
-*/
-int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb){
- struct SqliteDb *p;
- Tcl_CmdInfo cmdInfo;
- if( Tcl_GetCommandInfo(interp, zA, &cmdInfo) ){
- p = (struct SqliteDb*)cmdInfo.objClientData;
- *ppDb = p->db;
- return TCL_OK;
- }else{
- *ppDb = 0;
- return TCL_ERROR;
- }
- return TCL_OK;
-}
-
-/*
-** sqlite3_imposter db main rootpage {CREATE TABLE...} ;# setup an imposter
-** sqlite3_imposter db main ;# rm all imposters
-*/
-static int sqlite3_imposter(
- void *clientData,
- Tcl_Interp *interp,
- int objc,
- Tcl_Obj *CONST objv[]
-){
- sqlite3 *db;
- const char *zSchema;
- int iRoot;
- const char *zSql;
-
- if( objc!=3 && objc!=5 ){
- Tcl_WrongNumArgs(interp, 1, objv, "DB SCHEMA [ROOTPAGE SQL]");
- return TCL_ERROR;
- }
- if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
- zSchema = Tcl_GetString(objv[2]);
- if( objc==3 ){
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, zSchema, 0, 1);
- }else{
- if( Tcl_GetIntFromObj(interp, objv[3], &iRoot) ) return TCL_ERROR;
- zSql = Tcl_GetString(objv[4]);
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, zSchema, 1, iRoot);
- sqlite3_exec(db, zSql, 0, 0, 0);
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, zSchema, 0, 0);
- }
- return TCL_OK;
-}
-
-#include
-
-const char *sqlite3_checker_init_proc(Tcl_Interp *interp){
- Tcl_CreateObjCommand(interp, "sqlite3_imposter",
- (Tcl_ObjCmdProc*)sqlite3_imposter, 0, 0);
- sqlite3_auto_extension((void(*)(void))sqlite3_btreeinfo_init);
- sqlite3_auto_extension((void(*)(void))sqlite3_checkindex_init);
- sqlite3_auto_extension((void(*)(void))sqlite3_checkfreelist_init);
- return
-BEGIN_STRING
-INCLUDE $ROOT/ext/repair/sqlite3_checker.tcl
-END_STRING
-;
-}
diff --git a/ext/repair/sqlite3_checker.tcl b/ext/repair/sqlite3_checker.tcl
deleted file mode 100644
index 2ae6e15b1..000000000
--- a/ext/repair/sqlite3_checker.tcl
+++ /dev/null
@@ -1,264 +0,0 @@
-# This TCL script is the main driver script for the sqlite3_checker utility
-# program.
-#
-
-# Special case:
-#
-# sqlite3_checker --test FILENAME ARGS
-#
-# uses FILENAME in place of this script.
-#
-if {[lindex $argv 0]=="--test" && [llength $argv]>1} {
- set ::argv0 [lindex $argv 1]
- set argv [lrange $argv 2 end]
- source $argv0
- exit 0
-}
-
-# Emulate a TCL shell
-#
-proc tclsh {} {
- set line {}
- while {![eof stdin]} {
- if {$line!=""} {
- puts -nonewline "> "
- } else {
- puts -nonewline "% "
- }
- flush stdout
- append line [gets stdin]
- if {[info complete $line]} {
- if {[catch {uplevel #0 $line} result]} {
- puts stderr "Error: $result"
- } elseif {$result!=""} {
- puts $result
- }
- set line {}
- } else {
- append line \n
- }
- }
-}
-
-# Do an incremental integrity check of a single index
-#
-proc check_index {idxname batchsize bTrace} {
- set i 0
- set more 1
- set nerr 0
- set pct 00.0
- set max [db one {SELECT nEntry FROM sqlite_btreeinfo('main')
- WHERE name=$idxname}]
- puts -nonewline "$idxname: $i of $max rows ($pct%)\r"
- flush stdout
- if {$bTrace} {
- set sql {SELECT errmsg, current_key AS key,
- CASE WHEN rowid=1 THEN scanner_sql END AS traceOut
- FROM incremental_index_check($idxname)
- WHERE after_key=$key
- LIMIT $batchsize}
- } else {
- set sql {SELECT errmsg, current_key AS key, NULL AS traceOut
- FROM incremental_index_check($idxname)
- WHERE after_key=$key
- LIMIT $batchsize}
- }
- while {$more} {
- set more 0
- db eval $sql {
- set more 1
- if {$errmsg!=""} {
- incr nerr
- puts "$idxname: key($key): $errmsg"
- } elseif {$traceOut!=""} {
- puts "$idxname: $traceOut"
- }
- incr i
-
- }
- set x [format {%.1f} [expr {($i*100.0)/$max}]]
- if {$x!=$pct} {
- puts -nonewline "$idxname: $i of $max rows ($pct%)\r"
- flush stdout
- set pct $x
- }
- }
- puts "$idxname: $nerr errors out of $i entries"
-}
-
-# Print a usage message on standard error, then quit.
-#
-proc usage {} {
- set argv0 [file rootname [file tail [info nameofexecutable]]]
- puts stderr "Usage: $argv0 OPTIONS database-filename"
- puts stderr {
-Do sanity checking on a live SQLite3 database file specified by the
-"database-filename" argument.
-
-Options:
-
- --batchsize N Number of rows to check per transaction
-
- --freelist Perform a freelist check
-
- --index NAME Run a check of the index NAME
-
- --summary Print summary information about the database
-
- --table NAME Run a check of all indexes for table NAME
-
- --tclsh Run the built-in TCL interpreter (for debugging)
-
- --trace (Debugging only:) Output trace information on the scan
-
- --version Show the version number of SQLite
-}
- exit 1
-}
-
-set file_to_analyze {}
-append argv {}
-set bFreelistCheck 0
-set bSummary 0
-set zIndex {}
-set zTable {}
-set batchsize 1000
-set bAll 1
-set bTrace 0
-set argc [llength $argv]
-for {set i 0} {$i<$argc} {incr i} {
- set arg [lindex $argv $i]
- if {[regexp {^-+tclsh$} $arg]} {
- tclsh
- exit 0
- }
- if {[regexp {^-+version$} $arg]} {
- sqlite3 mem :memory:
- puts [mem one {SELECT sqlite_version()||' '||sqlite_source_id()}]
- mem close
- exit 0
- }
- if {[regexp {^-+freelist$} $arg]} {
- set bFreelistCheck 1
- set bAll 0
- continue
- }
- if {[regexp {^-+summary$} $arg]} {
- set bSummary 1
- set bAll 0
- continue
- }
- if {[regexp {^-+trace$} $arg]} {
- set bTrace 1
- continue
- }
- if {[regexp {^-+batchsize$} $arg]} {
- incr i
- if {$i>=$argc} {
- puts stderr "missing argument on $arg"
- exit 1
- }
- set batchsize [lindex $argv $i]
- continue
- }
- if {[regexp {^-+index$} $arg]} {
- incr i
- if {$i>=$argc} {
- puts stderr "missing argument on $arg"
- exit 1
- }
- set zIndex [lindex $argv $i]
- set bAll 0
- continue
- }
- if {[regexp {^-+table$} $arg]} {
- incr i
- if {$i>=$argc} {
- puts stderr "missing argument on $arg"
- exit 1
- }
- set zTable [lindex $argv $i]
- set bAll 0
- continue
- }
- if {[regexp {^-} $arg]} {
- puts stderr "Unknown option: $arg"
- usage
- }
- if {$file_to_analyze!=""} {
- usage
- } else {
- set file_to_analyze $arg
- }
-}
-if {$file_to_analyze==""} usage
-
-# If a TCL script is specified on the command-line, then run that
-# script.
-#
-if {[file extension $file_to_analyze]==".tcl"} {
- source $file_to_analyze
- exit 0
-}
-
-set root_filename $file_to_analyze
-regexp {^file:(//)?([^?]*)} $file_to_analyze all x1 root_filename
-if {![file exists $root_filename]} {
- puts stderr "No such file: $root_filename"
- exit 1
-}
-if {![file readable $root_filename]} {
- puts stderr "File is not readable: $root_filename"
- exit 1
-}
-
-if {[catch {sqlite3 db $file_to_analyze} res]} {
- puts stderr "Cannot open datababase $root_filename: $res"
- exit 1
-}
-
-if {$bFreelistCheck || $bAll} {
- puts -nonewline "freelist-check: "
- flush stdout
- db eval BEGIN
- puts [db one {SELECT checkfreelist('main')}]
- db eval END
-}
-if {$bSummary} {
- set scale 0
- set pgsz [db one {PRAGMA page_size}]
- db eval {SELECT nPage*$pgsz AS sz, name, tbl_name
- FROM sqlite_btreeinfo
- WHERE type='index'
- ORDER BY 1 DESC, name} {
- if {$scale==0} {
- if {$sz>10000000} {
- set scale 1000000.0
- set unit MB
- } else {
- set scale 1000.0
- set unit KB
- }
- }
- puts [format {%7.1f %s index %s of table %s} \
- [expr {$sz/$scale}] $unit $name $tbl_name]
- }
-}
-if {$zIndex!=""} {
- check_index $zIndex $batchsize $bTrace
-}
-if {$zTable!=""} {
- foreach idx [db eval {SELECT name FROM sqlite_master
- WHERE type='index' AND rootpage>0
- AND tbl_name=$zTable}] {
- check_index $idx $batchsize $bTrace
- }
-}
-if {$bAll} {
- set allidx [db eval {SELECT name FROM sqlite_btreeinfo('main')
- WHERE type='index' AND rootpage>0
- ORDER BY nEntry}]
- foreach idx $allidx {
- check_index $idx $batchsize $bTrace
- }
-}
diff --git a/ext/repair/test/README.md b/ext/repair/test/README.md
deleted file mode 100644
index 8cc954adf..000000000
--- a/ext/repair/test/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-To run these tests, first build sqlite3_checker:
-
-
-> make sqlite3_checker
-
-
-Then run the "test.tcl" script using:
-
-
-> ./sqlite3_checker --test $path/test.tcl
-
-
-Optionally add the full pathnames of individual *.test modules
diff --git a/ext/repair/test/checkfreelist01.test b/ext/repair/test/checkfreelist01.test
deleted file mode 100644
index 7e2dd51c3..000000000
--- a/ext/repair/test/checkfreelist01.test
+++ /dev/null
@@ -1,92 +0,0 @@
-# 2017-10-11
-
-set testprefix checkfreelist
-
-do_execsql_test 1.0 {
- PRAGMA page_size=1024;
- CREATE TABLE t1(a, b);
-}
-
-do_execsql_test 1.2 { SELECT checkfreelist('main') } {ok}
-do_execsql_test 1.3 {
- WITH s(i) AS (
- SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10000
- )
- INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
- DELETE FROM t1 WHERE rowid%3;
- PRAGMA freelist_count;
-} {6726}
-
-do_execsql_test 1.4 { SELECT checkfreelist('main') } {ok}
-do_execsql_test 1.5 {
- WITH freelist_trunk(i, d, n) AS (
- SELECT 1, NULL, sqlite_readint32(data, 32) FROM sqlite_dbpage WHERE pgno=1
- UNION ALL
- SELECT n, data, sqlite_readint32(data)
- FROM freelist_trunk, sqlite_dbpage WHERE pgno=n
- )
- SELECT i FROM freelist_trunk WHERE i!=1;
-} {
- 10009 9715 9343 8969 8595 8222 7847 7474 7102 6727 6354 5982 5608 5234
- 4860 4487 4112 3740 3367 2992 2619 2247 1872 1499 1125 752 377 5
-}
-
-do_execsql_test 1.6 { SELECT checkfreelist('main') } {ok}
-
-proc set_int {blob idx newval} {
- binary scan $blob I* ints
- lset ints $idx $newval
- binary format I* $ints
-}
-db func set_int set_int
-
-proc get_int {blob idx} {
- binary scan $blob I* ints
- lindex $ints $idx
-}
-db func get_int get_int
-
-do_execsql_test 1.7 {
- BEGIN;
- UPDATE sqlite_dbpage
- SET data = set_int(data, 1, get_int(data, 1)-1)
- WHERE pgno=4860;
- SELECT checkfreelist('main');
- ROLLBACK;
-} {{free-list count mismatch: actual=6725 header=6726}}
-
-do_execsql_test 1.8 {
- BEGIN;
- UPDATE sqlite_dbpage
- SET data = set_int(data, 5, (SELECT * FROM pragma_page_count)+1)
- WHERE pgno=4860;
- SELECT checkfreelist('main');
- ROLLBACK;
-} {{leaf page 10092 is out of range (child 3 of trunk page 4860)}}
-
-do_execsql_test 1.9 {
- BEGIN;
- UPDATE sqlite_dbpage
- SET data = set_int(data, 5, 0)
- WHERE pgno=4860;
- SELECT checkfreelist('main');
- ROLLBACK;
-} {{leaf page 0 is out of range (child 3 of trunk page 4860)}}
-
-do_execsql_test 1.10 {
- BEGIN;
- UPDATE sqlite_dbpage
- SET data = set_int(data, get_int(data, 1)+1, 0)
- WHERE pgno=5;
- SELECT checkfreelist('main');
- ROLLBACK;
-} {{leaf page 0 is out of range (child 247 of trunk page 5)}}
-
-do_execsql_test 1.11 {
- BEGIN;
- UPDATE sqlite_dbpage
- SET data = set_int(data, 1, 249)
- WHERE pgno=5;
- SELECT checkfreelist('main');
- ROLLBACK;
-} {{leaf count out of range (249) on trunk page 5}}
diff --git a/ext/repair/test/checkindex01.test b/ext/repair/test/checkindex01.test
deleted file mode 100644
index 97973aee7..000000000
--- a/ext/repair/test/checkindex01.test
+++ /dev/null
@@ -1,349 +0,0 @@
-# 2017-10-11
-#
-set testprefix checkindex
-
-do_execsql_test 1.0 {
- CREATE TABLE t1(a, b);
- CREATE INDEX i1 ON t1(a);
- INSERT INTO t1 VALUES('one', 2);
- INSERT INTO t1 VALUES('two', 4);
- INSERT INTO t1 VALUES('three', 6);
- INSERT INTO t1 VALUES('four', 8);
- INSERT INTO t1 VALUES('five', 10);
-
- CREATE INDEX i2 ON t1(a DESC);
-} {}
-
-proc incr_index_check {idx nStep} {
- set Q {
- SELECT errmsg, current_key FROM incremental_index_check($idx, $after)
- LIMIT $nStep
- }
-
- set res [list]
- while {1} {
- unset -nocomplain current_key
- set res1 [db eval $Q]
- if {[llength $res1]==0} break
- set res [concat $res $res1]
- set after [lindex $res end]
- }
-
- return $res
-}
-
-proc do_index_check_test {tn idx res} {
- uplevel [list do_execsql_test $tn.1 "
- SELECT errmsg, current_key FROM incremental_index_check('$idx');
- " $res]
-
- uplevel [list do_test $tn.2 "incr_index_check $idx 1" [list {*}$res]]
- uplevel [list do_test $tn.3 "incr_index_check $idx 2" [list {*}$res]]
- uplevel [list do_test $tn.4 "incr_index_check $idx 5" [list {*}$res]]
-}
-
-
-do_execsql_test 1.2.1 {
- SELECT rowid, errmsg IS NULL, current_key FROM incremental_index_check('i1');
-} {
- 1 1 'five',5
- 2 1 'four',4
- 3 1 'one',1
- 4 1 'three',3
- 5 1 'two',2
-}
-do_execsql_test 1.2.2 {
- SELECT errmsg IS NULL, current_key, index_name, after_key, scanner_sql
- FROM incremental_index_check('i1') LIMIT 1;
-} {
- 1
- 'five',5
- i1
- {}
- {SELECT (SELECT a IS i.i0 FROM 't1' AS t WHERE "rowid" COLLATE BINARY IS i.i1), quote(i0)||','||quote(i1) FROM (SELECT (a) AS i0, ("rowid" COLLATE BINARY) AS i1 FROM 't1' INDEXED BY 'i1' ORDER BY 1,2) AS i}
-}
-
-do_index_check_test 1.3 i1 {
- {} 'five',5
- {} 'four',4
- {} 'one',1
- {} 'three',3
- {} 'two',2
-}
-
-do_index_check_test 1.4 i2 {
- {} 'two',2
- {} 'three',3
- {} 'one',1
- {} 'four',4
- {} 'five',5
-}
-
-do_test 1.5 {
- set tblroot [db one { SELECT rootpage FROM sqlite_master WHERE name='t1' }]
- sqlite3_imposter db main $tblroot {CREATE TABLE xt1(a,b)}
- db eval {
- UPDATE xt1 SET a='six' WHERE rowid=3;
- DELETE FROM xt1 WHERE rowid = 5;
- }
- sqlite3_imposter db main
-} {}
-
-do_index_check_test 1.6 i1 {
- {row missing} 'five',5
- {} 'four',4
- {} 'one',1
- {row data mismatch} 'three',3
- {} 'two',2
-}
-
-do_index_check_test 1.7 i2 {
- {} 'two',2
- {row data mismatch} 'three',3
- {} 'one',1
- {} 'four',4
- {row missing} 'five',5
-}
-
-#--------------------------------------------------------------------------
-do_execsql_test 2.0 {
-
- CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c, d);
-
- INSERT INTO t2 VALUES(1, NULL, 1, 1);
- INSERT INTO t2 VALUES(2, 1, NULL, 1);
- INSERT INTO t2 VALUES(3, 1, 1, NULL);
-
- INSERT INTO t2 VALUES(4, 2, 2, 1);
- INSERT INTO t2 VALUES(5, 2, 2, 2);
- INSERT INTO t2 VALUES(6, 2, 2, 3);
-
- INSERT INTO t2 VALUES(7, 2, 2, 1);
- INSERT INTO t2 VALUES(8, 2, 2, 2);
- INSERT INTO t2 VALUES(9, 2, 2, 3);
-
- CREATE INDEX i3 ON t2(b, c, d);
- CREATE INDEX i4 ON t2(b DESC, c DESC, d DESC);
- CREATE INDEX i5 ON t2(d, c DESC, b);
-} {}
-
-do_index_check_test 2.1 i3 {
- {} NULL,1,1,1
- {} 1,NULL,1,2
- {} 1,1,NULL,3
- {} 2,2,1,4
- {} 2,2,1,7
- {} 2,2,2,5
- {} 2,2,2,8
- {} 2,2,3,6
- {} 2,2,3,9
-}
-
-do_index_check_test 2.2 i4 {
- {} 2,2,3,6
- {} 2,2,3,9
- {} 2,2,2,5
- {} 2,2,2,8
- {} 2,2,1,4
- {} 2,2,1,7
- {} 1,1,NULL,3
- {} 1,NULL,1,2
- {} NULL,1,1,1
-}
-
-do_index_check_test 2.3 i5 {
- {} NULL,1,1,3
- {} 1,2,2,4
- {} 1,2,2,7
- {} 1,1,NULL,1
- {} 1,NULL,1,2
- {} 2,2,2,5
- {} 2,2,2,8
- {} 3,2,2,6
- {} 3,2,2,9
-}
-
-#--------------------------------------------------------------------------
-do_execsql_test 3.0 {
-
- CREATE TABLE t3(w, x, y, z PRIMARY KEY) WITHOUT ROWID;
- CREATE INDEX t3wxy ON t3(w, x, y);
- CREATE INDEX t3wxy2 ON t3(w DESC, x DESC, y DESC);
-
- INSERT INTO t3 VALUES(NULL, NULL, NULL, 1);
- INSERT INTO t3 VALUES(NULL, NULL, NULL, 2);
- INSERT INTO t3 VALUES(NULL, NULL, NULL, 3);
-
- INSERT INTO t3 VALUES('a', NULL, NULL, 4);
- INSERT INTO t3 VALUES('a', NULL, NULL, 5);
- INSERT INTO t3 VALUES('a', NULL, NULL, 6);
-
- INSERT INTO t3 VALUES('a', 'b', NULL, 7);
- INSERT INTO t3 VALUES('a', 'b', NULL, 8);
- INSERT INTO t3 VALUES('a', 'b', NULL, 9);
-
-} {}
-
-do_index_check_test 3.1 t3wxy {
- {} NULL,NULL,NULL,1 {} NULL,NULL,NULL,2 {} NULL,NULL,NULL,3
- {} 'a',NULL,NULL,4 {} 'a',NULL,NULL,5 {} 'a',NULL,NULL,6
- {} 'a','b',NULL,7 {} 'a','b',NULL,8 {} 'a','b',NULL,9
-}
-do_index_check_test 3.2 t3wxy2 {
- {} 'a','b',NULL,7 {} 'a','b',NULL,8 {} 'a','b',NULL,9
- {} 'a',NULL,NULL,4 {} 'a',NULL,NULL,5 {} 'a',NULL,NULL,6
- {} NULL,NULL,NULL,1 {} NULL,NULL,NULL,2 {} NULL,NULL,NULL,3
-}
-
-#--------------------------------------------------------------------------
-# Test with an index that uses non-default collation sequences.
-#
-do_execsql_test 4.0 {
- CREATE TABLE t4(a INTEGER PRIMARY KEY, c1 TEXT, c2 TEXT);
- INSERT INTO t4 VALUES(1, 'aaa', 'bbb');
- INSERT INTO t4 VALUES(2, 'AAA', 'CCC');
- INSERT INTO t4 VALUES(3, 'aab', 'ddd');
- INSERT INTO t4 VALUES(4, 'AAB', 'EEE');
-
- CREATE INDEX t4cc ON t4(c1 COLLATE nocase, c2 COLLATE nocase);
-}
-
-do_index_check_test 4.1 t4cc {
- {} 'aaa','bbb',1
- {} 'AAA','CCC',2
- {} 'aab','ddd',3
- {} 'AAB','EEE',4
-}
-
-do_test 4.2 {
- set tblroot [db one { SELECT rootpage FROM sqlite_master WHERE name='t4' }]
- sqlite3_imposter db main $tblroot \
- {CREATE TABLE xt4(a INTEGER PRIMARY KEY, c1 TEXT, c2 TEXT)}
-
- db eval {
- UPDATE xt4 SET c1='hello' WHERE rowid=2;
- DELETE FROM xt4 WHERE rowid = 3;
- }
- sqlite3_imposter db main
-} {}
-
-do_index_check_test 4.3 t4cc {
- {} 'aaa','bbb',1
- {row data mismatch} 'AAA','CCC',2
- {row missing} 'aab','ddd',3
- {} 'AAB','EEE',4
-}
-
-#--------------------------------------------------------------------------
-# Test an index on an expression.
-#
-do_execsql_test 5.0 {
- CREATE TABLE t5(x INTEGER PRIMARY KEY, y TEXT, UNIQUE(y));
- INSERT INTO t5 VALUES(1, '{"x":1, "y":1}');
- INSERT INTO t5 VALUES(2, '{"x":2, "y":2}');
- INSERT INTO t5 VALUES(3, '{"x":3, "y":3}');
- INSERT INTO t5 VALUES(4, '{"w":4, "z":4}');
- INSERT INTO t5 VALUES(5, '{"x":5, "y":5}');
-
- CREATE INDEX t5x ON t5( json_extract(y, '$.x') );
- CREATE INDEX t5y ON t5( json_extract(y, '$.y') DESC );
-}
-
-do_index_check_test 5.1.1 t5x {
- {} NULL,4 {} 1,1 {} 2,2 {} 3,3 {} 5,5
-}
-
-do_index_check_test 5.1.2 t5y {
- {} 5,5 {} 3,3 {} 2,2 {} 1,1 {} NULL,4
-}
-
-do_index_check_test 5.1.3 sqlite_autoindex_t5_1 {
- {} {'{"w":4, "z":4}',4}
- {} {'{"x":1, "y":1}',1}
- {} {'{"x":2, "y":2}',2}
- {} {'{"x":3, "y":3}',3}
- {} {'{"x":5, "y":5}',5}
-}
-
-do_test 5.2 {
- set tblroot [db one { SELECT rootpage FROM sqlite_master WHERE name='t5' }]
- sqlite3_imposter db main $tblroot \
- {CREATE TABLE xt5(a INTEGER PRIMARY KEY, c1 TEXT);}
- db eval {
- UPDATE xt5 SET c1='{"x":22, "y":11}' WHERE rowid=1;
- DELETE FROM xt5 WHERE rowid = 4;
- }
- sqlite3_imposter db main
-} {}
-
-do_index_check_test 5.3.1 t5x {
- {row missing} NULL,4
- {row data mismatch} 1,1
- {} 2,2
- {} 3,3
- {} 5,5
-}
-
-do_index_check_test 5.3.2 sqlite_autoindex_t5_1 {
- {row missing} {'{"w":4, "z":4}',4}
- {row data mismatch} {'{"x":1, "y":1}',1}
- {} {'{"x":2, "y":2}',2}
- {} {'{"x":3, "y":3}',3}
- {} {'{"x":5, "y":5}',5}
-}
-
-#-------------------------------------------------------------------------
-#
-do_execsql_test 6.0 {
- CREATE TABLE t6(x INTEGER PRIMARY KEY, y, z);
- CREATE INDEX t6x1 ON t6(y, /* one,two,three */ z);
- CREATE INDEX t6x2 ON t6(z, -- hello,world,
- y);
-
- CREATE INDEX t6x3 ON t6(z -- hello,world
- , y);
-
- INSERT INTO t6 VALUES(1, 2, 3);
- INSERT INTO t6 VALUES(4, 5, 6);
-}
-
-do_index_check_test 6.1 t6x1 {
- {} 2,3,1
- {} 5,6,4
-}
-do_index_check_test 6.2 t6x2 {
- {} 3,2,1
- {} 6,5,4
-}
-do_index_check_test 6.2 t6x3 {
- {} 3,2,1
- {} 6,5,4
-}
-
-#-------------------------------------------------------------------------
-#
-do_execsql_test 7.0 {
- CREATE TABLE t7(x INTEGER PRIMARY KEY, y, z);
- INSERT INTO t7 VALUES(1, 1, 1);
- INSERT INTO t7 VALUES(2, 2, 0);
- INSERT INTO t7 VALUES(3, 3, 1);
- INSERT INTO t7 VALUES(4, 4, 0);
-
- CREATE INDEX t7i1 ON t7(y) WHERE z=1;
- CREATE INDEX t7i2 ON t7(y) /* hello,world */ WHERE z=1;
- CREATE INDEX t7i3 ON t7(y) WHERE -- yep
- z=1;
- CREATE INDEX t7i4 ON t7(y) WHERE z=1 -- yep;
-}
-do_index_check_test 7.1 t7i1 {
- {} 1,1 {} 3,3
-}
-do_index_check_test 7.2 t7i2 {
- {} 1,1 {} 3,3
-}
-do_index_check_test 7.3 t7i3 {
- {} 1,1 {} 3,3
-}
-do_index_check_test 7.4 t7i4 {
- {} 1,1 {} 3,3
-}
diff --git a/ext/repair/test/test.tcl b/ext/repair/test/test.tcl
deleted file mode 100644
index c073bb73c..000000000
--- a/ext/repair/test/test.tcl
+++ /dev/null
@@ -1,67 +0,0 @@
-# Run this script using
-#
-# sqlite3_checker --test $thisscript $testscripts
-#
-# The $testscripts argument is optional. If omitted, all *.test files
-# in the same directory as $thisscript are run.
-#
-set NTEST 0
-set NERR 0
-
-
-# Invoke the do_test procedure to run a single test
-#
-# The $expected parameter is the expected result. The result is the return
-# value from the last TCL command in $cmd.
-#
-# Normally, $expected must match exactly. But if $expected is of the form
-# "/regexp/" then regular expression matching is used. If $expected is
-# "~/regexp/" then the regular expression must NOT match. If $expected is
-# of the form "#/value-list/" then each term in value-list must be numeric
-# and must approximately match the corresponding numeric term in $result.
-# Values must match within 10%. Or if the $expected term is A..B then the
-# $result term must be in between A and B.
-#
-proc do_test {name cmd expected} {
- if {[info exists ::testprefix]} {
- set name "$::testprefix$name"
- }
-
- incr ::NTEST
- puts -nonewline $name...
- flush stdout
-
- if {[catch {uplevel #0 "$cmd;\n"} result]} {
- puts -nonewline $name...
- puts "\nError: $result"
- incr ::NERR
- } else {
- set ok [expr {[string compare $result $expected]==0}]
- if {!$ok} {
- puts "\n! $name expected: \[$expected\]\n! $name got: \[$result\]"
- incr ::NERR
- } else {
- puts " Ok"
- }
- }
- flush stdout
-}
-
-#
-# do_execsql_test TESTNAME SQL RES
-#
-proc do_execsql_test {testname sql {result {}}} {
- uplevel [list do_test $testname [list db eval $sql] [list {*}$result]]
-}
-
-if {[llength $argv]==0} {
- set dir [file dirname $argv0]
- set argv [glob -nocomplain $dir/*.test]
-}
-foreach testfile $argv {
- file delete -force test.db
- sqlite3 db test.db
- source $testfile
- catch {db close}
-}
-puts "$NERR errors out of $NTEST tests"
diff --git a/ext/rtree/geopoly.c b/ext/rtree/geopoly.c
index 0ae42e7b7..22166a6f9 100644
--- a/ext/rtree/geopoly.c
+++ b/ext/rtree/geopoly.c
@@ -200,7 +200,7 @@ static int geopolyParseNumber(GeoParse *p, GeoCoord *pVal){
/* The sqlite3AtoF() routine is much much faster than atof(), if it
** is available */
double r;
- (void)sqlite3AtoF((const char*)p->z, &r, j, SQLITE_UTF8);
+ (void)sqlite3AtoF((const char*)p->z, &r);
*pVal = r;
#else
*pVal = (GeoCoord)atof((const char*)p->z);
diff --git a/ext/session/session4.test b/ext/session/session4.test
index 55cb76f15..5e44a8eb6 100644
--- a/ext/session/session4.test
+++ b/ext/session/session4.test
@@ -135,6 +135,7 @@ foreach {tn blob} {
54 540101743400120003001200010000000000000002120002400C000000000002120002400C00000000000050040100000074310017FF0050040100000074310017FF7F00000000000000050100000000000000030100000003001700010000666F7572
55 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000100010080000001000000020003010100000300170100000003001700010000666F7572
56 5487ffffff7f
+ 57 54015052494e5446110017120004
} {
do_test 2.$tn {
set changeset [binary decode hex $blob]
diff --git a/ext/session/sessionC.test b/ext/session/sessionC.test
index 74370cb79..1997ba5e8 100644
--- a/ext/session/sessionC.test
+++ b/ext/session/sessionC.test
@@ -192,6 +192,16 @@ do_test 3.3 {
}
} {1 1 3 3}
+#-------------------------------------------------------------------------
+#
+reset_db
+set C [binary format c* 0x54 0x01 0x01 0x00 0x12 0x00 0x05]
+do_test 4.0 {
+ sqlite3changegroup grp
+ list [catch { grp add $C } msg] $msg
+} {1 SQLITE_CORRUPT}
+grp delete
finish_test
+
diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c
index 90fedc6db..5468dff28 100644
--- a/ext/session/sqlite3session.c
+++ b/ext/session/sqlite3session.c
@@ -349,6 +349,20 @@ static int sessionVarintGet(const u8 *aBuf, int *piVal){
return getVarint32(aBuf, *piVal);
}
+/*
+** Read a varint value from buffer aBuf[], size nBuf bytes, into *piVal.
+** Return the number of bytes read.
+*/
+static int sessionVarintGetSafe(const u8 *aBuf, int nBuf, int *piVal){
+ u8 aCopy[5];
+ const u8 *aRead = aBuf;
+ if( nBuf<5 ){
+ memcpy(aCopy, aBuf, nBuf);
+ aRead = aCopy;
+ }
+ return getVarint32(aRead, *piVal);
+}
+
/* Load an unaligned and unsigned 32-bit integer */
#define SESSION_UINT32(x) (((u32)(x)[0]<<24)|((x)[1]<<16)|((x)[2]<<8)|(x)[3])
@@ -643,14 +657,10 @@ static unsigned int sessionChangeHash(
int isPK = pTab->abPK[i];
if( bPkOnly && isPK==0 ) continue;
- /* It is not possible for eType to be SQLITE_NULL here. The session
- ** module does not record changes for rows with NULL values stored in
- ** primary key columns. */
assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
|| eType==SQLITE_TEXT || eType==SQLITE_BLOB
|| eType==SQLITE_NULL || eType==0
);
- assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) );
if( isPK ){
a++;
@@ -658,12 +668,16 @@ static unsigned int sessionChangeHash(
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
h = sessionHashAppendI64(h, sessionGetI64(a));
a += 8;
- }else{
+ }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
int n;
a += sessionVarintGet(a, &n);
h = sessionHashAppendBlob(h, n, a);
a += n;
}
+ /* It should not be possible for eType to be SQLITE_NULL or 0x00 here,
+ ** as the session module does not record changes for rows with NULL
+ ** values stored in primary key columns. But a corrupt changesets
+ ** may contain such a value. */
}else{
a += sessionSerialLen(a);
}
@@ -3072,10 +3086,13 @@ static int sessionGenerateChangeset(
}
if( pSession->rc ) return pSession->rc;
- rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0);
- if( rc!=SQLITE_OK ) return rc;
sqlite3_mutex_enter(sqlite3_db_mutex(db));
+ rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0);
+ if( rc!=SQLITE_OK ){
+ sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ return rc;
+ }
for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
if( pTab->nEntry ){
@@ -3558,7 +3575,8 @@ static int sessionReadRecord(
u8 *aVal = &pIn->aData[pIn->iNext];
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
int nByte;
- pIn->iNext += sessionVarintGet(aVal, &nByte);
+ int nRem = pIn->nData - pIn->iNext;
+ pIn->iNext += sessionVarintGetSafe(aVal, nRem, &nByte);
rc = sessionInputBuffer(pIn, nByte);
if( rc==SQLITE_OK ){
if( nByte<0 || nByte>pIn->nData-pIn->iNext ){
@@ -3611,7 +3629,8 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){
rc = sessionInputBuffer(pIn, 9);
if( rc==SQLITE_OK ){
- nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol);
+ int nBuf = pIn->nData - pIn->iNext;
+ nRead += sessionVarintGetSafe(&pIn->aData[pIn->iNext], nBuf, &nCol);
/* The hard upper limit for the number of columns in an SQLite
** database table is, according to sqliteLimit.h, 32676. So
** consider any table-header that purports to have more than 65536
@@ -3631,8 +3650,15 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){
while( (pIn->iNext + nRead)nData && pIn->aData[pIn->iNext + nRead] ){
nRead++;
}
+
+ /* Break out of the loop if if the nul-terminator byte has been found.
+ ** Otherwise, read some more input data and keep seeking. If there is
+ ** no more input data, consider the changeset corrupt. */
if( (pIn->iNext + nRead)nData ) break;
rc = sessionInputBuffer(pIn, nRead + 100);
+ if( rc==SQLITE_OK && (pIn->iNext + nRead)>=pIn->nData ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }
}
*pnByte = nRead+1;
return rc;
@@ -3764,10 +3790,10 @@ static int sessionChangesetNextOne(
memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
}
- /* Make sure the buffer contains at least 10 bytes of input data, or all
- ** remaining data if there are less than 10 bytes available. This is
- ** sufficient either for the 'T' or 'P' byte and the varint that follows
- ** it, or for the two single byte values otherwise. */
+ /* Make sure the buffer contains at least 2 bytes of input data, or all
+ ** remaining data if there are less than 2 bytes available. This is
+ ** sufficient either for the 'T' or 'P' byte that begins a new table,
+ ** or for the "op" and "bIndirect" single bytes otherwise. */
p->rc = sessionInputBuffer(&p->in, 2);
if( p->rc!=SQLITE_OK ) return p->rc;
@@ -3797,11 +3823,13 @@ static int sessionChangesetNextOne(
return (p->rc = SQLITE_CORRUPT_BKPT);
}
- p->op = op;
- p->bIndirect = p->in.aData[p->in.iNext++];
- if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
+ if( (op!=SQLITE_UPDATE && op!=SQLITE_DELETE && op!=SQLITE_INSERT)
+ || (p->in.iNext>=p->in.nData)
+ ){
return (p->rc = SQLITE_CORRUPT_BKPT);
}
+ p->op = op;
+ p->bIndirect = p->in.aData[p->in.iNext++];
if( paRec ){
int nVal; /* Number of values to buffer */
diff --git a/ext/session/test_session.c b/ext/session/test_session.c
index 6ad5b3774..46a9e3a38 100644
--- a/ext/session/test_session.c
+++ b/ext/session/test_session.c
@@ -7,6 +7,8 @@
#include
#include "tclsqlite.h"
+#include
+
#ifndef SQLITE_AMALGAMATION
typedef unsigned char u8;
#endif
@@ -856,6 +858,21 @@ static int testStreamInput(
return SQLITE_OK;
}
+/*
+** This works like Tcl_GetByteArrayFromObj(), except that it returns a buffer
+** allocated using malloc() that must be freed by the caller. This is done
+** because Tcl's buffers are often padded by a few bytes, which prevents
+** small overreads from being detected when tests are run under asan.
+*/
+static void *testGetByteArrayFromObj(Tcl_Obj *p, Tcl_Size *pnByte){
+ Tcl_Size nByte = 0;
+ void *aByte = Tcl_GetByteArrayFromObj(p, &nByte);
+ void *aCopy = malloc(nByte ? (size_t)nByte : 1);
+ memcpy(aCopy, aByte, (size_t)nByte);
+ *pnByte = nByte;
+ return aCopy;
+}
+
static int SQLITE_TCLAPI testSqlite3changesetApply(
int iVersion,
@@ -920,7 +937,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
return TCL_ERROR;
}
db = *(sqlite3 **)info.objClientData;
- pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
+ pChangeset = (void *)testGetByteArrayFromObj(objv[2], &nChangeset);
ctx.pConflictScript = objv[3];
ctx.pFilterScript = objc==5 ? objv[4] : 0;
ctx.interp = interp;
@@ -972,6 +989,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
}
}
+ free(pChangeset);
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc, 0);
}else{
@@ -1195,7 +1213,12 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
pCS = objv[2];
pScript = objv[3];
- pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset);
+ /* Take a copy of the changeset into an exact sized buffer allocated
+ ** using malloc(). The Tcl buffer will be padded by a few bytes, which
+ ** prevents small overreads from being detected by ASAN when the tests
+ ** are run. */
+ pChangeset = (void*)testGetByteArrayFromObj(pCS, &nChangeset);
+
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
if( isInvert ){
int f = SQLITE_CHANGESETSTART_INVERT;
@@ -1216,32 +1239,33 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr);
}
}
- if( rc!=SQLITE_OK ){
- return test_session_error(interp, rc, 0);
- }
- while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
- Tcl_Obj *pVar = 0; /* Tcl value to set $VARNAME to */
- pVar = testIterData(pIter);
- Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
- rc = Tcl_EvalObjEx(interp, pScript, 0);
- if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
- sqlite3changeset_finalize(pIter);
- return rc==TCL_BREAK ? TCL_OK : rc;
+ if( rc==SQLITE_OK ){
+ while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
+ Tcl_Obj *pVar = 0; /* Tcl value to set $VARNAME to */
+ pVar = testIterData(pIter);
+ Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
+ rc = Tcl_EvalObjEx(interp, pScript, 0);
+ if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
+ sqlite3changeset_finalize(pIter);
+ free(pChangeset);
+ return rc==TCL_BREAK ? TCL_OK : rc;
+ }
}
- }
- if( isCheckNext ){
- int rc2 = sqlite3changeset_next(pIter);
- rc = sqlite3changeset_finalize(pIter);
- assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
- }else{
- rc = sqlite3changeset_finalize(pIter);
+ if( isCheckNext ){
+ int rc2 = sqlite3changeset_next(pIter);
+ rc = sqlite3changeset_finalize(pIter);
+ assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
+ }else{
+ rc = sqlite3changeset_finalize(pIter);
+ }
}
+
+ free(pChangeset);
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc, 0);
}
-
return TCL_OK;
}
diff --git a/ext/wasm/EXPORTED_FUNCTIONS.fiddle.in b/ext/wasm/EXPORTED_FUNCTIONS.fiddle.in
deleted file mode 100644
index 103704df1..000000000
--- a/ext/wasm/EXPORTED_FUNCTIONS.fiddle.in
+++ /dev/null
@@ -1,10 +0,0 @@
-_fiddle_db_arg
-_fiddle_db_filename
-_fiddle_exec
-_fiddle_experiment
-_fiddle_interrupt
-_fiddle_main
-_fiddle_reset_db
-_fiddle_db_handle
-_fiddle_db_vfs
-_fiddle_export_db
diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile
index 937e16d6e..dc3c2d255 100644
--- a/ext/wasm/GNUmakefile
+++ b/ext/wasm/GNUmakefile
@@ -1,5 +1,6 @@
-#######################################################################
-# This GNU makefile creates the canonical sqlite3 WASM builds.
+#
+# This GNU makefile creates the canonical sqlite3 WASM builds. Plus some
+# others.
#
# This build assumes a Linux platform and is not intended for
# general-purpose client-level use, except for creating builds with
@@ -10,28 +11,33 @@
#
# default, all = build in dev mode
#
-# o0, o1, o2, o3, os, oz = full clean/rebuild with the -Ox level indicated
-# by the target name. Rebuild is necessary for all components to get
-# the desired optimization level.
+# o0, o1, o2, o3, os, oz = full clean/rebuild with the -Ox level
+# indicated by the target name. A clean rebuild is necessary for
+# all components to get the desired optimization level.
#
# dist = create end user deliverables. Add dist.build=oX to build
-# with a specific optimization level, where oX is one of the
-# above-listed o? or qo? target names.
+# with a specific optimization level, where oX is one of the
+# above-listed o? target names.
#
# snapshot = like dist, but uses a zip file name which clearly
-# marks it as a prerelease/snapshot build.
+# marks it as a prerelease/snapshot build.
#
# clean = clean up
#
# Required tools beyond those needed for the canonical builds:
#
# - Emscripten SDK: https://emscripten.org/docs/getting_started/downloads.html
+#
# - The bash shell
+#
# - GNU make, GNU sed, GNU awk, GNU grep (all in the $PATH and without
# a "g" prefix like they have on some non-GNU systems)
-# - wasm-strip for release builds: https://github.com/WebAssembly/wabt
+#
+# - wasm-strip for release builds: https://github.com/WebAssembly/wabt.
+# It will build without this but the .wasm files will be huge.
+#
# - InfoZip for 'dist' zip file
-########################################################################
+#
default: all
MAKEFILE = $(lastword $(MAKEFILE_LIST))
CLEAN_FILES =
@@ -80,6 +86,7 @@ emo.strip = 💈
emo.test = 🧪
emo.tool = 🔨
emo.wasm-opt = 🧼
+emo.cleanup = 🧼
# 👷🪄🧮🧫🧽🍿⛽🚧🎱🪚🏆🧼
#
@@ -199,7 +206,7 @@ b.mkdir@ = if [ ! -d $(dir $@) ]; then \
# $1 = logtag, $2 = src file(s). $3 = dest dir
b.cp = $(call b.mkdir@); \
echo '$(logtag.$(1)) $(emo.disk) $(2) ==> $3'; \
- cp -p $(2) $(3) || exit
+ cp -f -p $(2) $(3) || exit
#
# $(call b.c-pp.shcmd,LOGTAG,src,dest,-Dx=y...)
@@ -213,9 +220,8 @@ b.cp = $(call b.mkdir@); \
# $4 = optional $(bin.c-pp) flags
define b.c-pp.shcmd
$(call b.mkdir@); \
-$(call b.echo,$(1),$(emo.disk)$(emo.lock) $(bin.c-pp) $(4) $(if $(loud.if),$(2))); \
-rm -f $(3); \
-$(bin.c-pp) -o $(3) $(4) $(2) || exit; \
+$(call b.echo,$(1),$(emo.disk)$(emo.lock)[$(3)] $(4)); \
+rm -f $(3); $(bin.c-pp) -o $(3) $(4) $(2) || exit; \
chmod -w $(3)
endef
@@ -227,10 +233,38 @@ endef
# Args: as for $(b.c-pp.shcmd).
define b.c-pp.target
$(3): $$(MAKEFILE_LIST) $$(bin.c-pp) $(2)
- @$$(call b.c-pp.shcmd,$(1),$(2),$(3),$(4) $(b.c-pp.target.flags))
+ @$$(call b.mkdir@)
+ @$$(call b.c-pp.shcmd,$(1),$(2),$(3),$(4) $$(b.c-pp.target.flags))
CLEAN_FILES += $(3)
endef
+
+#
+# The various -D... values used by *.c-pp.js include:
+#
+# -Dtarget:es6-module: for all ESM module builds
+#
+# -Dtarget:node: for node.js builds
+#
+# -Dtarget:es6-module -Dtarget:es6-bundler-friendly: intended for
+# "bundler-friendly" ESM module build. These have some restrictions
+# on how URL() objects are constructed in some contexts: URLs which
+# refer to files which are part of this project must be referenced
+# as string literals so that bundlers' static-analysis tools can
+# find those files and include them in their bundles.
+#
+# -Dtarget:es6-module -Dtarget:node: is intended for use by node.js
+# for node.js, as opposed to by node.js on behalf of a
+# browser. Mixing -sENVIRONMENT=web and -sENVIRONMENT=node leads to
+# ambiguity and confusion on node's part, as it's unable to
+# reliably determine whether the target is a browser or node.
+#
+# To repeat: all node.js builds are 100% untested and unsupported.
+#
+# Most c-pp.D.X are set via $(bin.mkwb) and X is a build name.
+# Those make rules reference c-pp.D.64bit, so it should be defined in
+# advance.
+#
c-pp.D.64bit = -Dbits64
#
@@ -248,11 +282,21 @@ c-pp.D.64bit = -Dbits64
#
# This is intended to be used in makefile targets which generate an
# Emscripten module and where $@ is the module's .js/.mjs file.
+#
+# This is inherently fragile and has been broken by Emscripten updates
+# before.
+#
+ifeq (1,1)
+define b.strip-js-emcc-bindings
+echo "$(1) $(emo.garbage) Stripping export wrappers."; \
+sed -i -e '/var _sqlite3.*makeInvalidEarly.*;/d' \
+-e '/assert.*sqlite3.*missing.*;/d' \
+-e '/_sqlite.*createExportWrapper.*;/d' $@
+endef
+else
b.strip-js-emcc-bindings = \
- sed -i -e '/^.*= \(_sqlite3\|_fiddle\)[^=]*=.*createExportWrapper/d' \
- -e '/^var \(_sqlite3\|_fiddle\)[^=]*=.*makeInvalidEarlyAccess/d' $@ || exit; \
- echo '$(1) $(emo.garbage) (Probably) /createExportWrapper()/d and /makeInvalidEarlyAccess()/d'
-
+ echo '$(1) $(emo.bug) (disabled because it breaks emsdk 4.0.16+)'
+endif
#
# Set up sqlite3.c and sqlite3.h...
@@ -271,33 +315,36 @@ b.strip-js-emcc-bindings = \
# $(sqlite3.canonical.c) must point to the sqlite3.c in
# the sqlite3 canonical source tree, as that source file
# is required for certain utility and test code.
+#
sqlite3.canonical.c = $(dir.top)/sqlite3.c
sqlite3.c ?= $(firstword $(wildcard $(dir.top)/sqlite3-see.c) $(sqlite3.canonical.c))
-sqlite3.h = $(dir $(sqlite3.c))/sqlite3.h
+sqlite3.h = $(dir $(sqlite3.c))sqlite3.h
#
# bin.version-info = binary to output various sqlite3 version info for
# embedding in the JS files and in building the distribution zip file.
# It must NOT be in $(dir.tmp) because we need it to survive the
# cleanup process for the dist build to work properly.
+#
bin.version-info = ./version-info
$(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
$(CC) -o $@ -I$(dir $(sqlite3.h)) $(dir.tool)/version-info.c
t-version-info: $(bin.version-info)
DISTCLEAN_FILES += $(bin.version-info)
+
#
# bin.stripcomments is used for stripping C/C++-style comments from JS
# files. The JS files contain large chunks of documentation which we
# don't need for all builds. That app's -k flag is of particular
# importance here, as it allows us to retain the opening comment
# block(s), which contain the license header and version info.
+#
bin.stripccomments = $(dir.tool)/stripccomments
$(bin.stripccomments): $(bin.stripccomments).c $(MAKEFILE)
$(CC) -o $@ $<
t-stripccomments: $(bin.stripccomments)
DISTCLEAN_FILES += $(bin.stripccomments)
-
ifeq (1,$(MAKING_CLEAN))
SQLITE_C_IS_SEE = 0
else
@@ -325,7 +372,7 @@ endif
# undefine barebones # relatively new gmake feature, not ubiquitous
#
-# It's important that sqlite3.h be built to completion before any
+# It's important that sqlite3.[ch] be built to completion before any
# other parts of the build run, thus we use .NOTPARALLEL to disable
# parallel build of that file and its dependants. However, that makes
# the whole build non-parallelizable because everything has a dep on
@@ -342,8 +389,10 @@ $(sqlite3.h):
# $(MAKE) -C $(dir.top) sqlite3.c
$(sqlite3.c): $(sqlite3.h)
+#
# Common options for building sqlite3-wasm.c and speedtest1.c.
# Explicit ENABLEs...
+#
SQLITE_OPT.common = \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_TEMP_STORE=2 \
@@ -360,11 +409,15 @@ SQLITE_OPT.common = \
# removing them from this list will serve only to break the speedtest1
# builds.
+#
# Currently always needed but TODO is paring tester1.c-pp.js down
# to be able to run without this:
+#
SQLITE_OPT.common += -DSQLITE_WASM_ENABLE_C_TESTS
+#
# Extra flags for full-featured builds...
+#
SQLITE_OPT.full-featured = \
-DSQLITE_ENABLE_BYTECODE_VTAB \
-DSQLITE_ENABLE_DBPAGE_VTAB \
@@ -421,13 +474,14 @@ else
# -DSQLITE_OMIT_WINDOWFUNC
endif
+#
#SQLITE_OPT += -DSQLITE_DEBUG
# Enabling SQLITE_DEBUG will break sqlite3_wasm_vfs_create_file()
# (and thus sqlite3_js_vfs_create_file()). Those functions are
# deprecated and alternatives are in place, but this crash behavior
# can be used to find errant uses of sqlite3_js_vfs_create_file()
# in client code.
-########################################################################
+#
# The following flags are hard-coded into sqlite3-wasm.c and cannot be
# modified via the build process:
#
@@ -436,10 +490,9 @@ endif
# SQLITE_OMIT_DEPRECATED
# SQLITE_OMIT_UTF16
# SQLITE_OMIT_SHARED_CACHE
-########################################################################
-
+#
-########################################################################
+#
# Adding custom C code via sqlite3_wasm_extra_init.c:
#
# If the canonical build process finds the file
@@ -460,7 +513,7 @@ endif
# make sqlite3_wasm_extra_init.c=my_custom_stuff.c
#
# See example_extra_init.c for an example implementation.
-########################################################################
+#
sqlite3_wasm_extra_init.c ?= $(wildcard sqlite3_wasm_extra_init.c)
cflags.wasm_extra_init =
ifneq (,$(sqlite3_wasm_extra_init.c))
@@ -481,7 +534,7 @@ endif
#
WASM_CUSTOM_INSTANTIATE = 1
-########################################################################
+#
# $(bin.c-pp): a minimal text file preprocessor. Like C's but much
# less so.
#
@@ -512,6 +565,7 @@ WASM_CUSTOM_INSTANTIATE = 1
#
# -D... flags which should be included in all invocations should be
# appended to $(b.c-pp.target.flags).
+#
bin.c-pp = ./c-pp-lite
$(bin.c-pp): c-pp-lite.c $(sqlite3.c) $(MAKEFILE)
$(CC) -O0 -o $@ c-pp-lite.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \
@@ -523,7 +577,12 @@ b.c-pp.target.flags ?=
ifeq (1,$(SQLITE_C_IS_SEE))
b.c-pp.target.flags += -Denable-see
endif
+api.oo1 ?= 1
+ifeq (0,$(api.oo1))
+ b.c-pp.target.flags += -Domit-oo1
+endif
+#
# cflags.common = C compiler flags for all builds
cflags.common = -I. -I$(dir $(sqlite3.c)) -std=c99 -fPIC
# emcc.WASM_BIGINT = 1 for BigInt (C int64) support, else 0. The API
@@ -531,13 +590,14 @@ cflags.common = -I. -I$(dir $(sqlite3.c)) -std=c99 -fPIC
# _are not tested_ on any regular basis.
emcc.WASM_BIGINT ?= 1
emcc.MEMORY64 ?= 0
-########################################################################
+#
# https://emscripten.org/docs/tools_reference/settings_reference.html#memory64
#
# 64-bit build requires wasm-strip 1.0.36 (maybe 1.0.35, but not
# 1.0.34) or will fail to strip with "tables may not be 64-bit".
-########################################################################
+#
+#
# emcc_opt = optimization-related flags. These are primarily used by
# the various oX targets. build times for -O levels higher than 0 are
# painful at dev-time.
@@ -549,14 +609,17 @@ emcc.MEMORY64 ?= 0
# -O2 (which consistently creates the fastest-running deliverables).
# Build time suffers greatly compared to -O0, which is why -O0 is the
# default.
+#
ifeq (,$(filter $(OPTIMIZED_TARGETS),$(MAKECMDGOALS)))
emcc_opt ?= -O0
else
emcc_opt ?= -Oz
endif
+#
# When passing emcc_opt from the CLI, += and re-assignment have no
# effect, so emcc_opt+=-g3 doesn't work. So...
+#
emcc_opt_full = $(emcc_opt) -g3
# ^^^ ALWAYS use -g3. See below for why.
#
@@ -583,26 +646,26 @@ emcc_opt_full = $(emcc_opt) -g3
# Much practice has demonstrated that -O2 consistently gives the best
# runtime speeds, but not by a large enough factor to rule out use of
# -Oz when smaller deliverable size is a priority.
-########################################################################
+#
-########################################################################
+#
# EXPORTED_FUNCTIONS.* = files for use with Emscripten's
# -sEXPORTED_FUNCTION flag.
-EXPORTED_FUNCTIONS.api.core = $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-core
-EXPORTED_FUNCTIONS.api.in = $(EXPORTED_FUNCTIONS.api.core)
-ifeq (1,$(SQLITE_C_IS_SEE))
- EXPORTED_FUNCTIONS.api.in += $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-see
-endif
-ifeq (0,$(wasm-bare-bones))
- EXPORTED_FUNCTIONS.api.in += $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-extras
-endif
+#
+EXPORTED_FUNCTIONS.api.in = $(dir.api)/EXPORTED_FUNCTIONS.c-pp
EXPORTED_FUNCTIONS.api = $(dir.tmp)/EXPORTED_FUNCTIONS.api
-$(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(sqlite3.c) $(MAKEFILE)
- @$(call b.mkdir@)
- cat $(EXPORTED_FUNCTIONS.api.in) > $@
+EXPORTED_FUNCTIONS.c-pp.flags =
+ifeq (1,$(wasm-bare-bones))
+ EXPORTED_FUNCTIONS.c-pp.flags += -Dbare-bones
+endif
+$(eval $(call b.c-pp.target,filter,\
+ $(EXPORTED_FUNCTIONS.api.in),\
+ $(EXPORTED_FUNCTIONS.api),\
+ $(EXPORTED_FUNCTIONS.c-pp.flags)))
-########################################################################
+#
# emcc flags for .c/.o/.wasm/.js.
+#
emcc.flags =
ifeq (1,$(emcc.verbose))
emcc.flags += -v
@@ -690,9 +753,9 @@ ifeq (,$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)))
$(error emcc.INITIAL_MEMORY must be one of: 4, 8, 16, 32, 64, 96, 128 (megabytes))
endif
emcc.jsflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY))
+#
# /INITIAL_MEMORY
-########################################################################
-#emcc.jsflags += -sMEMORY64=$(emcc.MEMORY64)
+#
emcc.jsflags += $(emcc.environment)
emcc.jsflags += -sSTACK_SIZE=512KB
@@ -701,7 +764,8 @@ emcc.jsflags += -sSTACK_SIZE=512KB
# VFS, which requires twice that for its xRead() and xWrite() methods.
# 2023-03: those methods have since been adapted to use a malloc()'d
# buffer.
-########################################################################
+
+#
# $(sqlite3.js.init-func) is the name Emscripten assigns our exported
# module init/load function. This symbol name is hard-coded in
# $(extern-post-js.js) as well as in numerous docs.
@@ -741,7 +805,7 @@ emcc.jsflags += -sLLD_REPORT_UNDEFINED
#emcc.jsflags += --experimental-pic --unresolved-symbols=ingore-all --import-undefined
#emcc.jsflags += --unresolved-symbols=ignore-all
-########################################################################
+#
# -sSINGLE_FILE:
# https://github.com/emscripten-core/emscripten/blob/main/src/settings.js
#
@@ -750,12 +814,7 @@ emcc.jsflags += -sLLD_REPORT_UNDEFINED
# cannot wasm-strip the binary before it gets encoded into the JS
# file. The result is that the generated JS file is, because of the
# -g3 debugging info, _huge_.
-########################################################################
-
-
-sqlite3.wasm = $(dir.dout)/sqlite3.wasm
-sqlite3-wasm.c = $(dir.api)/sqlite3-wasm.c
-sqlite3-wasm.c.in = $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c)
+#
#
# b.call.patch-export-default is used by mkwasmbuilds.c and the
@@ -798,51 +857,6 @@ if [ x1 = x$(1) ]; then \
fi
endef
-#
-# The various -D... values used by *.c-pp.js include:
-#
-# -Dtarget:es6-module: for all ESM module builds
-#
-# -Dtarget:node: for node.js builds
-#
-# -Dtarget:es6-module -Dtarget:es6-bundler-friendly: intended for
-# "bundler-friendly" ESM module build. These have some restrictions
-# on how URL() objects are constructed in some contexts: URLs which
-# refer to files which are part of this project must be referenced
-# as string literals so that bundlers' static-analysis tools can
-# find those files and include them in their bundles.
-#
-# -Dtarget:es6-module -Dtarget:node: is intended for use by node.js
-# for node.js, as opposed to by node.js on behalf of a
-# browser. Mixing -sENVIRONMENT=web and -sENVIRONMENT=node leads to
-# ambiguity and confusion on node's part, as it's unable to
-# reliably determine whether the target is a browser or node.
-#
-# To repeat: all node.js builds are 100% untested and unsupported.
-#
-########################################################################
-
-#
-# Inputs/outputs for the sqlite3-api.js family.
-#
-# sqlite3-api.jses = the list of JS files which make up
-# sqlite3-api.js, in the order they need to be assembled.
-sqlite3-api.jses = $(sqlite3-license-version.js)
-sqlite3-api.jses += $(dir.api)/sqlite3-api-prologue.js
-sqlite3-api.jses += $(dir.common)/whwasmutil.js
-sqlite3-api.jses += $(dir.jacc)/jaccwabyt.js
-sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.c-pp.js
-sqlite3-api.jses += $(sqlite3-api-build-version.js)
-sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.c-pp.js
-sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.c-pp.js
-sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.c-pp.js
-ifeq (0,$(wasm-bare-bones))
- sqlite3-api.jses += $(dir.api)/sqlite3-vtab-helper.c-pp.js
-endif
-sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js
-sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js
-sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js
-
#
# $(sqlite3-license-version.js) contains the license header and
# in-comment build version info.
@@ -852,17 +866,15 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js
#
sqlite3-license-version.js = $(dir.tmp)/sqlite3-license-version.js
$(sqlite3-license-version.js): $(bin.version-info) \
- $(dir.api)/sqlite3-license-version-header.js
- @echo '$(logtag.@) $(emo.disk)'; { \
- $(call b.mkdir@); \
+ $(dir.api)/sqlite3-license-version-header.js $(MAKEFILE)
+ @$(call b.mkdir@); echo '$(logtag.@) $(emo.disk)'; { \
cat $(dir.api)/sqlite3-license-version-header.js || exit $$?; \
- echo '/*'; \
+ echo '/* @preserve'; \
echo '** This code was built from sqlite3 version...'; \
echo '**'; \
awk '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' $(sqlite3.h); \
awk '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \
echo '**'; echo '** Emscripten SDK: $(emcc.version)'; \
- echo '**'; \
echo '*/'; \
} > $@
@@ -881,6 +893,34 @@ $(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE)
echo '});'; \
} > $@
+#
+# Inputs/outputs for the sqlite3-api.js family.
+#
+# sqlite3-api.jses = the list of JS files which make up
+# sqlite3-api.js, in the order they need to be assembled.
+sqlite3-api.jses = $(sqlite3-license-version.js)
+sqlite3-api.jses += $(dir.api)/sqlite3-api-prologue.js
+sqlite3-api.jses += $(sqlite3-api-build-version.js)
+sqlite3-api.jses += $(dir.common)/whwasmutil.js
+sqlite3-api.jses += $(dir.jacc)/jaccwabyt.js
+sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.c-pp.js
+sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.c-pp.js
+sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.c-pp.js
+sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.c-pp.js
+ifeq (0,$(wasm-bare-bones))
+ sqlite3-api.jses += $(dir.api)/sqlite3-vtab-helper.c-pp.js
+endif
+sqlite3-api.jses += $(dir.api)/sqlite3-vfs-kvvfs.c-pp.js
+sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js
+sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js
+
+# Parallel builds can fail if $(sqlite3-license-version.js) is not
+# created early enough, so make all files in $(sqlite-api.jses) except
+# for $(sqlite3-license-version.js) depend on
+# $(sqlite3-license-version.js).
+deps.jses = $(filter-out $(sqlite3-license-version.js),$(sqlite3-api.jses))
+$(deps.jses): $(sqlite3-license-version.js)
+
#
# extern-post-js* and extern-pre-js* are files for use with
# Emscripten's --extern-pre-js and --extern-post-js flags.
@@ -918,7 +958,7 @@ $(post-js.in.js): $(MKDIR.bld) $(post-jses.js) $(MAKEFILE)
done > $@
#
-# speedtest1 decls needed before the $(bin.mkws)-generated makefile
+# speedtest1 decls needed before the $(bin.mkwb)-generated makefile
# is included.
#
bin.speedtest1 = ../../speedtest1
@@ -957,12 +997,21 @@ endif
# /shell.c
########################################################################
+#
+# Fiddle-related decls we need before .wasmbuilds is included
+#
+
+fiddle.c.in = $(dir.top)/shell.c $(sqlite3-wasm.c)
+
EXPORTED_FUNCTIONS.fiddle = $(dir.tmp)/EXPORTED_FUNCTIONS.fiddle
-$(EXPORTED_FUNCTIONS.fiddle): $(fiddle.EXPORTED_FUNCTIONS.in) $(MAKEFILE_LIST)
- @$(b.mkdir@)
- @sort -u $(fiddle.EXPORTED_FUNCTIONS.in) > $@
+$(EXPORTED_FUNCTIONS.fiddle): $(EXPORTED_FUNCTIONS.api.in) \
+ $(MAKEFILE_LIST) $(bin.c-pp)
+ @$(call b.mkdir@)
+ @$(call b.c-pp.shcmd,fiddle,$(EXPORTED_FUNCTIONS.api.in),\
+ $@,$(EXPORTED_FUNCTIONS.c-pp.flags) -Dfiddle)
@echo $(logtag.@) $(emo.disk)
+
emcc.flags.fiddle = \
$(emcc.cflags) $(emcc_opt_full) \
--minify 0 \
@@ -987,26 +1036,23 @@ emcc.flags.fiddle = \
-USQLITE_WASM_BARE_BONES \
-DSQLITE_SHELL_FIDDLE
-clean: clean-fiddle
-clean-fiddle:
- rm -f $(dir.fiddle)/fiddle-module.js \
- $(dir.fiddle)/*.wasm \
- $(dir.fiddle)/sqlite3-opfs-*.js \
- $(dir.fiddle)/*.gz \
- EXPORTED_FUNCTIONS.fiddle
- rm -fr $(dir.fiddle-debug)
-
emcc.flags.fiddle.debug = $(emcc.flags.fiddle) \
-DSQLITE_DEBUG \
-DSQLITE_ENABLE_SELECTTRACE \
-DSQLITE_ENABLE_WHERETRACE
-fiddle.EXPORTED_FUNCTIONS.in = \
- EXPORTED_FUNCTIONS.fiddle.in \
- $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-core \
- $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-extras
-
-fiddle.c.in = $(dir.top)/shell.c $(sqlite3-wasm.c)
+clean: clean-fiddle
+clean-fiddle:
+ rm -f $(dir.fiddle)/fiddle-module.js \
+ $(dir.fiddle)/*.wasm \
+ $(dir.fiddle)/sqlite3-opfs-*.js \
+ $(dir.fiddle)/*.gz \
+ $(dir.fiddle)/index.html \
+ $(EXPORTED_FUNCTIONS.fiddle)
+ rm -fr $(dir.fiddle-debug)
+distclean: distclean-fiddle
+distclean-fiddle:
+ rm -fr $(dir.fiddle)/jqterm
#
# WASMFS build - unsupported and untested. We used WASMFS
@@ -1028,6 +1074,14 @@ cflags.wasmfs = -DSQLITE_ENABLE_WASMFS
# end wasmfs (the rest is in mkwasmbuilds.c)
#
+#
+#
+#
+sqlite3-wasm.c = $(dir.api)/sqlite3-wasm.c
+# List of input files for compiling $(sqlite3-wasm.c). That file
+# #include's sqlite3.c directly, so it's implicitly includes here.
+sqlite3-wasm.c.in = $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c)
+
#
# $(bin.mkwb) is used for generating much of the makefile code for the
# various wasm builds. It used to be generated in this makefile via a
@@ -1083,10 +1137,10 @@ sqlite3.ext.js =
define gen-worker1
# $1 = X.ext part of sqlite3-worker1X.ext
# $2 = $(c-pp.D.NAME)
-$(call b.c-pp.target,filter,$(dir.api)/sqlite3-worker1.c-pp.js,\
- $(dir.dout)/sqlite3-worker1$(1),$(2))
-sqlite3.ext.js += $(dir.dout)/sqlite3-worker1$(1)
-all: $(dir.dout)/sqlite3-worker1$(1)
+$(call b.c-pp.target,filter,$$(dir.api)/sqlite3-worker1.c-pp.js,\
+ $$(dir.dout)/sqlite3-worker1$(1),$(2))
+sqlite3.ext.js += $$(dir.dout)/sqlite3-worker1$(1)
+all: $$(dir.dout)/sqlite3-worker1$(1)
endef
$(eval $(call gen-worker1,.js,$(c-pp.D.vanilla)))
@@ -1100,10 +1154,10 @@ $(eval $(call gen-worker1,-bundler-friendly.mjs,$(c-pp.D.bundler)))
define gen-promiser
# $1 = X.ext part of sqlite3-worker1-promiserX.ext
# $2 = $(c-pp.D.NAME)
-$(call b.c-pp.target,filter,$(dir.api)/sqlite3-worker1-promiser.c-pp.js,\
- $(dir.dout)/sqlite3-worker1-promiser$(1),$(2))
-sqlite3.ext.js += $(dir.dout)/sqlite3-worker1-promiser$(1)
-all: $(dir.dout)/sqlite3-worker1-promiser$(1)
+$(call b.c-pp.target,filter,$$(dir.api)/sqlite3-worker1-promiser.c-pp.js,\
+ $$(dir.dout)/sqlite3-worker1-promiser$(1),$(2))
+sqlite3.ext.js += $$(dir.dout)/sqlite3-worker1-promiser$(1)
+all: $$(dir.dout)/sqlite3-worker1-promiser$(1)
endef
$(eval $(call gen-promiser,.js,$(c-pp.D.vanilla)))
@@ -1145,12 +1199,19 @@ $(dir.dout)/sqlite3-opfs-async-proxy.js: $(dir.api)/sqlite3-opfs-async-proxy.js
# we don't otherwise have a great place to attach them such that
# they're always copied when we need them.
#
+# The var $(out.$(B).js) comes from $(bin.mkwb) and $(B) is the name
+# of a build set up by that tool, e.g. b-vanilla or b-esm64.
+#
$(foreach B,$(b.names),$(eval $(out.$(B).js): $(sqlite3.ext.js)))
+
#
# b-all: builds all available js/wasm builds.
#
$(foreach B,$(b.names),$(eval b-all: $(out.$(B).js)))
+#$(foreach B,$(b.names),$(eval pre: $(pre-js.$(B).js)))
+$(foreach B,$(b.names),$(eval post: $(post-js.$(B).js)))
+
#
# speedtest1 is our primary benchmarking tool.
#
@@ -1160,7 +1221,7 @@ $(foreach B,$(b.names),$(eval b-all: $(out.$(B).js)))
# These flags get applied via $(bin.mkwb).
emcc.speedtest1.common = $(emcc_opt_full)
emcc.speedtest1 = -I. -I$(dir $(sqlite3.canonical.c))
-emcc.speedtest1 += -sENVIRONMENT=web
+emcc.speedtest1 += -sENVIRONMENT=web,worker
emcc.speedtest1 += -sALLOW_MEMORY_GROWTH
emcc.speedtest1 += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.32)
emcc.speedtest1.common += -sINVOKE_RUN=0
@@ -1199,28 +1260,37 @@ speedtest1.exit-runtime1 = -sEXIT_RUNTIME=1
# -sEXIT_RUNTIME=1 but we need EXIT_RUNTIME=0 for the worker-based app
# which runs speedtest1 multiple times.
-$(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api.core)
- @$(call b.echo,@,$(emo.disk)); \
- $(call b.mkdir@); \
- { echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api.core); } > $@ || exit
+$(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api)
+ @$(call b.mkdir@); $(call b.echo,@,$(emo.disk)); \
+ { echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api); } > $@ || exit
speedtest1: b-speedtest1
+st: speedtest1
#
# Generate 64-bit variants of speedtest1*.{js,html}
#
+# $1 = input file
+# $2 = output file
+#
+# TODO: preprocess these like we do the rest.
+#
define gen-st64
$(2): $(1)
@$$(call b.echo,speedtest164,$$(emo.disk)$(emo.lock) Creating from $$<)
- @rm -f $$@; \
- sed -e 's/$(3)\.js/$(3)-64bit\.js/' < $$< > $$@; \
+ rm -f $$@; \
+ sed -e 's/speedtest1\.js/speedtest1-64bit\.js/' \
+ -e 's/speedtest1-worker\.js/speedtest1-worker-64bit\.js/' \
+ < $$< > $$@; \
chmod -w $$@
-b-speedtest164: $(2)
+$(2): b-speedtest164
+speedtest1: $(1) $(2)
CLEAN_FILES += $(2)
endef
+speedtest1: b-speedtest164
-$(eval $(call gen-st64,speedtest1.html,speedtest1-64bit.html,speedtest1))
-$(eval $(call gen-st64,speedtest1-worker.html,speedtest1-worker-64bit.html,speedtest1-worker))
-$(eval $(call gen-st64,speedtest1-worker.js,speedtest1-worker-64bit.js,speedtest1-worker))
+$(eval $(call gen-st64,speedtest1.html,speedtest1-64bit.html))
+$(eval $(call gen-st64,speedtest1-worker.html,speedtest1-worker-64bit.html))
+$(eval $(call gen-st64,speedtest1-worker.js,speedtest1-worker-64bit.js))
# end speedtest1.js
########################################################################
@@ -1244,9 +1314,11 @@ $(eval $(call gen-st64,speedtest1-worker.js,speedtest1-worker-64bit.js,speedtest
#
# To create those, we filter tester1.c-pp.js/html with $(bin.c-pp)...
+#
# tester1.js variants:
+#
define gen-tester1.js
-# $1 = build name to have dep on
+# $1 = build name to have a dep on
# $2 = suffix for tester1SUFFIX JS
# $3 = $(bin.c-pp) flags
$(call b.c-pp.target,test,tester1.c-pp.js,tester1$(2),$(3))
@@ -1255,20 +1327,18 @@ tester1-$(1): tester1$(2)
tester1: tester1$(2)
endef
-$(eval $(call gen-tester1.js,vanilla,.js, \
- $(c-pp.D.vanilla) \
- -Dsqlite3.js=$(dir.dout)/sqlite3.js))
-$(eval $(call gen-tester1.js,vanilla64,-64bit.js, \
- $(c-pp.D.vanilla64) \
- -Dsqlite3.js=$(dir.dout)/sqlite3-64bit.js))
-$(eval $(call gen-tester1.js,esm,.mjs, \
- $(c-pp.D.esm) \
- -Dsqlite3.js=$(dir.dout)/sqlite3.mjs))
-$(eval $(call gen-tester1.js,esm64,-64bit.mjs, \
- $(c-pp.D.esm64) \
- -Dsqlite3.js=$(dir.dout)/sqlite3-64bit.mjs))
+$(eval $(call gen-tester1.js,vanilla,.js,\
+ $(c-pp.D.vanilla) -Dsqlite3.js=$(dir.dout)/sqlite3.js))
+$(eval $(call gen-tester1.js,vanilla64,-64bit.js,\
+ $(c-pp.D.vanilla64) -Dsqlite3.js=$(dir.dout)/sqlite3-64bit.js))
+$(eval $(call gen-tester1.js,esm,.mjs,\
+ $(c-pp.D.esm) -Dsqlite3.js=$(dir.dout)/sqlite3.mjs))
+$(eval $(call gen-tester1.js,esm64,-64bit.mjs,\
+ $(c-pp.D.esm64) -Dsqlite3.js=$(dir.dout)/sqlite3-64bit.mjs))
+#
# tester1.html variants:
+#
define gen-tester1.html
# $1 = build name to have a dep on
# $2 = filename suffix: empty, -64bit, -esm, esm-64bit
@@ -1303,9 +1373,11 @@ $(eval $(call gen-tester1.html,esm64,-esm-64bit,\
-Dtester1.js=tester1-64bit.mjs \
-Dsqlite3.js=$(dir.dout)/sqlite3-64bit.mjs))
-# tester1-worker.html variants:
-# There is no ESM variant of this file. Instead, that page accepts a
-# ?esm URL flag to switch to ESM mode.
+#
+# tester1-worker.html variants: There is no ESM variant of this
+# file. Instead, that page accepts the ?esm URL flag to switch to ESM
+# mode.
+#
$(eval $(call b.c-pp.target,test,tester1-worker.c-pp.html,\
tester1-worker.html,-Dbitness=32))
$(eval $(call b.c-pp.target,test,tester1-worker.c-pp.html,\
@@ -1315,8 +1387,57 @@ tester1-worker.html: tester1.mjs
tester1-worker-64bit.html: tester1-64bit.mjs
all: tester1
+#
# end tester1
-########################################################################
+#
+
+#
+# jquery.terminal support for fiddle:
+#
+# If a clone of https://github.com/jcubic/jquery.terminal
+# is found in $(JQTERM), defaulting to $(HOME)/src/jquery.terminal
+# then add jquery.terminal support to fiddle.
+#
+# To build that package, from its checkout dir:
+#
+# npm install
+# make
+#
+c-pp.D.fiddle ?=
+JQTERM ?= $(HOME)/src/jquery.terminal
+dir.jqtermExt = $(firstword $(wildcard $(JQTERM)))
+#$(info dir.jqtermExt=$(dir.jqtermExt))
+ifeq (0,$(MAKING_CLEAN))
+ifeq (,$(wildcard $(dir.jqtermExt)/js/jquery.terminal.min.js))
+$(info $(emo.magic) To add jquery.terminal support to fiddle, set JQTERM=/path/to/its/built/checkout)
+else
+$(info $(emo.magic) jquery.terminal found in $(dir.jqtermExt) - adding it to fiddle. Make sure it is built!)
+
+dir.jqterm = $(dir.fiddle)/jqterm
+$(dir.fiddle)/jqterm/jquery.terminal.bundle.min.js:
+ @$(call b.mkdir@)
+ cat $(dir.jqtermExt)/js/jquery-1*.min.js \
+ $(dir.jqtermExt)/js/jquery.terminal.min.js > $@
+
+$(dir.fiddle)/jqterm/jquery.terminal.min.css: $(dir.jqtermExt)/css/jquery.terminal.min.css
+ @$(call b.mkdir@)
+ @$(call b.cp,fiddle,$<,$(dir $@))
+
+$(dir.fiddle)/index.html: $(dir.fiddle)/jqterm/jquery.terminal.bundle.min.js \
+ $(dir.fiddle)/jqterm/jquery.terminal.min.css
+c-pp.D.fiddle += -Djqterm
+endif
+endif
+# ^^^ JQTERM/MAKING_CLEAN
+
+#
+# Generate fiddle/index.html. Must come after JQTERM is handled.
+#
+$(dir.fiddle)/index.html: $(dir.fiddle)/index.c-pp.html
+$(eval $(call b.c-pp.target,fiddle,\
+ $(dir.fiddle)/index.c-pp.html,$(dir.fiddle)/index.html,$(c-pp.D.fiddle)))
+$(out.fiddle.wasm): $(dir.fiddle)/index.html
+
#
# Convenience rules to rebuild with various -Ox levels. Much
@@ -1327,6 +1448,7 @@ all: tester1
#
# Achtung: build times with anything higher than -O0 are somewhat
# painful, which is why -O0 is the default.
+#
.PHONY: o0 o1 o2 o3 os oz
emcc-opt-extra =
#ifeq (1,$(wasm-bare-bones))
@@ -1377,7 +1499,9 @@ push-testing:
ssh wasm-testing 'cd $(wasm-testing.dir) && bash .gzip' || \
echo "SSH failed: it's likely that stale content will be served via old gzip files."
+#
# build everything needed by push-testing with -Oz
+#
.PHONY: for-testing
for-testing: emcc_opt=-Oz
for-testing: loud=1
@@ -1404,9 +1528,9 @@ update-docs:
exit 127
else
wasm.docs.jswasm = $(wasm.docs.home)/jswasm
-update-docs: $(bin.stripccomments) $(out.sqlite3.js) $(out.sqlite3.wasm)
+update-docs: $(bin.stripccomments) $(out.vanilla.js) $(out.vanilla.wasm)
@echo "Copying files to the /wasm docs. Be sure to use an -Oz build for this!";
- cp -p $(sqlite3.wasm) $(wasm.docs.jswasm)/.
+ cp -p $(out.vanilla.wasm) $(wasm.docs.jswasm)/.
$(bin.stripccomments) -k -k < $(out.vanilla.js) \
| sed -e '/^[ \t]*$$/d' > $(wasm.docs.jswasm)/sqlite3.js
cp -p demo-123.js demo-123.html demo-123-worker.html $(wasm.docs.home)/.
@@ -1462,13 +1586,46 @@ endif
dist-name-prefix = sqlite-wasm$(dist-name-extra)
.PHONY: dist
dist:
- ./mkdist.sh $(dist-name-prefix)
+ $(bin.bash) ./mkdist.sh $(dist-name-prefix)
snapshot:
- ./mkdist.sh $(dist-name-prefix) --snapshot
+ $(bin.bash) ./mkdist.sh $(dist-name-prefix) --snapshot
endif
# ^^^ making dist/snapshot
CLEAN_FILES += $(wildcard sqlite-wasm-*.zip)
+########################################################################
+# The npm target is specifically for preparing files for the downstream
+# https://github.com/sqlite/sqlite-wasm (npm) distribution.
+#
+# Per agreement with that project's maintainers, these filenames need
+# to remain stable. To avoid breakage in their deployment process, any
+# changes (like renaming files) which potentially break their deployment
+# needs to be communicated to that project via opening a new ticket or
+# direct coordination with its maintainers.
+#
+# This target does a full clean/rebuild so that we can ensure that the
+# optimization level is set consistently across all files.
+#
+npm.bundle.zip = npm-bundle.zip
+CLEAN_FILES += $(npm.bundle.zip)
+# Distributables which need to be built for npm:
+npm_files = $(addprefix $(dir.dout)/, \
+sqlite3-bundler-friendly.mjs \
+sqlite3-opfs-async-proxy.js \
+sqlite3-worker1-bundler-friendly.mjs \
+sqlite3-worker1-promiser.mjs \
+sqlite3-worker1.mjs \
+sqlite3.mjs \
+sqlite3.wasm \
+sqlite3-node.mjs \
+)
+npm: $(sqlite3.canonical.c)
+ @echo "$(emo.cleanup) Forcing a clean rebuild to ensure consistent optimization flags."
+ $(MAKE) clean
+ $(MAKE) -e "emcc_opt=-Oz $(emcc-opt-extra)" $(npm_files)
+ rm -f $(npm.bundle.zip); zip -r $(npm.bundle.zip) $(npm_files)
+ unzip -l $(npm.bundle.zip)
+
########################################################################
# Explanation of, and some commentary on, various emcc build flags
# follows. Full docs for these can be found at:
diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core b/ext/wasm/api/EXPORTED_FUNCTIONS.c-pp
similarity index 61%
rename from ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core
rename to ext/wasm/api/EXPORTED_FUNCTIONS.c-pp
index 506054510..2cdddf1e7 100644
--- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core
+++ b/ext/wasm/api/EXPORTED_FUNCTIONS.c-pp
@@ -75,6 +75,7 @@ _sqlite3_limit
_sqlite3_malloc
_sqlite3_malloc64
_sqlite3_msize
+_sqlite3_next_stmt
_sqlite3_open
_sqlite3_open_v2
_sqlite3_overload_function
@@ -155,3 +156,92 @@ _sqlite3_vtab_in_next
_sqlite3_vtab_nochange
_sqlite3_vtab_on_conflict
_sqlite3_vtab_rhs_value
+//#if not bare-bones
+_sqlite3_column_database_name
+_sqlite3_column_origin_name
+_sqlite3_column_table_name
+_sqlite3_create_module
+_sqlite3_create_module_v2
+_sqlite3_create_window_function
+_sqlite3_declare_vtab
+_sqlite3_drop_modules
+_sqlite3_preupdate_blobwrite
+_sqlite3_preupdate_count
+_sqlite3_preupdate_depth
+_sqlite3_preupdate_hook
+_sqlite3_preupdate_new
+_sqlite3_preupdate_old
+_sqlite3_progress_handler
+_sqlite3_set_authorizer
+_sqlite3_vtab_collation
+_sqlite3_vtab_distinct
+_sqlite3_vtab_in
+_sqlite3_vtab_in_first
+_sqlite3_vtab_in_next
+_sqlite3_vtab_nochange
+_sqlite3_vtab_on_conflict
+_sqlite3_vtab_rhs_value
+_sqlite3changegroup_add
+_sqlite3changegroup_add_strm
+_sqlite3changegroup_delete
+_sqlite3changegroup_new
+_sqlite3changegroup_output
+_sqlite3changegroup_output_strm
+_sqlite3changeset_apply
+_sqlite3changeset_apply_strm
+_sqlite3changeset_apply_v2
+_sqlite3changeset_apply_v2_strm
+_sqlite3changeset_apply_v3
+_sqlite3changeset_apply_v3_strm
+_sqlite3changeset_concat
+_sqlite3changeset_concat_strm
+_sqlite3changeset_conflict
+_sqlite3changeset_finalize
+_sqlite3changeset_fk_conflicts
+_sqlite3changeset_invert
+_sqlite3changeset_invert_strm
+_sqlite3changeset_new
+_sqlite3changeset_next
+_sqlite3changeset_old
+_sqlite3changeset_op
+_sqlite3changeset_pk
+_sqlite3changeset_start
+_sqlite3changeset_start_strm
+_sqlite3changeset_start_v2
+_sqlite3changeset_start_v2_strm
+_sqlite3session_attach
+_sqlite3session_changeset
+_sqlite3session_changeset_size
+_sqlite3session_changeset_strm
+_sqlite3session_config
+_sqlite3session_create
+_sqlite3session_delete
+_sqlite3session_diff
+_sqlite3session_enable
+_sqlite3session_indirect
+_sqlite3session_isempty
+_sqlite3session_memory_used
+_sqlite3session_object_config
+_sqlite3session_patchset
+_sqlite3session_patchset_strm
+_sqlite3session_table_filter
+//#endif not bare-bones
+//#if enable-see
+_sqlite3_key
+_sqlite3_key_v2
+_sqlite3_rekey
+_sqlite3_rekey_v2
+_sqlite3_activate_see
+//#endif enable-see
+//#if fiddle
+_fiddle_db_arg
+_fiddle_db_filename
+_fiddle_exec
+_fiddle_experiment
+_fiddle_interrupt
+_fiddle_main
+_fiddle_reset_db
+_fiddle_db_handle
+_fiddle_db_vfs
+_fiddle_export_db
+//#endif fiddle
diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras
deleted file mode 100644
index e8304b5f2..000000000
--- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras
+++ /dev/null
@@ -1,68 +0,0 @@
-_sqlite3_column_database_name
-_sqlite3_column_origin_name
-_sqlite3_column_table_name
-_sqlite3_create_module
-_sqlite3_create_module_v2
-_sqlite3_create_window_function
-_sqlite3_declare_vtab
-_sqlite3_drop_modules
-_sqlite3_preupdate_blobwrite
-_sqlite3_preupdate_count
-_sqlite3_preupdate_depth
-_sqlite3_preupdate_hook
-_sqlite3_preupdate_new
-_sqlite3_preupdate_old
-_sqlite3_progress_handler
-_sqlite3_set_authorizer
-_sqlite3_vtab_collation
-_sqlite3_vtab_distinct
-_sqlite3_vtab_in
-_sqlite3_vtab_in_first
-_sqlite3_vtab_in_next
-_sqlite3_vtab_nochange
-_sqlite3_vtab_on_conflict
-_sqlite3_vtab_rhs_value
-_sqlite3changegroup_add
-_sqlite3changegroup_add_strm
-_sqlite3changegroup_delete
-_sqlite3changegroup_new
-_sqlite3changegroup_output
-_sqlite3changegroup_output_strm
-_sqlite3changeset_apply
-_sqlite3changeset_apply_strm
-_sqlite3changeset_apply_v2
-_sqlite3changeset_apply_v2_strm
-_sqlite3changeset_apply_v3
-_sqlite3changeset_apply_v3_strm
-_sqlite3changeset_concat
-_sqlite3changeset_concat_strm
-_sqlite3changeset_conflict
-_sqlite3changeset_finalize
-_sqlite3changeset_fk_conflicts
-_sqlite3changeset_invert
-_sqlite3changeset_invert_strm
-_sqlite3changeset_new
-_sqlite3changeset_next
-_sqlite3changeset_old
-_sqlite3changeset_op
-_sqlite3changeset_pk
-_sqlite3changeset_start
-_sqlite3changeset_start_strm
-_sqlite3changeset_start_v2
-_sqlite3changeset_start_v2_strm
-_sqlite3session_attach
-_sqlite3session_changeset
-_sqlite3session_changeset_size
-_sqlite3session_changeset_strm
-_sqlite3session_config
-_sqlite3session_create
-_sqlite3session_delete
-_sqlite3session_diff
-_sqlite3session_enable
-_sqlite3session_indirect
-_sqlite3session_isempty
-_sqlite3session_memory_used
-_sqlite3session_object_config
-_sqlite3session_patchset
-_sqlite3session_patchset_strm
-_sqlite3session_table_filter
diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see
deleted file mode 100644
index 83f3a97db..000000000
--- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see
+++ /dev/null
@@ -1,5 +0,0 @@
-_sqlite3_key
-_sqlite3_key_v2
-_sqlite3_rekey
-_sqlite3_rekey_v2
-_sqlite3_activate_see
diff --git a/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api b/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api
deleted file mode 100644
index aab1d8bd3..000000000
--- a/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api
+++ /dev/null
@@ -1,3 +0,0 @@
-FS
-wasmMemory
-
diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md
index 3c9669e6b..279d216bf 100644
--- a/ext/wasm/api/README.md
+++ b/ext/wasm/api/README.md
@@ -23,7 +23,7 @@ this writing, but is not set in stone forever and may change at any
time. This doc targets maintainers of this code and those wanting to
dive in to the details, not end user.
-First off, a pikchr of the proverbial onion:
+First off, a [pikchr][] of the proverbial onion:
```pikchr toggle center
scale = 0.85
@@ -60,14 +60,14 @@ maintenance point of view.
At the center of the onion is `sqlite3-api.js`, which gets generated
by concatenating the following files together in their listed order:
-- **`sqlite3-api-prologue.js`**\
+- **`sqlite3-api-prologue.js`**
Contains the initial bootstrap setup of the sqlite3 API
- objects. This is exposed as a function, rather than objects, so that
+ objects. This is exposed as a bootstrapping function so that
the next step can pass in a config object which abstracts away parts
of the WASM environment, to facilitate plugging it in to arbitrary
WASM toolchains. The bootstrapping function gets removed from the
global scope in a later stage of the bootstrapping process.
-- **`../common/whwasmutil.js`**\
+- **`../common/whwasmutil.js`**
A semi-third-party collection of JS/WASM utility code intended to
replace much of the Emscripten glue. The sqlite3 APIs internally use
these APIs instead of their Emscripten counterparts, in order to be
@@ -77,79 +77,78 @@ by concatenating the following files together in their listed order:
toolchains. It is "semi-third-party" in that it was created in order
to support this tree but is standalone and maintained together
with...
-- **`../jaccwabyt/jaccwabyt.js`**\
+- **`../jaccwabyt/jaccwabyt.js`**
Another semi-third-party API which creates bindings between JS
and C structs, such that changes to the struct state from either JS
or C are visible to the other end of the connection. This is also an
independent spinoff project, conceived for the sqlite3 project but
maintained separately.
-- **`sqlite3-api-glue.js`**\
+- **`sqlite3-api-glue.js`**
Invokes functionality exposed by the previous two files to flesh out
low-level parts of `sqlite3-api-prologue.js`. Most of these pieces
involve populating the `sqlite3.capi.wasm` object and creating
`sqlite3.capi.sqlite3_...()` bindings. This file also deletes most
global-scope symbols the above files create, effectively moving them
into the scope being used for initializing the API.
-- **`/sqlite3-api-build-version.js`**\
+- **`/sqlite3-api-build-version.js`**
Gets created by the build process and populates the
`sqlite3.version` object. This part is not critical, but records the
version of the library against which this module was built.
-- **`sqlite3-api-oo1.js`**\
+- **`sqlite3-api-oo1.js`**
Provides a high-level object-oriented wrapper to the lower-level C
API, colloquially known as OO API #1. Its API is similar to other
high-level sqlite3 JS wrappers and should feel relatively familiar
to anyone familiar with such APIs. It is not a "required component"
and can be elided from builds which do not want it.
-- **`sqlite3-api-worker1.js`**\
+- **`sqlite3-api-worker1.js`**
A Worker-thread-based API which uses OO API #1 to provide an
interface to a database which can be driven from the main Window
thread via the Worker message-passing interface. Like OO API #1,
this is an optional component, offering one of any number of
potential implementations for such an API.
- - **`sqlite3-worker1.js`**\
+ - **`sqlite3-worker1.js`**
Is not part of the amalgamated sources and is intended to be
loaded by a client Worker thread. It loads the sqlite3 module
and runs the Worker #1 API which is implemented in
`sqlite3-api-worker1.js`.
- - **`sqlite3-worker1-promiser.js`**\
+ - **`sqlite3-worker1-promiser.js`**
Is likewise not part of the amalgamated sources and provides
a Promise-based interface into the Worker #1 API. This is
a far user-friendlier way to interface with databases running
in a Worker thread.
-- **`sqlite3-vfs-helper.js`**\
+- **`sqlite3-vfs-helper.c-pp.js`**
Installs the `sqlite3.vfs` namespace, which contain helpers for use
by downstream code which creates `sqlite3_vfs` implementations.
-- **`sqlite3-vtab-helper.js`**\
+- **`sqlite3-vtab-helper.c-pp.js`**
Installs the `sqlite3.vtab` namespace, which contain helpers for use
by downstream code which creates `sqlite3_module` implementations.
-- **`sqlite3-vfs-opfs.c-pp.js`**\
+- **`sqlite3-vfs-opfs.c-pp.js`**
is an sqlite3 VFS implementation which supports the [Origin-Private
FileSystem (OPFS)][OPFS] as a storage layer to provide persistent
storage for database files in a browser. It requires...
- - **`sqlite3-opfs-async-proxy.js`**\
+ - **`sqlite3-opfs-async-proxy.js`**
is the asynchronous backend part of the [OPFS][] proxy. It
speaks directly to the (async) OPFS API and channels those
results back to its synchronous counterpart. This file, because
it must be started in its own Worker, is not part of the
amalgamation.
-- **`sqlite3-vfs-opfs-sahpool.c-pp.js`**\
+- **`sqlite3-vfs-opfs-sahpool.c-pp.js`**
is another sqlite3 VFS supporting the [OPFS][], but uses a
completely different approach than the above-listed one.
-- **`sqlite3-api-cleanup.js`**\
- The previous files do not immediately extend the library. Instead
- they add callback functions to be called during its
- bootstrapping. Some also temporarily create global objects in order
- to communicate their state to the files which follow them. This file
- cleans up any dangling globals and runs the API bootstrapping
- process, which is what finally executes the initialization code
- installed by the previous files. As of this writing, this code
- ensures that the previous files leave no more than a single global
- symbol installed - `sqlite3InitModule()`. When adapting the API for
- non-Emscripten toolchains, this "should" be the only file, of those
- in this list, where changes are needed. The Emscripten-specific
- pieces described below may also require counterparts in any as-yet
- hypothetical alternative build.
+The previous files do not immediately extend the library. Instead they
+install a global function `sqlite3ApiBootstrap()`, which downstream
+code must call to configure the library for the current JS/WASM
+environment. Each file listed above pushes a callback into the
+bootstrapping queue, to be called as part of `sqlite3ApiBootstrap()`.
+Some files also temporarily create global objects in order to
+communicate their state to the files which follow them. Those
+get cleaned up vi `post-js-footer.js`, described below.
+
+Adapting the build for non-Emscripten toolchains essentially requires packaging
+the above files, concatated together, into that toolchain's "JS glue"
+and, in the final stage of that glue, call `sqlite3ApiBootstrap()` and
+return its result to the end user.
**Files with the extension `.c-pp.js`** are intended [to be processed
with `c-pp`](#c-pp), noting that such preprocessing may be applied
@@ -171,23 +170,27 @@ from this file rather than `sqlite3.c`.
The following Emscripten-specific files are injected into the
build-generated `sqlite3.js` along with `sqlite3-api.js`.
-- **`extern-pre-js.js`**\
+- **`extern-pre-js.js`**
Emscripten-specific header for Emscripten's `--extern-pre-js`
flag. As of this writing, that file is only used for experimentation
purposes and holds no code relevant to the production deliverables.
-- **`pre-js.c-pp.js`**\
+- **`pre-js.c-pp.js`**
Emscripten-specific header for Emscripten's `--pre-js` flag. This
file overrides certain Emscripten behavior before Emscripten does
most of its work.
-- **`post-js-header.js`**\
+- **`post-js-header.js`**
Emscripten-specific header for the `--post-js` input. It opens up,
but does not close, a function used for initializing the library.
-- (**`sqlite3-api.js`** gets sandwiched between these ↑ two
- ↓ files.)
-- **`post-js-footer.js`**\
+- **`sqlite3-api.js`** gets sandwiched between these ↑ two
+ ↓ files.
+- **`post-js-footer.js`**
Emscripten-specific footer for the `--post-js` input. This closes
- off the function opened by `post-js-header.js`.
-- **`extern-post-js.c-pp.js`**\
+ off the function opened by `post-js-header.js`. This file cleans up
+ any dangling globals and runs `sqlite3ApiBootstrap()`. As of this
+ writing, this code ensures that the previous files leave no more
+ than a single global symbol installed - `sqlite3InitModule()`.
+
+- **`extern-post-js.c-pp.js`**
Emscripten-specific header for Emscripten's `--extern-post-js`
flag. This file is run in the global scope. It overwrites the
Emscripten-installed `sqlite3InitModule()` function with one which
@@ -205,9 +208,10 @@ Preprocessing of Source Files
Certain files in the build require preprocessing to filter in/out
parts which differ between vanilla JS, ES6 Modules, and node.js
builds. The preprocessor application itself is in
-[`c-pp.c`](/file/ext/wasm/c-pp.c) and the complete technical details
-of such preprocessing are maintained in
+[`c-pp-lite.c`](/file/ext/wasm/c-pp-lite.c) and the complete technical
+details of such preprocessing are maintained in
[`GNUMakefile`](/file/ext/wasm/GNUmakefile).
[OPFS]: https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system
+[pikchr]: https://pikchr.org
diff --git a/ext/wasm/api/extern-post-js.c-pp.js b/ext/wasm/api/extern-post-js.c-pp.js
index 606e02ae2..cac6e4ab4 100644
--- a/ext/wasm/api/extern-post-js.c-pp.js
+++ b/ext/wasm/api/extern-post-js.c-pp.js
@@ -26,7 +26,7 @@ const toExportForESM =
*/
const originalInit = sqlite3InitModule;
if(!originalInit){
- throw new Error("Expecting globalThis.sqlite3InitModule to be defined by the Emscripten build.");
+ throw new Error("Expecting sqlite3InitModule to be defined by the Emscripten build.");
}
/**
We need to add some state which our custom Module.locateFile()
@@ -73,6 +73,8 @@ const toExportForESM =
const sIM = globalThis.sqlite3InitModule = function ff(...args){
//console.warn("Using replaced sqlite3InitModule()",globalThis.location);
+ sIMS.emscriptenLocateFile = args[0]?.locateFile /* see pre-js.c-pp.js [tag:locateFile] */;
+ sIMS.emscriptenInstantiateWasm = args[0]?.instantiateWasm /* see pre-js.c-pp.js [tag:locateFile] */;
return originalInit(...args).then((EmscriptenModule)=>{
sIMS.debugModule("sqlite3InitModule() sIMS =",sIMS);
sIMS.debugModule("sqlite3InitModule() EmscriptenModule =",EmscriptenModule);
diff --git a/ext/wasm/api/post-js-footer.js b/ext/wasm/api/post-js-footer.js
index c6a2e1517..f8050ddd3 100644
--- a/ext/wasm/api/post-js-footer.js
+++ b/ext/wasm/api/post-js-footer.js
@@ -1,3 +1,72 @@
+/*
+ 2022-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 the tail end of the sqlite3-api.js constellation,
+ closing the function scope opened by post-js-header.js.
+
+ In terms of amalgamation code placement, this file is appended
+ immediately after the final sqlite3-api-*.js piece. Those files
+ cooperate to prepare sqlite3ApiBootstrap() and this file calls it.
+ It is run within a context which gives it access to Emscripten's
+ Module object, after sqlite3.wasm is loaded but before
+ sqlite3ApiBootstrap() has been called.
+
+ Because this code resides (after building) inside the function
+ installed by post-js-header.js, it has access to state set up by
+ pre-js.c-pp.js and friends.
+*/
+try{
+ /* We are in the closing block of Module.runSQLite3PostLoadInit(), so
+ its arguments are visible here. */
+
+ /* Config options for sqlite3ApiBootstrap(). */
+ const bootstrapConfig = Object.assign(
+ Object.create(null),
+ /** The WASM-environment-dependent configuration for sqlite3ApiBootstrap() */
+ {
+ memory: ('undefined'!==typeof wasmMemory)
+ ? wasmMemory
+ : EmscriptenModule['wasmMemory'],
+ exports: ('undefined'!==typeof wasmExports)
+ ? wasmExports /* emscripten >=3.1.44 */
+ : (Object.prototype.hasOwnProperty.call(EmscriptenModule,'wasmExports')
+ ? EmscriptenModule['wasmExports']
+ : EmscriptenModule['asm']/* emscripten <=3.1.43 */)
+ },
+ globalThis.sqlite3ApiBootstrap.defaultConfig, // default options
+ globalThis.sqlite3ApiConfig || {} // optional client-provided options
+ );
+
+ sqlite3InitScriptInfo.debugModule("Bootstrapping lib config", bootstrapConfig);
+
+ /**
+ For purposes of the Emscripten build, call sqlite3ApiBootstrap().
+ Ideally clients should be able to inject their own config here,
+ but that's not practical in this particular build constellation
+ because of the order everything happens in. Clients may either
+ define globalThis.sqlite3ApiConfig or modify
+ globalThis.sqlite3ApiBootstrap.defaultConfig to tweak the default
+ configuration used by a no-args call to sqlite3ApiBootstrap(),
+ but must have first loaded their WASM module in order to be able
+ to provide the necessary configuration state.
+ */
+ const p = globalThis.sqlite3ApiBootstrap(bootstrapConfig);
+ delete globalThis.sqlite3ApiBootstrap;
+ return p /* the eventual result of globalThis.sqlite3InitModule() */;
+}catch(e){
+ console.error("sqlite3ApiBootstrap() error:",e);
+ throw e;
+}
+
//console.warn("This is the end of the Module.runSQLite3PostLoadInit handler.");
}/*Module.runSQLite3PostLoadInit(...)*/;
//console.warn("This is the end of the setup of the (pending) Module.runSQLite3PostLoadInit");
diff --git a/ext/wasm/api/post-js-header.js b/ext/wasm/api/post-js-header.js
index cdc6b3a38..670051bd8 100644
--- a/ext/wasm/api/post-js-header.js
+++ b/ext/wasm/api/post-js-header.js
@@ -3,14 +3,13 @@
post-js.js for use with Emscripten's --post-js flag, so it gets
injected in the earliest stages of sqlite3InitModule().
- This function wraps the whole SQLite3 library but does not
- bootstrap it.
-
Running this function will bootstrap the library and return
a Promise to the sqlite3 namespace object.
+
+ In the canonical builds, this gets called by extern-post-js.c-pp.js
*/
-Module.runSQLite3PostLoadInit = function(
- sqlite3InitScriptInfo /* populated by extern-post-js.c-pp.js */,
+Module.runSQLite3PostLoadInit = async function(
+ sqlite3InitScriptInfo,
EmscriptenModule/*the Emscripten-style module object*/,
sqlite3IsUnderTest
){
@@ -35,7 +34,6 @@ Module.runSQLite3PostLoadInit = function(
- sqlite3-vtab-helper.c-pp.js => Utilities for virtual table impls
- sqlite3-vfs-opfs.c-pp.js => OPFS VFS
- sqlite3-vfs-opfs-sahpool.c-pp.js => OPFS SAHPool VFS
- - sqlite3-api-cleanup.js => final bootstrapping phase
- post-js-footer.js => this file's epilogue
And all of that gets sandwiched between extern-pre-js.js and
diff --git a/ext/wasm/api/pre-js.c-pp.js b/ext/wasm/api/pre-js.c-pp.js
index 8a4a0f9fd..3910cb000 100644
--- a/ext/wasm/api/pre-js.c-pp.js
+++ b/ext/wasm/api/pre-js.c-pp.js
@@ -14,12 +14,36 @@
itself. i.e. try to keep file-local symbol names obnoxiously
collision-resistant.
*/
+/**
+ This file was preprocessed using:
+
+//#@policy error
+ @c-pp::argv@
+//#@policy off
+*/
+//#if unsupported-build
+/**
+ UNSUPPORTED BUILD:
+
+ This SQLite JS build configuration is entirely unsupported! It has
+ not been tested beyond the ability to compile it. It may not
+ load. It may not work properly. Only builds _directly_ targeting
+ browser environments ("vanilla" JS and ESM modules) are supported
+ and tested. Builds which _indirectly_ target browsers (namely
+ bundler-friendly builds) are not supported deliverables.
+*/
+//#endif
+//#if not target:es6-bundler-friendly
(function(Module){
const sIMS =
globalThis.sqlite3InitModuleState/*from extern-post-js.c-pp.js*/
|| Object.assign(Object.create(null),{
- debugModule: ()=>{
- console.warn("globalThis.sqlite3InitModuleState is missing");
+ /* In WASMFS builds this file gets loaded once per thread,
+ but sqlite3InitModuleState is not getting set for the
+ worker threads? That those workers seem to function fine
+ despite that is curious. */
+ debugModule: function(){
+ console.warn("globalThis.sqlite3InitModuleState is missing",arguments);
}
});
delete globalThis.sqlite3InitModuleState;
@@ -47,6 +71,14 @@
approach.
*/
Module['locateFile'] = function(path, prefix) {
+ if( this.emscriptenLocateFile instanceof Function ){
+ /* [tag:locateFile] Client-overridden impl. We do not support
+ this but offer it as a back-door which will go away the
+ moment either Emscripten changes that interface or we manage
+ to get non-Emscripten builds working.
+ https://sqlite.org/forum/forumpost/1eec339854c935bd */
+ return this.emscriptenLocateFile(path, prefix);
+ }
//#if target:es6-module
return new URL(path, import.meta.url).href;
//#else
@@ -72,8 +104,7 @@
//#endif target:es6-module
}.bind(sIMS);
-//#if Module.instantiateWasm
-//#if not wasmfs
+//#if Module.instantiateWasm and not wasmfs and not target:node
/**
Override Module.instantiateWasm().
@@ -82,8 +113,15 @@
https://github.com/emscripten-core/emscripten/issues/17951
In such builds we must disable this.
+
+ It's disabled in the (unsupported/untested) node builds because
+ node does not do fetch().
*/
Module['instantiateWasm'] = function callee(imports,onSuccess){
+ if( this.emscriptenInstantiateWasm instanceof Function ){
+ /* See [tag:locateFile]. Same story here */
+ return this.emscriptenInstantiateWasm(imports, onSuccess);
+ }
const sims = this;
const uri = Module.locateFile(
sims.wasmFilename, (
@@ -109,7 +147,7 @@
.then(finalThen)
return loadWasm();
}.bind(sIMS);
-//#endif not wasmfs
-//#endif Module.instantiateWasm
+//#endif Module.instantiateWasm and not wasmfs
})(Module);
+//#endif not target:es6-bundler-friendly
/* END FILE: api/pre-js.js. */
diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js
deleted file mode 100644
index 223566326..000000000
--- a/ext/wasm/api/sqlite3-api-cleanup.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- 2022-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 the tail end of the sqlite3-api.js constellation,
- intended to be appended after all other sqlite3-api-*.js files so
- that it can finalize any setup and clean up any global symbols
- temporarily used for setting up the API's various subsystems.
-
- In Emscripten builds it's run in the context of what amounts to a
- Module.postRun handler, though it's no longer actually a postRun
- handler because Emscripten 4.0 changed postRun semantics in an
- incompatible way.
-
- In terms of amalgamation code placement, this file is appended
- immediately after the final sqlite3-api-*.js piece. Those files
- cooperate to prepare sqlite3ApiBootstrap() and this file calls it.
- It is run within a context which gives it access to Emscripten's
- Module object, after sqlite3.wasm is loaded but before
- sqlite3ApiBootstrap() has been called.
-
- Because this code resides (after building) inside the function
- installed by post-js-header.js, it has access to the
-*/
-'use strict';
-if( 'undefined' === typeof EmscriptenModule/*from post-js-header.js*/ ){
- console.warn("This is not running in the context of Module.runSQLite3PostLoadInit()");
- throw new Error("sqlite3-api-cleanup.js expects to be running in the "+
- "context of its Emscripten module loader.");
-}
-try{
- /* Config options for sqlite3ApiBootstrap(). */
- const bootstrapConfig = Object.assign(
- Object.create(null),
- globalThis.sqlite3ApiBootstrap.defaultConfig, // default options
- globalThis.sqlite3ApiConfig || {}, // optional client-provided options
- /** The WASM-environment-dependent configuration for sqlite3ApiBootstrap() */
- {
- memory: ('undefined'!==typeof wasmMemory)
- ? wasmMemory
- : EmscriptenModule['wasmMemory'],
- exports: ('undefined'!==typeof wasmExports)
- ? wasmExports /* emscripten >=3.1.44 */
- : (Object.prototype.hasOwnProperty.call(EmscriptenModule,'wasmExports')
- ? EmscriptenModule['wasmExports']
- : EmscriptenModule['asm']/* emscripten <=3.1.43 */)
- }
- );
-
- /** Figure out if this is a 32- or 64-bit WASM build. */
- bootstrapConfig.wasmPtrIR =
- 'number'===(typeof bootstrapConfig.exports.sqlite3_libversion())
- ? 'i32' :'i64';
- const sIMS = sqlite3InitScriptInfo;
- sIMS.debugModule("Bootstrapping lib config", sIMS);
-
- /**
- For purposes of the Emscripten build, call sqlite3ApiBootstrap().
- Ideally clients should be able to inject their own config here,
- but that's not practical in this particular build constellation
- because of the order everything happens in. Clients may either
- define globalThis.sqlite3ApiConfig or modify
- globalThis.sqlite3ApiBootstrap.defaultConfig to tweak the default
- configuration used by a no-args call to sqlite3ApiBootstrap(),
- but must have first loaded their WASM module in order to be able
- to provide the necessary configuration state.
- */
- const p = globalThis.sqlite3ApiBootstrap(bootstrapConfig);
- delete globalThis.sqlite3ApiBootstrap;
- return p /* the eventual result of globalThis.sqlite3InitModule() */;
-}catch(e){
- console.error("sqlite3ApiBootstrap() error:",e);
- throw e;
-}
-throw new Error("Maintenance required: this line should never be reached");
diff --git a/ext/wasm/api/sqlite3-api-glue.c-pp.js b/ext/wasm/api/sqlite3-api-glue.c-pp.js
index 1c42b0150..d268331a3 100644
--- a/ext/wasm/api/sqlite3-api-glue.c-pp.js
+++ b/ext/wasm/api/sqlite3-api-glue.c-pp.js
@@ -120,7 +120,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["sqlite3_column_double","f64", "sqlite3_stmt*", "int"],
["sqlite3_column_int","int", "sqlite3_stmt*", "int"],
["sqlite3_column_name","string", "sqlite3_stmt*", "int"],
+//#define proxy-text-apis=1
+//#if not proxy-text-apis
+/* Search this file for tag:proxy-text-apis to see what this is about. */
["sqlite3_column_text","string", "sqlite3_stmt*", "int"],
+//#endif
["sqlite3_column_type","int", "sqlite3_stmt*", "int"],
["sqlite3_column_value","sqlite3_value*", "sqlite3_stmt*", "int"],
["sqlite3_commit_hook", "void*", [
@@ -196,6 +200,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["sqlite3_libversion_number", "int"],
["sqlite3_limit", "int", ["sqlite3*", "int", "int"]],
["sqlite3_malloc", "*","int"],
+ ["sqlite3_next_stmt", "sqlite3_stmt*", ["sqlite3*","sqlite3_stmt*"]],
["sqlite3_open", "int", "string", "*"],
["sqlite3_open_v2", "int", "string", "*", "int", "string"],
/* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled
@@ -317,7 +322,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["sqlite3_value_numeric_type", "int", "sqlite3_value*"],
["sqlite3_value_pointer", "*", "sqlite3_value*", "string:static"],
["sqlite3_value_subtype", "int", "sqlite3_value*"],
+//#if not proxy-text-apis
["sqlite3_value_text", "string", "sqlite3_value*"],
+//#endif
["sqlite3_value_type", "int", "sqlite3_value*"],
["sqlite3_vfs_find", "*", "string"],
["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"],
@@ -969,10 +976,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
"entry SQLITE_WASM_DEALLOC (=="+capi.SQLITE_WASM_DEALLOC+").");
}
const __rcMap = Object.create(null);
- for(const t of ['resultCodes']){
- for(const e of Object.entries(wasm.ctype[t])){
- __rcMap[e[1]] = e[0];
- }
+ for(const e of Object.entries(wasm.ctype['resultCodes'])){
+ __rcMap[e[1]] = e[0];
}
/**
For the given integer, returns the SQLITE_xxx result code as a
@@ -984,8 +989,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const notThese = Object.assign(Object.create(null),{
// For each struct to NOT register, map its name to true:
WasmTestStruct: true,
- /* We unregister the kvvfs VFS from Worker threads below. */
- sqlite3_kvvfs_methods: !util.isUIThread(),
/* sqlite3_index_info and friends require int64: */
sqlite3_index_info: !wasm.bigIntEnabled,
sqlite3_index_constraint: !wasm.bigIntEnabled,
@@ -1654,6 +1657,45 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}/*sqlite3_bind_text/blob()*/
+//#if proxy-text-apis
+ if(!capi.sqlite3_column_text){
+ /*[tag:proxy-text-apis]
+ As discussed at:
+
+ https://sqlite.org/forum/forumpost/d77281aec2df9ada
+
+ Summary: there are opinions that sqlite3_column_text() and
+ sqlite3_value_text() should handle strings such that embedded
+ NULs are retained. This block does that. This block does _not_
+ apply that special-case behavior to any number of _other_
+ APIs which return C-strings. That discrepancy makes this
+ block highly arguable, but one can also argue that these two
+ specific functions can get away with such acrobatics without
+ it being called voodoo in a pejorative sense.
+ */
+ const argStmt = wasm.xWrap.argAdapter('sqlite3_stmt*'),
+ argInt = wasm.xWrap.argAdapter('int'),
+ argValue = wasm.xWrap.argAdapter('sqlite3_value*'),
+ newStr =
+ (cstr,n)=>wasm.typedArrayToString(wasm.heap8u(),
+ Number(cstr), Number(cstr)+n)
+ capi.sqlite3_column_text = function(stmt, colIndex){
+ const a0 = argStmt(stmt), a1 = argInt(colIndex);
+ const cstr = wasm.exports.sqlite3_column_text(a0, a1);
+ return cstr
+ ? newStr(cstr,wasm.exports.sqlite3_column_bytes(a0, a1))
+ : null;
+ };
+ capi.sqlite3_value_text = function(val){
+ const a0 = argValue(val);
+ const cstr = wasm.exports.sqlite3_value_text(a0);
+ return cstr
+ ? newStr(cstr,wasm.exports.sqlite3_value_bytes(a0))
+ : null;
+ };
+ }/*text-return-related bindings*/
+//#endif proxy-text-apis
+
{/* sqlite3_config() */
/**
Wraps a small subset of the C API's sqlite3_config() options.
@@ -1740,105 +1782,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
};
}/* auto-extension */
- const pKvvfs = capi.sqlite3_vfs_find("kvvfs");
- if( pKvvfs ){/* kvvfs-specific glue */
- if(util.isUIThread()){
- const kvvfsMethods = new capi.sqlite3_kvvfs_methods(
- wasm.exports.sqlite3__wasm_kvvfs_methods()
- );
- delete capi.sqlite3_kvvfs_methods;
-
- const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack,
- pstack = wasm.pstack;
-
- const kvvfsStorage = (zClass)=>
- ((115/*=='s'*/===wasm.peek(zClass))
- ? sessionStorage : localStorage);
-
- /**
- Implementations for members of the object referred to by
- sqlite3__wasm_kvvfs_methods(). We swap out the native
- implementations with these, which use localStorage or
- sessionStorage for their backing store.
- */
- const kvvfsImpls = {
- xRead: (zClass, zKey, zBuf, nBuf)=>{
- const stack = pstack.pointer,
- astack = wasm.scopedAllocPush();
- try {
- const zXKey = kvvfsMakeKey(zClass,zKey);
- if(!zXKey) return -3/*OOM*/;
- const jKey = wasm.cstrToJs(zXKey);
- const jV = kvvfsStorage(zClass).getItem(jKey);
- if(!jV) return -1;
- const nV = jV.length /* We are relying 100% on v being
- ASCII so that jV.length is equal
- to the C-string's byte length. */;
- if(nBuf<=0) return nV;
- else if(1===nBuf){
- wasm.poke(zBuf, 0);
- return nV;
- }
- const zV = wasm.scopedAllocCString(jV);
- if(nBuf > nV + 1) nBuf = nV + 1;
- wasm.heap8u().copyWithin(
- Number(zBuf), Number(zV), wasm.ptr.addn(zV, nBuf,- 1)
- );
- wasm.poke(wasm.ptr.add(zBuf, nBuf, -1), 0);
- return nBuf - 1;
- }catch(e){
- sqlite3.config.error("kvstorageRead()",e);
- return -2;
- }finally{
- pstack.restore(stack);
- wasm.scopedAllocPop(astack);
- }
- },
- xWrite: (zClass, zKey, zData)=>{
- const stack = pstack.pointer;
- try {
- const zXKey = kvvfsMakeKey(zClass,zKey);
- if(!zXKey) return 1/*OOM*/;
- const jKey = wasm.cstrToJs(zXKey);
- kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData));
- return 0;
- }catch(e){
- sqlite3.config.error("kvstorageWrite()",e);
- return capi.SQLITE_IOERR;
- }finally{
- pstack.restore(stack);
- }
- },
- xDelete: (zClass, zKey)=>{
- const stack = pstack.pointer;
- try {
- const zXKey = kvvfsMakeKey(zClass,zKey);
- if(!zXKey) return 1/*OOM*/;
- kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey));
- return 0;
- }catch(e){
- sqlite3.config.error("kvstorageDelete()",e);
- return capi.SQLITE_IOERR;
- }finally{
- pstack.restore(stack);
- }
- }
- }/*kvvfsImpls*/;
- for(const k of Object.keys(kvvfsImpls)){
- kvvfsMethods[kvvfsMethods.memberKey(k)] =
- wasm.installFunction(
- kvvfsMethods.memberSignature(k),
- kvvfsImpls[k]
- );
- }
- }else{
- /* Worker thread: unregister kvvfs to avoid it being used
- for anything other than local/sessionStorage. It "can"
- be used that way but it's not really intended to be. */
- capi.sqlite3_vfs_unregister(pKvvfs);
- }
- }/*pKvvfs*/
-
/* Warn if client-level code makes use of FuncPtrAdapter. */
wasm.xWrap.FuncPtrAdapter.warnOnUse = true;
@@ -1944,7 +1887,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
tgt[memKey] = fProxy;
}else{
- const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name));
+ const pFunc = wasm.installFunction(fProxy, sigN);
tgt[memKey] = pFunc;
if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
tgt.addOnDispose('ondispose.__removeFuncList handler',
diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js
index f7a4e9ebd..9338eef33 100644
--- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js
+++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js
@@ -26,6 +26,20 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
the sqlite3 binding if, e.g., the wrapper is in the main thread
and the sqlite3 API is in a worker. */
+ const outWrapper = function(f){
+ return (...args)=>f("sqlite3.oo1:",...args);
+ };
+
+ const debug = sqlite3.__isUnderTest
+ ? outWrapper(console.debug.bind(console))
+ : outWrapper(sqlite3.config.debug);
+ const warn = sqlite3.__isUnderTest
+ ? outWrapper(console.warn.bind(console))
+ : outWrapper(sqlite3.config.warn);
+ const error = sqlite3.__isUnderTest
+ ? outWrapper(console.error.bind(console))
+ : outWrapper(sqlite3.config.error);
+
/**
In order to keep clients from manipulating, perhaps
inadvertently, the underlying pointer values of DB and Stmt
@@ -88,7 +102,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
wasm.installFunction('i(ippp)', function(t,c,p,x){
if(capi.SQLITE_TRACE_STMT===t){
// x == SQL, p == sqlite3_stmt*
- console.log("SQL TRACE #"+(++this.counter)+' via sqlite3@'+c+':',
+ console.log("SQL TRACE #"+(++this.counter),
+ 'via sqlite3@'+c+'['+capi.sqlite3_db_filename(c,null)+']',
wasm.cstrToJs(x));
}
}.bind({counter: 0}));
@@ -213,41 +228,18 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
properties:
- `.filename`: the db filename. It may be a special name like ":memory:"
- or "".
+ or "". It may also be a URI-style name.
- `.flags`: as documented in the DB constructor.
- `.vfs`: as documented in the DB constructor.
It also accepts those as the first 3 arguments.
+
+ In non-default builds it may accept additional configuration
+ options.
*/
const dbCtorHelper = function ctor(...args){
- if(!ctor._name2vfs){
- /**
- Map special filenames which we handle here (instead of in C)
- to some helpful metadata...
-
- As of 2022-09-20, the C API supports the names :localStorage:
- and :sessionStorage: for kvvfs. However, C code cannot
- determine (without embedded JS code, e.g. via Emscripten's
- EM_JS()) whether the kvvfs is legal in the current browser
- context (namely the main UI thread). In order to help client
- code fail early on, instead of it being delayed until they
- try to read or write a kvvfs-backed db, we'll check for those
- names here and throw if they're not legal in the current
- context.
- */
- ctor._name2vfs = Object.create(null);
- const isWorkerThread = ('function'===typeof importScripts/*===running in worker thread*/)
- ? (n)=>toss3("The VFS for",n,"is only available in the main window thread.")
- : false;
- ctor._name2vfs[':localStorage:'] = {
- vfs: 'kvvfs', filename: isWorkerThread || (()=>'local')
- };
- ctor._name2vfs[':sessionStorage:'] = {
- vfs: 'kvvfs', filename: isWorkerThread || (()=>'session')
- };
- }
const opt = ctor.normalizeArgs(...args);
//sqlite3.config.debug("DB ctor",opt);
let pDb;
@@ -269,12 +261,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
sqlite3.config.error("Invalid DB ctor args",opt,arguments);
toss3("Invalid arguments for DB constructor:", arguments, "opts:", opt);
}
- let fnJs = wasm.isPtr(fn) ? wasm.cstrToJs(fn) : fn;
- const vfsCheck = ctor._name2vfs[fnJs];
- if(vfsCheck){
- vfsName = vfsCheck.vfs;
- fn = fnJs = vfsCheck.filename(fnJs);
- }
let oflags = 0;
if( flagsStr.indexOf('c')>=0 ){
oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
@@ -299,7 +285,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}finally{
wasm.pstack.restore(stack);
}
- this.filename = fnJs;
+ this.filename =
+ /* A poor design choice we have to keep: this.filename may be
+ in the form "file:....?....". It really should have been
+ sqlite3_db_filename(pDb) but that discrepancy went too long
+ unnoticed to be able to change without risk of
+ breakage. DB.dbFilename() can be used to fetch _just_ the
+ name part.
+ */ wasm.isPtr(fn) ? wasm.cstrToJs(fn) : fn;
+
}
__ptrMap.set(this, pDb);
__stmtMap.set(this, Object.create(null));
@@ -390,12 +384,12 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
The given db filename must be resolvable using whatever
filesystem layer (virtual or otherwise) is set up for the default
- sqlite3 VFS.
+ sqlite3 VFS or a VFS which can resolve it must be specified.
- Note that the special sqlite3 db names ":memory:" and ""
- (temporary db) have their normal special meanings here and need
- not resolve to real filenames, but "" uses an on-storage
- temporary database and requires that the VFS support that.
+ The special sqlite3 db names ":memory:" and "" (temporary db)
+ have their normal special meanings here and need not resolve to
+ real filenames, but "" uses an on-storage temporary database and
+ requires that the VFS support that.
The second argument specifies the open/create mode for the
database. It must be string containing a sequence of letters (in
@@ -808,6 +802,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
sqlite3_db_filename() value for the given database name,
defaulting to "main". The argument may be either a JS string
or a pointer to a WASM-allocated C-string.
+
+ this.filename may be in the form of a URI-style string, whereas
+ the returned string contains only the filename part.
*/
dbFilename: function(dbName='main'){
return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName);
@@ -929,15 +926,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
result set, but only if that statement has any result rows. The
callback's "this" is the options object, noting that this
function synthesizes one if the caller does not pass one to
- exec(). The second argument passed to the callback is always
- the current Stmt object, as it's needed if the caller wants to
- fetch the column names or some such (noting that they could
- also be fetched via `this.columnNames`, if the client provides
- the `columnNames` option). If the callback returns a literal
- `false` (as opposed to any other falsy value, e.g. an implicit
- `undefined` return), any ongoing statement-`step()` iteration
- stops without an error. The return value of the callback is
- otherwise ignored.
+ exec(). The first argument passed to the callback is described
+ below. The second argument is always the current Stmt object,
+ as it's needed if the caller wants to fetch the column names or
+ some such (noting that they could also be fetched via
+ `this.columnNames`, if the client provides the `columnNames`
+ option). If the callback returns a literal `false` (as opposed
+ to any other falsy value, e.g. an implicit `undefined` return),
+ any ongoing statement-`step()` iteration stops without an
+ error. The return value of the callback is otherwise ignored.
ACHTUNG: The callback MUST NOT modify the Stmt object. Calling
any of the Stmt.get() variants, Stmt.getColumnName(), or
@@ -970,20 +967,23 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
A.3) `'stmt'` causes the current Stmt to be passed to the
callback, but this mode will trigger an exception if
`resultRows` is an array because appending the transient
- statement to the array would be downright unhelpful.
+ statement to the array would be downright unhelpful. This
+ option is a legacy feature, retained for backwards
+ compatibility. The statement object is passed as the second
+ argument to the callback, as described above.
B) An integer, indicating a zero-based column in the result
- row. Only that one single value will be passed on.
+ row. Only that one single value, in JS form, will be passed on.
C) A string with a minimum length of 2 and leading character of
'$' will fetch the row as an object, extract that one field,
- and pass that field's value to the callback. Note that these
- keys are case-sensitive so must match the case used in the
+ and pass that field's value to the callback. These keys are
+ case-sensitive so must match the case used in the
SQL. e.g. `"select a A from t"` with a `rowMode` of `'$A'`
would work but `'$a'` would not. A reference to a column not in
the result set will trigger an exception on the first row (as
- the check is not performed until rows are fetched). Note also
- that `$` is a legal identifier character in JS so need not be
+ the check is not performed until rows are fetched). Note that
+ `$` is a legal identifier character in JS so need not be
quoted.
Any other `rowMode` value triggers an exception.
@@ -1023,6 +1023,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- `callback` and `resultRows`: permit an array entries with
semantics similar to those described for `bind` above.
+ OTOH, this function already does too much.
*/
exec: function(/*(sql [,obj]) || (obj)*/){
affirmDbOpen(this);
@@ -1046,7 +1047,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/* Optimization: if the SQL is a TypedArray we can save some string
conversion costs. */;
/* Allocate the two output pointers (ppStmt, pzTail) and heap
- space for the SQL (pSql). When prepare_v2() returns, pzTail
+ space for the SQL (pSql). When prepare_v3() returns, pzTail
will point to somewhere in pSql. */
let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql);
const ppStmt = wasm.scopedAlloc(
@@ -1058,8 +1059,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const pSqlEnd = wasm.ptr.add(pSql, sqlByteLen);
if(isTA) wasm.heap8().set(arg.sql, pSql);
else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false);
- wasm.poke(wasm.ptr.add(pSql, sqlByteLen), 0/*NUL terminator*/);
- while(pSql && wasm.peek(pSql, 'i8')
+ wasm.poke8(wasm.ptr.add(pSql, sqlByteLen), 0/*NUL terminator*/);
+ while(pSql && wasm.peek8(pSql)
/* Maintenance reminder:^^^ _must_ be 'i8' or else we
will very likely cause an endless loop. What that's
doing is checking for a terminating NUL byte. If we
@@ -1123,6 +1124,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/* In order to trigger an exception in the
INSERT...RETURNING locking scenario:
https://sqlite.org/forum/forumpost/36f7a2e7494897df
+ [tag:insert-returning-reset]
*/).finalize();
stmt = null;
}/*prepare() loop*/
@@ -1139,6 +1141,132 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return arg.returnVal();
}/*exec()*/,
+//#if nope
+ /**
+ Experimental and untested - do not use.
+
+ Prepares one or more SQL statements, passing each to a callback
+ for processing.
+
+ It requires an options object with the following properties:
+
+ - "sql": SQL in any format accepted by exec().
+
+ - "callback" (function): gets passed each prepared statement,
+ as described below.
+
+ - "asPointer" (bool=false): if true, the callback is passed the
+ WASM (sqlite3*) pointer instead of a Stmt object.
+
+ - "saveSql" (array): if set, the SQL of each prepared statement
+ is appended to this array. This can be used without a callback
+ to split SQL into its component statements. Purely empty
+ statements (for for which sqlite3_prepare() returns a NULL
+ sqlite3_stmt, i.e. spaces and comments) are not added to this
+ list unless...
+
+ - "saveEmpty" (bool=false): If true, empty statements are
+ retained in opt.saveSql, but their leading/trailing whitespace
+ is trimmed (as for queries) so they may be empty.
+
+ For each statement in the input SQL:
+
+ 1) If opt.saveSql is set, the SQL is appended to it.
+
+ 2) If callback is set, callback(S) is called, where S is either
+ a Stmt object (by default) or an (sqlite3*) WASM pointer (if
+ opt.asPointer is true). If the callback returns a literal true
+ (as opposed to any other truthy value), ownership of S is
+ transferred to the callback, otherwise S is reset and finalized
+ as soon as the callback returns. If the callback throws, S is
+ unconditionally finalized.
+
+ If neither of opt.saveSql nor opt.callback are set, this
+ function does nothing more than prepare and finalize each
+ statement, which will trigger an exception if any of them
+ contain invalid SQL.
+ */
+ forEachStmt: function(opt){
+ affirmDbOpen(this);
+ opt ??= Object.create(null);
+ if(!opt.sql){
+ return toss3("exec() requires an SQL string.");
+ }
+ const sql = util.flexibleString(opt.sql);
+ const callback = opt.callback;
+ let stmt, pStmt;
+ const stack = wasm.scopedAllocPush();
+ const saveSql = Array.isArray(opt.saveSql) ? opt.saveSql : undefined;
+ try{
+ const isTA = util.isSQLableTypedArray(opt.sql)
+ /* Optimization: if the SQL is a TypedArray we can save some string
+ conversion costs. */;
+ /* Allocate the two output pointers (ppStmt, pzTail) and heap
+ space for the SQL (pSql). When prepare_v3() returns, pzTail
+ will point to somewhere in pSql. */
+ let sqlByteLen = isTA ? opt.sql.byteLength : wasm.jstrlen(sql);
+ const ppStmt = wasm.scopedAlloc(
+ /* output (sqlite3_stmt**) arg and pzTail */
+ (2 * wasm.ptr.size) + (sqlByteLen + 1/* SQL + NUL */)
+ );
+ const pzTail = wasm.ptr.add(ppStmt, wasm.ptr.size) /* final arg to sqlite3_prepare_v2() */;
+ let pSql = wasm.ptr.add(pzTail, wasm.ptr.size) /* start of the SQL string */;
+ const pSqlEnd = wasm.ptr.add(pSql, sqlByteLen);
+ if(isTA) wasm.heap8().set(sql, pSql);
+ else wasm.jstrcpy(sql, wasm.heap8(), pSql, sqlByteLen, false);
+ wasm.poke8(wasm.ptr.add(pSql, sqlByteLen), 0/*NUL terminator*/);
+ while( pSql && wasm.peek8(pSql) ){
+ pStmt = stmt = null;
+ wasm.pokePtr([ppStmt, pzTail], 0);
+ const zHead = pSql;
+ DB.checkRc(this, capi.sqlite3_prepare_v3(
+ this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail
+ ));
+ [pStmt, pSql] = wasm.peekPtr([ppStmt, pzTail]);
+ sqlByteLen = wasm.ptr.addn(pSqlEnd,-pSql);
+ if(opt.saveSql){
+ if( pStmt ) opt.saveSql.push(capi.sqlite3_sql(pStmt).trim());
+ else if( opt.saveEmpty ){
+ saveSql.push(wasm.typedArrayToString(
+ wasm.heap8u(), Number(zHead),
+ wasm.ptr.addn(zHead, sqlByteLen)
+ ).trim(/*arguable*/));
+ }
+ }
+ if(!pStmt) continue;
+ //sqlite3.config.debug("forEachStmt() pSql =",capi.sqlite3_sql(pStmt));
+ if( !opt.callback ){
+ capi.sqlite3_finalize(pStmt);
+ pStmt = null;
+ continue;
+ }
+ stmt = opt.asPointer ? null : new Stmt(this, pStmt, BindTypes);
+ if( true===callaback(stmt || pStmt) ){
+ stmt = pStmt = null /*callback took ownership */;
+ }else if(stmt){
+ pStmt = null;
+ stmt.reset(
+ /* See [tag:insert-returning-reset]. The thinking here is
+ that if the callback didn't throw for this, it
+ probably should have.
+ */).finalize();
+ stmt = null;
+ }else{
+ const rx = capi.sqlite3_reset(pStmt/*[tag:insert-returning-reset]*/);
+ capi.sqlite3_finalize(pStmt);
+ pStmt = null;
+ DB.checkRc(this, rx);
+ }
+ }/*prepare() loop*/
+ }finally{
+ if(stmt) stmt.finalize();
+ else if(pStmt) capi.sqlite3_finalize(pStmt);
+ wasm.scopedAllocPop(stack);
+ }
+ return this;
+ }/*forEachStmt()*/,
+//#endif nope
+
/**
Creates a new UDF (User-Defined Function) which is accessible
via SQL code. This function may be called in any of the
@@ -2295,55 +2423,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Stmt
}/*oo1 object*/;
- if(util.isUIThread()){
- /**
- Functionally equivalent to DB(storageName,'c','kvvfs') except
- that it throws if the given storage name is not one of 'local'
- or 'session'.
-
- As of version 3.46, the argument may optionally be an options
- object in the form:
-
- {
- filename: 'session'|'local',
- ... etc. (all options supported by the DB ctor)
- }
-
- noting that the 'vfs' option supported by main DB
- constructor is ignored here: the vfs is always 'kvvfs'.
- */
- sqlite3.oo1.JsStorageDb = function(storageName='session'){
- const opt = dbCtorHelper.normalizeArgs(...arguments);
- storageName = opt.filename;
- if('session'!==storageName && 'local'!==storageName){
- toss3("JsStorageDb db name must be one of 'session' or 'local'.");
- }
- opt.vfs = 'kvvfs';
- dbCtorHelper.call(this, opt);
- };
- const jdb = sqlite3.oo1.JsStorageDb;
- jdb.prototype = Object.create(DB.prototype);
- /** Equivalent to sqlite3_js_kvvfs_clear(). */
- jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
- /**
- Clears this database instance's storage or throws if this
- instance has been closed. Returns the number of
- database blocks which were cleaned up.
- */
- jdb.prototype.clearStorage = function(){
- return jdb.clearStorage(affirmDbOpen(this).filename);
- };
- /** Equivalent to sqlite3_js_kvvfs_size(). */
- jdb.storageSize = capi.sqlite3_js_kvvfs_size;
- /**
- Returns the _approximate_ number of bytes this database takes
- up in its storage or throws if this instance has been closed.
- */
- jdb.prototype.storageSize = function(){
- return jdb.storageSize(affirmDbOpen(this).filename);
- };
- }/*main-window-only bits*/
-
});
//#else
/* Built with the omit-oo1 flag. */
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js
index 069f3fdb5..c53acee76 100644
--- a/ext/wasm/api/sqlite3-api-prologue.js
+++ b/ext/wasm/api/sqlite3-api-prologue.js
@@ -27,13 +27,14 @@
/**
sqlite3ApiBootstrap() is the only global symbol persistently
exposed by this API. It is intended to be called one time at the
- end of the API amalgamation process, passed configuration details
- for the current environment, and then optionally be removed from
- the global object using `delete globalThis.sqlite3ApiBootstrap`.
+ end of the API amalgamation process and passed configuration details
+ for the current environment.
This function is not intended for client-level use. It is intended
for use in creating bundles configured for specific WASM
- environments.
+ environments. That said, the "sqlite3-api.js" intermediary build
+ file aims to be suitable for dropping in to custom builds, and it
+ exposes only this function.
This function expects a configuration object, intended to abstract
away details specific to any given WASM environment, primarily so
@@ -93,9 +94,10 @@
can be replaced with (e.g.) empty functions to squelch all such
output.
- - `wasmfsOpfsDir`[^1]: Specifies the "mount point" of the OPFS-backed
- filesystem in WASMFS-capable builds.
-
+ - `wasmfsOpfsDir`[^1]: Specifies the "mount point" of the
+ OPFS-backed filesystem in WASMFS-capable builds. This is only
+ used in WASMFS-capable builds of the library (which the canonical
+ builds do not include).
[^1] = This property may optionally be a function, in which case
this function calls that function to fetch the value,
@@ -125,7 +127,8 @@
Both sqlite3ApiBootstrap.defaultConfig and
globalThis.sqlite3ApiConfig get deleted by sqlite3ApiBootstrap()
because any changes to them made after that point would have no
- useful effect.
+ useful effect. This function also deletes itself from globalThis
+ when it's called.
This function returns a Promise to the sqlite3 namespace object,
which resolves after the async pieces of the library init are
@@ -177,14 +180,6 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
}
});
- /**
- Eliminate any confusion about whether these config objects may
- be used after library initialization by eliminating the outward-facing
- objects...
- */
- delete globalThis.sqlite3ApiConfig;
- delete sqlite3ApiBootstrap.defaultConfig;
-
/**
The main sqlite3 binding API gets installed into this object,
mimicking the C API as closely as we can. The numerous members
@@ -757,6 +752,11 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
toss: function(...args){throw new Error(args.join(' '))},
toss3,
typedArrayPart: wasm.typedArrayPart,
+ assert: function(arg,msg){
+ if( !arg ){
+ util.toss("Assertion failed:",msg);
+ }
+ },
/**
Given a byte array or ArrayBuffer, this function throws if the
lead bytes of that buffer do not hold a SQLite3 database header,
@@ -796,25 +796,10 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
/**
wasm.X properties which are used for configuring the wasm
- environment via whwashutil.js.
+ environment via whwashutil.js. This object gets fleshed out with
+ a number of WASM-specific utilities, in sqlite3-api-glue.c-pp.js.
*/
Object.assign(wasm, {
- /**
- The WASM IR (Intermediate Representation) value for
- pointer-type values. If set then it MUST be one of 'i32' or
- 'i64' (else an exception will be thrown). If it's not set, it
- will default to 'i32'.
- */
- pointerIR: config.wasmPtrIR,
-
- /**
- True if BigInt support was enabled via (e.g.) the
- Emscripten -sWASM_BIGINT flag, else false. When
- enabled, certain 64-bit sqlite3 APIs are enabled which
- are not otherwise enabled due to JS/WASM int64
- impedance mismatches.
- */
- bigIntEnabled: !!config.bigIntEnabled,
/**
The symbols exported by the WASM environment.
@@ -825,8 +810,9 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
/**
When Emscripten compiles with `-sIMPORTED_MEMORY`, it
initializes the heap and imports it into wasm, as opposed to
- the other way around. In this case, the memory is not
- available via this.exports.memory.
+ the other way around. In this case, the memory is not available
+ via this.exports.memory so the client must pass it in via
+ config.memory.
*/
memory: config.memory
|| config.exports['memory']
@@ -834,6 +820,29 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
"in either config.exports.memory (exported)",
"or config.memory (imported)."),
+ /**
+ The WASM pointer size. If set then it MUST be one of 4 or 8 and
+ it MUST correspond to the WASM environment's pointer size. We
+ figure out the size by calling some un-JS-wrapped WASM function
+ which returns a pointer-type value. If that value is a BigInt,
+ it's 64-bit, else it's 32-bit. The pieces which populate
+ sqlite3.wasm (whwasmutil.js) can figure this out _if_ they can
+ allocate, but we have a chicken/egg situation there which makes
+ it illegal for that code to invoke wasm.dealloc() at the time
+ it would be needed. So we need to configure it ahead of time
+ (here) instead.
+ */
+ pointerSize: ('number'===typeof config.exports.sqlite3_libversion()) ? 4 : 8,
+
+ /**
+ True if BigInt support was enabled via (e.g.) the
+ Emscripten -sWASM_BIGINT flag, else false. When
+ enabled, certain 64-bit sqlite3 APIs are enabled which
+ are not otherwise enabled due to JS/WASM int64
+ impedance mismatches.
+ */
+ bigIntEnabled: !!config.bigIntEnabled,
+
/**
WebAssembly.Table object holding the indirect function call
table. Defaults to exports.__indirect_function_table.
@@ -883,7 +892,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
Like this.alloc.impl(), this.realloc.impl() is a direct binding
to the underlying realloc() implementation which does not throw
- exceptions, instead returning 0 on allocation error.
+ exceptions, instead returning 0 (or 0n) on allocation error.
*/
realloc: undefined/*installed later*/,
@@ -949,7 +958,11 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
};
wasm.realloc.impl = wasm.exports[keyRealloc];
wasm.dealloc = function f(m){
- f.impl(wasm.ptr.coerce(m)/*tag:64bit*/);
+ f.impl(wasm.ptr.coerce(m)/*tag:64bit*/)
+ /* This coerce() is the reason we have to set wasm.pointerSize before
+ calling WhWasmUtilInstaller(). If we don't, that code will call
+ into this very early in its init, before wasm.ptr has been set up,
+ resulting in a null deref here. */;
};
wasm.dealloc.impl = wasm.exports[keyDealloc];
}
@@ -1020,18 +1033,18 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
}/*compileOptionUsed()*/;
/**
- sqlite3.wasm.pstack (pseudo-stack) holds a special-case intended
- solely for short-lived, small data. In practice, it's primarily
- used to allocate output pointers. It mus not be used for any
- memory which needs to outlive the scope in which it's obtained
- from pstack.
+ sqlite3.wasm.pstack (pseudo-stack) holds a special-case allocator
+ intended solely for short-lived, small data. In practice, it's
+ primarily used to allocate output pointers. It must not be used
+ for any memory which needs to outlive the scope in which it's
+ obtained from pstack.
The library guarantees only that a minimum of 2kb are available
in this allocator, and it may provide more (it's a build-time
value). pstack.quota and pstack.remaining can be used to get the
total resp. remaining amount of memory.
- It has only a single intended usage:
+ It has only a single intended usage pattern:
```
const stackPos = pstack.pointer;
@@ -1048,13 +1061,11 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
```
This allocator is much faster than a general-purpose one but is
- limited to usage patterns like the one shown above.
+ limited to usage patterns like the one shown above (which are
+ pretty common when using sqlite3.capi).
- It operates from a static range of memory which lives outside of
- space managed by Emscripten's stack-management, so does not
- collide with Emscripten-provided stack allocation APIs. The
- memory lives in the WASM heap and can be used with routines such
- as wasm.poke() and wasm.heap8u().slice().
+ The memory lives in the WASM heap and can be used with routines
+ such as wasm.poke() and wasm.heap8u().slice().
*/
wasm.pstack = Object.assign(Object.create(null),{
/**
@@ -1128,7 +1139,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
argument: if it's 1, it returns a single pointer value. If it's
more than 1, it returns the same as allocChunks().
- When a returned pointers will refer to a 64-bit value, e.g. a
+ When a returned pointer will refer to a 64-bit value, e.g. a
double or int64, and that value must be written or fetched,
e.g. using wasm.poke() or wasm.peek(), it is
important that the pointer in question be aligned to an 8-byte
@@ -1196,6 +1207,9 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
}
})/*wasm.pstack properties*/;
+ /**
+ Docs: https://sqlite.org/wasm/doc/trunk/api-c-style.md#sqlite3_randomness
+ */
capi.sqlite3_randomness = (...args)=>{
if(1===args.length
&& util.isTypedArray(args[0])
@@ -1230,8 +1244,6 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
wasm.exports.sqlite3_randomness(...args);
};
- /** State for sqlite3_wasmfs_opfs_dir(). */
- let __wasmfsOpfsDir = undefined;
/**
If the wasm environment has a WASMFS/OPFS-backed persistent
storage directory, its path is returned by this function. If it
@@ -1255,7 +1267,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
WASMFS capability requires a custom build.
*/
capi.sqlite3_wasmfs_opfs_dir = function(){
- if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir;
+ if(undefined !== this.dir) return this.dir;
// If we have no OPFS, there is no persistent dir
const pdir = config.wasmfsOpfsDir;
if(!pdir
@@ -1263,21 +1275,21 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|| !globalThis.FileSystemDirectoryHandle
|| !globalThis.FileSystemFileHandle
|| !wasm.exports.sqlite3__wasm_init_wasmfs){
- return __wasmfsOpfsDir = "";
+ return this.dir = "";
}
try{
if(pdir && 0===wasm.xCallWrapped(
'sqlite3__wasm_init_wasmfs', 'i32', ['string'], pdir
)){
- return __wasmfsOpfsDir = pdir;
+ return this.dir = pdir;
}else{
- return __wasmfsOpfsDir = "";
+ return this.dir = "";
}
}catch(e){
// sqlite3__wasm_init_wasmfs() is not available
- return __wasmfsOpfsDir = "";
+ return this.dir = "";
}
- };
+ }.bind(Object.create(null));
/**
Returns true if sqlite3.capi.sqlite3_wasmfs_opfs_dir() is a
@@ -1333,7 +1345,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
*/
capi.sqlite3_js_vfs_list = function(){
const rc = [];
- let pVfs = capi.sqlite3_vfs_find(wasm.ptr.coerce(0));
+ let pVfs = capi.sqlite3_vfs_find(wasm.ptr.null);
while(pVfs){
const oVfs = new capi.sqlite3_vfs(pVfs);
rc.push(wasm.cstrToJs(oVfs.$zName));
@@ -1401,7 +1413,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
or not provided, then "main" is assumed.
*/
capi.sqlite3_js_db_vfs =
- (dbPointer, dbName=0)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName);
+ (dbPointer, dbName=wasm.ptr.null)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName);
/**
A thin wrapper around capi.sqlite3_aggregate_context() which
@@ -1597,86 +1609,6 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
return x===v ? undefined : x;
}
- if( util.isUIThread() ){
- /* Features specific to the main window thread... */
-
- /**
- Internal helper for sqlite3_js_kvvfs_clear() and friends.
- Its argument should be one of ('local','session',"").
- */
- const __kvvfsInfo = function(which){
- const rc = Object.create(null);
- rc.prefix = 'kvvfs-'+which;
- rc.stores = [];
- if('session'===which || ""===which) rc.stores.push(globalThis.sessionStorage);
- if('local'===which || ""===which) rc.stores.push(globalThis.localStorage);
- return rc;
- };
-
- /**
- Clears all storage used by the kvvfs DB backend, deleting any
- DB(s) stored there. Its argument must be either 'session',
- 'local', or "". In the first two cases, only sessionStorage
- resp. localStorage is cleared. If it's an empty string (the
- default) then both are cleared. Only storage keys which match
- the pattern used by kvvfs are cleared: any other client-side
- data are retained.
-
- This function is only available in the main window thread.
-
- Returns the number of entries cleared.
- */
- capi.sqlite3_js_kvvfs_clear = function(which=""){
- let rc = 0;
- const kvinfo = __kvvfsInfo(which);
- kvinfo.stores.forEach((s)=>{
- const toRm = [] /* keys to remove */;
- let i;
- for( i = 0; i < s.length; ++i ){
- const k = s.key(i);
- if(k.startsWith(kvinfo.prefix)) toRm.push(k);
- }
- toRm.forEach((kk)=>s.removeItem(kk));
- rc += toRm.length;
- });
- return rc;
- };
-
- /**
- This routine guesses the approximate amount of
- window.localStorage and/or window.sessionStorage in use by the
- kvvfs database backend. Its argument must be one of
- ('session', 'local', ""). In the first two cases, only
- sessionStorage resp. localStorage is counted. If it's an empty
- string (the default) then both are counted. Only storage keys
- which match the pattern used by kvvfs are counted. The returned
- value is the "length" value of every matching key and value,
- noting that JavaScript stores each character in 2 bytes.
-
- Note that the returned size is not authoritative from the
- perspective of how much data can fit into localStorage and
- sessionStorage, as the precise algorithms for determining
- those limits are unspecified and may include per-entry
- overhead invisible to clients.
- */
- capi.sqlite3_js_kvvfs_size = function(which=""){
- let sz = 0;
- const kvinfo = __kvvfsInfo(which);
- kvinfo.stores.forEach((s)=>{
- let i;
- for(i = 0; i < s.length; ++i){
- const k = s.key(i);
- if(k.startsWith(kvinfo.prefix)){
- sz += k.length;
- sz += s.getItem(k).length;
- }
- }
- });
- return sz * 2 /* because JS uses 2-byte char encoding */;
- };
-
- }/* main-window-only bits */
-
/**
Wraps all known variants of the C-side variadic
sqlite3_db_config().
@@ -1953,55 +1885,58 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
return (0===v) ? undefined : capi.sqlite3_value_to_js(v, throwIfCannotConvert);
};
- /**
- Internal impl of sqlite3_preupdate_new/old_js() and
- sqlite3changeset_new/old_js().
- */
- const __newOldValue = function(pObj, iCol, impl){
- impl = capi[impl];
- if(!this.ptr) this.ptr = wasm.allocPtr();
- else wasm.pokePtr(this.ptr, 0);
- const rc = impl(pObj, iCol, this.ptr);
- if(rc) return SQLite3Error.toss(rc,arguments[2]+"() failed with code "+rc);
- const pv = wasm.peekPtr(this.ptr);
- return pv ? capi.sqlite3_value_to_js( pv, true ) : undefined;
- }.bind(Object.create(null));
-
- /**
- A wrapper around sqlite3_preupdate_new() which fetches the
- sqlite3_value at the given index and returns the result of
- passing it to sqlite3_value_to_js(). Throws on error.
- */
- capi.sqlite3_preupdate_new_js =
- (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_new');
+ if( true ){ /* changeset/preupdate additions... */
+ /**
+ Internal impl of sqlite3_preupdate_new/old_js() and
+ sqlite3changeset_new/old_js().
+ */
+ const __newOldValue = function(pObj, iCol, impl){
+ impl = capi[impl];
+ if(!this.ptr) this.ptr = wasm.allocPtr();
+ else wasm.pokePtr(this.ptr, 0);
+ const rc = impl(pObj, iCol, this.ptr);
+ if(rc) return SQLite3Error.toss(rc,arguments[2]+"() failed with code "+rc);
+ const pv = wasm.peekPtr(this.ptr);
+ return pv ? capi.sqlite3_value_to_js( pv, true ) : undefined;
+ }.bind(Object.create(null));
- /**
- The sqlite3_preupdate_old() counterpart of
- sqlite3_preupdate_new_js(), with an identical interface.
- */
- capi.sqlite3_preupdate_old_js =
- (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_old');
+ /**
+ A wrapper around sqlite3_preupdate_new() which fetches the
+ sqlite3_value at the given index and returns the result of
+ passing it to sqlite3_value_to_js(). Throws on error.
+ */
+ capi.sqlite3_preupdate_new_js =
+ (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_new');
- /**
- A wrapper around sqlite3changeset_new() which fetches the
- sqlite3_value at the given index and returns the result of
- passing it to sqlite3_value_to_js(). Throws on error.
+ /**
+ The sqlite3_preupdate_old() counterpart of
+ sqlite3_preupdate_new_js(), with an identical interface.
+ */
+ capi.sqlite3_preupdate_old_js =
+ (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_old');
- If sqlite3changeset_new() succeeds but has no value to report,
- this function returns the undefined value, noting that undefined
- is a valid conversion from an `sqlite3_value`, so is unambiguous.
- */
- capi.sqlite3changeset_new_js =
- (pChangesetIter, iCol) => __newOldValue(pChangesetIter, iCol,
- 'sqlite3changeset_new');
+ /**
+ A wrapper around sqlite3changeset_new() which fetches the
+ sqlite3_value at the given index and returns the result of
+ passing it to sqlite3_value_to_js(). Throws on error.
+
+ If sqlite3changeset_new() succeeds but has no value to report,
+ this function returns the undefined value, noting that
+ undefined is not a valid conversion from an `sqlite3_value`, so
+ is unambiguous.
+ */
+ capi.sqlite3changeset_new_js =
+ (pChangesetIter, iCol) => __newOldValue(pChangesetIter, iCol,
+ 'sqlite3changeset_new');
- /**
- The sqlite3changeset_old() counterpart of
- sqlite3changeset_new_js(), with an identical interface.
- */
- capi.sqlite3changeset_old_js =
- (pChangesetIter, iCol)=>__newOldValue(pChangesetIter, iCol,
- 'sqlite3changeset_old');
+ /**
+ The sqlite3changeset_old() counterpart of
+ sqlite3changeset_new_js(), with an identical interface.
+ */
+ capi.sqlite3changeset_old_js =
+ (pChangesetIter, iCol)=>__newOldValue(pChangesetIter, iCol,
+ 'sqlite3changeset_old');
+ }/*changeset/preupdate additions*/
/* The remainder of the API will be set up in later steps. */
const sqlite3 = {
@@ -2013,10 +1948,10 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
config,
/**
Holds the version info of the sqlite3 source tree from which
- the generated sqlite3-api.js gets built. Note that its version
- may well differ from that reported by sqlite3_libversion(), but
- that should be considered a source file mismatch, as the JS and
- WASM files are intended to be built and distributed together.
+ the generated sqlite3-api.js gets built. Its version may well
+ differ from that reported by sqlite3_libversion(), but that
+ should be considered a source file mismatch, as the JS and WASM
+ files are intended to be built and distributed together.
This object is initially a placeholder which gets replaced by a
build-generated object.
@@ -2041,9 +1976,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
async init will be fatal to the init as a whole, but init
routines are themselves welcome to install dummy catch()
handlers which are not fatal if their failure should be
- considered non-fatal. If called more than once, the second and
- subsequent calls are no-ops which return a pre-resolved
- Promise.
+ considered non-fatal.
Ideally this function is called as part of the Promise chain
which handles the loading and bootstrapping of the API. If not
@@ -2060,18 +1993,14 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
*/
asyncPostInit: async function ff(){
if(ff.isReady instanceof Promise) return ff.isReady;
- let lia = sqlite3ApiBootstrap.initializersAsync;
- delete sqlite3ApiBootstrap.initializersAsync;
+ let lia = this.initializersAsync;
+ delete this.initializersAsync;
const postInit = async ()=>{
if(!sqlite3.__isUnderTest){
/* Delete references to internal-only APIs which are used by
some initializers. Retain them when running in test mode
so that we can add tests for them. */
delete sqlite3.util;
- /* It's conceivable that we might want to expose
- StructBinder to client-side code, but it's only useful if
- clients build their own sqlite3.wasm which contains their
- own C struct types. */
delete sqlite3.StructBinder;
}
return sqlite3;
@@ -2090,7 +2019,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
let p = Promise.resolve(sqlite3);
while(lia.length) p = p.then(lia.shift());
return ff.isReady = p.catch(catcher);
- },
+ }.bind(sqlite3ApiBootstrap),
/**
scriptInfo ideally gets injected into this object by the
infrastructure which assembles the JS/WASM module. It contains
@@ -2105,6 +2034,9 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
*/
scriptInfo: undefined
};
+ if( ('undefined'!==typeof sqlite3IsUnderTest/* from post-js-header.js */) ){
+ sqlite3.__isUnderTest = !!sqlite3IsUnderTest;
+ }
try{
sqlite3ApiBootstrap.initializers.forEach((f)=>{
f(sqlite3);
@@ -2117,16 +2049,34 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
}
delete sqlite3ApiBootstrap.initializers;
sqlite3ApiBootstrap.sqlite3 = sqlite3;
- delete globalThis.sqlite3ApiBootstrap;
- delete globalThis.sqlite3ApiConfig;
- sqlite3InitScriptInfo.debugModule(
- "sqlite3ApiBootstrap() complete", sqlite3
- );
- sqlite3.scriptInfo /* used by some async init code */ =
- sqlite3InitScriptInfo /* from post-js-header.js */;
- if( (sqlite3.__isUnderTest = sqlite3IsUnderTest /* from post-js-header.js */) ){
- sqlite3.config.emscripten = EmscriptenModule;
- const iw = sqlite3InitScriptInfo.instantiateWasm;
+ if( 'undefined'!==typeof sqlite3InitScriptInfo/* from post-js-header.js */ ){
+ sqlite3InitScriptInfo.debugModule(
+ "sqlite3ApiBootstrap() complete", sqlite3
+ );
+ sqlite3.scriptInfo
+ /* Used by some async init code. As of 2025-11-15 this is still
+ in use by the OPFS VFS for locating its worker. In non-Emscripten
+ builds, this would need to be injected in somewhere to get
+ that VFS loading. */ = sqlite3InitScriptInfo;
+ }
+ if( sqlite3.__isUnderTest ){
+ if( 'undefined'!==typeof EmscriptenModule ){
+ sqlite3.config.emscripten = EmscriptenModule;
+ }
+ /*
+ The problem with exposing these pieces (in non-testing runs) via
+ sqlite3.wasm is that it exposes non-SQLite pieces to the
+ clients, who may come to expect it to remain. _We_ only have
+ these data because we've overridden Emscripten's wasm file
+ loader, and if we lose that capability for some reason then
+ we'll lose access to this metadata.
+
+ These data are interesting for exploring how the wasm/JS
+ pieces connect, e.g. for exploring exactly what Emscripten
+ imports into WASM from its JS glue, but it's not
+ SQLite-related.
+ */
+ const iw = sqlite3.scriptInfo?.instantiateWasm;
if( iw ){
/* Metadata injected by the custom Module.instantiateWasm()
in pre-js.c-pp.js. */
@@ -2135,10 +2085,21 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
sqlite3.wasm.imports = iw.imports;
}
}
+
+ /**
+ Eliminate any confusion about whether these config objects may
+ be used after library initialization by eliminating the outward-facing
+ objects...
+ */
+ delete globalThis.sqlite3ApiConfig;
+ delete globalThis.sqlite3ApiBootstrap;
+ delete sqlite3ApiBootstrap.defaultConfig;
return sqlite3.asyncPostInit().then((s)=>{
- sqlite3InitScriptInfo.debugModule(
- "sqlite3.asyncPostInit() complete", sqlite3
- );
+ if( 'undefined'!==typeof sqlite3InitScriptInfo/* from post-js-header.js */ ){
+ sqlite3InitScriptInfo.debugModule(
+ "sqlite3.asyncPostInit() complete", s
+ );
+ }
delete s.asyncPostInit;
delete s.scriptInfo;
delete s.emscripten;
diff --git a/ext/wasm/api/sqlite3-license-version-header.js b/ext/wasm/api/sqlite3-license-version-header.js
index 482989463..dd32f4666 100644
--- a/ext/wasm/api/sqlite3-license-version-header.js
+++ b/ext/wasm/api/sqlite3-license-version-header.js
@@ -1,4 +1,5 @@
-/*
+/* @preserve
+**
** LICENSE for the sqlite3 WebAssembly/JavaScript APIs.
**
** This bundle (typically released as sqlite3.js or sqlite3.mjs)
diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js
index e10d0dd50..79fc47393 100644
--- a/ext/wasm/api/sqlite3-opfs-async-proxy.js
+++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js
@@ -1,4 +1,4 @@
-/*
+/* @preserve
2022-09-16
The author disclaims copyright to this source code. In place of a
diff --git a/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js
new file mode 100644
index 000000000..e3fb72287
--- /dev/null
+++ b/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js
@@ -0,0 +1,2095 @@
+/*
+ 2025-11-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 houses the "kvvfs" pieces of the SQLite3 JS API. Most of
+ kvvfs is implemented in src/os_kv.c and exposed/extended for use
+ here via sqlite3-wasm.c.
+
+ Main project home page: https://sqlite.org
+
+ Documentation home page: https://sqlite.org/wasm
+*/
+//#if omit-kvvfs
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ /* These are JS plumbing, not part of the public API */
+ delete sqlite3.capi.sqlite3_kvvfs_methods;
+ delete sqlite3.capi.KVVfsFile;
+}
+//#else
+//#@policy error
+//#savepoint begin
+//#define kvvfs-v2-added-in=3.52.0
+/**
+ kvvfs - the Key/Value VFS - is an SQLite3 VFS which delegates
+ storage of its pages and metadata to a key-value store.
+
+ It was conceived in order to support JS's localStorage and
+ sessionStorage objects. Its native implementation uses files as
+ key/value storage (one file per record) but the JS implementation
+ replaces a few methods so that it can use the aforementioned
+ objects as storage.
+
+ It uses a bespoke ASCII encoding to store each db page as a
+ separate record and stores some metadata, like the db's unencoded
+ size and its journal, as individual records.
+
+ kvvfs is significantly less efficient than a plain in-memory db but
+ it also, as a side effect of its design, offers a JSON-friendly
+ interchange format for exporting and importing databases.
+
+ kvvfs is _not_ designed for heavy db loads. It is relatively
+ malloc()-heavy, having to de/allocate frequently, and it
+ spends much of its time converting the raw db pages into and out of
+ an ASCII encoding.
+
+ But it _does_ work and is "performant enough" for db work of the
+ scale of a db which will fit within sessionStorage or localStorage
+ (just 2-3mb).
+
+ "Version 2" extends it to support using Storage-like objects as
+ backing storage, Storage being the JS class which localStorage and
+ sessionStorage both derive from. This essentially moves the backing
+ store from whatever localStorage and sessionStorage use to an
+ in-memory object.
+
+ This effort is primarily a stepping stone towards eliminating, if
+ it proves possible, the POSIX I/O API dependencies in SQLite's WASM
+ builds. That is: if this VFS works properly, it can be set as the
+ default VFS and we can eliminate the "unix" VFS from the JS/WASM
+ builds (as opposed to server-wise/WASI builds). That still, as of
+ 2025-11-23, a ways away, but it's the main driver for version 2 of
+ kvvfs.
+
+ Version 2 remains compatible with version 1 databases and always
+ writes localStorage/sessionStorage metadata in the v1 format, so
+ such dbs can be manipulated freely by either version. For transient
+ storage objects (new in version 2), the format of its record keys
+ is simpified, requiring less space than v1 keys by eliding
+ redundant (in this context) info from the keys.
+
+ Another benefit of v2 is its ability to export dbs into a
+ JSON-friendly (but not human-friendly) format.
+
+ A potential, as-yet-unproven, benefit, would be the ability to plug
+ arbitrary Storage-compatible objects in so that clients could,
+ e.g. asynchronously post updates to db pages to some back-end for
+ backups.
+*/
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ 'use strict';
+ const capi = sqlite3.capi,
+ sqlite3_kvvfs_methods = capi.sqlite3_kvvfs_methods,
+ KVVfsFile = capi.KVVfsFile,
+ pKvvfs = sqlite3.capi.sqlite3_vfs_find("kvvfs")
+
+ /* These are JS plumbing, not part of the public API */
+ delete capi.sqlite3_kvvfs_methods;
+ delete capi.KVVfsFile;
+
+ if( !pKvvfs ) return /* nothing to do */;
+ if( 0 ){
+ /* This working would be our proverbial holy grail, in that it
+ would allow us to eliminate the current default VFS, which
+ relies on POSIX I/O APIs. Eliminating that dependency would get
+ us one giant step closer to creating wasi-sdk builds. */
+ capi.sqlite3_vfs_register(pKvvfs, 1);
+ }
+
+ const util = sqlite3.util,
+ wasm = sqlite3.wasm,
+ toss3 = util.toss3,
+ hop = (o,k)=>Object.prototype.hasOwnProperty.call(o,k);
+
+ const kvvfsMethods = new sqlite3_kvvfs_methods(
+ /* Wraps the static sqlite3_api_methods singleton */
+ wasm.exports.sqlite3__wasm_kvvfs_methods()
+ );
+ util.assert( 32<=kvvfsMethods.$nKeySize, "unexpected kvvfsMethods.$nKeySize: "+kvvfsMethods.$nKeySize);
+
+ /**
+ Most of the VFS-internal state.
+ */
+ const cache = Object.assign(Object.create(null),{
+ /** Regex matching journal file names. */
+ rxJournalSuffix: /-journal$/,
+ /** Frequently-used C-string. */
+ zKeyJrnl: wasm.allocCString("jrnl"),
+ /** Frequently-used C-string. */
+ zKeySz: wasm.allocCString("sz"),
+ /**
+ The maximum size of a kvvfs record key. It is historically only
+ 32, a limitation currently retained only because it's convenient to
+ do so (the underlying code has outgrown the need for the artifically
+ low limit).
+
+ We cache this value here because the end of this init code will
+ dispose of kvvfsMethods, invalidating it.
+ */
+ keySize: kvvfsMethods.$nKeySize,
+ /**
+ WASM heap memory buffers to optimize out some frequent
+ allocations.
+ */
+ buffer: Object.assign(Object.create(null),{
+ /**
+ The size of each buffer in this.pool.
+
+ kvvfsMethods.$nBufferSize is slightly larger than the output
+ space needed for a kvvfs-encoded 64kb db page in a worse-cast
+ encoding (128kb). It is not suitable for arbitrary buffer
+ use, only page de/encoding.
+ */
+ n: kvvfsMethods.$nBufferSize,
+ /**
+ Map of buffer ids to wasm.alloc()'d pointers of size
+ this.n. (Re)used by various internals.
+
+ Buffer ids 0 and 1 are used in the API internals. Other
+ names are used in higher-level APIs.
+
+ See memBuffer() and memBufferFree().
+ */
+ pool: Object.create(null)
+ })
+ });
+
+ /**
+ Returns a (cached) wasm.alloc()'d buffer of cache.buffer.n size,
+ throwing on OOM.
+
+ We leak this one-time alloc because we've no better option.
+ sqlite3_vfs does not have a finalizer, so we've no place to hook
+ in the cleanup. We "could" extend sqlite3_shutdown() to have a
+ cleanup list for stuff like this but that function is never
+ used in JS, so it's hardly worth it.
+ */
+ cache.memBuffer = (id=0)=>cache.buffer.pool[id] ??= wasm.alloc(cache.buffer.n);
+
+ /** Frees the buffer with the given id. */
+ cache.memBufferFree = (id)=>{
+ const b = cache.buffer.pool[id];
+ if( b ){
+ wasm.dealloc(b);
+ delete cache.buffer.pool[id];
+ }
+ };
+
+ const noop = ()=>{};
+ const debug = sqlite3.__isUnderTest
+ ? (...args)=>sqlite3.config.debug?.("kvvfs:", ...args)
+ : noop;
+ const warn = (...args)=>sqlite3.config.warn?.("kvvfs:", ...args);
+ const error = (...args)=>sqlite3.config.error?.("kvvfs:", ...args);
+
+ /**
+ Implementation of JS's Storage interface for use as backing store
+ of the kvvfs. Storage is a native class and its constructor
+ cannot be legally called from JS, making it impossible to
+ directly subclass Storage. This class implements (only) the
+ Storage interface, to make it a drop-in replacement for
+ localStorage/sessionStorage. (Any behavioral discrepancies are to
+ be considered bugs.)
+
+ This impl simply proxies a plain, prototype-less Object, suitable
+ for JSON-ing.
+
+ Design note: Storage has a bit of an odd iteration-related
+ interface as does not (AFAIK) specify specific behavior regarding
+ modification during traversal. Because of that, this class does
+ some seemingly unnecessary things with its #keys member, deleting
+ and recreating it whenever a property index might be invalidated.
+ */
+ class KVVfsStorage {
+ #map = Object.create(null);
+ #keys = null;
+ #size = 0;
+
+ constructor(){
+ this.clear();
+ }
+
+ #getKeys(){
+ return this.#keys ??= Object.keys(this.#map);
+ }
+
+ key(n){
+ if(n < 0 || n >= this.#size) return null;
+ return this.#getKeys()[n];
+ }
+
+ getItem(k){
+ return this.#map[k] ?? null;
+ }
+
+ setItem(k,v){
+ if( !(k in this.#map) ){
+ ++this.#size;
+ this.#keys = null;
+ }
+ this.#map[k] = ''+v;
+ }
+
+ removeItem(k){
+ if( k in this.#map ){
+ delete this.#map[k];
+ --this.#size;
+ this.#keys = null;
+ }
+ }
+
+ clear(){
+ this.#map = Object.create(null);
+ this.#keys = null;
+ this.#size = 0;
+ }
+
+ get length() {
+ return this.#size;
+ }
+ }/*KVVfsStorage*/;
+
+ /** True if v is the name of one of the special persistant Storage
+ objects. */
+ const kvvfsIsPersistentName = (v)=>'local'===v || 'session'===v;
+
+ /**
+ Keys in kvvfs have a prefix of "kvvfs-NAME-", where NAME is the
+ db name. This key is redundant in JS but it's how kvvfs works (it
+ saves each key to a separate file, so needs a distinct namespace
+ per data source name). We retain this prefix in 'local' and
+ 'session' storage for backwards compatibility and so that they
+ can co-exist with client data in their storage, but we elide them
+ from "v2" storage, where they're superfluous.
+ */
+ const kvvfsKeyPrefix = (v)=>kvvfsIsPersistentName(v) ? 'kvvfs-'+v+'-' : '';
+
+ /**
+ Throws if storage name n (JS string) is not valid for use as a
+ storage name. Much of this goes back to kvvfs having a fixed
+ buffer size for its keys, and the storage name needing to be
+ encoded in the keys for local/session storage.
+
+ The second argument must only be true when called from xOpen() -
+ it makes names with a "-journal" suffix legal.
+ */
+ const validateStorageName = function(n,mayBeJournal=false){
+ if( kvvfsIsPersistentName(n) ) return;
+ const len = (new Blob([n])).size/*byte length*/;
+ if( !len ) toss3(capi.SQLITE_MISUSE, "Empty name is not permitted.");
+ let maxLen = cache.keySize - 1;
+ if( cache.rxJournalSuffix.test(n) ){
+ if( !mayBeJournal ){
+ toss3(capi.SQLITE_MISUSE,
+ "Storage names may not have a '-journal' suffix.");
+ }
+ }else if( ['-wal','-shm'].filter(v=>n.endsWith(v)).length ){
+ toss3(capi.SQLITE_MISUSE,
+ "Storage names may not have a -wal or -shm suffix.");
+ }else{
+ maxLen -= 8 /* so we have space for a matching "-journal" suffix */;
+ }
+ if( len > maxLen ){
+ toss3(capi.SQLITE_RANGE, "Storage name is too long. Limit =", maxLen);
+ }
+ let i;
+ for( i = 0; i < len; ++i ){
+ const ch = n.codePointAt(i);
+ if( ch<32 ){
+ toss3(capi.SQLITE_RANGE,
+ "Illegal character ("+ch+"d) in storage name:",n);
+ }
+ }
+ };
+
+ /**
+ Create a new instance of the objects which go into
+ cache.storagePool, with a refcount of 1. If passed a Storage-like
+ object as its second argument, it is used for the storage,
+ otherwise it creates a new KVVfsStorage object.
+ */
+ const newStorageObj = (name,storage=undefined)=>Object.assign(Object.create(null),{
+ /**
+ JS string value of this KVVfsFile::$zClass. i.e. the storage's
+ name.
+ */
+ jzClass: name,
+ /**
+ Refcount. This keeps dbs and journals pointing to the same
+ storage for the life of both and enables kvvfs to behave more
+ like a conventional filesystem (a stepping stone towards
+ downstream API goals). Managed by xOpen() and xClose().
+ */
+ refc: 1,
+ /**
+ If true, this storage will be removed by xClose() or
+ sqlite3_js_kvvfs_unlink() when refc reaches 0. The others will
+ persist when refc==0, to give the illusion of real back-end
+ storage. Managed by xOpen() and sqlite3_js_kvvfs_reserve(). By
+ default this is false but the delete-on-close=1 flag can be
+ used to set this to true.
+ */
+ deleteAtRefc0: false,
+ /**
+ The backing store. Must implement the Storage interface.
+ */
+ storage: storage || new KVVfsStorage,
+ /**
+ The storage prefix used for kvvfs keys. It is
+ "kvvfs-STORAGENAME-" for local/session storage and an empty
+ string for other storage. local/session storage must use the
+ long form (A) for backwards compatibility and (B) so that kvvfs
+ can coexist with non-db client data in those backends. Neither
+ (A) nor (B) are concerns for KVVfsStorage objects.
+
+ This prefix mirrors the one generated by os_kv.c's
+ kvrecordMakeKey() and must stay in sync with that one.
+ */
+ keyPrefix: kvvfsKeyPrefix(name),
+ /**
+ KVVfsFile instances currently using this storage. Managed by
+ xOpen() and xClose().
+ */
+ files: [],
+ /**
+ If set, it's an array of objects with various event
+ callbacks. See sqlite3_js_kvvfs_listen(). When there are no
+ listeners, this member is set to undefined (instead of an empty
+ array) to allow us to more easily optimize out calls to
+ notifyListeners() for the common case of no listeners.
+ */
+ listeners: undefined
+ });
+
+ /**
+ Public interface for kvvfs v2. The capi.sqlite3_js_kvvfs_...()
+ routines remain in place for v1. Some members of this class proxy
+ to those functions but use different default argument values in
+ some cases.
+ */
+ const kvvfs = sqlite3.kvvfs = Object.create(null);
+ if( sqlite3.__isUnderTest ){
+ /* For inspection via the dev tools console. */
+ kvvfs.log = Object.assign(Object.create(null),{
+ xOpen: false,
+ xClose: false,
+ xWrite: false,
+ xRead: false,
+ xSync: false,
+ xAccess: false,
+ xFileControl: false,
+ xRcrdRead: false,
+ xRcrdWrite: false,
+ xRcrdDelete: false,
+ });
+ }
+
+ /**
+ Deletes the cache.storagePool entries for store (a
+ cache.storagePool entry) and its db/journal counterpart.
+ */
+ const deleteStorage = function(store){
+ const other = cache.rxJournalSuffix.test(store.jzClass)
+ ? store.jzClass.replace(cache.rxJournalSuffix,'')
+ : store.jzClass+'-journal';
+ kvvfs?.log?.xClose
+ && debug("cleaning up storage handles [", store.jzClass, other,"]",store);
+ delete cache.storagePool[store.jzClass];
+ delete cache.storagePool[other];
+ if( !sqlite3.__isUnderTest ){
+ /* In test runs, leave these for inspection. If we delete them here,
+ any prior dumps of them emitted via the console get cleared out
+ because the console shows live objects instead of call-time
+ static dumps. */
+ delete store.storage;
+ delete store.refc;
+ }
+ };
+
+ /**
+ Add both store.jzClass and store.jzClass+"-journal"
+ to cache,storagePool.
+ */
+ const installStorageAndJournal = (store)=>
+ cache.storagePool[store.jzClass] =
+ cache.storagePool[store.jzClass+'-journal'] = store;
+
+ /**
+ The public name of the current thread's transient storage
+ object. A storage object with this name gets preinstalled.
+ */
+ const nameOfThisThreadStorage = '.';
+
+ /**
+ Map of JS-stringified KVVfsFile::zClass names to
+ reference-counted Storage objects. These objects are created in
+ xOpen(). Their refcount is decremented in xClose(), and the
+ record is destroyed if the refcount reaches 0. We refcount so
+ that concurrent active xOpen()s on a given name, and within a
+ given thread, use the same storage object.
+ */
+ cache.storagePool = Object.assign(Object.create(null),{
+ /* Start off with mappings for well-known names. */
+ [nameOfThisThreadStorage]: newStorageObj(nameOfThisThreadStorage)
+ });
+
+ if( globalThis.Storage ){
+ /* If available, install local/session storage. */
+ if( globalThis.localStorage instanceof globalThis.Storage ){
+ cache.storagePool.local = newStorageObj('local', globalThis.localStorage);
+ }
+ if( globalThis.sessionStorage instanceof globalThis.Storage ){
+ cache.storagePool.session = newStorageObj('session', globalThis.sessionStorage);
+ }
+ }
+
+ cache.builtinStorageNames = Object.keys(cache.storagePool);
+
+ const isBuiltinName = (n)=>cache.builtinStorageNames.indexOf(n)>-1;
+
+ /* Add "-journal" twins for each cache.storagePool entry... */
+ for(const k of Object.keys(cache.storagePool)){
+ /* Journals in kvvfs are are stored as individual records within
+ their Storage-ish object, named "{storage.keyPrefix}jrnl". We
+ always map the db and its journal to the same Storage
+ object. */
+ const orig = cache.storagePool[k];
+ cache.storagePool[k+'-journal'] = orig;
+ }
+
+ cache.setError = (e=undefined, dfltErrCode=capi.SQLITE_ERROR)=>{
+ if( e ){
+ cache.lastError = e;
+ return (e.resultCode | 0) || dfltErrCode;
+ }
+ delete cache.lastError;
+ return 0;
+ };
+
+ cache.popError = ()=>{
+ const e = cache.lastError;
+ delete cache.lastError;
+ return e;
+ };
+
+ /** Exception handler for notifyListeners(). */
+ const catchForNotify = (e)=>{
+ warn("kvvfs.listener handler threw:",e);
+ };
+
+ const kvvfsDecode = wasm.exports.sqlite3__wasm_kvvfs_decode;
+ const kvvfsEncode = wasm.exports.sqlite3__wasm_kvvfs_encode;
+
+ /**
+ Listener events and their argument(s) (via the callback(ev)
+ ev.data member):
+
+ 'open': number of opened handles on this storage.
+
+ 'close': number of opened handles on this storage.
+
+ 'write': key, value
+
+ 'delete': key
+
+ 'sync': true if it's from xSync(), false if it's from
+ xFileControl().
+
+ For efficiency's sake, all calls to this function should
+ be in the form:
+
+ store.listeners && notifyListeners(...);
+
+ Failing to do so will trigger an exceptin in this function (which
+ will be ignored but may produce a console warning).
+ */
+ const notifyListeners = async function(eventName,store,...args){
+ try{
+ //cache.rxPageNoSuffix ??= /(\d+)$/;
+ if( store.keyPrefix && args[0] ){
+ args[0] = args[0].replace(store.keyPrefix,'');
+ }
+ let u8enc, z0, z1, wcache;
+ for(const ear of store.listeners){
+ const ev = Object.create(null);
+ ev.storageName = store.jzClass;
+ ev.type = eventName;
+ const decodePages = ear.decodePages;
+ const f = ear.events[eventName];
+ if( f ){
+ if( !ear.includeJournal && args[0]==='jrnl' ){
+ continue;
+ }
+ if( 'write'===eventName && ear.decodePages && +args[0]>0 ){
+ /* Decode pages to Uint8Array, caching the result in
+ wcache in case we have more listeners. */
+ ev.data = [args[0]];
+ if( wcache?.[args[0]] ){
+ ev.data[1] = wcache[args[0]];
+ continue;
+ }
+ u8enc ??= new TextEncoder('utf-8');
+ z0 ??= cache.memBuffer(10);
+ z1 ??= cache.memBuffer(11);
+ const u = u8enc.encode(args[1]);
+ const heap = wasm.heap8u();
+ heap.set(u, Number(z0));
+ heap[wasm.ptr.addn(z0, u.length)] = 0;
+ const rc = kvvfsDecode(z0, z1, cache.buffer.n);
+ if( rc>0 ){
+ wcache ??= Object.create(null);
+ wcache[args[0]]
+ = ev.data[1]
+ = heap.slice(Number(z1), wasm.ptr.addn(z1,rc));
+ }else{
+ continue;
+ }
+ }else{
+ ev.data = args.length
+ ? ((args.length===1) ? args[0] : args)
+ : undefined;
+ }
+ try{f(ev)?.catch?.(catchForNotify)}
+ catch(e){
+ warn("notifyListeners [",store.jzClass,"]",eventName,e);
+ }
+ }
+ }
+ }catch(e){
+ catchForNotify(e);
+ }
+ }/*notifyListeners()*/;
+
+ /**
+ Returns the storage object mapped to the given string zClass
+ (C-string pointer or JS string).
+ */
+ const storageForZClass = (zClass)=>
+ 'string'===typeof zClass
+ ? cache.storagePool[zClass]
+ : cache.storagePool[wasm.cstrToJs(zClass)];
+
+//#if nope
+ // fileForDb() works but we don't have a current need for it.
+ /**
+ Expects an (sqlite3*). Uses sqlite3_file_control() to extract its
+ (sqlite3_file*). On success it returns a new KVVfsFile instance
+ wrapping that pointer, which the caller must eventual call
+ dispose() on (which won't free the underlying pointer, just the
+ wrapper). Returns null if no handle is found (which would
+ indicate either that pDb is not using kvvfs or a severe bug in
+ its management).
+ */
+ const fileForDb = function(pDb){
+ const stack = wasm.pstack.pointer;
+ try{
+ const pOut = wasm.pstack.allocPtr();
+ return wasm.exports.sqlite3_file_control(
+ pDb, wasm.ptr.null, capi.SQLITE_FCNTL_FILE_POINTER, pOut
+ )
+ ? null
+ : new KVVfsFile(wasm.peekPtr(pOut));
+ }finally{
+ wasm.pstack.restore(stack);
+ }
+ };
+
+ /**
+ Expects an object from the storagePool map. The $szPage and
+ $szDb members of each store.files entry is set to -1 in an attempt
+ to trigger those values to reload.
+ */
+ const alertFilesToReload = (store)=>{
+ try{
+ for( const f of store.files ){
+ // FIXME: we need to use one of the C APIs for this, maybe an
+ // fcntl.
+ f.$szPage = -1;
+ f.$szDb = -1n
+ }
+ }catch(e){
+ error("alertFilesToReload()",store,e);
+ throw e;
+ }
+ };
+//#endif nope
+
+ const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKey;
+ /**
+ Returns a C string from kvvfsMakeKey() OR returns zKey. In the
+ former case the memory is static, so must be copied before a
+ second call. zKey MUST be a pointer passed to a VFS/file method,
+ to allow us to avoid an alloc and/or an snprintf(). It requires
+ C-string arguments for zClass and zKey. zClass may be NULL but
+ zKey may not.
+ */
+ const zKeyForStorage = (store, zClass, zKey)=>{
+ //debug("zKeyForStorage(",store, wasm.cstrToJs(zClass), wasm.cstrToJs(zKey));
+ return (zClass && store.keyPrefix) ? kvvfsMakeKey(zClass, zKey) : zKey;
+ };
+
+ const jsKeyForStorage = (store,zClass,zKey)=>
+ wasm.cstrToJs(zKeyForStorage(store, zClass, zKey));
+
+ const storageGetDbSize = (store)=>+store.storage.getItem(store.keyPrefix + "sz");
+
+ /**
+ sqlite3_file pointers => objects, each of which has:
+
+ .file = KVVfsFile instance
+
+ .jzClass = JS-string form of f.$zClass
+
+ .storage = Storage object. It is shared between a db and its
+ journal.
+ */
+ const pFileHandles = new Map();
+
+ /**
+ Original WASM functions for methods we partially override.
+ */
+ const originalMethods = {
+ vfs: Object.create(null),
+ ioDb: Object.create(null),
+ ioJrnl: Object.create(null)
+ };
+
+ /** Returns the appropriate originalMethods[X] instance for the
+ given a KVVfsFile instance. */
+ const originalIoMethods = (kvvfsFile)=>
+ originalMethods[kvvfsFile.$isJournal ? 'ioJrnl' : 'ioDb'];
+
+ const pVfs = new capi.sqlite3_vfs(kvvfsMethods.$pVfs);
+ const pIoDb = new capi.sqlite3_io_methods(kvvfsMethods.$pIoDb);
+ const pIoJrnl = new capi.sqlite3_io_methods(kvvfsMethods.$pIoJrnl);
+ const recordHandler =
+ Object.create(null)/** helper for some vfs
+ routines. Populated later. */;
+ const kvvfsInternal = Object.assign(Object.create(null),{
+ pFileHandles,
+ cache,
+ storageForZClass,
+ KVVfsStorage,
+ /**
+ BUG: changing to a page size other than the default,
+ then vacuuming, corrupts the db. As a workaround,
+ until this is resolved, we forcibly disable
+ (pragma page_size=...) changes.
+ */
+ disablePageSizeChange: true
+ });
+ if( kvvfs.log ){
+ // this is a test build
+ kvvfs.internal = kvvfsInternal;
+ }
+
+ /**
+ Implementations for members of the object referred to by
+ sqlite3__wasm_kvvfs_methods(). We swap out some native
+ implementations with these so that we can use JS Storage for
+ their backing store.
+ */
+ const methodOverrides = {
+
+ /**
+ sqlite3_kvvfs_methods's member methods. These perform the
+ fetching, setting, and removal of storage keys on behalf of
+ kvvfs. In the native impl these write each db page to a
+ separate file. This impl stores each db page as a single
+ record in a Storage object which is mapped to zClass.
+
+ A db's size is stored in a record named kvvfs[-storagename]-sz
+ and the journal is stored in kvvfs[-storagename]-jrnl. The
+ [-storagename] part is a remnant of the native impl (so that
+ it has unique filenames per db) and is only used for
+ localStorage and sessionStorage. We elide that part (to save
+ space) from other storage objects but retain it on those two
+ to avoid invalidating pre-version-2 session/localStorage dbs.
+
+ The interface docs for these methods are in src/os_kv.c's
+ kvrecordRead(), kvrecordWrite(), and kvrecordDelete().
+ */
+ recordHandler: {
+ xRcrdRead: (zClass, zKey, zBuf, nBuf)=>{
+ try{
+ const jzClass = wasm.cstrToJs(zClass);
+ const store = storageForZClass(jzClass);
+ if( !store ) return -1;
+ const jXKey = jsKeyForStorage(store, zClass, zKey);
+ kvvfs?.log?.xRcrdRead && warn("xRcrdRead", jzClass, jXKey, nBuf, store );
+ const jV = store.storage.getItem(jXKey);
+ if(null===jV) return -1;
+ const nV = jV.length /* We are relying 100% on v being
+ ** ASCII so that jV.length is equal
+ ** to the C-string's byte length. */;
+ if( 0 ){
+ debug("xRcrdRead", jXKey, store, jV);
+ }
+ if(nBuf<=0) return nV;
+ else if(1===nBuf){
+ wasm.poke(zBuf, 0);
+ return nV;
+ }
+ if( nBuf+1{
+ try {
+ const store = storageForZClass(zClass);
+ const jxKey = jsKeyForStorage(store, zClass, zKey);
+ const jData = wasm.cstrToJs(zData);
+ kvvfs?.log?.xRcrdWrite && warn("xRcrdWrite",jxKey, store);
+ store.storage.setItem(jxKey, jData);
+ store.listeners && notifyListeners('write', store, jxKey, jData);
+ return 0;
+ }catch(e){
+ error("kvrecordWrite()",e);
+ return cache.setError(e, capi.SQLITE_IOERR);
+ }
+ },
+
+ xRcrdDelete: (zClass, zKey)=>{
+ try {
+ const store = storageForZClass(zClass);
+ const jxKey = jsKeyForStorage(store, zClass, zKey);
+ kvvfs?.log?.xRcrdDelete && warn("xRcrdDelete",jxKey, store);
+ store.storage.removeItem(jxKey);
+ store.listeners && notifyListeners('delete', store, jxKey);
+ return 0;
+ }catch(e){
+ error("kvrecordDelete()",e);
+ return cache.setError(e, capi.SQLITE_IOERR);
+ }
+ }
+ }/*recordHandler*/,
+
+ /**
+ Override certain operations of the underlying sqlite3_vfs and
+ the two sqlite3_io_methods instances so that we can tie
+ Storage objects to db names.
+ */
+ vfs:{
+ /* sqlite3_kvvfs_methods::pVfs's methods */
+ xOpen: function(pProtoVfs,zName,pProtoFile,flags,pOutFlags){
+ cache.popError();
+ let zToFree /* alloc()'d memory for temp db name */;
+ if( 0 ){
+ /* tester1.js makes it a lot further if we do this. */
+ flags |= capi.SQLITE_OPEN_CREATE;
+ }
+ try{
+ if( !zName ){
+ zToFree = wasm.allocCString(""+pProtoFile+"."
+ +(Math.random() * 100000 | 0));
+ zName = zToFree;
+ }
+ const jzClass = wasm.cstrToJs(zName);
+ kvvfs?.log?.xOpen && debug("xOpen",jzClass,"flags =",flags);
+ validateStorageName(jzClass, true);
+ if( (flags & (capi.SQLITE_OPEN_MAIN_DB
+ | capi.SQLITE_OPEN_TEMP_DB
+ | capi.SQLITE_OPEN_TRANSIENT_DB))
+ && cache.rxJournalSuffix.test(jzClass) ){
+ toss3(capi.SQLITE_ERROR,
+ "DB files may not have a '-journal' suffix.");
+ }
+ let s = storageForZClass(jzClass);
+ if( !s && !(flags & capi.SQLITE_OPEN_CREATE) ){
+ toss3(capi.SQLITE_ERROR, "Storage not found:", jzClass);
+ }
+ const rc = originalMethods.vfs.xOpen(pProtoVfs, zName, pProtoFile,
+ flags, pOutFlags);
+ if( rc ) return rc;
+ let deleteAt0 = !!(capi.SQLITE_OPEN_DELETEONCLOSE & flags);
+ if(wasm.isPtr(arguments[1]/*original zName*/)){
+ if(capi.sqlite3_uri_boolean(zName, "delete-on-close", 0)){
+ deleteAt0 = true;
+ }
+ }
+ const f = new KVVfsFile(pProtoFile);
+ util.assert(f.$zClass, "Missing f.$zClass");
+ f.addOnDispose(zToFree);
+ zToFree = undefined;
+ //debug("xOpen", jzClass, s);
+ if( s ){
+ ++s.refc;
+ //no if( true===deleteAt0 ) s.deleteAtRefc0 = true;
+ s.files.push(f);
+ wasm.poke32(pOutFlags, flags);
+ }else{
+ wasm.poke32(pOutFlags, flags | capi.SQLITE_OPEN_CREATE);
+ util.assert( !f.$isJournal, "Opening a journal before its db? "+jzClass );
+ /* Map both zName and zName-journal to the same storage. */
+ const nm = jzClass.replace(cache.rxJournalSuffix,'');
+ s = newStorageObj(nm);
+ installStorageAndJournal(s);
+ s.files.push(f);
+ s.deleteAtRefc0 = deleteAt0;
+ kvvfs?.log?.xOpen
+ && debug("xOpen installed storage handle [",nm, nm+"-journal","]", s);
+ }
+ pFileHandles.set(pProtoFile, {store: s, file: f, jzClass});
+ s.listeners && notifyListeners('open', s, s.files.length);
+ return 0;
+ }catch(e){
+ warn("xOpen:",e);
+ return cache.setError(e);
+ }finally{
+ zToFree && wasm.dealloc(zToFree);
+ }
+ }/*xOpen()*/,
+
+ xDelete: function(pVfs, zName, iSyncFlag){
+ cache.popError();
+ try{
+ const jzName = wasm.cstrToJs(zName);
+ if( cache.rxJournalSuffix.test(jzName) ){
+ recordHandler.xRcrdDelete(zName, cache.zKeyJrnl);
+ }/*
+ else: historically not done, but maybe otherwise delete
+ all db pages from storageForZClass(zName)?
+ */
+ return 0;
+ }catch(e){
+ warn("xDelete",e);
+ return cache.setError(e);
+ }
+ },
+
+ xAccess: function(pProtoVfs, zPath, flags, pResOut){
+ cache.popError();
+ try{
+ const s = storageForZClass(zPath);
+ const jzPath = s?.jzClass || wasm.cstrToJs(zPath);
+ if( kvvfs?.log?.xAccess ){
+ debug("xAccess",jzPath,"flags =",
+ flags,"*pResOut =",wasm.peek32(pResOut),
+ "store =",s);
+ }
+ if( !s ){
+ // From the API docs:
+ /** The xAccess method returns [SQLITE_OK] on success or some
+ ** non-zero error code if there is an I/O error or if the name of
+ ** the file given in the second argument is illegal.
+ */
+ // However, returning non-0 from here is fatal, so we don't do that.
+ try{validateStorageName(jzPath)}
+ catch(e){
+ //warn("xAccess is ignoring name validation failure:",e);
+ wasm.poke32(pResOut, 0);
+ return 0;
+ }
+ }
+ if( s ){
+ const key = s.keyPrefix+
+ (cache.rxJournalSuffix.test(jzPath) ? "jrnl" : "1");
+ const res = s.storage.getItem(key) ? 0 : 1;
+ /* This res value looks completely backwards to me, and
+ is the opposite of the native kvvfs's impl, but it's
+ working, whereas reimplementing the native one
+ faithfully does not. Read the lib-level code of where
+ this is invoked, my expectation is that we set res to 0
+ for not-exists. */
+ //warn("access res",jzPath,res);
+ wasm.poke32(pResOut, res);
+ }else{
+ wasm.poke32(pResOut, 0);
+ }
+ return 0;
+ }catch(e){
+ error('xAccess',e);
+ return cache.setError(e);
+ }
+ },
+
+ xRandomness: function(pVfs, nOut, pOut){
+ const heap = wasm.heap8u();
+ let i = 0;
+ const npOut = Number(pOut);
+ for(; i < nOut; ++i) heap[npOut + i] = (Math.random()*255000) & 0xFF;
+ return nOut;
+ },
+
+ xGetLastError: function(pVfs,nOut,pOut){
+ const e = cache.popError();
+ debug('xGetLastError',e);
+ if(e){
+ const scope = wasm.scopedAllocPush();
+ try{
+ const [cMsg, n] = wasm.scopedAllocCString(e.message, true);
+ wasm.cstrncpy(pOut, cMsg, nOut);
+ if(n > nOut) wasm.poke8(wasm.ptr.add(pOut,nOut,-1), 0);
+ debug("set xGetLastError",e.message);
+ return (e.resultCode | 0) || capi.SQLITE_IOERR;
+ }catch(e){
+ return capi.SQLITE_NOMEM;
+ }finally{
+ wasm.scopedAllocPop(scope);
+ }
+ }
+ return 0;
+ }
+
+//#if nope
+ // these impls work but there's currently no pressing need _not_ use
+ // the native impls.
+ xCurrentTime: function(pVfs,pOut){
+ wasm.poke64f(pOut, 2440587.5 + (Date.now()/86400000));
+ return 0;
+ },
+
+ xCurrentTimeInt64: function(pVfs,pOut){
+ wasm.poke64(pOut, (2440587.5 * 86400000) + Date.now());
+ return 0;
+ }
+//#endif
+ }/*.vfs*/,
+
+ /**
+ kvvfs has separate sqlite3_api_methods impls for some of the
+ methods depending on whether it's a db or journal file. Some
+ of the methods use shared impls but others are specific to
+ either db or journal files.
+ */
+ ioDb:{
+ /* sqlite3_kvvfs_methods::pIoDb's methods */
+ xClose: function(pFile){
+ cache.popError();
+ try{
+ const h = pFileHandles.get(pFile);
+ kvvfs?.log?.xClose && debug("xClose", pFile, h);
+ if( h ){
+ pFileHandles.delete(pFile);
+ const s = h.store;//storageForZClass(h.jzClass);
+ s.files = s.files.filter((v)=>v!==h.file);
+ if( --s.refc<=0 && s.deleteAtRefc0 ){
+ deleteStorage(s);
+ }
+ originalMethods.ioDb/*same for journals*/.xClose(pFile);
+ h.file.dispose();
+ s.listeners && notifyListeners('close', s, s.files.length);
+ }else{
+ /* Can happen if xOpen fails */
+ }
+ return 0;
+ }catch(e){
+ error("xClose",e);
+ return cache.setError(e);
+ }
+ },
+
+ xFileControl: function(pFile, opId, pArg){
+ cache.popError();
+ try{
+ const h = pFileHandles.get(pFile);
+ util.assert(h, "Missing KVVfsFile handle");
+ kvvfs?.log?.xFileControl && debug("xFileControl",h,'op =',opId);
+ if( opId===capi.SQLITE_FCNTL_PRAGMA
+ && kvvfsInternal.disablePageSizeChange ){
+ /* pArg== length-3 (char**) */
+ //const argv = wasm.cArgvToJs(3, pArg); // the easy way
+ const zName = wasm.peekPtr(wasm.ptr.add(pArg, wasm.ptr.size));
+ if( "page_size"===wasm.cstrToJs(zName) ){
+ kvvfs?.log?.xFileControl
+ && debug("xFileControl pragma",wasm.cstrToJs(zName));
+ const zVal = wasm.peekPtr(wasm.ptr.add(pArg, 2*wasm.ptr.size));
+ if( zVal ){
+ /* Without this, pragma page_size=N; followed by a
+ vacuum breaks the db. With this, it continues
+ working but does not actually change the page
+ size. */
+ kvvfs?.log?.xFileControl
+ && warn("xFileControl pragma", h,
+ "NOT setting page size to", wasm.cstrToJs(zVal));
+ h.file.$szPage = -1;
+ return 0/*corrupts: capi.SQLITE_NOTFOUND*/;
+ }else if( h.file.$szPage>0 ){
+ kvvfs?.log?.xFileControl &&
+ warn("xFileControl", h, "getting page size",h.file.$szPage);
+ wasm.pokePtr(pArg, wasm.allocCString(""+h.file.$szPage)
+ /* memory now owned by the library */);
+ return 0;//capi.SQLITE_NOTFOUND;
+ }
+ }
+ }
+ const rc = originalMethods.ioDb.xFileControl(pFile, opId, pArg);
+ if( 0==rc && capi.SQLITE_FCNTL_SYNC===opId ){
+ h.store.listeners && notifyListeners('sync', h.store, false);
+ }
+ return rc;
+ }catch(e){
+ error("xFileControl",e);
+ return cache.setError(e);
+ }
+ },
+
+ xSync: function(pFile,flags){
+ cache.popError();
+ try{
+ const h = pFileHandles.get(pFile);
+ kvvfs?.log?.xSync && debug("xSync", h);
+ util.assert(h, "Missing KVVfsFile handle");
+ const rc = originalMethods.ioDb.xSync(pFile, flags);
+ if( 0==rc && h.store.listeners ) notifyListeners('sync', h.store, true);
+ return rc;
+ }catch(e){
+ error("xSync",e);
+ return cache.setError(e);
+ }
+ },
+
+//#if not nope
+ // We override xRead/xWrite only for logging/debugging. They
+ // should otherwise be disabled (it's faster that way).
+ xRead: function(pFile,pTgt,n,iOff64){
+ cache.popError();
+ try{
+ if( kvvfs?.log?.xRead ){
+ const h = pFileHandles.get(pFile);
+ util.assert(h, "Missing KVVfsFile handle");
+ debug("xRead", n, iOff64, h);
+ }
+ return originalMethods.ioDb.xRead(pFile, pTgt, n, iOff64);
+ }catch(e){
+ error("xRead",e);
+ return cache.setError(e);
+ }
+ },
+ xWrite: function(pFile,pSrc,n,iOff64){
+ cache.popError();
+ try{
+ if( kvvfs?.log?.xWrite ){
+ const h = pFileHandles.get(pFile);
+ util.assert(h, "Missing KVVfsFile handle");
+ debug("xWrite", n, iOff64, h);
+ }
+ return originalMethods.ioDb.xWrite(pFile, pSrc, n, iOff64);
+ }catch(e){
+ error("xWrite",e);
+ return cache.setError(e);
+ }
+ },
+//#endif nope
+
+//#if nope
+ xTruncate: function(pFile,i64){},
+ xFileSize: function(pFile,pi64Out){},
+ xLock: function(pFile,iLock){},
+ xUnlock: function(pFile,iLock){},
+ xCheckReservedLock: function(pFile,piOut){},
+ xSectorSize: function(pFile){},
+ xDeviceCharacteristics: function(pFile){}
+//#endif
+ }/*.ioDb*/,
+
+ ioJrnl:{
+ /* sqlite3_kvvfs_methods::pIoJrnl's methods. Those set to true
+ are copied as-is from the ioDb objects. Others are specific
+ to journal files. */
+ xClose: true,
+//#if nope
+ xRead: function(pFile,pTgt,n,iOff64){},
+ xWrite: function(pFile,pSrc,n,iOff64){},
+ xTruncate: function(pFile,i64){},
+ xSync: function(pFile,flags){},
+ xFileControl: function(pFile, opId, pArg){},
+ xFileSize: function(pFile,pi64Out){},
+ xLock: true,
+ xUnlock: true,
+ xCheckReservedLock: true,
+ xSectorSize: true,
+ xDeviceCharacteristics: true
+//#endif
+ }/*.ioJrnl*/
+ }/*methodOverrides*/;
+
+ debug("pVfs and friends", pVfs, pIoDb, pIoJrnl,
+ kvvfsMethods, capi.sqlite3_file.structInfo,
+ KVVfsFile.structInfo);
+ try {
+ util.assert( cache.buffer.n>1024*129, "Heap buffer is not large enough"
+ /* Native is SQLITE_KVOS_SZ is 133073 as of this writing */ );
+ for(const e of Object.entries(methodOverrides.recordHandler)){
+ // Overwrite kvvfsMethods's callbacks
+ const k = e[0], f = e[1];
+ recordHandler[k] = f;
+ if( 0 ){
+ // bug: this should work
+ kvvfsMethods.installMethod(k, f);
+ }else{
+ kvvfsMethods[kvvfsMethods.memberKey(k)] =
+ wasm.installFunction(kvvfsMethods.memberSignature(k), f);
+ }
+ }
+ for(const e of Object.entries(methodOverrides.vfs)){
+ // Overwrite some pVfs entries and stash the original impls
+ const k = e[0], f = e[1], km = pVfs.memberKey(k),
+ member = pVfs.structInfo.members[k]
+ || util.toss("Missing pVfs.structInfo[",k,"]");
+ originalMethods.vfs[k] = wasm.functionEntry(pVfs[km]);
+ pVfs[km] = wasm.installFunction(member.signature, f);
+ }
+ for(const e of Object.entries(methodOverrides.ioDb)){
+ // Similar treatment for pVfs.$pIoDb a.k.a. pIoDb...
+ const k = e[0], f = e[1], km = pIoDb.memberKey(k);
+ originalMethods.ioDb[k] = wasm.functionEntry(pIoDb[km])
+ || util.toss("Missing native pIoDb[",km,"]");
+ pIoDb[km] = wasm.installFunction(pIoDb.memberSignature(k), f);
+ }
+ for(const e of Object.entries(methodOverrides.ioJrnl)){
+ // Similar treatment for pVfs.$pIoJrnl a.k.a. pIoJrnl...
+ const k = e[0], f = e[1], km = pIoJrnl.memberKey(k);
+ originalMethods.ioJrnl[k] = wasm.functionEntry(pIoJrnl[km])
+ || util.toss("Missing native pIoJrnl[",km,"]");
+ if( true===f ){
+ /* use pIoDb's copy */
+ pIoJrnl[km] = pIoDb[km] || util.toss("Missing copied pIoDb[",km,"]");
+ }else{
+ pIoJrnl[km] = wasm.installFunction(pIoJrnl.memberSignature(k), f);
+ }
+ }
+ }finally{
+ kvvfsMethods.dispose();
+ pVfs.dispose();
+ pIoDb.dispose();
+ pIoJrnl.dispose();
+ }
+
+ /*
+ That gets all of the low-level bits out of the way. What follows
+ are the public API additions.
+ */
+
+ /**
+ Clears all storage used by the kvvfs DB backend, deleting any
+ DB(s) stored there.
+
+ Its argument must be the name of a kvvfs storage object:
+
+ - 'session'
+ - 'local'
+ - '' - see below.
+ - A transient kvvfs storage object name.
+
+ In the first two cases, only sessionStorage resp. localStorage is
+ cleared. An empty string resolves to both 'local' and 'session'
+ storage.
+
+ Returns the number of entries cleared.
+
+ As of kvvfs version 2:
+
+ This API is available in Worker threads but does not have access
+ to localStorage or sessionStorage in them. Prior versions did not
+ include this API in Worker threads.
+
+ Differences in this function in version 2:
+
+ - It accepts an arbitrary storage name. In v1 this was a silent
+ no-op for any names other than ('local','session','').
+
+ - It throws if a db currently has the storage opened UNLESS the
+ storage object is localStorage or sessionStorage. That version 1
+ did not throw for this case was due to an architectural
+ limitation which has since been overcome, but removal of
+ JsStorageDb.prototype.clearStorage() would be a backwards compatibility
+ break, so this function permits wiping the storage for those two
+ cases even if they are opened. Use with case.
+ */
+ const sqlite3_js_kvvfs_clear = function callee(which){
+ if( ''===which ){
+ return callee('local') + callee('session');
+ }
+ const store = storageForZClass(which);
+ if( !store ) return 0;
+ if( store.files.length ){
+ if( globalThis.localStorage===store.storage
+ || globalThis.sessionStorage===store.storage ){
+ /* backwards compatibility: allow these to be cleared
+ while opened. */
+ }else{
+ /* Interestingly, kvvfs recovers just fine when the storage is
+ wiped, so long as the db is not in use and its schema is
+ recreated before it's used, but client apps should not have
+ to be faced with that eventuality mid-query (where it
+ _will_ cause failures). Therefore we disallow it when
+ storage handles are opened. Kvvfs version 1 could not
+ detect this case - see the if() block above.
+ */
+ toss3(capi.SQLITE_ACCESS,
+ "Cannot clear in-use database storage.");
+ }
+ }
+ const s = store.storage;
+ const toRm = [] /* keys to remove */;
+ let i, n = s.length;
+ //debug("kvvfs_clear",store,s);
+ for( i = 0; i < n; ++i ){
+ const k = s.key(i);
+ //debug("kvvfs_clear ?",k);
+ if(!store.keyPrefix || k.startsWith(store.keyPrefix)) toRm.push(k);
+ }
+ toRm.forEach((kk)=>s.removeItem(kk));
+ //alertFilesToReload(store);
+ return toRm.length;
+ };
+
+ /**
+ This routine estimates the approximate amount of
+ storage used by the given kvvfs back-end.
+
+ Its arguments are as documented for sqlite3_js_kvvfs_clear(),
+ only the operation this performs is different.
+
+ The returned value is twice the "length" value of every matching
+ key and value, noting that JavaScript stores each character in 2
+ bytes.
+
+ The returned size is not authoritative from the perspective of
+ how much data can fit into localStorage and sessionStorage, as
+ the precise algorithms for determining those limits are
+ unspecified and may include per-entry overhead invisible to
+ clients.
+ */
+ const sqlite3_js_kvvfs_size = function callee(which){
+ if( ''===which ){
+ return callee('local') + callee('session');
+ }
+ const store = storageForZClass(which);
+ if( !store ) return 0;
+ const s = store.storage;
+ let i, sz = 0;
+ for(i = 0; i < s.length; ++i){
+ const k = s.key(i);
+ if(!store.keyPrefix || k.startsWith(store.keyPrefix)){
+ sz += k.length;
+ sz += s.getItem(k).length;
+ }
+ }
+ return sz * 2 /* because JS uses 2-byte char encoding */;
+ };
+
+ /**
+ Exports a kvvfs storage object to an object, optionally
+ JSON-friendly.
+
+ Usages:
+
+ thisfunc(storageName);
+ thisfunc(options);
+
+ In the latter case, the options object must be an object with
+ the following properties:
+
+ - "name" (string) required. The storage to export.
+
+ - "decodePages" (bool=false). If true, the .pages result property
+ holdes Uint8Array objects holding the raw binary-format db
+ pages. The default is to use kvvfs-encoded string pages
+ (JSON-friendly).
+
+ - "includeJournal" (bool=false). If true and the db has a current
+ journal, it is exported as well. (Kvvfs journals are stored as a
+ single record within the db's storage object.)
+
+ The returned object is structured as follows...
+
+ - "name": the name of the storage. This is 'local' or 'session'
+ for localStorage resp. sessionStorage, and an arbitrary name for
+ transient storage. This propery may be changed before passing
+ this object to sqlite3_js_kvvfs_import() in order to
+ import into a different storage object.
+
+ - "timestamp": the time this function was called, in Unix
+ epoch milliseconds.
+
+ - "size": the unencoded db size.
+
+ - "journal": if options.includeJournal is true and this db has a
+ journal, it is stored as a string here, otherwise this property
+ is not set.
+
+ - "pages": An array holding the raw encoded db pages in their
+ proper order.
+
+ Throws if this db is not opened.
+
+ The encoding of the underlying database is not part of this
+ interface - it is simply passed on as-is. Interested parties are
+ directed to src/os_kv.c in the SQLite source tree, with the
+ caveat that that code also does not offer a public interface.
+ i.e. the encoding is a private implementation detail of kvvfs.
+ The format may be changed in the future but kvvfs will continue
+ to support the current form.
+
+ Added in version @kvvfs-v2-added-in@.
+ */
+ const sqlite3_js_kvvfs_export = function callee(...args){
+ let opt;
+ if( 1===args.length && 'object'===typeof args[0] ){
+ opt = args[0];
+ }else if(args.length){
+ opt = Object.assign(Object.create(null),{
+ name: args[0],
+ //decodePages: true
+ });
+ }
+ const store = opt ? storageForZClass(opt.name) : null;
+ if( !store ){
+ toss3(capi.SQLITE_NOTFOUND,
+ "There is no kvvfs storage named",opt?.name);
+ }
+ //debug("store to export=",store);
+ const s = store.storage;
+ const rc = Object.assign(Object.create(null),{
+ name: store.jzClass,
+ timestamp: Date.now(),
+ pages: []
+ });
+ const pages = Object.create(null);
+ let xpages;
+ const keyPrefix = store.keyPrefix;
+ const rxTail = keyPrefix
+ ? /^kvvfs-[^-]+-(\w+)/ /* X... part of kvvfs-NAME-X... */
+ : undefined;
+ let i = 0, n = s.length;
+ for( ; i < n; ++i ){
+ const k = s.key(i);
+ if( !keyPrefix || k.startsWith(keyPrefix) ){
+ let kk = (keyPrefix ? rxTail.exec(k) : undefined)?.[1] ?? k;
+ switch( kk ){
+ case 'jrnl':
+ if( opt.includeJournal ) rc.journal = s.getItem(k);
+ break;
+ case 'sz':
+ rc.size = +s.getItem(k);
+ break;
+ default:
+ kk = +kk /* coerce to number */;
+ if( !util.isInt32(kk) || kk<=0 ){
+ toss3(capi.SQLITE_RANGE, "Malformed kvvfs key: "+k);
+ }
+ if( opt.decodePages ){
+ const spg = s.getItem(k),
+ n = spg.length,
+ z = cache.memBuffer(0),
+ zDec = cache.memBuffer(1),
+ heap = wasm.heap8u()/* MUST be inited last*/;
+ let i = 0;
+ for( ; i < n; ++i ){
+ heap[wasm.ptr.add(z, i)] = spg.codePointAt(i) & 0xff;
+ }
+ heap[wasm.ptr.add(z, i)] = 0;
+ //debug("Decoding",i,"page bytes");
+ const nDec = kvvfsDecode(
+ z, zDec, cache.buffer.n
+ );
+ //debug("Decoded",nDec,"page bytes");
+ pages[kk] = heap.slice(Number(zDec), wasm.ptr.addn(zDec, nDec));
+ }else{
+ pages[kk] = s.getItem(k);
+ }
+ break;
+ }
+ }
+ }
+ if( opt.decodePages ) cache.memBufferFree(1);
+ /* Now sort the page numbers and move them into an array. In JS
+ property keys are always strings, so we have to coerce them to
+ numbers so we can get them sorted properly for the array. */
+ Object.keys(pages).map((v)=>+v).sort().forEach(
+ (v)=>rc.pages.push(pages[v])
+ );
+ return rc;
+ }/* sqlite3_js_kvvfs_export */;
+
+ /**
+ The counterpart of sqlite3_js_kvvfs_export(). Its
+ argument must be the result of that function() or
+ a compatible one.
+
+ This either replaces the contents of an existing transient
+ storage object or installs one named exp.name, setting
+ the storage's db contents to that of the exp object.
+
+ Throws on error. Error conditions include:
+
+ - The given storage object is currently opened by any db.
+ Performing this page-by-page import would invoke undefined
+ behavior on them.
+
+ - Malformed input object.
+
+ If it throws after starting the import then it clears the storage
+ before returning, to avoid leaving the db in an undefined
+ state. It may throw for any of the above-listed conditions before
+ reaching that step, in which case the db is not modified. If
+ exp.name refers to a new storage name then if it throws, the name
+ does not get installed.
+
+ Added in version @kvvfs-v2-added-in@.
+ */
+ const sqlite3_js_kvvfs_import = function(exp, overwrite=false){
+ if( !exp?.timestamp
+ || !exp.name
+ || undefined===exp.size
+ || !Array.isArray(exp.pages) ){
+ toss3(capi.SQLITE_MISUSE, "Malformed export object.");
+ }else if( !exp.size
+ || (exp.size !== (exp.size | 0))
+ //|| (exp.size % cache.fixedPageSize)
+ || exp.size>=0x7fffffff ){
+ toss3(capi.SQLITE_RANGE, "Invalid db size: "+exp.size);
+ }
+
+ validateStorageName(exp.name);
+ let store = storageForZClass(exp.name);
+ const isNew = !store;
+ if( store ){
+ if( !overwrite ){
+ //warn("Storage exists:",arguments,store);
+ toss3(capi.SQLITE_ACCESS,
+ "Storage '"+exp.name+"' already exists and",
+ "overwrite was not specified.");
+ }else if( !store.files || !store.jzClass ){
+ toss3(capi.SQLITE_ERROR,
+ "Internal storage object", exp.name,"seems to be malformed.");
+ }else if( store.files.length ){
+ toss3(capi.SQLITE_IOERR_ACCESS,
+ "Cannot import db storage while it is in use.");
+ }
+ sqlite3_js_kvvfs_clear(exp.name);
+ }else{
+ store = newStorageObj(exp.name);
+ //warn("Installing new storage:",store);
+ }
+ //debug("Importing store",store.poolEntry.files.length, store);
+ //debug("object to import:",exp);
+ const keyPrefix = kvvfsKeyPrefix(exp.name);
+ let zEnc;
+ try{
+ /* Force the native KVVfsFile instances to re-read the db
+ and page size. */;
+ const s = store.storage;
+ s.setItem(keyPrefix+'sz', exp.size);
+ if( exp.journal ) s.setItem(keyPrefix+'jrnl', exp.journal);
+ if( exp.pages[0] instanceof Uint8Array ){
+ /* raw binary pages */
+ //debug("pages",exp.pages);
+ exp.pages.forEach((u,ndx)=>{
+ const n = u.length;
+ if( 0 && cache.fixedPageSize !== n ){
+ util.toss3(capi.SQLITE_RANGE,"Unexpected page size:", n);
+ }
+ zEnc ??= cache.memBuffer(1);
+ const zBin = cache.memBuffer(0),
+ heap = wasm.heap8u()/*MUST be inited last*/;
+ /* Copy u to the heap and encode the heap copy via C. This
+ is _presumably_ faster than porting the encoding algo to
+ JS. */
+ heap.set(u, Number(zBin));
+ heap[wasm.ptr.addn(zBin,n)] = 0;
+ const rc = kvvfsEncode(zBin, n, zEnc);
+ util.assert( rc < cache.buffer.n,
+ "Impossibly long output - possibly smashed the heap" );
+ util.assert( 0===wasm.peek8(wasm.ptr.add(zEnc,rc)),
+ "Expecting NUL-terminated encoded output" );
+ const jenc = wasm.cstrToJs(zEnc);
+ //debug("(un)encoded page:",u,jenc);
+ s.setItem(keyPrefix+(ndx+1), jenc);
+ });
+ }else if( exp.pages[0] ){
+ /* kvvfs-encoded pages */
+ exp.pages.forEach((v,ndx)=>s.setItem(keyPrefix+(ndx+1), v));
+ }
+ if( isNew ) installStorageAndJournal(store);
+ }catch{
+ if( !isNew ){
+ try{sqlite3_js_kvvfs_clear(exp.name);}catch(ee){/*ignored*/}
+ }
+ }finally{
+ if( zEnc ) cache.memBufferFree(1);
+ }
+ return this;
+ };
+
+ /**
+ If no kvvfs storage exists with the given name, one is
+ installed. If one exists, its reference count is increased so
+ that it won't be freed by the closing of a database or journal
+ file.
+
+ Throws if the name is not valid for a new storage object.
+
+ Added in version @kvvfs-v2-added-in@.
+ */
+ const sqlite3_js_kvvfs_reserve = function(name){
+ let store = storageForZClass(name);
+ if( store ){
+ ++store.refc;
+ return;
+ }
+ validateStorageName(name);
+ installStorageAndJournal(newStorageObj(name));
+ };
+
+ /**
+ Conditionally "unlinks" a kvvfs storage object, reducing its
+ reference count by 1.
+
+ This is a no-op if name ends in "-journal" or refers to a
+ built-in storage object.
+
+ It will not lower the refcount below the number of
+ currently-opened db/journal files for the storage (so that it
+ cannot delete it out from under them).
+
+ If the refcount reaches 0 then the storage object is
+ removed.
+
+ Returns true if it reduces the refcount, else false. A result of
+ true does not necessarily mean that the storage unit was removed,
+ just that its refcount was lowered. Similarly, a result of false
+ does not mean that the storage is removed - it may still have
+ opened handles.
+
+ Added in version @kvvfs-v2-added-in@.
+ */
+ const sqlite3_js_kvvfs_unlink = function(name){
+ const store = storageForZClass(name);
+ if( !store
+ || kvvfsIsPersistentName(store.jzClass)
+ || isBuiltinName(store.jzClass)
+ || cache.rxJournalSuffix.test(name) ) return false;
+ if( store.refc > store.files.length || 0===store.files.length ){
+ if( --store.refc<=0 ){
+ /* Ignoring deleteAtRefc0 for an explicit unlink */
+ deleteStorage(store);
+ }
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ Adds an event listener to a kvvfs storage object. The idea is
+ that this can be used to asynchronously back up one kvvfs storage
+ object to another or another channel entirely. (The caveat in the
+ latter case is that kvvfs's format is not readily consumable by
+ downstream code.)
+
+ Its argument must be an object with the following properties:
+
+ - storage: the name of the kvvfs storage object.
+
+ - reserve [=false]: if true, sqlite3_js_kvvfs_reserve() is used
+ to ensure that the storage exists if it does not already.
+ If this is false and the storage does not exist then an
+ exception is thrown.
+
+ - events: an object which may have any of the following
+ callback function properties: open, close, write, delete.
+
+ - decodePages [=false]: if true, write events will receive each
+ db page write in the form of a Uint8Array holding the raw binary
+ db page. The default is to emit the kvvfs-format page because it
+ requires no extra work, we already have it in hand, and it's
+ often smaller. It's not great for interchange, though.
+
+ - includeJournal [=false]: if true, writes and deletes of
+ "jrnl" records are included. If false, no events are sent
+ for journal updates.
+
+ Passing the same object to sqlite3_js_kvvfs_unlisten() will
+ remove the listener.
+
+ Each one of the events callbacks will be called asynchronously
+ when the given storage performs those operations. They may be
+ asynchronous functions but are not required to be (the events are
+ fired async either way, but making the event callbacks async may
+ be advantageous when multiple listeners are involved). All
+ exceptions, including those via Promises, are ignored but may (or
+ may not) trigger warning output on the console.
+
+ Each callback gets passed a single object with the following
+ properties:
+
+ .type = the same as the name of the callback
+
+ .storageName = the name of the storage object
+
+ .data = callback-dependent:
+
+ - 'open' and 'close' get an integer, the number of
+ currently-opened handles on the storage.
+
+ - 'write' gets a length-two array holding the key and value which
+ were written. The key is always a string, even if it's a db page
+ number. For db-page records, the value's type depends on
+ opt.decodePages. All others, including the journal, are strings.
+ (The journal, being a kvvfs-specific format, is delivered in
+ that same JSON-friendly format.) More details below.
+
+ - 'delete' gets the string-type key of the deleted record.
+
+ - 'sync' gets a boolean value: true if it was triggered by db
+ file's xSync(), false if it was triggered by xFileControl(). The
+ latter triggers before the xSync() and also triggers if the DB
+ has PRAGMA SYNCHRONOUS=OFF (in which case xSync() is not
+ triggered).
+
+ The key/value arguments to 'write', and key argument to 'delete',
+ are in one of the following forms:
+
+ - 'sz' = the unencoded db size as a string. This specific key is
+ key is never deleted, so is only ever passed to 'write' events.
+
+ - 'jrnl' = the current db journal as a kvvfs-encoded string. This
+ journal format is not useful anywhere except in the kvvfs
+ internals. These events are not fired if opt.includeJournal is
+ false.
+
+ - '[1-9][0-9]*' (a db page number) = Its type depends on
+ opt.decodePages. These may be written and deleted in arbitrary
+ order.
+
+ Design note: JS has StorageEvents but only in the main thread,
+ which is why the listeners are not based on that.
+
+ Added in version @kvvfs-v2-added-in@.
+ */
+ const sqlite3_js_kvvfs_listen = function(opt){
+ if( !opt || 'object'!==typeof opt ){
+ toss3(capi.SQLITE_MISUSE, "Expecting a listener object.");
+ }
+ let store = storageForZClass(opt.storage);
+ if( !store ){
+ if( opt.storage && opt.reserve ){
+ sqlite3_js_kvvfs_reserve(opt.storage);
+ store = storageForZClass(opt.storage);
+ util.assert(store,
+ "Unexpectedly cannot fetch reserved storage "
+ +opt.storage);
+ }else{
+ toss3(capi.SQLITE_NOTFOUND,"No such storage:",opt.storage);
+ }
+ }
+ if( opt.events ){
+ (store.listeners ??= []).push(opt);
+ }
+ };
+
+ /**
+ Removes the kvvfs event listeners for the given options
+ object. It must be passed the same object instance which was
+ passed to sqlite3_js_kvvfs_listen().
+
+ This has no side effects if opt is invalid or is not a match for
+ any listeners.
+
+ Return true if it unregisters its argument, else false.
+
+ Added in version @kvvfs-v2-added-in@.
+ */
+ const sqlite3_js_kvvfs_unlisten = function(opt){
+ const store = storageForZClass(opt?.storage);
+ if( store?.listeners && opt.events ){
+ const n = store.listeners.length;
+ store.listeners = store.listeners.filter((v)=>v!==opt);
+ const rc = n>store.listeners.length;
+ if( !store.listeners.length ){
+ // to speed up downstream checks for listeners
+ store.listeners = undefined;
+ }
+ return rc;
+ }
+ return false;
+ };
+
+ sqlite3.kvvfs.reserve = sqlite3_js_kvvfs_reserve;
+ sqlite3.kvvfs.import = sqlite3_js_kvvfs_import;
+ sqlite3.kvvfs.export = sqlite3_js_kvvfs_export;
+ sqlite3.kvvfs.unlink = sqlite3_js_kvvfs_unlink;
+ sqlite3.kvvfs.listen = sqlite3_js_kvvfs_listen;
+ sqlite3.kvvfs.unlisten = sqlite3_js_kvvfs_unlisten;
+ sqlite3.kvvfs.exists = (name)=>!!storageForZClass(name);
+ sqlite3.kvvfs.estimateSize = sqlite3_js_kvvfs_size;
+ sqlite3.kvvfs.clear = sqlite3_js_kvvfs_clear;
+
+
+ if( globalThis.Storage ){
+ /**
+ Prior to version 2, kvvfs was only available in the main
+ thread. We retain that for the v1 APIs, exposing them only in
+ the main UI thread. As of version 2, kvvfs is available in all
+ threads but only via its v2 interface (sqlite3.kvvfs).
+
+ These versions have a default argument value of "" which the v2
+ versions lack.
+ */
+ capi.sqlite3_js_kvvfs_size = (which="")=>sqlite3_js_kvvfs_size(which);
+ capi.sqlite3_js_kvvfs_clear = (which="")=>sqlite3_js_kvvfs_clear(which);
+ }
+
+//#if not omit-oo1
+ if(sqlite3.oo1?.DB){
+ /**
+ Functionally equivalent to DB(storageName,'c','kvvfs') except
+ that it throws if the given storage name is not one of 'local'
+ or 'session'.
+
+ As of version 3.46, the argument may optionally be an options
+ object in the form:
+
+ {
+ filename: 'session'|'local',
+ ... etc. (all options supported by the DB ctor)
+ }
+
+ noting that the 'vfs' option supported by main DB
+ constructor is ignored here: the vfs is always 'kvvfs'.
+ */
+ const DB = sqlite3.oo1.DB;
+ sqlite3.oo1.JsStorageDb = function(
+ storageName = sqlite3.oo1.JsStorageDb.defaultStorageName
+ ){
+ const opt = DB.dbCtorHelper.normalizeArgs(...arguments);
+ opt.vfs = 'kvvfs';
+ if( 0 ){
+ // Current tests rely on these, but that's arguably a bug
+ if( opt.flags ) opt.flags = 'cw'+opt.flags;
+ else opt.flags = 'cw';
+ }
+ switch( opt.filename ){
+ /* sqlite3_open(), in these builds, recognizes the names
+ below and performs some magic which we want to bypass
+ here for sanity's sake. */
+ case ":sessionStorage:": opt.filename = 'session'; break;
+ case ":localStorage:": opt.filename = 'local'; break;
+ }
+ const m = /(file:(\/\/)?)([^?]+)/.exec(opt.filename);
+ validateStorageName( m ? m[3] : opt.filename);
+ DB.dbCtorHelper.call(this, opt);
+ };
+ sqlite3.oo1.JsStorageDb.defaultStorageName
+ = cache.storagePool.session ? 'session' : nameOfThisThreadStorage;
+ const jdb = sqlite3.oo1.JsStorageDb;
+ jdb.prototype = Object.create(DB.prototype);
+ jdb.clearStorage = sqlite3_js_kvvfs_clear;
+ /**
+ DEPRECATED: the inherited method of this name (as opposed to
+ the "static" class method) is deprecated with version 2 of
+ kvvfs. This function will, for backwards comaptibility,
+ continue to work with localStorage and sessionStorage, but will
+ throw for all other storage because they are opened. Version 1
+ was not capable of recognizing that the storage was opened so
+ permitted wiping it out at any time, but that was arguably a
+ bug.
+
+ Clears this database instance's storage or throws if this
+ instance has been closed. Returns the number of
+ database pages which were cleaned up.
+ */
+ jdb.prototype.clearStorage = function(){
+ return jdb.clearStorage(this.affirmOpen().dbFilename(), true);
+ };
+ /** Equivalent to sqlite3_js_kvvfs_size(). */
+ jdb.storageSize = sqlite3_js_kvvfs_size;
+ /**
+ Returns the _approximate_ number of bytes this database takes
+ up in its storage or throws if this instance has been closed.
+ */
+ jdb.prototype.storageSize = function(){
+ return jdb.storageSize(this.affirmOpen().dbFilename(), true);
+ };
+ }/*sqlite3.oo1.JsStorageDb*/
+//#endif not omit-oo1
+
+ if( sqlite3.__isUnderTest && sqlite3.vtab ){
+ /**
+ An eponymous vtab for inspecting the kvvfs state. This is only
+ intended for use in testing and development, not part of the
+ public API.
+ */
+ const cols = Object.assign(Object.create(null),{
+ rowid: {type: 'INTEGER'},
+ name: {type: 'TEXT'},
+ nRef: {type: 'INTEGER'},
+ nOpen: {type: 'INTEGER'},
+ isTransient: {type: 'INTEGER'},
+ dbSize: {type: 'INTEGER'}
+ });
+ Object.keys(cols).forEach((v,i)=>cols[v].colId = i);
+
+ const VT = sqlite3.vtab;
+ const ProtoCursor = Object.assign(Object.create(null),{
+ row: function(){
+ return cache.storagePool[this.names[this.rowid]];
+ }
+ });
+ Object.assign(Object.create(ProtoCursor),{
+ rowid: 0,
+ names: Object.keys(cache.storagePool)
+ .filter(v=>!cache.rxJournalSuffix.test(v))
+ });
+ const cursorState = function(cursor, reset){
+ const o = (cursor instanceof capi.sqlite3_vtab_cursor)
+ ? cursor
+ : VT.xCursor.get(cursor);
+ if( reset || !o.vTabState ){
+ o.vTabState = Object.assign(Object.create(ProtoCursor),{
+ rowid: 0,
+ names: Object.keys(cache.storagePool)
+ .filter(v=>!cache.rxJournalSuffix.test(v))
+ });
+ }
+ return o.vTabState;
+ };
+
+ const dbg = 1 ? ()=>{} : (...args)=>debug("vtab",...args);
+
+ const theModule = function f(){
+ return f.mod ??= new sqlite3.capi.sqlite3_module().setupModule({
+ catchExceptions: true,
+ methods: {
+ xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){
+ dbg("xConnect");
+ try{
+ const xcol = [];
+ Object.keys(cols).forEach((k)=>{
+ xcol.push(k+" "+cols[k].type);
+ });
+ const rc = capi.sqlite3_declare_vtab(
+ pDb, "CREATE TABLE ignored("+xcol.join(',')+")"
+ );
+ if(0===rc){
+ const t = VT.xVtab.create(ppVtab);
+ util.assert(
+ (t === VT.xVtab.get(wasm.peekPtr(ppVtab))),
+ "output pointer check failed"
+ );
+ }
+ return rc;
+ }catch(e){
+ return VT.xError('xConnect', e, capi.SQLITE_ERROR);
+ }
+ },
+ xCreate: wasm.ptr.null, // eponymous only
+ //xCreate: true, // copy xConnect, i.e. also eponymous only
+ xDisconnect: function(pVtab){
+ dbg("xDisconnect",...arguments);
+ VT.xVtab.dispose(pVtab);
+ return 0;
+ },
+ xOpen: function(pVtab, ppCursor){
+ dbg("xOpen",...arguments);
+ VT.xCursor.create(ppCursor);
+ return 0;
+ },
+ xClose: function(pCursor){
+ dbg("xClose",...arguments);
+ const c = VT.xCursor.unget(pCursor);
+ delete c.vTabState;
+ c.dispose();
+ return 0;
+ },
+ xNext: function(pCursor){
+ dbg("xNext",...arguments);
+ const c = VT.xCursor.get(pCursor);
+ ++cursorState(c).rowid;
+ return 0;
+ },
+ xColumn: function(pCursor, pCtx, iCol){
+ dbg("xColumn",...arguments);
+ //const c = VT.xCursor.get(pCursor);
+ const st = cursorState(pCursor);
+ const store = st.row();
+ util.assert(store, "Unexpected xColumn call");
+ switch(iCol){
+ case cols.rowid.colId:
+ capi.sqlite3_result_int(pCtx, st.rowid);
+ break;
+ case cols.name.colId:
+ capi.sqlite3_result_text(pCtx, store.jzClass, -1, capi.SQLITE_TRANSIENT);
+ break;
+ case cols.nRef.colId:
+ capi.sqlite3_result_int(pCtx, store.refc);
+ break;
+ case cols.nOpen.colId:
+ capi.sqlite3_result_int(pCtx, store.files.length);
+ break;
+ case cols.isTransient.colId:
+ capi.sqlite3_result_int(pCtx, !!store.deleteAtRefc0);
+ break;
+ case cols.dbSize.colId:
+ capi.sqlite3_result_int(pCtx, storageGetDbSize(store));
+ break;
+ default:
+ capi.sqlite3_result_error(pCtx, "Invalid column id: "+iCol);
+ return capi.SQLITE_RANGE;
+ }
+ return 0;
+ },
+ xRowid: function(pCursor, ppRowid64){
+ dbg("xRowid",...arguments);
+ const st = cursorState(pCursor);
+ VT.xRowid(ppRowid64, st.rowid);
+ return 0;
+ },
+ xEof: function(pCursor){
+ const st = cursorState(pCursor);
+ dbg("xEof?="+(!st.row()),...arguments);
+ return !st.row();
+ },
+ xFilter: function(pCursor, idxNum, idxCStr,
+ argc, argv/* [sqlite3_value* ...] */){
+ dbg("xFilter",...arguments);
+ const st = cursorState(pCursor, true);
+ return 0;
+ },
+ xBestIndex: function(pVtab, pIdxInfo){
+ dbg("xBestIndex",...arguments);
+ //const t = VT.xVtab.get(pVtab);
+ const pii = new capi.sqlite3_index_info(pIdxInfo);
+ pii.$estimatedRows = cache.storagePool.size;
+ pii.$estimatedCost = 1.0;
+ pii.dispose();
+ return 0;
+ }
+ }
+ })/*setupModule*/;
+ }/*theModule()*/;
+
+ sqlite3.kvvfs.create_module = function(pDb, name="sqlite_kvvfs"){
+ return capi.sqlite3_create_module(pDb, name, theModule(),
+ wasm.ptr.null);
+ };
+
+ }/* virtual table */
+
+//#if nope
+ /**
+ The idea here is a simpler wrapper for listening to kvvfs
+ changes. Clients would override its onXyz() event methods
+ instead of providing callbacks for sqlite3.kvvfs.listen(), the
+ main (only?) benefit of which is that this class would do the
+ sorting-out and validation of event state before calling the
+ overloaded callbacks.
+ */
+ kvvfs.Listener = class KvvfsListener {
+ #store;
+ #listener;
+
+ constructor(opt){
+ this.#listenTo(opt);
+ }
+
+ #event(ev){
+ switch(ev.type){
+ case 'open': this.onOpen(ev.data); break;
+ case 'close': this.onClose(ev.data); break;
+ case 'sync': this.onSync(ev.data); break;
+ case 'delete':
+ switch(ev.data){
+ case 'jrnl': break;
+ default:{
+ const n = +ev.data;
+ util.assert( n>0, "Expecting positive db page number" );
+ this.onPageChange(n, null);
+ break;
+ }
+ }
+ break;
+ case 'write':{
+ const key = ev.data[0], val = ev.data[1];
+ switch( key ){
+ case 'jrnl': break;
+ case 'sz':{
+ const sz = +val;
+ util.assert( sz>0, "Expecting a db page number" );
+ this.onSizeChange(sz);
+ break;
+ }
+ default:
+ T.assert( +key>0, "Expecting a positive db page number" );
+ this.onPageChange(+key, val);
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ #listenTo(opt){
+ if(this.#listener){
+ sqlite3_js_kvvfs_unlisten(this.#listener);
+ this.#listener = undefined;
+ }
+ const eventHandler = async function(ev){this.event(ev)}.bind(this);
+ const li = Object.assign(
+ { /* Defaults */
+ reserve: false,
+ includeJournal: false,
+ decodePages: false,
+ storage: null
+ },
+ (/*client options*/opt||{}),
+ {/*hard-coded options*/
+ events: Object.assign(Object.create(null),{
+ 'open': eventHandler,
+ 'close': eventHandler,
+ 'write': eventHandler,
+ 'delete': eventHandler,
+ 'sync': eventHandler
+ })
+ }
+ );
+ sqlite3_js_kvvfs_listen(li);
+ this.#listener = li;
+ }
+
+ async onSizeChange(sz){}
+ async onPageChange(pgNo,content/*null for delete*/){}
+ async onSync(mode/*true=xSync, false=xFileControl*/){}
+ async onOpen(count){}
+ async onClose(count){}
+ }/*KvvfsListener*/;
+//#endif nope
+
+})/*globalThis.sqlite3ApiBootstrap.initializers*/;
+//#savepoint rollback
+//#endif not omit-kvvfs
diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
index 69be338b0..c1fee5e1a 100644
--- a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
+++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
@@ -358,7 +358,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try{
const [cMsg, n] = wasm.scopedAllocCString(e.message, true);
wasm.cstrncpy(pOut, cMsg, nOut);
- if(n > nOut) wasm.poke8(pOut + nOut - 1, 0);
+ if(n > nOut) wasm.poke8(wasm.ptr.add(pOut,nOut,-1), 0);
}catch(e){
return capi.SQLITE_NOMEM;
}finally{
@@ -410,7 +410,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}/*vfsMethods*/;
/**
- Creates and initializes an sqlite3_vfs instance for an
+ Creates, initializes, and returns an sqlite3_vfs instance for an
OpfsSAHPool. The argument is the VFS's name (JS string).
Throws if the VFS name is already registered or if something
@@ -1157,8 +1157,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
described at the end of these docs.
This function accepts an options object to configure certain
- parts but it is only acknowledged for the very first call and
- ignored for all subsequent calls.
+ parts but it is only acknowledged for the very first call for
+ each distinct name and ignored for all subsequent calls with that
+ same name.
The options, in alphabetical order:
@@ -1224,7 +1225,14 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- Paths given to it _must_ be absolute. Relative paths will not
be properly recognized. This is arguably a bug but correcting it
requires some hoop-jumping in routines which have no business
- doing such tricks.
+ doing such tricks. (2026-01-19 (2.5 years later): the specifics
+ are lost to history, but this was a side effect of xOpen()
+ receiving an immutable C-string filename, to which no implicit
+ "/" can be prefixed without causing a discrepancy between what
+ the user provided and what the VFS stores. Its conceivable that
+ that quirk could be glossed over in xFullPathname(), but
+ regressions when doing so cannot be ruled out, so there are no
+ current plans to change this behavior.)
- It is possible to install multiple instances under different
names, each sandboxed from one another inside their own private
diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
index 2b636460d..ffa90ed06 100644
--- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
+++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
@@ -16,7 +16,7 @@
asynchronous Origin-Private FileSystem (OPFS) APIs using a second
Worker, implemented in sqlite3-opfs-async-proxy.js. This file is
intended to be appended to the main sqlite3 JS deliverable somewhere
- after sqlite3-api-oo1.js and before sqlite3-api-cleanup.js.
+ after sqlite3-api-oo1.js.
*/
'use strict';
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
@@ -589,7 +589,7 @@ const installOpfsVfs = function callee(options){
/**
Returns an array of the deserialized state stored by the most
- recent serialize() operation (from from this thread or the
+ recent serialize() operation (from this thread or the
counterpart thread), or null if the serialization buffer is
empty. If passed a truthy argument, the serialization buffer
is cleared after deserialization.
@@ -924,7 +924,7 @@ const installOpfsVfs = function callee(options){
fh.filename = zName;
fh.sab = new SharedArrayBuffer(state.fileBufferSize);
fh.flags = flags;
- fh.readOnly = !(sqlite3.SQLITE_OPEN_CREATE & flags)
+ fh.readOnly = !(capi.SQLITE_OPEN_CREATE & flags)
&& !!(flags & capi.SQLITE_OPEN_READONLY);
const rc = opRun('xOpen', pFile, zName, flags, opfsFlags);
if(!rc){
@@ -1441,7 +1441,7 @@ installOpfsVfs.defaultProxyUri =
globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
try{
let proxyJs = installOpfsVfs.defaultProxyUri;
- if(sqlite3.scriptInfo.sqlite3Dir){
+ if( sqlite3?.scriptInfo?.sqlite3Dir ){
installOpfsVfs.defaultProxyUri =
sqlite3.scriptInfo.sqlite3Dir + proxyJs;
//sqlite3.config.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri);
diff --git a/ext/wasm/api/sqlite3-vtab-helper.c-pp.js b/ext/wasm/api/sqlite3-vtab-helper.c-pp.js
index 4c2338fc5..80f4bfac2 100644
--- a/ext/wasm/api/sqlite3-vtab-helper.c-pp.js
+++ b/ext/wasm/api/sqlite3-vtab-helper.c-pp.js
@@ -172,10 +172,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Works like unget() plus it calls dispose() on the
StructType object.
*/
- dispose: (pCObj)=>{
- const o = __xWrap(pCObj,true);
- if(o) o.dispose();
- }
+ dispose: (pCObj)=>__xWrap(pCObj,true)?.dispose?.()
});
};
diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c
index 4d5e9b296..dbfcdb704 100644
--- a/ext/wasm/api/sqlite3-wasm.c
+++ b/ext/wasm/api/sqlite3-wasm.c
@@ -93,6 +93,18 @@
#undef SQLITE_ENABLE_API_ARMOR
#define SQLITE_ENABLE_API_ARMOR 1
+/**********************************************************************/
+/* SQLITE_EXPERIMENTAL_PRAGMA_20251114 */
+/*
+** See:
+** https://sqlite.org/src/info/e2b3f1a9480a9be3
+** https://github.com/rhashimoto/wa-sqlite/discussions/301
+**
+** It is enabled here for the sake of VFS experimentors.
+*/
+#undef SQLITE_EXPERIMENTAL_PRAGMA_20251114
+#define SQLITE_EXPERIMENTAL_PRAGMA_20251114
+
/**********************************************************************/
/* SQLITE_O... */
#undef SQLITE_OMIT_DEPRECATED
@@ -136,8 +148,8 @@
/*
** If SQLITE_WASM_BARE_BONES is defined, undefine most of the ENABLE
** macros. This will, when using the canonical makefile, also elide
-** any C functions from the WASM exports which are listed in
-** ./EXPORT_FUNCTIONS.sqlite3-extras.
+** any C functions from the WASM exports: see
+** ./EXPORTED_FUNCTIONS.c-pp.
*/
#ifdef SQLITE_WASM_BARE_BONES
# undef SQLITE_ENABLE_COLUMN_METADATA
@@ -217,15 +229,18 @@
** not by client code, so an argument can be made for reducing their
** visibility by not including them in any build-time export lists.
**
-** 2022-09-11: it's not yet _proven_ that this approach works in
-** non-Emscripten builds. If not, such builds will need to export
-** those using the --export=... wasm-ld flag (or equivalent). As of
-** this writing we are tied to Emscripten for various reasons
-** and cannot test the library with other build environments.
+** 2025-12-01: for use in non-Emscripten builds, we need a more
+** invasive macro which explicitly names the export:
+** SQLITE_WASM_EXPORT2.
*/
#define SQLITE_WASM_EXPORT __attribute__((used,visibility("default")))
-// See also:
-//__attribute__((export_name("theExportedName"), used, visibility("default")))
+#define SQLITE_WASM_EXPORT_NAMED(X) __attribute__((export_name(#X),used,visibility("default")))
+#define SQLITE_WASM_EXPORT2(RETTYPE,NAME,SIG) SQLITE_WASM_EXPORT_NAMED(NAME) RETTYPE NAME SIG
+
+#if 1
+/** Increase the kvvfs key size limit from 32. */
+#define KVRECORD_KEY_SZ 128
+#endif
/*
** Which sqlite3.c we're using needs to be configurable to enable
@@ -249,45 +264,6 @@
#undef INC__STRINGIFY
#undef SQLITE_C
-#if 0
-/*
-** An EXPERIMENT in implementing a stack-based allocator analog to
-** Emscripten's stackSave(), stackAlloc(), stackRestore().
-** Unfortunately, this cannot work together with Emscripten because
-** Emscripten defines its own native one and we'd stomp on each
-** other's memory. Other than that complication, basic tests show it
-** to work just fine.
-**
-** Another option is to malloc() a chunk of our own and call that our
-** "stack".
-*/
-SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_end(void){
- extern void __heap_base
- /* see https://stackoverflow.com/questions/10038964 */;
- return &__heap_base;
-}
-SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_begin(void){
- extern void __data_end;
- return &__data_end;
-}
-static void * pWasmStackPtr = 0;
-SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_ptr(void){
- if(!pWasmStackPtr) pWasmStackPtr = sqlite3__wasm_stack_end();
- return pWasmStackPtr;
-}
-SQLITE_WASM_EXPORT void sqlite3__wasm_stack_restore(void * p){
- pWasmStackPtr = p;
-}
-SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_alloc(int n){
- if(n<=0) return 0;
- n = (n + 7) & ~7 /* align to 8-byte boundary */;
- unsigned char * const p = (unsigned char *)sqlite3__wasm_stack_ptr();
- unsigned const char * const b = (unsigned const char *)sqlite3__wasm_stack_begin();
- if(b + n >= p || b + n < b/*overflow*/) return 0;
- return pWasmStackPtr = p - n;
-}
-#endif /* stack allocator experiment */
-
/*
** State for the "pseudo-stack" allocator implemented in
** sqlite3__wasm_pstack_xyz(). In order to avoid colliding with
@@ -326,7 +302,7 @@ SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_ptr(void){
*/
SQLITE_WASM_EXPORT void sqlite3__wasm_pstack_restore(unsigned char * p){
assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos);
- assert(0==((unsigned long long)p & 0x7));
+ assert(0==((unsigned long long)p & 0x7) /* 8-byte aligned */);
if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){
PStack.pPos = p;
}
@@ -336,10 +312,10 @@ SQLITE_WASM_EXPORT void sqlite3__wasm_pstack_restore(unsigned char * p){
** the memory on success, 0 on error (including a negative n value). n
** is always adjusted to be a multiple of 8 and returned memory is
** always zeroed out before returning (because this keeps the client
-** JS code from having to do so, and most uses of the pstack will
-** call for doing so).
+** JS code from having to do so, and most uses of the pstack call for
+** doing so).
*/
-SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_alloc(int n){
+SQLITE_WASM_EXPORT2(void *,sqlite3__wasm_pstack_alloc,(int n)){
if( n<=0 ) return 0;
n = (n + 7) & ~7 /* align to 8-byte boundary */;
if( PStack.pBegin + n > PStack.pPos /*not enough space left*/
@@ -351,7 +327,7 @@ SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_alloc(int n){
** Return the number of bytes left which can be
** sqlite3__wasm_pstack_alloc()'d.
*/
-SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_remaining(void){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_pstack_remaining,(void)){
assert(PStack.pPos >= PStack.pBegin);
assert(PStack.pPos <= PStack.pEnd);
return (int)(PStack.pPos - PStack.pBegin);
@@ -362,7 +338,7 @@ SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_remaining(void){
** any space which is currently allocated. This value is a
** compile-time constant.
*/
-SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_quota(void){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_pstack_quota,(void)){
return (int)(PStack.pEnd - PStack.pBegin);
}
@@ -375,8 +351,7 @@ struct WasmTestStruct {
void (*xFunc)(void*);
};
typedef struct WasmTestStruct WasmTestStruct;
-SQLITE_WASM_EXPORT
-void sqlite3__wasm_test_struct(WasmTestStruct * s){
+SQLITE_WASM_EXPORT2(void,sqlite3__wasm_test_struct,(WasmTestStruct * s)){
if(s){
if( 0 ){
/* Do not be alarmed by the small (and odd) pointer values.
@@ -416,8 +391,7 @@ void sqlite3__wasm_test_struct(WasmTestStruct * s){
** fails to compile with "tables may not be 64-bit" but does not tell
** us where it's happening.
*/
-SQLITE_WASM_EXPORT
-const char * sqlite3__wasm_enum_json(void){
+SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_enum_json,(void)){
static char aBuffer[1024 * 20] =
{0} /* where the JSON goes. 2025-09-19: output size=19295, but
that can vary slightly from build to build, so a little
@@ -620,6 +594,7 @@ const char * sqlite3__wasm_enum_json(void){
DefGroup(encodings) {
/* Noting that the wasm binding only aims to support UTF-8. */
DefInt(SQLITE_UTF8);
+ DefInt(SQLITE_UTF8_ZT);
DefInt(SQLITE_UTF16LE);
DefInt(SQLITE_UTF16BE);
DefInt(SQLITE_UTF16);
@@ -1007,13 +982,15 @@ const char * sqlite3__wasm_enum_json(void){
/** ^^^ indirection needed to expand CurrentStruct */
#define StructBinder StructBinder_(CurrentStruct)
#define _StructBinder CloseBrace(2)
-#define M(MEMBER,SIG) \
- outf("%s\"%s\": " \
- "{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \
- (n++ ? ", " : ""), #MEMBER, \
- (int)offsetof(CurrentStruct,MEMBER), \
- (int)sizeof(((CurrentStruct*)0)->MEMBER), \
- SIG)
+#define M3(MEMBER,SIG,READONLY) \
+ outf("%s\"%s\": " \
+ "{\"offset\":%d,\"sizeof\":%d,\"signature\":\"%s\"%s}", \
+ (n++ ? ", " : ""), #MEMBER, \
+ (int)offsetof(CurrentStruct,MEMBER), \
+ (int)sizeof(((CurrentStruct*)0)->MEMBER), \
+ SIG, (READONLY ? ",\"readOnly\":true" : ""))
+#define M(MEMBER,SIG) M3(MEMBER,SIG,0)
+#define MRO(MEMBER,SIG) M3(MEMBER,SIG,1)
nStruct = 0;
out(", \"structs\": ["); {
@@ -1076,11 +1053,30 @@ const char * sqlite3__wasm_enum_json(void){
#undef CurrentStruct
#define CurrentStruct sqlite3_kvvfs_methods
+ /* From os_kv.c */
+ StructBinder {
+ M(xRcrdRead, "i(sspi)");
+ M(xRcrdWrite, "i(sss)");
+ M(xRcrdDelete, "i(ss)");
+ MRO(nKeySize, "i");
+ MRO(nBufferSize, "i");
+ M(pVfs, "p");
+ M(pIoDb, "p");
+ M(pIoJrnl, "p");
+ } _StructBinder;
+#undef CurrentStruct
+
+#define CurrentStruct KVVfsFile
+ /* From os_kv.c */
StructBinder {
- M(xRead, "i(sspi)");
- M(xWrite, "i(sss)");
- M(xDelete, "i(ss)");
- M(nKeySize, "i");
+ M(base, "p")/*sqlite3_file base*/;
+ M(zClass, "s");
+ M(isJournal, "i");
+ M(nJrnl, "i")/*actually unsigned!*/;
+ M(aJrnl, "p");
+ M(szPage, "i");
+ M(szDb, "j");
+ M(aData, "p");
} _StructBinder;
#undef CurrentStruct
@@ -1137,7 +1133,13 @@ const char * sqlite3__wasm_enum_json(void){
** sqlite3_index_info, we have to uplift those into constructs we
** can access by type name. These structs _must_ match their
** in-sqlite3_index_info counterparts byte for byte.
- */
+ **
+ ** 2025-11-21: this uplifing is no longer necessary, as Jaccwabyt
+ ** can now handle nested structs, but "it ain't broke" so there's
+ ** no pressing need to rewire this. Also, it's conceivable that
+ ** rewiring it might break downstream vtab impls, so it shouldn't
+ ** be rewired.
+ */
typedef struct {
int iColumn;
unsigned char op;
@@ -1232,6 +1234,8 @@ const char * sqlite3__wasm_enum_json(void){
#undef StructBinder_
#undef StructBinder__
#undef M
+#undef MRO
+#undef M3
#undef _StructBinder
#undef CloseBrace
#undef out
@@ -1249,8 +1253,7 @@ const char * sqlite3__wasm_enum_json(void){
** method, SQLITE_MISUSE is returned, else the result of the xDelete()
** call is returned.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vfs_unlink,(sqlite3_vfs *pVfs, const char *zName)){
int rc = SQLITE_MISUSE /* ??? */;
if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0);
if( zName && pVfs && pVfs->xDelete ){
@@ -1267,8 +1270,7 @@ int sqlite3__wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
** defaulting to "main" if zDbName is 0. Returns 0 if no db with the
** given name is open.
*/
-SQLITE_WASM_EXPORT
-sqlite3_vfs * sqlite3__wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
+SQLITE_WASM_EXPORT2(sqlite3_vfs *,sqlite3__wasm_db_vfs,(sqlite3 *pDb, const char *zDbName)){
sqlite3_vfs * pVfs = 0;
sqlite3_file_control(pDb, zDbName ? zDbName : "main",
SQLITE_FCNTL_VFS_POINTER, &pVfs);
@@ -1290,8 +1292,7 @@ sqlite3_vfs * sqlite3__wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
** Returns 0 on success, an SQLITE_xxx code on error. Returns
** SQLITE_MISUSE if pDb is NULL.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_db_reset(sqlite3 *pDb){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_reset,(sqlite3 *pDb)){
int rc = SQLITE_MISUSE;
if( pDb ){
sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0);
@@ -1372,10 +1373,10 @@ int sqlite3__wasm_db_export_chunked( sqlite3* pDb,
** If `*pOut` is not NULL, the caller is responsible for passing it to
** sqlite3_free() to free it.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
- unsigned char **pOut,
- sqlite3_int64 *nOut, unsigned int mFlags ){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_serialize,
+ (sqlite3 *pDb, const char *zSchema,
+ unsigned char **pOut,
+ sqlite3_int64 *nOut, unsigned int mFlags)){
unsigned char * z;
if( !pDb || !pOut ) return SQLITE_MISUSE;
if( nOut ) *nOut = 0;
@@ -1436,11 +1437,9 @@ int sqlite3__wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
** portability, so that the API can still work in builds where BigInt
** support is disabled or unavailable.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_vfs_create_file( sqlite3_vfs *pVfs,
- const char *zFilename,
- const unsigned char * pData,
- int nData ){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vfs_create_file,
+ (sqlite3_vfs *pVfs, const char *zFilename,
+ const unsigned char * pData, int nData)){
int rc;
sqlite3_file *pFile = 0;
sqlite3_io_methods const *pIo;
@@ -1526,10 +1525,9 @@ int sqlite3__wasm_vfs_create_file( sqlite3_vfs *pVfs,
** zFilename, appends pData bytes to it, and returns 0 on success or
** SQLITE_IOERR on error.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_posix_create_file( const char *zFilename,
- const unsigned char * pData,
- int nData ){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_posix_create_file,
+ (const char *zFilename, const unsigned char * pData,
+ int nData)){
int rc;
FILE * pFile = 0;
int fileExisted = 0;
@@ -1549,22 +1547,30 @@ int sqlite3__wasm_posix_create_file( const char *zFilename,
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
-** Allocates sqlite3KvvfsMethods.nKeySize bytes from
-** sqlite3__wasm_pstack_alloc() and returns 0 if that allocation fails,
-** else it passes that string to kvstorageMakeKey() and returns a
-** NUL-terminated pointer to that string. It is up to the caller to
-** use sqlite3__wasm_pstack_restore() to free the returned pointer.
+** This returns either a pointer to a static buffer or zKeyIn directly
+** (if zClass is NULL or empty).
*/
-SQLITE_WASM_EXPORT
-char * sqlite3__wasm_kvvfsMakeKeyOnPstack(const char *zClass,
- const char *zKeyIn){
+SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_kvvfsMakeKey,
+ (const char *zClass, const char *zKeyIn)){
+ static char buf[SQLITE_KVOS_SZ+1] = {0};
assert(sqlite3KvvfsMethods.nKeySize>24);
- char *zKeyOut =
- (char *)sqlite3__wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize);
- if(zKeyOut){
- kvstorageMakeKey(zClass, zKeyIn, zKeyOut);
+ if( zClass && *zClass ){
+ kvrecordMakeKey(zClass, zKeyIn, buf);
+ return buf;
+ }else{
+#if 1
+ /* We can return zKeyIn here only because the JS API takes special
+ ** care with its lifetime.*/
+ return zKeyIn;
+#else
+ /* It would be nice to be able to return zKeyIn directly here, but
+ ** it may have been allocated as part of the automated JS-to-WASM
+ ** conversions, in which case it will be freed before reaching the
+ ** caller. */
+ sqlite3_snprintf(KVRECORD_KEY_SZ, buf, "%s", zKeyIn);
+ return buf;
+#endif
}
- return zKeyOut;
}
/*
@@ -1574,8 +1580,7 @@ char * sqlite3__wasm_kvvfsMakeKeyOnPstack(const char *zClass,
** Returns the pointer to the singleton object which holds the kvvfs
** I/O methods and associated state.
*/
-SQLITE_WASM_EXPORT
-sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){
+SQLITE_WASM_EXPORT2(sqlite3_kvvfs_methods *,sqlite3__wasm_kvvfs_methods,(void)){
return &sqlite3KvvfsMethods;
}
@@ -1590,8 +1595,8 @@ sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){
** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a
** valid value.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vtab_config,
+ (sqlite3 *pDb, int op, int arg)){
switch(op){
case SQLITE_VTAB_DIRECTONLY:
case SQLITE_VTAB_INNOCUOUS:
@@ -1611,8 +1616,8 @@ int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){
** Wrapper for the variants of sqlite3_db_config() which take
** (int,int*) variadic args.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_ip,
+ (sqlite3 *pDb, int op, int arg1, int* pArg2)){
switch(op){
case SQLITE_DBCONFIG_ENABLE_FKEY:
case SQLITE_DBCONFIG_ENABLE_TRIGGER:
@@ -1647,8 +1652,9 @@ int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
** Wrapper for the variants of sqlite3_db_config() which take
** (void*,int,int) variadic args.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_pii,
+ (sqlite3 *pDb, int op, void * pArg1, int arg2,
+ int arg3)){
switch(op){
case SQLITE_DBCONFIG_LOOKASIDE:
return sqlite3_db_config(pDb, op, pArg1, arg2, arg3);
@@ -1663,8 +1669,8 @@ int sqlite3__wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, in
** Wrapper for the variants of sqlite3_db_config() which take
** (const char *) variadic args.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_s,(sqlite3 *pDb, int op,
+ const char *zArg)){
switch(op){
case SQLITE_DBCONFIG_MAINDBNAME:
return sqlite3_db_config(pDb, op, zArg);
@@ -1680,8 +1686,7 @@ int sqlite3__wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
** Binding for combinations of sqlite3_config() arguments which take
** a single integer argument.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_config_i(int op, int arg){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_config_i,(int op, int arg)){
return sqlite3_config(op, arg);
}
@@ -1692,8 +1697,7 @@ int sqlite3__wasm_config_i(int op, int arg){
** Binding for combinations of sqlite3_config() arguments which take
** two int arguments.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_config_ii(int op, int arg1, int arg2){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_config_ii,(int op, int arg1, int arg2)){
return sqlite3_config(op, arg1, arg2);
}
@@ -1704,8 +1708,7 @@ int sqlite3__wasm_config_ii(int op, int arg1, int arg2){
** Binding for combinations of sqlite3_config() arguments which take
** a single i64 argument.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_config_j,(int op, sqlite3_int64 arg)){
return sqlite3_config(op, arg);
}
@@ -1717,8 +1720,7 @@ int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){
** sqlite3_mprintf()'s %Q modifier (if addQuotes is true) or %q (if
** addQuotes is 0). Returns NULL if z is NULL or on OOM.
*/
-SQLITE_WASM_EXPORT
-char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){
+SQLITE_WASM_EXPORT2(char *,sqlite3__wasm_qfmt_token,(char *z, int addQuotes)){
char * rc = 0;
if( z ){
rc = addQuotes
@@ -1728,6 +1730,21 @@ char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){
return rc;
}
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** A WASM wrapper for the interal os_kv.c:kvvfsDecode() for internal
+** use by the kvvfs v2 API.
+*/
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_kvvfs_decode,(const char *a, char *aOut, int nOut)){
+ return kvvfsDecode(a, aOut, nOut);
+}
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_kvvfs_encode,(const char *a, int nA, char *aOut)){
+ return kvvfsEncode(a, nA, aOut);
+}
+
+
#if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS)
#include
#include
@@ -1753,8 +1770,7 @@ char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){
** the virtual FS fails. In builds compiled without SQLITE_ENABLE_WASMFS
** defined, SQLITE_NOTFOUND is returned without side effects.
*/
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_init_wasmfs(const char *zMountPoint){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_init_wasmfs,(const char *zMountPoint)){
static backend_t pOpfs = 0;
if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs";
if( !pOpfs ){
@@ -1773,8 +1789,7 @@ int sqlite3__wasm_init_wasmfs(const char *zMountPoint){
return pOpfs ? 0 : SQLITE_NOMEM;
}
#else
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_init_wasmfs(const char *zUnused){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_init_wasmfs,(const char *zUnused)){
//emscripten_console_warn("WASMFS OPFS is not compiled in.");
(void)zUnused;
return SQLITE_NOTFOUND;
@@ -1783,52 +1798,43 @@ int sqlite3__wasm_init_wasmfs(const char *zUnused){
#if SQLITE_WASM_ENABLE_C_TESTS
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_test_intptr(int * p){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_test_intptr,(int * p)){
return *p = *p * 2;
}
-SQLITE_WASM_EXPORT
-void * sqlite3__wasm_test_voidptr(void * p){
+SQLITE_WASM_EXPORT2(void *,sqlite3__wasm_test_voidptr,(void * p)){
return p;
}
-SQLITE_WASM_EXPORT
-int64_t sqlite3__wasm_test_int64_max(void){
+SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64_max,(void)){
return (int64_t)0x7fffffffffffffff;
}
-SQLITE_WASM_EXPORT
-int64_t sqlite3__wasm_test_int64_min(void){
+SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64_min,(void)){
return ~sqlite3__wasm_test_int64_max();
}
-SQLITE_WASM_EXPORT
-int64_t sqlite3__wasm_test_int64_times2(int64_t x){
+SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64_times2,(int64_t x)){
return x * 2;
}
-SQLITE_WASM_EXPORT
-void sqlite3__wasm_test_int64_minmax(int64_t * min, int64_t *max){
+SQLITE_WASM_EXPORT2(void,sqlite3__wasm_test_int64_minmax,(int64_t * min, int64_t *max)){
*max = sqlite3__wasm_test_int64_max();
*min = sqlite3__wasm_test_int64_min();
/*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
}
-SQLITE_WASM_EXPORT
-int64_t sqlite3__wasm_test_int64ptr(int64_t * p){
+SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64ptr,(int64_t * p)){
/*printf("sqlite3__wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
return *p = *p * 2;
}
-SQLITE_WASM_EXPORT
-void sqlite3__wasm_test_stack_overflow(int recurse){
+SQLITE_WASM_EXPORT2(void,sqlite3__wasm_test_stack_overflow,(int recurse)){
if(recurse) sqlite3__wasm_test_stack_overflow(recurse);
}
/* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */
-SQLITE_WASM_EXPORT
-char * sqlite3__wasm_test_str_hello(int fail){
+SQLITE_WASM_EXPORT2(char *,sqlite3__wasm_test_str_hello,(int fail)){
char * s = fail ? 0 : (char *)sqlite3_malloc(6);
if(s){
memcpy(s, "hello", 5);
@@ -1942,8 +1948,8 @@ static int sqlite3__wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
return *z==0;
}
-SQLITE_WASM_EXPORT
-int sqlite3__wasm_SQLTester_strglob(const char *zGlob, const char *z){
+SQLITE_WASM_EXPORT2(int,sqlite3__wasm_SQLTester_strglob,
+ (const char *zGlob, const char *z)){
return !sqlite3__wasm_SQLTester_strnotglob(zGlob, z);
}
diff --git a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js
index 1a09bf9a6..b282c5e6e 100644
--- a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js
+++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js
@@ -19,10 +19,12 @@
slightly simpler client-side interface than the slightly-lower-level
Worker API does.
- This script necessarily exposes one global symbol, but clients may
- freely `delete` that symbol after calling it.
+ In non-ESM builds this file necessarily exposes one global symbol,
+ but clients may freely `delete` that symbol after calling it.
*/
+//#if not defined target:es6-module
'use strict';
+//#endif
/**
Configures an sqlite3 Worker API #1 Worker such that it can be
manipulated via a Promise-based interface and returns a factory
@@ -109,10 +111,12 @@
the callback is called one time for each row of the result set,
passed the same worker message format as the worker API emits:
- {type:typeString,
+ {
+ type:typeString,
row:VALUE,
rowNumber:1-based-#,
- columnNames: array}
+ columnNames: array
+ }
Where `typeString` is an internally-synthesized message type string
used temporarily for worker message dispatching. It can be ignored
@@ -123,10 +127,9 @@
callback.
At the end of the result set, the same event is fired with
- (row=undefined, rowNumber=null) to indicate that
- the end of the result set has been reached. Note that the rows
- arrive via worker-posted messages, with all the implications
- of that.
+ (row=undefined, rowNumber=null) to indicate that the end of the
+ result set has been reached. The rows arrive via worker-posted
+ messages, with all the implications of that.
Notable shortcomings:
@@ -257,7 +260,9 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = {
type: 'module'
});
//#elif target:es6-module
- return new Worker(new URL("sqlite3-worker1.js", import.meta.url));
+ return new Worker(new URL("sqlite3-worker1.mjs", import.meta.url),{
+ type: 'module'
+ });
//#else
let theJs = "sqlite3-worker1.js";
if(this.currentScript){
@@ -281,7 +286,7 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = {
})
//#endif
,
- onerror: (...args)=>console.error('worker1 promiser error',...args)
+ onerror: (...args)=>console.error('sqlite3Worker1Promiser():',...args)
}/*defaultConfig*/;
/**
@@ -343,6 +348,7 @@ globalThis.sqlite3Worker1Promiser.v2.defaultConfig =
incompatibility.
*/
export default sqlite3Worker1Promiser.v2;
+delete globalThis.sqlite3Worker1Promiser;
//#endif /* target:es6-module */
//#else
/* Built with the omit-oo1 flag. */
diff --git a/ext/wasm/api/sqlite3-worker1.c-pp.js b/ext/wasm/api/sqlite3-worker1.c-pp.js
index 036c4c6ea..db27c8fc0 100644
--- a/ext/wasm/api/sqlite3-worker1.c-pp.js
+++ b/ext/wasm/api/sqlite3-worker1.c-pp.js
@@ -33,9 +33,9 @@
directory from which `sqlite3.js` will be loaded.
*/
//#if target:es6-bundler-friendly
-import {default as sqlite3InitModule} from './sqlite3-bundler-friendly.mjs';
+import sqlite3InitModule from './sqlite3-bundler-friendly.mjs';
//#elif target:es6-module
- return new Worker(new URL("sqlite3.js", import.meta.url));
+import sqlite3InitModule from './sqlite3.mjs';
//#else
"use strict";
{
diff --git a/ext/wasm/c-pp-lite.c b/ext/wasm/c-pp-lite.c
index 2120c457d..b8d67f6a3 100644
--- a/ext/wasm/c-pp-lite.c
+++ b/ext/wasm/c-pp-lite.c
@@ -270,9 +270,10 @@ static void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...);
/*
** Opens the given file and processes its contents as c-pp, sending
** all output to the global c-pp output channel. Fails fatally on
-** error.
+** error. If bRaw is true then the file's contents are passed through
+** verbatim, rather than being preprocessed.
*/
-static void cmpp_process_file(const char * zName);
+static void cmpp_process_file(const char * zName, int bRaw);
/*
** Operator policy for cmpp_kvp_parse().
@@ -970,7 +971,6 @@ void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...){
char * z = 0;
int n = 0;
va_list va;
- if(!str) fatal("sqlite3_str_new() failed");
va_start(va, zSql);
sqlite3_str_vappendf(str, zSql, va);
va_end(va);
@@ -2213,12 +2213,23 @@ static void cmpp_kwd_endif(CmppKeyword const * pKw, CmppTokenizer *t){
static void cmpp_kwd_include(CmppKeyword const * pKw, CmppTokenizer *t){
char const * zFile;
char * zResolved;
+ int bRaw = 0 /* -raw flag */;
if(CT_skip(t)) return;
- else if(t->args.argc!=2){
- cmpp_kwd__err(pKw, t, "Expecting exactly 1 filename argument");
+ else if(t->args.argc<2 || t->args.argc>3){
+ cmpp_kwd__err(pKw, t, "Expected args: ?-raw? filename");
}
- zFile = (const char *)t->args.argv[1];
- if(db_including_has(zFile)){
+ if(t->args.argc==2){
+ zFile = (const char *)t->args.argv[1];
+ }else{
+ if( 0==strcmp("-raw", (char*)t->args.argv[1]) ){
+ bRaw = 1;
+ }else{
+ cmpp_kwd__err(pKw, t, "Unhandled argument: %s",
+ t->args.argv[1]);
+ }
+ zFile = (const char *)t->args.argv[2];
+ }
+ if(!bRaw && db_including_has(zFile)){
/* Note that different spellings of the same filename
** will elude this check, but that seems okay, as different
** spellings means that we're not re-running the exact same
@@ -2229,16 +2240,15 @@ static void cmpp_kwd_include(CmppKeyword const * pKw, CmppTokenizer *t){
}
zResolved = db_include_search(zFile);
if(zResolved){
- db_including_add(zFile, t->zName, t->token.lineNo);
- cmpp_process_file(zResolved);
- db_include_rm(zFile);
+ if( bRaw ) db_including_add(zFile, t->zName, t->token.lineNo);
+ cmpp_process_file(zResolved, bRaw);
+ if( bRaw ) db_include_rm(zFile);
db_free(zResolved);
}else{
cmpp_t__err(t, "file not found: %s", zFile);
}
}
-
static void cmpp_dump_defines( FILE * fp, int bIndent ){
sqlite3_stmt * const q = g_stmt(GStmt_defSelAll);
while( SQLITE_ROW==sqlite3_step(q) ){
@@ -2392,7 +2402,7 @@ CmppKeyword aKeywords[] = {
{S(Endif), 0, TT_Endif, cmpp_kwd_endif},
{S(Error), 0, TT_Error, cmpp_kwd_error},
{S(If), 1, TT_If, cmpp_kwd_if},
- {S(Include), 0, TT_Include, cmpp_kwd_include},
+ {S(Include), 1, TT_Include, cmpp_kwd_include},
{S(Pragma), 1, TT_Pragma, cmpp_kwd_pragma},
{S(Savepoint), 1, TT_Savepoint, cmpp_kwd_savepoint},
{S(Stderr), 0, TT_Stderr, cmpp_kwd_stderr},
@@ -2444,14 +2454,25 @@ void cmpp_process_string(const char * zName,
g.tok = oldTok;
}
-void cmpp_process_file(const char * zName){
+void cmpp_process_file(const char * zName, int bRaw){
FileWrapper fw = FileWrapper_empty;
FileWrapper_open(&fw, zName, "r");
g_FileWrapper_link(&fw);
FileWrapper_slurp(&fw);
g_debug(1,("Read %u byte(s) from [%s]\n", fw.nContent, fw.zName));
if( fw.zContent ){
- cmpp_process_string(zName, fw.zContent, fw.nContent);
+ if( bRaw ){
+ FileWrapper fw = FileWrapper_empty;
+ FileWrapper_open(&fw, zName, "rb");
+ g_FileWrapper_link(&fw);
+ FileWrapper_slurp(&fw);
+ if( 1!=fwrite(fw.zContent, fw.nContent, 1, g.out.pFile) ){
+ fatal("fwrite() failed with errno %d\n", errno);
+ }
+ g_FileWrapper_close(&fw);
+ }else{
+ cmpp_process_string(zName, fw.zContent, fw.nContent);
+ }
}
g_FileWrapper_close(&fw);
}
@@ -2580,6 +2601,21 @@ static int arg_is_flag( char const *zFlag, char const *zArg,
return 0;
}
+static void define_argv(int argc, char const * const * argv){
+ sqlite3_str * const s = sqlite3_str_new(g.db);
+ sqlite3_str_append(s, "c-pp::argv=", 11);
+ for( int i = 0; i < argc; ++i ){
+ if( i ) sqlite3_str_appendchar(s, 1, ' ');
+ sqlite3_str_appendf(s, "%s", argv[i]);
+ }
+ char * const z = sqlite3_str_finish(s);
+ assert(z);
+ if(z){
+ db_define_add(z, NULL);
+ sqlite3_free(z);
+ }
+}
+
int main(int argc, char const * const * argv){
int rc = 0;
int inclCount = 0;
@@ -2617,6 +2653,7 @@ int main(int argc, char const * const * argv){
g.sqlTrace.expandSql = expandMode;
}
cmpp_initdb();
+ define_argv(argc, argv);
}
for(int i = 1; i < argc; ++i){
int negate = 0;
@@ -2674,7 +2711,7 @@ int main(int argc, char const * const * argv){
DOIT {
++nFile;
g_out_open;
- cmpp_process_file(zVal);
+ cmpp_process_file(zVal, 0);
}
}
ISFLAG("e"){
@@ -2757,7 +2794,7 @@ int main(int argc, char const * const * argv){
++inclCount;
}
FileWrapper_open(&g.out, g.out.zName, "w");
- cmpp_process_file("-");
+ cmpp_process_file("-", 0);
}
}
}
diff --git a/ext/wasm/common/SqliteTestUtil.js b/ext/wasm/common/SqliteTestUtil.js
index 2c17824c5..a817b79f8 100644
--- a/ext/wasm/common/SqliteTestUtil.js
+++ b/ext/wasm/common/SqliteTestUtil.js
@@ -44,7 +44,8 @@
/** abort() if expr is false. If expr is a function, it
is called and its result is evaluated.
*/
- assert: function f(expr, msg){
+ assert: function f(expr, ...msg){
+ msg = msg?.join?.(' ');
if(!f._){
f._ = ('undefined'===typeof abort
? (msg)=>{throw new Error(msg)}
diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js
index 1c678f31f..bca05a1ee 100644
--- a/ext/wasm/common/whwasmutil.js
+++ b/ext/wasm/common/whwasmutil.js
@@ -16,27 +16,35 @@
More specifically:
- https://fossil.wanderinghorse.net/r/jaccwabyt/file/common/whwasmutil.js
+ https://fossil.wanderinghorse.net/r/jaccwabyt/dir/wasmutil
and SQLite:
https://sqlite.org
This file is kept in sync between both of those trees.
+
+ This build was generated using:
+
+ ./c-pp -o js/whwasmutil.js -@policy=error wasmutil/whwasmutil.c-pp.js
+
+ by libcmpp 2.x 2fc4afc31f6505c27b9c34988973a2bd9b157d559247cdd26868ae75632c3a5e @ 2025-11-16 23:03:27.352 UTC
*/
/**
- The primary goal of this function is to replace, where possible,
- Emscripten-generated glue code with equivalent utility code which
- can be used in arbitrary WASM environments built with toolchains
- other than Emscripten. To that end, it populates the given object
- with various WASM-specific APIs. These APIs work with both 32- and
- 64-bit WASM builds.
+ The primary goal of this function is to provide JS/WASM utility
+ code similar to some of that provided by Emscripten-generated
+ builds, the difference being that this one can be used in arbitrary
+ WASM environments built with toolchains other than Emscripten. To
+ that end, it populates the given object with various WASM-specific
+ APIs. These APIs work with both 32- and 64-bit WASM builds.
Forewarning: this API explicitly targets only browser environments.
If a given non-browser environment has the capabilities needed for
a given feature (e.g. TextEncoder), great, but it does not go out
of its way to account for them and does not provide compatibility
- crutches for them.
+ crutches for them. That said: no specific incompatibilities with,
+ e.g., node.js are known (whereas it is known that some folks
+ use this with node.js).
Intended usage:
@@ -217,7 +225,9 @@
newly-created (or config-provided) target. The current approach
seemed better at the time.
*/
-globalThis.WhWasmUtilInstaller = function(target){
+'use strict';
+globalThis.WhWasmUtilInstaller =
+function WhWasmUtilInstaller(target){
'use strict';
if(undefined===target.bigIntEnabled){
target.bigIntEnabled = !!globalThis['BigInt64Array'];
@@ -227,6 +237,14 @@ globalThis.WhWasmUtilInstaller = function(target){
all args with a space between each. */
const toss = (...args)=>{throw new Error(args.join(' '))};
+ if( !target.pointerSize && !target.pointerIR
+ && target.alloc && target.dealloc ){
+ /* Try to determine the pointer size by allocating. */
+ const ptr = target.alloc(1);
+ target.pointerSize = ('bigint'===typeof ptr ? 8 : 4);
+ target.dealloc(ptr);
+ }
+
/**
As of 2025-09-21, this library works with 64-bit WASM modules
built with Emscripten's -sMEMORY64=1.
@@ -659,12 +677,14 @@ globalThis.WhWasmUtilInstaller = function(target){
const ft = target.functionTable();
const oldLen = __asPtrType(ft.length);
let ptr;
- while(cache.freeFuncIndexes.length){
- ptr = cache.freeFuncIndexes.pop();
- if(ft.get(ptr)){ /* Table was modified via a different API */
+ while( (ptr = cache.freeFuncIndexes.pop()) ){
+ if(ft.get(ptr)){
+ /* freeFuncIndexes's entry is stale. Table was modified via a
+ different API */
ptr = null;
continue;
}else{
+ /* This index is free. We'll re-use it. */
break;
}
}
@@ -755,10 +775,10 @@ globalThis.WhWasmUtilInstaller = function(target){
has no side effects and returns undefined.
*/
target.uninstallFunction = function(ptr){
- if(!ptr && 0!==ptr) return undefined;
- const fi = cache.freeFuncIndexes;
+ if(!ptr && __NullPtr!==ptr) return undefined;
+
const ft = target.functionTable();
- fi.push(ptr);
+ cache.freeFuncIndexes.push(ptr);
const rc = ft.get(ptr);
ft.set(ptr, null);
return rc;
@@ -996,12 +1016,12 @@ globalThis.WhWasmUtilInstaller = function(target){
target.heap8u().
*/
target.cstrlen = function(ptr){
- if(!ptr || !target.isPtr(ptr)) return null;
+ if(!ptr || !target.isPtr/*64*/(ptr)) return null;
ptr = Number(ptr) /*tag:64bit*/;
const h = heapWrappers().HEAP8U;
let pos = ptr;
for( ; h[pos] !== 0; ++pos ){}
- return Number(pos - ptr);
+ return pos - ptr;
};
/** Internal helper to use in operations which need to distinguish
@@ -2452,7 +2472,8 @@ globalThis.WhWasmUtilInstaller = function(target){
- If `wasmUtilTarget.alloc` is not set and
`instance.exports.malloc` is, it installs
`wasmUtilTarget.alloc()` and `wasmUtilTarget.dealloc()`
- wrappers for the exports `malloc` and `free` functions.
+ wrappers for the exports' `malloc` and `free` functions
+ if exports.malloc exists.
It returns a function which, when called, initiates loading of the
module and returns a Promise. When that Promise resolves, it calls
@@ -2475,7 +2496,9 @@ globalThis.WhWasmUtilInstaller = function(target){
Error handling is up to the caller, who may attach a `catch()` call
to the promise.
*/
-globalThis.WhWasmUtilInstaller.yawl = function(config){
+globalThis.WhWasmUtilInstaller
+.yawl = function yawl(config){
+ 'use strict';
const wfetch = ()=>fetch(config.uri, {credentials: 'same-origin'});
const wui = this;
const finalThen = function(arg){
@@ -2500,7 +2523,7 @@ globalThis.WhWasmUtilInstaller.yawl = function(config){
tgt.alloc = function(n){
return exports.malloc(n) || toss("Allocation of",n,"bytes failed.");
};
- tgt.dealloc = function(m){exports.free(m)};
+ tgt.dealloc = function(m){m && exports.free(m)};
}
wui(tgt);
}
@@ -2519,4 +2542,6 @@ globalThis.WhWasmUtilInstaller.yawl = function(config){
.then(finalThen)
;
return loadWasm;
-}.bind(globalThis.WhWasmUtilInstaller)/*yawl()*/;
+}.bind(
+globalThis.WhWasmUtilInstaller
+)/*yawl()*/;
diff --git a/ext/wasm/demo-jsstorage.js b/ext/wasm/demo-jsstorage.js
index 587aa9cc5..e3ab5a9e5 100644
--- a/ext/wasm/demo-jsstorage.js
+++ b/ext/wasm/demo-jsstorage.js
@@ -16,7 +16,7 @@
*/
'use strict';
(function(){
- const T = self.SqliteTestUtil;
+ const T = globalThis.SqliteTestUtil;
const toss = function(...args){throw new Error(args.join(' '))};
const debug = console.debug.bind(console);
const eOutput = document.querySelector('#test-output');
@@ -40,7 +40,7 @@
const error = function(...args){
logHtml('error',...args);
};
-
+
const runTests = function(sqlite3){
const capi = sqlite3.capi,
oo = sqlite3.oo1,
@@ -51,7 +51,7 @@
error("This build is not kvvfs-capable.");
return;
}
-
+
const dbStorage = 0 ? 'session' : 'local';
const theStore = 's'===dbStorage[0] ? sessionStorage : localStorage;
const db = new oo.JsStorageDb( dbStorage );
@@ -108,7 +108,7 @@
}
};
- sqlite3InitModule(self.sqlite3TestModule).then((sqlite3)=>{
+ sqlite3InitModule(globalThis.sqlite3TestModule).then((sqlite3)=>{
runTests(sqlite3);
});
})();
diff --git a/ext/wasm/demo-worker1-promiser.c-pp.html b/ext/wasm/demo-worker1-promiser.c-pp.html
index e0b487bdf..a1005beb9 100644
--- a/ext/wasm/demo-worker1-promiser.c-pp.html
+++ b/ext/wasm/demo-worker1-promiser.c-pp.html
@@ -6,10 +6,10 @@
-//#if target=es6-module
- worker-promise (via ESM) tests
+//#if target:es6-module
+ Worker1-promiser (ESM) tests
//#else
- worker-promise tests
+ Worker1-promiser tests
//#endif
@@ -32,7 +32,7 @@
-//#if target=es6-module
+//#if target:es6-module
//#else
diff --git a/ext/wasm/demo-worker1.js b/ext/wasm/demo-worker1.js
index 1a05cc7ac..348741bf8 100644
--- a/ext/wasm/demo-worker1.js
+++ b/ext/wasm/demo-worker1.js
@@ -18,7 +18,7 @@
*/
'use strict';
(function(){
- const T = self.SqliteTestUtil;
+ const T = globalThis.SqliteTestUtil;
const SW = new Worker("jswasm/sqlite3-worker1.js");
const DbState = {
id: undefined
@@ -323,7 +323,7 @@
switch(ev.result){
case 'worker1-ready':
log("Message:",ev);
- self.sqlite3TestModule.setStatus(null);
+ globalThis.sqlite3TestModule.setStatus(null);
runTests();
return;
default:
@@ -344,5 +344,5 @@
};
log("Init complete, but async init bits may still be running.");
log("Installing Worker into global scope SW for dev purposes.");
- self.SW = SW;
+ globalThis.SW = SW;
})();
diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js
index a5f3e25b7..4b1ea2c53 100644
--- a/ext/wasm/fiddle/fiddle-worker.js
+++ b/ext/wasm/fiddle/fiddle-worker.js
@@ -175,10 +175,6 @@
"features (e.g. upload) do not yet work with OPFS.");
}
stdout('\nEnter ".help" for usage hints.');
- this.exec([ // initialization commands...
- '.nullvalue NULL',
- '.headers on'
- ].join('\n'));
return true;
},
/**
diff --git a/ext/wasm/fiddle/index.html b/ext/wasm/fiddle/index.c-pp.html
similarity index 95%
rename from ext/wasm/fiddle/index.html
rename to ext/wasm/fiddle/index.c-pp.html
index 378cb3902..1f818286b 100644
--- a/ext/wasm/fiddle/index.html
+++ b/ext/wasm/fiddle/index.c-pp.html
@@ -5,20 +5,29 @@
SQLite3 Fiddle
+//#if jqterm
-
+
+
+//#endif
-[sqlite3]: https://sqlite.org
-[emscripten]: https://emscripten.org
-[sgb]: https://wanderinghorse.net/home/stephan/
[appendix-g]: #appendix-g
-[StructBinderFactory]: #api-binderfactory
-[StructCtors]: #api-structctor
-[StructType]: #api-structtype
+[BigInt64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array
+[c-pp]: https://fossil.wanderinghorse.net/r/c-pp
+[Emscripten]: https://emscripten.org
+[jaccwabyt.js]: /file/jaccwabyt/jaccwabyt.c-pp.js
+[MDN]: https://developer.mozilla.org/docs/Web/API
+[sgb]: https://wanderinghorse.net/home/stephan/
+[sqlite3]: https://sqlite.org
[StructBinder]: #api-structbinder
+[StructBinderFactory]: #api-binderfactory
+[StructCtor]: #api-structctor
[StructInstance]: #api-structinstance
-[^export-func]: In Emscripten, add its name, prefixed with `_`, to the
- project's `EXPORT_FUNCTIONS` list.
-[BigInt64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array
+[StructType]: #api-structtype
[TextDecoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder
[TextEncoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
-[MDN]: https://developer.mozilla.org/docs/Web/API
+[WASI-SDK]: https://github.com/WebAssembly/wasi-sdk
+[whwasmutil.js]: /file/wasmutil/whwasmutil.c-pp.js
+
+[^export-func]: In Emscripten, add its name, prefixed with `_`, to the
+ project's `EXPORT_FUNCTIONS` list.
diff --git a/ext/wasm/mkdist.sh b/ext/wasm/mkdist.sh
index 84780668b..78b93e5e0 100755
--- a/ext/wasm/mkdist.sh
+++ b/ext/wasm/mkdist.sh
@@ -52,7 +52,7 @@ for arg in $@; do
;;
--snapshot)
- snapshotSuffix=$(date +%Y%m%d)
+ snapshotSuffix=-snapshot-$(date +%Y%m%d)
;;
-?|--help)
diff --git a/ext/wasm/mkwasmbuilds.c b/ext/wasm/mkwasmbuilds.c
index 2730d9d76..37f2967d8 100644
--- a/ext/wasm/mkwasmbuilds.c
+++ b/ext/wasm/mkwasmbuilds.c
@@ -40,14 +40,22 @@
/*
** Flags for use with BuildDef::flags.
**
-** Maintenance reminder: do not combine flags within this enum,
-** e.g. F_BUNDLER_FRIENDLY=0x02|F_ESM, as that will lead
-** to breakage in some of the flag checks.
+** Maintenance reminder: do not combine F_... flags within this enum,
+** e.g. F_BUNDLER_FRIENDLY=0x02|F_ESM, as that will lead to breakage
+** in some of the flag checks.
*/
enum BuildDefFlags {
/* Indicates an ESM module build. */
F_ESM = 0x01,
- /* Indicates a "bundler-friendly" build mode. */
+ /* Indicates a "bundler-friendly" build mode. These are untested and
+ ** unsupported, provided solely for the downstream npm subproject
+ ** (who is responsible for any testing of these).
+ **
+ ** The only difference beween bundler-friendly and esm builds is
+ ** that bundlers require static filename strings in a few places due
+ ** to limitations of bundler tooling, whereas vanilla and JS can
+ ** both work with dynamic strings.
+ */
F_BUNDLER_FRIENDLY = 1<<1,
/* Indicates that this build is unsupported. Such builds are not
** added to the 'all' target. The unsupported builds exist primarily
@@ -57,8 +65,11 @@ enum BuildDefFlags {
F_NOT_IN_ALL = 1<<3,
/* If it's a 64-bit build. */
F_64BIT = 1<<4,
- /* Indicates a node.js-for-node.js build (untested and
- ** unsupported). */
+ /* Indicates a node.js-for-node.js build. This build is very
+ ** specificially untested and unsupported, but the downstream npm
+ ** project makes use of it. None of our JS code is specific to node,
+ ** but Emscripten's generated sqlite3.js differs between
+ ** for-the-browser and for-node builds. */
F_NODEJS = 1<<5,
/* Indicates a wasmfs build (untested and unsupported). */
F_WASMFS = 1<<6,
@@ -71,13 +82,13 @@ enum BuildDefFlags {
** only their JS file and patch their JS to use the WASM file from a
** canonical build which uses that same WASM file. Reusing X.wasm
** that way can only work for builds which are processed identically
- ** by Emscripten. For a given set of C flags (as opposed to
+ ** by Emscripten. For a given set of C flags (as distinct from
** JS-influencing flags), all builds of X.js and Y.js will produce
** identical X.wasm and Y.wasm files. Their JS files may well
** differ, however.
*/
- CP_JS = 1 << 30,
- CP_WASM = 1 << 31,
+ CP_JS = 1 << 30, /* X.js or X.mjs, depending on F_ESM */
+ CP_WASM = 1 << 31, /* X.wasm */
CP_ALL = CP_JS | CP_WASM
};
@@ -94,11 +105,10 @@ enum BuildDefFlags {
** final build directory $(dir.dout).
**
** To keep parallel builds from stepping on each other, each distinct
-** build goes into its own subdir $(dir.dout.BuildName)[^1], i.e.
-** $(dir.dout)/BuildName. Builds which produce deliverables we'd like
-** to keep/distribute copy their final results into the build dir
-** $(dir.dout). See the notes for the CP_JS enum entry for more
-** details on that.
+** build goes into its own subdir $(dir.dout.$(BuildDef::zBaseName).
+** Builds which produce deliverables we'd like to keep/distribute copy
+** their final results into the build dir $(dir.dout). See the notes
+** for the CP_JS enum entry for more details on that.
**
** The final result of each build is a pair of JS/WASM files, but
** getting there requires generation of several files, primarily as
@@ -116,9 +126,9 @@ enum BuildDefFlags {
**
** --extern-post-js = gets injected immediately after
** sqlite3InitModule(), in the global scope. In this step we replace
-** sqlite3InitModule() with a slightly customized, the main purpose of
-** which is to (A) give us (not Emscripten) control over the arguments
-** it accepts and (B) to run the library bootstrap step.
+** sqlite3InitModule() with a slightly customized one, the main
+** purpose of which is to (A) give us (not Emscripten) control over
+** the arguments it accepts and (B) to run the library bootstrap step.
**
** Then there's sqlite3-api.BuildName.js, which is the entire SQLite3
** JS API (generated from the list defined in $(sqlite3-api.jses)). It
@@ -126,9 +136,7 @@ enum BuildDefFlags {
**
** Each of those inputs has to be generated before passing them on to
** Emscripten so that any build-specific capabilities can get filtered
-** in or out (using ./c-pp.c).
-**
-** [^1]: The legal BuildNames are in this file's BuildDef_map macro.
+** in or out (using ./c-pp-lite.c).
*/
struct BuildDef {
/*
@@ -143,8 +151,8 @@ struct BuildDef {
**
** The convention for 32- vs 64-bit pairs is to give them similar
** emoji, e.g. a cookie for 32-bit and a donut or cake for 64.
- ** Alternately, the same emoji a "64" suffix, excep that that throws
- ** off the output alignment in parallel builds ;).
+ ** Alternately, the same emoji with a "64" suffix, except that that
+ ** throws off the output alignment in parallel builds ;).
*/
const char *zEmo;
/*
@@ -161,13 +169,13 @@ struct BuildDef {
const char *zCmppD; /* Extra -D... flags for c-pp */
const char *zEmcc; /* Full flags for emcc. Normally NULL for default. */
const char *zEmccExtra; /* Extra flags for emcc */
- const char *zDeps; /* Extra deps */
+ const char *zDeps; /* Extra make target deps */
const char *zEnv; /* emcc -sENVIRONMENT=... value */
/*
** Makefile code "ifeq (...)". If set, this build is enclosed in a
** $zIfCond/endif block.
*/
- const char *zIfCond; /* makefile "ifeq (...)" or similar */
+ const char *zIfCond;
int flags; /* Flags from BuildDefFlags */
};
typedef struct BuildDef BuildDef;
@@ -183,7 +191,7 @@ typedef struct BuildDef BuildDef;
** logtag.NAME = Used for decorating log output
**
** etc.
-***/
+*/
#define BuildDefs_map(E) \
E(vanilla) E(vanilla64) \
E(esm) E(esm64) \
@@ -196,9 +204,8 @@ typedef struct BuildDef BuildDef;
** The set of WASM builds for the library (as opposed to the apps
** (fiddle, speedtest1)). Their order in BuildDefs_map is mostly
** insignificant, but some makefile vars used by some builds are set
-** up by prior builds. Because of that, the (sqlite3, vanilla),
-** (sqlite3, esm), and (sqlite3, bundler-friendly) builds should be
-** defined first (in that order).
+** up by prior builds. Because of that, the vanilla, esm, and
+** bundler-friendly builds should be defined first (in that order).
*/
struct BuildDefs {
#define E(N) BuildDef N;
@@ -284,7 +291,7 @@ const BuildDefs oBuildDefs = {
" -DSQLITE_SPEEDTEST1_WASM"
" $(SQLITE_OPT)"
" -USQLITE_WASM_BARE_BONES"
- " -USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c)"
+ " -USQLITE_C -DSQLITE_C=$(sqlite3.c)"
" $(speedtest1.exit-runtime0)"
" $(speedtest1.c.in)"
" -lm",
@@ -312,7 +319,7 @@ const BuildDefs oBuildDefs = {
" -DSQLITE_SPEEDTEST1_WASM"
" $(SQLITE_OPT)"
" -USQLITE_WASM_BARE_BONES"
- " -USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c)"
+ " -USQLITE_C -DSQLITE_C=$(sqlite3.c)"
" $(speedtest1.exit-runtime0)"
" $(speedtest1.c.in)"
" -lm",
@@ -345,8 +352,7 @@ const BuildDefs oBuildDefs = {
.zEnv = 0,
.zDeps = 0,
.zIfCond = 0,
- .flags = CP_JS | F_BUNDLER_FRIENDLY | F_ESM
- //| F_NOT_IN_ALL
+ .flags = CP_JS | F_BUNDLER_FRIENDLY | F_ESM | F_NOT_IN_ALL
},
/* 64-bit bundler-friendly. */
@@ -371,7 +377,7 @@ const BuildDefs oBuildDefs = {
.node = {
.zEmo = "🍟",
.zBaseName = "sqlite3-node",
- .zDotWasm = 0,
+ .zDotWasm = "sqlite3",
.zCmppD = "-Dtarget:node $(c-pp.D.bundler)",
.zEmcc = 0,
.zEmccExtra = 0,
@@ -382,21 +388,21 @@ const BuildDefs oBuildDefs = {
** node. */,
.zDeps = 0,
.zIfCond = 0,
- .flags = CP_ALL | F_UNSUPPORTED | F_NODEJS
+ .flags = CP_JS | F_UNSUPPORTED | F_ESM | F_NODEJS
},
/* 64-bit node. */
.node64 = {
.zEmo = "🍔",
.zBaseName = "sqlite3-node-64bit",
- .zDotWasm = 0,
+ .zDotWasm = "sqlite3-64bit",
.zCmppD = "-Dtarget:node $(c-pp.D.bundler)",
.zEmcc = 0,
.zEmccExtra = 0,
.zEnv = "node",
.zDeps = 0,
.zIfCond = 0,
- .flags = CP_ALL | F_UNSUPPORTED | F_NODEJS | F_64BIT
+ .flags = CP_JS | F_UNSUPPORTED | F_ESM | F_NODEJS | F_64BIT
},
/* Entirely unsupported. */
@@ -487,8 +493,8 @@ static void mk_prologue(void){
** configuration). Comments like "saves nothing" may not be
** technically correct: "nothing" means "some neglible amount."
**
- ** Note that performance gains/losses are _not_ taken into
- ** account here: only wasm file size.
+ ** Performance gains or losses are _not_ taken into account
+ ** here, only wasm file size.
*/
"--enable-bulk-memory-opt " /* required */
"--all-features " /* required */
@@ -638,7 +644,7 @@ static void mk_pre_post(char const *zBuildName, BuildDef const * pB){
pB->zDotWasm);
}
ps("");
- pf("\t@$(call b.c-pp.shcmd,"
+ pf("\t@$(call b.mkdir@); $(call b.c-pp.shcmd,"
"%s,"
"$(pre-js.in.js),"
"$(pre-js.%s.js),"
@@ -777,13 +783,14 @@ static void mk_lib_mode(const char *zBuildName, const BuildDef * pB){
zBuildName, zBuildName, zBaseName);
pf("dir.dout.%s ?= $(dir.dout)/%s\n", zBuildName, zBuildName);
- pf("out.%s.base ?= $(dir.dout.%s)/%s\n",
- zBuildName, zBuildName, zBaseName);
pf("c-pp.D.%s ?= %s\n", zBuildName, pB->zCmppD ? pB->zCmppD : "");
if( pB->flags & F_64BIT ){
pf("c-pp.D.%s += $(c-pp.D.64bit)\n", zBuildName);
}
+ if( pB->flags & F_UNSUPPORTED ){
+ pf("c-pp.D.%s += -Dunsupported-build\n", zBuildName);
+ }
pf("emcc.environment.%s ?= %s\n", zBuildName,
pB->zEnv ? pB->zEnv : oBuildDefs.vanilla.zEnv);
@@ -914,10 +921,9 @@ static void mk_lib_mode(const char *zBuildName, const BuildDef * pB){
pf("\n%dbit: $(out.%s.js)\n"
"$(out.%s.wasm): $(out.%s.js)\n"
- "b-%s: $(out.%s.js) $(out.%s.wasm)\n",
+ "b-%s: $(out.%s.wasm)\n",
(F_64BIT & pB->flags) ? 64 : 32, zBuildName,
- zBuildName, zBuildName,
- zBuildName, zBuildName, zBuildName);
+ zBuildName, zBuildName, zBuildName, zBuildName);
if( CP_JS & pB->flags ){
pf("$(dir.dout)/%s%s: $(out.%s.js)\n",
@@ -988,11 +994,18 @@ static void mk_fiddle(void){
pf("$(out.%s.js): $(MAKEFILE_LIST) "
"$(EXPORTED_FUNCTIONS.fiddle) "
"$(fiddle.c.in) "
- "$(pre-post.%s.deps)\n",
+ "$(pre-post.%s.deps)",
zBuildName, zBuildName);
+ if( isDebug ){
+ pf(" $(dir.fiddle)/fiddle-worker.js"
+ " $(dir.fiddle)/fiddle.js"
+ " $(dir.fiddle)/index.html");
+ }
+ ps("");
emit_compile_start(zBuildName);
- pf("\t$(b.cmd@)$(bin.emcc) -o $@"
- " $(emcc.flags.%s)" /* set in fiddle.make */
+ pf("\t@$(call b.mkdir@)\n"
+ "\t$(b.cmd@)$(bin.emcc) -o $@"
+ " $(emcc.flags.%s)" /* set in GNUmakefile */
" $(pre-post.%s.flags)"
" $(fiddle.c.in)"
"\n",
@@ -1057,7 +1070,7 @@ int main(int argc, char const ** argv){
BuildDefs_map(E) if( 0==strcmp("prologue",zArg) ){
mk_prologue();
}else {
- fprintf(stderr,"Unkown build name: %s\n", zArg);
+ fprintf(stderr,"Unknown build name: %s\n", zArg);
rc = 1;
break;
}
diff --git a/ext/wasm/speedtest1-worker.js b/ext/wasm/speedtest1-worker.js
index ba11fd163..9355ba93f 100644
--- a/ext/wasm/speedtest1-worker.js
+++ b/ext/wasm/speedtest1-worker.js
@@ -1,7 +1,7 @@
'use strict';
(function(){
let speedtestJs = 'speedtest1.js';
- const urlParams = new URL(self.location.href).searchParams;
+ const urlParams = new URL(globalThis.location.href).searchParams;
if(urlParams.has('sqlite3.dir')){
speedtestJs = urlParams.get('sqlite3.dir') + '/' + speedtestJs;
}
@@ -14,9 +14,9 @@
const wasmfsDir = function f(wasmUtil){
if(undefined !== f._) return f._;
const pdir = '/opfs';
- if( !self.FileSystemHandle
- || !self.FileSystemDirectoryHandle
- || !self.FileSystemFileHandle){
+ if( !globalThis.FileSystemHandle
+ || !globalThis.FileSystemDirectoryHandle
+ || !globalThis.FileSystemFileHandle){
return f._ = "";
}
try{
@@ -92,7 +92,7 @@
}
};
- self.onmessage = function(msg){
+ globalThis.onmessage = function(msg){
msg = msg.data;
switch(msg.type){
case 'run':
@@ -111,7 +111,8 @@
setStatus: (text)=>mPost('load-status',text)
};
log("Initializing speedtest1 module...");
- self.sqlite3InitModule(EmscriptenModule).then(async (sqlite3)=>{
+ globalThis.sqlite3InitModule.__isUnderTest = true;
+ globalThis.sqlite3InitModule(EmscriptenModule).then(async (sqlite3)=>{
const S = globalThis.S = App.sqlite3 = sqlite3;
log("Loaded speedtest1 module. Setting up...");
App.pDir = wasmfsDir(S.wasm);
diff --git a/ext/wasm/speedtest1.html b/ext/wasm/speedtest1.html
index cce617185..d41f20a4e 100644
--- a/ext/wasm/speedtest1.html
+++ b/ext/wasm/speedtest1.html
@@ -44,132 +44,137 @@
mounted, else it returns an empty string.
*/
const wasmfsDir = function f(wasmUtil){
- if(undefined !== f._) return f._;
- const pdir = '/persistent';
- if( !self.FileSystemHandle
- || !self.FileSystemDirectoryHandle
- || !self.FileSystemFileHandle){
- return f._ = "";
- }
- try{
- if(0===wasmUtil.xCallWrapped(
- 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir
- )){
- return f._ = pdir;
- }else{
- return f._ = "";
- }
- }catch(e){
- // sqlite3_wasm_init_wasmfs() is not available
- return f._ = "";
+ if(undefined !== f._) return f._;
+ const pdir = '/persistent';
+ if( !self.FileSystemHandle
+ || !self.FileSystemDirectoryHandle
+ || !self.FileSystemFileHandle){
+ return f._ = "";
+ }
+ try{
+ if(0===wasmUtil.xCallWrapped(
+ 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir
+ )){
+ return f._ = pdir;
+ }else{
+ return f._ = "";
}
+ }catch(e){
+ // sqlite3_wasm_init_wasmfs() is not available
+ return f._ = "";
+ }
};
wasmfsDir._ = undefined;
const eOut = document.querySelector('#test-output');
const log2 = function(cssClass,...args){
- const ln = document.createElement('div');
- if(cssClass) ln.classList.add(cssClass);
- ln.append(document.createTextNode(args.join(' ')));
- eOut.append(ln);
- //this.e.output.lastElementChild.scrollIntoViewIfNeeded();
+ const ln = document.createElement('div');
+ if(cssClass) ln.classList.add(cssClass);
+ ln.append(document.createTextNode(args.join(' ')));
+ eOut.append(ln);
+ //this.e.output.lastElementChild.scrollIntoViewIfNeeded();
};
const logList = [];
const dumpLogList = function(){
- logList.forEach((v)=>log2('',v));
- logList.length = 0;
+ logList.forEach((v)=>log2('',v));
+ logList.length = 0;
};
/* we cannot update DOM while speedtest is running unless we run
speedtest in a worker thread. */;
const log = (...args)=>{
- console.log(...args);
- logList.push(args.join(' '));
+ console.log(...args);
+ logList.push(args.join(' '));
};
const logErr = function(...args){
- console.error(...args);
- logList.push('ERROR: '+args.join(' '));
+ console.error(...args);
+ logList.push('ERROR: '+args.join(' '));
};
const runTests = function(sqlite3){
- const capi = sqlite3.capi, wasm = sqlite3.wasm;
- //console.debug('sqlite3 =',sqlite3);
- const pDir = wasmfsDir(wasm);
- if(pDir){
- console.warn("Persistent storage:",pDir);
- }
- const scope = wasm.scopedAllocPush();
- let dbFile = pDir+"/speedtest1.db";
- const urlParams = new URL(self.location.href).searchParams;
- const argv = ["speedtest1"];
- if(urlParams.has('flags')){
- argv.push(...(urlParams.get('flags').split(',')));
- }
+ globalThis.S = sqlite3;
+ const capi = sqlite3.capi, wasm = sqlite3.wasm;
+ //console.debug('sqlite3 (global S) =',sqlite3);
+ const pDir = wasmfsDir(wasm);
+ if(pDir){
+ console.warn("wasmfs storage:",pDir);
+ }
+ const scope = wasm.scopedAllocPush();
+ let dbFile = pDir+"/speedtest1.db";
+ const urlParams = new URL(self.location.href).searchParams;
+ const argv = ["speedtest1"];
+ if(urlParams.has('flags')){
+ argv.push(...(urlParams.get('flags').split(',')));
+ }
- let forceSize = 0;
- let vfs, pVfs = 0;
- if(urlParams.has('vfs')){
- vfs = urlParams.get('vfs');
- pVfs = capi.sqlite3_vfs_find(vfs);
- if(!pVfs){
- log2('error',"Unknown VFS:",vfs);
- return;
- }
- argv.push("--vfs", vfs);
- log2('',"Using VFS:",vfs);
- if('kvvfs' === vfs){
- forceSize = 2 /* >2 is too big as of mid-2025 */;
- dbFile = 'session';
- log2('warning',"kvvfs VFS: forcing --size",forceSize,
- "and filename '"+dbFile+"'.");
- capi.sqlite3_js_kvvfs_clear(dbFile);
- }
+ let forceSize = 0;
+ let vfs, pVfs = 0;
+ if(urlParams.has('vfs')){
+ vfs = urlParams.get('vfs');
+ pVfs = capi.sqlite3_vfs_find(vfs);
+ if(!pVfs){
+ log2('error',"Unknown VFS:",vfs);
+ return;
}
- if(forceSize){
- argv.push('--size',forceSize);
- }else{
- [
- 'size'
- ].forEach(function(k){
- const v = urlParams.get(k);
- if(v) argv.push('--'+k, urlParams[k]);
- });
+ argv.push("--vfs", vfs);
+ log2('',"Using VFS:",vfs);
+ if('kvvfs' === vfs){
+ forceSize = 5
+ /* --size > 2 is too big for local/session storage
+ as of mid-2025, but kvvfs v2 (late 2025)
+ lets us use transient storage. */;
+ dbFile = 'transient';
+ log2('warning',"kvvfs VFS: forcing --size",forceSize,
+ "and filename '"+dbFile+"'.");
+ capi.sqlite3_js_kvvfs_clear(dbFile);
+ }
+ }
+ if(forceSize){
+ argv.push('--size',forceSize);
+ }else{
+ [
+ 'size'
+ ].forEach(function(k){
+ const v = urlParams.get(k);
+ if(v && +v) argv.push('--'+k, urlParams[k]);
+ });
+ }
+ argv.push(
+ "--singlethread",
+ //"--nomutex",
+ //"--nosync",
+ //"--memdb", // note that memdb trumps the filename arg
+ "--nomemstat"
+ );
+ argv.push("--big-transactions"/*important for tests 410 and 510!*/,
+ dbFile);
+ console.log("argv =",argv);
+ // These log messages are not emitted to the UI until after main() returns. Fixing that
+ // requires moving the main() call and related cleanup into a timeout handler.
+ if(pDir) wasm.sqlite3_wasm_vfs_unlink(pVfs,dbFile);
+ log2('',"WASM heap size:",sqlite3.wasm.heap8().byteLength,"bytes");
+ log2('',"WASM pointer size:",sqlite3.wasm.ptr.size);
+ log2('',"Starting native app:\n ",argv.join(' '));
+ log2('',"This will take a while and the browser might warn about the runaway JS.",
+ "Give it time...");
+ logList.length = 0;
+ setTimeout(function(){
+ wasm.xCall('wasm_main', argv.length,
+ wasm.scopedAllocMainArgv(argv));
+ wasm.scopedAllocPop(scope);
+ if('kvvfs'===vfs){
+ logList.unshift("KVVFS "+dbFile+" size = "+
+ capi.sqlite3_js_kvvfs_size(dbFile));
}
- argv.push(
- "--singlethread",
- //"--nomutex",
- //"--nosync",
- //"--memdb", // note that memdb trumps the filename arg
- "--nomemstat"
- );
- argv.push("--big-transactions"/*important for tests 410 and 510!*/,
- dbFile);
- console.log("argv =",argv);
- // These log messages are not emitted to the UI until after main() returns. Fixing that
- // requires moving the main() call and related cleanup into a timeout handler.
if(pDir) wasm.sqlite3_wasm_vfs_unlink(pVfs,dbFile);
+ logList.unshift("Done running native main(). Output:");
+ dumpLogList();
log2('',"WASM heap size:",sqlite3.wasm.heap8().byteLength,"bytes");
- log2('',"WASM pointer size:",sqlite3.wasm.ptr.size);
- log2('',"Starting native app:\n ",argv.join(' '));
- log2('',"This will take a while and the browser might warn about the runaway JS.",
- "Give it time...");
- logList.length = 0;
- setTimeout(function(){
- wasm.xCall('wasm_main', argv.length,
- wasm.scopedAllocMainArgv(argv));
- wasm.scopedAllocPop(scope);
- if('kvvfs'===vfs){
- logList.unshift("KVVFS "+dbFile+" size = "+
- capi.sqlite3_js_kvvfs_size(dbFile));
- }
- if(pDir) wasm.sqlite3_wasm_vfs_unlink(pVfs,dbFile);
- logList.unshift("Done running native main(). Output:");
- dumpLogList();
- log2('',"WASM heap size:",sqlite3.wasm.heap8().byteLength,"bytes");
- }, 50);
+ }, 50);
}/*runTests()*/;
self.sqlite3TestModule.print = log;
self.sqlite3TestModule.printErr = logErr;
+ sqlite3InitModule.__isUnderTest = true;
sqlite3InitModule(self.sqlite3TestModule).then(runTests);
})();
diff --git a/ext/wasm/tester1-worker.c-pp.html b/ext/wasm/tester1-worker.c-pp.html
index e461b6cbf..71d827a35 100644
--- a/ext/wasm/tester1-worker.c-pp.html
+++ b/ext/wasm/tester1-worker.c-pp.html
@@ -13,10 +13,18 @@