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. -
  • Run: `mkdir $HOME/local` -
  • Run: `./configure --prefix=$HOME/local` -
  • Run: `make install` +
  • Run:   `mkdir $HOME/local` +
  • Run:   `./configure --prefix=$HOME/local`
    + SQLite deliverable builds add:   `--static CFLAGS=-Os` +
  • Run:   `make install`

    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 @@

      4.4 Special Directives

    • %right
    • %realloc
    • %stack_overflow +
    • %stack_size_limit
    • %stack_size
    • %start_symbol
    • %syntax_error @@ -1203,20 +1204,33 @@

      4.4.25 The %wildcard directive

      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. +

      5.0 Error Processing

      diff --git a/doc/testrunner.md b/doc/testrunner.md index d1696e9d1..90ef4b71f 100644 --- a/doc/testrunner.md +++ b/doc/testrunner.md @@ -4,6 +4,12 @@
      • 1. Overview +
      • 2. Binary Tests
        • 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 "&lt;", "&amp;", "&gt;", "&quot;", +and "&#39;", 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: + + +
           QuoteCsv +
          zColumnSep",""," +
          zRowSep"\\n""\\r\\n" +
          zNull"NULL""" +
          eTextQRF_TEXT_SqlQRF_TEXT_Csv +
          eBlobQRF_BLOB_SqlQRF_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, "", 4); + for(i=0; inCol; i++){ + const char *zCName = sqlite3_column_name(p->pStmt, i); + sqlite3_str_append(p->pOut, "\n", 5); + qrfEncodeText(p, p->pOut, zCName); + } + sqlite3_str_append(p->pOut, "\n\n", 7); + } + sqlite3_str_append(p->pOut, "", 4); + for(i=0; inCol; i++){ + sqlite3_str_append(p->pOut, "\n", 5); + qrfRenderValue(p, p->pOut, i); + } + sqlite3_str_append(p->pOut, "\n\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 @@

          sqlite3 tester #1: Worker thread (@bitness@-bit WASM)

          Variants: - conventional UI thread, - conventional worker, - ESM in UI thread, - ESM worker + conventional UI thread: + (32-bit, + 64-bit), + conventional worker: + (32-bit, + 64-bit), + ESM in UI thread: + (32-bit, + 64-bit), + ESM worker: + (32-bit + 64-bit)
          diff --git a/ext/wasm/tester1.c-pp.html b/ext/wasm/tester1.c-pp.html index 95fe52219..4bb53ee56 100644 --- a/ext/wasm/tester1.c-pp.html +++ b/ext/wasm/tester1.c-pp.html @@ -11,11 +11,19 @@

          -
          Variants: - conventional UI thread, - conventional worker, - ESM in UI thread, - ESM worker +
          Variants (32-bit): + conventional UI thread: + (32-bit, + 64-bit), + conventional worker: + (32-bit, + 64-bit), + ESM in UI thread: + (32-bit, + 64-bit), + ESM worker: + (32-bit + 64-bit)
          diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index f72e0803f..083b5eca4 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -153,6 +153,10 @@ globalThis.sqlite3InitModule = sqlite3InitModule; logClass('error',...args); }; + const debug = (...args)=>{ + console.debug('tester1',...args); + }; + const toss = (...args)=>{ error(...args); throw new Error(args.join(' ')); @@ -210,8 +214,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule; filter.test(error.message) passes. If it's a function, the test passes if filter(error) returns truthy. If it's a string, the test passes if the filter matches the exception message - precisely. In all other cases the test fails, throwing an - Error. + precisely. If filter is a number then it is compared against + the resultCode property of the exception. In all other cases + the test fails, throwing an Error. If it throws, msg is used as the error report unless it's falsy, in which case a default is used. @@ -225,7 +230,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule; if(filter instanceof RegExp) pass = filter.test(err.message); else if(filter instanceof Function) pass = filter(err); else if('string' === typeof filter) pass = (err.message === filter); + else if('number' === typeof filter) pass = (err.resultCode === filter); if(!pass){ + console.error("Filter",filter,"rejected exception",err); throw new Error(msg || ("Filter rejected this exception: <<"+err.message+">>")); } return this; @@ -369,6 +376,88 @@ globalThis.sqlite3InitModule = sqlite3InitModule; clearOnInit: true, initialCapacity: 6 }; + +//#if enable-see + /** + Code consolidator for SEE sanity checks for various VFSes. ctor + is the VFS's oo1.DB-type constructor. ctorOptFunc(bool) is a + function which must return a constructor args object for ctor. It + is passed true if the db needs to be cleaned up/unlinked before + opening it (OPFS) and false if not (how that is done is + VFS-dependent). dbUnlink is a function which is expected to + unlink() the db file if the ctorOpfFunc does not do so when + passed true (kvvfs). + + This function initializes the db described by ctorOptFunc(...), + writes some secret info into it, and re-opens it twice to + confirmi that it can be read with an SEE key and cannot be read + without one. + */ + T.seeBaseCheck = function(ctor, ctorOptFunc, dbUnlink){ + let initDb = true; + const tryKey = function(keyKey, key, expectCount){ + let db; + //console.debug('tryKey()',arguments); + try { + if (initDb) { + const ctoropt = ctorOptFunc(initDb); + initDb = false; + db = new ctor({ + ...ctoropt, + [keyKey]: key + }); + db.exec([ + "drop table if exists t;", + "create table t(a);" + ]); + db.close(); + db = null; + // Ensure that it's actually encrypted... + let err; + try { + db = new ctor(ctorOptFunc(false)); + T.assert(db, 'db opened') /* opening is fine, but... */; + db.exec("select 1 from sqlite_schema"); + console.warn("(should not be reached) sessionStorage =", sessionStorage); + } catch (e) { + err = e; + } finally { + db.close() + db = null; + } + T.assert(err, "Expecting an exception") + .assert(capi.SQLITE_NOTADB == err.resultCode, + "Expecting NOTADB"); + }/*initDb*/ + db = new ctor({ + ...ctorOptFunc(false), + [keyKey]: key + }); + db.exec("insert into t(a) values (1),(2)"); + T.assert(expectCount === db.selectValue('select sum(a) from t')); + } finally { + if (db) db.close(); + } + }; + const hexFoo = new Uint8Array([0x66,0x6f,0x6f]/*=="foo"*/); + dbUnlink(); + tryKey('textkey', 'foo', 3); + T.assert( !initDb ); + tryKey('textkey', 'foo', 6); + dbUnlink(); + initDb = true; + tryKey('key', 'foo', 3); + T.assert( !initDb ); + tryKey('key', hexFoo, 6); + dbUnlink(); + initDb = true; + tryKey('hexkey', hexFoo, 3); + T.assert( !initDb ); + tryKey('hexkey', hexFoo, 6); + dbUnlink(); + }, +//#endif enable-see + //////////////////////////////////////////////////////////////////////// // End of infrastructure setup. Now define the tests... //////////////////////////////////////////////////////////////////////// @@ -431,7 +520,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; assert(wasmCtypes.structs[1/*sqlite3_io_methods*/ ].members.xFileSize.offset>0); [ /* Spot-check a handful of constants to make sure they got installed... */ - 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8', + 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8','SQLITE_UTF8_ZT', 'SQLITE_STATIC', 'SQLITE_DIRECTONLY', 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE' ].forEach((k)=>T.assert('number' === typeof capi[k])); @@ -945,7 +1034,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let ptr = k1.pointer; k1.dispose(); T.assert(undefined === k1.pointer). - mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/); + mustThrowMatching(()=>{k1.$pP=1}, /disposed/); }finally{ k1.dispose(); k2.dispose(); @@ -1549,6 +1638,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule; T.assert(blob instanceof Uint8Array). assert(0x68===blob[0] && 0x69===blob[1]); blob = null; + blob = db.selectValue("select ?1", new Uint8Array([97,0,98,0,99]), + sqlite3.capi.SQLITE_TEXT); + T.assert("a\0b\0c"===blob, "Something is amiss with embedded NULs"); let counter = 0, colNames = []; list.length = 0; db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{ @@ -2543,7 +2635,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .assert(wasm.isPtr(tmplMod.$xRowid)) .assert(wasm.isPtr(tmplMod.$xCreate)) .assert(tmplMod.$xCreate === tmplMod.$xConnect, - "setup() must make these equivalent and "+ + "setupModule() must make these equivalent and "+ "installMethods() must avoid re-compiling identical functions"); tmplMod.$xCreate = wasm.ptr.null /* make tmplMod eponymous-only */; let rc = capi.sqlite3_create_module( @@ -2794,25 +2886,69 @@ globalThis.sqlite3InitModule = sqlite3InitModule; //////////////////////////////////////////////////////////////////////// T.g('kvvfs') .t({ - name: 'kvvfs is disabled in worker', - predicate: ()=>(isWorker() || "test is only valid in a Worker"), + name: 'kvvfs v1 API availability', test: function(sqlite3){ - T.assert( - !capi.sqlite3_vfs_find('kvvfs'), - "Expecting kvvfs to be unregistered." - ); + const capi = sqlite3.capi; + if( isUIThread() ){ + T.assert( !!capi.sqlite3_js_kvvfs_size ) + .assert( !!capi.sqlite3_js_kvvfs_clear ); + }else{ + /* Historical behaviour retained not for compatibility but + to help avoid some confusion between the v1 and v2 kvvfs + APIs (namely in how the v1 variants handle empty + strings). */ + T.assert( !capi.sqlite3_js_kvvfs_clear ) + .assert( !capi.sqlite3_js_kvvfs_size ); + } + const k = sqlite3.kvvfs; + T.assert( k && 'object'===typeof k ); + for(const n of ['reserve', 'import', 'export', + 'unlink', 'listen', 'unlisten', + 'exists', + 'estimateSize', 'clear'] ){ + T.assert( k[n] instanceof Function ); + } + + if( 0 ){ + const scope = wasm.scopedAllocPush(); + try{ + const pg = [ + "53514C69746520666F726D61742033b20b0101b402020d02d02l01d0", + "4l01jb02b2E91E00Dd011FD3b1FD3dxl2B010617171701377461626C", + "656B767666736B7676667302435245415445205441424C45206B76766673286129" + ].join(''); + const n = pg.length; + const pI = wasm.scopedAlloc( n+1 ); + const nO = 8192 * 2; + const pO = wasm.scopedAlloc( nO ); + const heap = wasm.heap8u(); + let i; + for( i=0; i(globalThis.sessionStorage || "sessionStorage is unavailable"), test: function(sqlite3){ - const filename = this.kvvfsDbFile = 'session'; + const JDb = sqlite3.oo1.JsStorageDb; const pVfs = capi.sqlite3_vfs_find('kvvfs'); T.assert(looksLikePtr(pVfs)); - const JDb = this.JDb = sqlite3.oo1.JsStorageDb; - const unlink = this.kvvfsUnlink = ()=>JDb.clearStorage(this.kvvfsDbFile); + let x = sqlite3.kvvfs.internal.storageForZClass('session'); + T.assert( 0 === x.files.length ) + .assert( globalThis.sessionStorage===x.storage ) + .assert( 'kvvfs-session-' === x.keyPrefix ); + const filename = this.kvvfsDbFile = 'session'; + const unlink = this.kvvfsUnlink = ()=>sqlite3.kvvfs.clear(filename); unlink(); let db = new JDb(filename); try { @@ -2820,87 +2956,533 @@ globalThis.sqlite3InitModule = sqlite3InitModule; 'create table kvvfs(a);', 'insert into kvvfs(a) values(1),(2),(3)' ]); - T.assert(3 === db.selectValue('select count(*) from kvvfs')); + T.assert(3 === db.selectValue('select count(*) from kvvfs')) + .assert( db.storageSize() > 0, "Db size counting is broken" ); db.close(); + db = undefined; db = new JDb(filename); db.exec('insert into kvvfs(a) values(4),(5),(6)'); T.assert(6 === db.selectValue('select count(*) from kvvfs')); }finally{ - db.close(); + if( db ) db.close(); } + //console.debug("sessionStorage",globalThis.sessionStorage); } }/*kvvfs sanity checks*/) -//#if enable-see .t({ - name: 'kvvfs with SEE encryption', - predicate: ()=>(isUIThread() - || "Only available in main thread."), + name: 'transient kvvfs', + //predicate: ()=>false, test: function(sqlite3){ - this.kvvfsUnlink(); - let initDb = true; - const tryKey = function(keyKey, key, expectCount){ - let db; - //console.debug('tryKey()',arguments); - const ctoropt = { - filename: this.kvvfsDbFile - //vfs: 'kvvfs' - //,flags: 'ct' + const filename = '.' /* preinstalled instance */; + const JDb = sqlite3.oo1.JsStorageDb; + const DB = sqlite3.oo1.DB; + T.mustThrowMatching(()=>new JDb(""), capi.SQLITE_MISUSE); + T.mustThrowMatching(()=>{ + new JDb("this\ns an illegal - contains control characters"); + /* We don't have a way to get error strings from xOpen() + to this point? xOpen() does not have a handle to the + db and SQLite is not calling xGetLastError() to fetch + the error string. */ + }, capi.SQLITE_RANGE); + T.mustThrowMatching(()=>{new JDb("foo-journal");}, + capi.SQLITE_MISUSE); + T.mustThrowMatching(()=>{new JDb("foo-wal");}, + capi.SQLITE_MISUSE); + T.mustThrowMatching(()=>{new JDb("foo-shm");}, + capi.SQLITE_MISUSE); + T.mustThrowMatching(()=>{ + new JDb("01234567890123456789"+ + "01234567890123456789"+ + "01234567890123456789"+ + "01234567890123456789"+ + "01234567890123456789"+ + "01234567890123456789"+ + "0"/*too long*/); + }, capi.SQLITE_RANGE); + { + const name = "01234567890123456789012" /* max name length */; + (new JDb(name)).close(); + T.assert( sqlite3.kvvfs.unlink(name) ); + } + + sqlite3.kvvfs.clear(filename); + let db = new JDb(filename); + const sqlSetup = [ + 'create table kvvfs(a);', + 'insert into kvvfs(a) values(1),(2),(3)' + ]; + try { + T.assert( 0===db.storageSize(), "expecting 0 storage size" ); + T.mustThrowMatching(()=>db.clearStorage(), /in-use/); + //db.clearStorage(); + T.assert( 0===db.storageSize(), "expecting 0 storage size" ); + db.exec(sqlSetup); + T.assert( 0db.clearStorage(), /in-use/); + //db.clearStorage(/*wiping everything out from under it*/); + T.assert( 0{ + db.close(); + db = undefined; }; - try { - if (initDb) { - initDb = false; - db = new this.JDb({ - ...ctoropt, - [keyKey]: key - }); - db.exec([ - "drop table if exists t;", - "create table t(a);" - ]); - db.close(); - // Ensure that it's actually encrypted... - let err; - try { - db = new this.JDb(ctoropt); - T.assert(db, 'db opened') /* opening is fine, but... */; - db.exec("select 1 from sqlite_schema"); - console.warn("(should not be reached) sessionStorage =", sessionStorage); - } catch (e) { - err = e; - } finally { - db.close() - } - T.assert(err, "Expecting an exception") - .assert(sqlite3.capi.SQLITE_NOTADB == err.resultCode, - "Expecting NOTADB"); - }/*initDb*/ - //console.debug('tryKey()',arguments); - db = new sqlite3.oo1.DB({ - ...ctoropt, - vfs: 'kvvfs', - [keyKey]: key + T.assert(3 === db.selectValue('select count(*) from kvvfs')); + close(); + + const exportDb = sqlite3.kvvfs.export; + db = new JDb(filename); + db.exec('insert into kvvfs(a) values(4),(5),(6)'); + T.assert(6 === db.selectValue('select count(*) from kvvfs')); + const exp = exportDb({name:filename,includeJournal:true}); + T.assert( filename===exp.name, "Broken export filename" ) + .assert( exp?.size > 0, "Missing db size" ) + .assert( exp?.pages?.length > 0, "Missing db pages" ); + console.debug("kvvfs to Object:",exp); + close(); + + const dbFileRaw = 'file:new-storage?vfs=kvvfs&delete-on-close=1'; + db = new DB({ + filename: dbFileRaw, + //flags: 'ct' + }); + db.exec(sqlSetup); + const dbFilename = db.dbFilename(); + //console.warn("db.dbFilename() =",dbFilename); + T.assert(3 === db.selectValue('select count(*) from kvvfs')); + debug("kvvfs to Object:",exportDb(dbFilename)); + const n = sqlite3.kvvfs.estimateSize( dbFilename ); + T.assert( n>0, "Db size count failed" ); + + if( 1 ){ + // Concurrent open of that same name uses the same storage + const x = new JDb(dbFilename); + T.assert(3 === db.selectValue('select count(*) from kvvfs')); + x.close(); + } + close(); + // When the final instance of a name is closed, the storage + // disappears... + T.mustThrowMatching(function(){ + /* Ensure that 'new-storage' was deleted when its refcount + went to 0, because of its 'transient' flag. By default + the objects are retained, like a filesystem would. */ + let ddb = new JDb(dbFilename); + try{ddb.selectValue('select a from kvvfs')} + finally{ddb.close()} + }, /no such table: kvvfs/); + }finally{ + if( db ) db.close(); + } + } + }/*transient kvvfs*/) + .t({ + name: 'concurrent transient kvvfs', + //predicate: ()=>false, + test: function(sqlite3){ + const filename = 'myStorage'; + const kvvfs = sqlite3.kvvfs; + const DB = sqlite3.oo1.DB; + const JDb = sqlite3.oo1.JsStorageDb; + let db; + let duo; + let q1, q2; + const sqlSetup = [ + 'create table kvvfs(a);', + 'insert into kvvfs(a) values(1),(2),(3)' + ]; + const sqlCount = 'select count(*) from kvvfs'; + + try { + const exportDb = sqlite3.kvvfs.export; + const dbFileRaw = 'file:'+filename+'?vfs=kvvfs&delete-on-close=1'; + sqlite3.kvvfs.clear(filename); + db = new DB(dbFileRaw); + db.exec(sqlSetup); + T.assert(3 === db.selectValue(sqlCount)); + + duo = new JDb(filename); + duo.exec('insert into kvvfs(a) values(4),(5),(6)'); + T.assert(6 === db.selectValue(sqlCount)); + const expOpt = { + name: filename, + decodePages: true + }; + let exp = exportDb(expOpt); + let expectRows = 6; + debug("exported db",exp); + db.close(); + T.assert(expectRows === duo.selectValue(sqlCount)); + duo.close(); + T.mustThrowMatching(function(){ + let ddb = new JDb(filename); + try{ddb.selectValue('select a from kvvfs')} + finally{ddb.close()} + }, /.*no such table: kvvfs.*/); + + T.assert( kvvfs.unlink(filename) ) + .assert( !kvvfs.exists(filename) ); + + const importDb = sqlite3.kvvfs.import; + duo = new JDb(dbFileRaw); + T.mustThrowMatching(()=>importDb(exp,true), /.*in use.*/); + duo.close(); + importDb(exp, true); + duo = new JDb(dbFileRaw); + T.assert(expectRows === duo.selectValue(sqlCount)); + let newCount; + try{ + duo.transaction(()=>{ + duo.exec("insert into kvvfs(a) values(7)"); + newCount = duo.selectValue(sqlCount); + T.assert(false, "rolling back"); }); - db.exec("insert into t(a) values (1),(2)"); - T.assert(expectCount === db.selectValue('select sum(a) from t')); - } finally { - if (db) db.close(); + }catch(e){/*ignored*/} + T.assert(7===newCount, "Unexpected row count before rollback") + .assert(expectRows === duo.selectValue(sqlCount), + "Unexpected row count after rollback"); + duo.close(); + + T.assert( kvvfs.unlink(filename) ) + .assert( !kvvfs.exists(filename) ); + + importDb(exp, true); + db = new JDb({ + filename, + flags: 'c' + /* BUG: without the 'c' flag, the db works until we try to + vacuum, at which point it fails with "read only db". */ + }); + duo = new JDb(filename); + T.assert(expectRows === duo.selectValue(sqlCount)); + const sqlIns1 = "insert into kvvfs(a) values(?)"; + q1 = db.prepare(sqlIns1); + q2 = duo.prepare(sqlIns1); + if( 0 ){ + q1.bind('from q1').stepFinalize(); + ++expectRows; + T.assert(expectRows === duo.selectValue(sqlCount), + "Unexpected record count."); + q2.bind('from q1').stepFinalize(); + ++expectRows; + }else{ + q1.bind('from q1'); + T.assert(capi.SQLITE_DONE===capi.sqlite3_step(q1), + "Unexpected step result"); + ++expectRows; + T.assert(expectRows === duo.selectValue(sqlCount), + "Unexpected record count."); + q2.bind('from q1').step(); + ++expectRows; + } + T.assert(expectRows === db.selectValue(sqlCount), + "Unexpected record count."); + q1.finalize(); + q2.finalize(); + + if( 1 ){ + debug("Begin vacuum/page size test..."); + const defaultPageSize = 1024 * 8 /* build-time default */; + const pageSize = 0 + ? defaultPageSize + : 1024 * 16 /* any valid value other than the default */; + if( 0 ){ + debug("Export before vacuum", exportDb(expOpt)); + debug("page size before vacuum", + db.selectArray( + "select page_size from pragma_page_size()" + )); + } + //kvvfs.log.xFileControl = true; + //kvvfs.log.xAccess = true; + db.exec([ + "BEGIN;", + "insert into kvvfs(a) values(randomblob(16000/*>pg size*/));", + "COMMIT;", + "delete from kvvfs where octet_length(a)>100;", + "pragma page_size="+pageSize+";", + "vacuum;", + "select 1;" + ]); + const expectPageSize = kvvfs.internal.disablePageSizeChange + ? defaultPageSize + : pageSize; + const gotPageSize = db.selectValue( + "select page_size from pragma_page_size()" + ); + T.assert(+gotPageSize === expectPageSize, + "Expecting page size",expectPageSize, + "got",gotPageSize); + T.assert(expectRows === duo.selectValue(sqlCount), + "Unexpected record count."); + kvvfs.log.xAccess = kvvfs.log.xFileControl = false; + T.assert(expectRows === duo.selectValue(sqlCount), + "Unexpected record count."); + exp = exportDb(expOpt); + debug("Exported page-expanded db",exp); + if( 0 ){ + debug("vacuumed export",exp); + } + debug("End vacuum/page size test."); + }else{ + expectRows = 6; } - }.bind(this); - const hexFoo = new Uint8Array([0x66,0x6f,0x6f]/*=="foo"*/); - tryKey('textkey', 'foo', 3); - T.assert( !initDb ); - tryKey('textkey', 'foo', 6); - this.kvvfsUnlink(); - initDb = true; - tryKey('key', 'foo', 3); - T.assert( !initDb ); - tryKey('key', hexFoo, 6); - this.kvvfsUnlink(); - initDb = true; - tryKey('hexkey', hexFoo, 3); - T.assert( !initDb ); - tryKey('hexkey', hexFoo, 6); - this.kvvfsUnlink(); + + db.close(); + duo.close(); + T.assert( kvvfs.unlink(exp.name) ) + .assert( !kvvfs.exists(exp.name) ); + importDb(exp); + T.assert( kvvfs.exists(exp.name) ); + db = new JDb(exp.name); + //debug("column count after export",db.selectValue(sqlCount)); + T.assert(expectRows === db.selectValue(sqlCount), + "Unexpected record count."); + + /* + TODO: more advanced concurrent use tests, e.g. looping + over a query in one connection while writing from + another. Currently that will probably corrupt the db, and + kvvfs's journaling does not support multiple journals per + storage unit. We need to test the locking and fix it as + appropriate. + */ + }finally{ + q1?.finalize?.(); + q2?.finalize?.(); + db?.close?.(); + duo?.close?.(); + } + } + }/*concurrent transient kvvfs*/) + + .t({ + name: 'kvvfs listeners (experiment)', + test: function(sqlite3){ + const kvvfs = sqlite3.kvvfs; + const filename = 'listen'; + let db; + try { + const DB = sqlite3.oo1.DB; + const sqlSetup = [ + 'create table kvvfs(a);', + 'insert into kvvfs(a) values(1),(2),(3)' + ]; + const sqlCount = "select count(*) from kvvfs"; + const sqlSelectSchema = "select * from sqlite_schema"; + const counts = Object.create(null); + const incr = (key)=>counts[key] = 1 + (counts[key] ?? 0); + const pglog = Object.assign(Object.create(null),{ + pages: [], + jrnl: undefined, + size: undefined, + includeJournal: false, + decodePages: true, + exception: new Error("Testing that exceptions from listeners do not interfere") + }); + const toss = ()=>{ + if( pglog.exception ){ + const e = pglog.exception; + delete pglog.exception; + throw e; + } + }; + + const listener = { + storage: filename, + reserve: true, + includeJournal: pglog.includeJournal, + decodePages: pglog.decodePages, + events: { + /** + These may be async but must not be in this case + because we can't test their result without a lot of + hoop-jumping if they are. Kvvfs calls these + asynchronously, though. + */ + 'open': (ev)=>{ + //console.warn('open',ev); + incr(ev.type); + T.assert(filename===ev.storageName) + .assert('number'===typeof ev.data); + }, + 'close': (ev)=>{ + //console.warn('close',ev); + incr(ev.type); + T.assert('number'===typeof ev.data); + toss(); + }, + 'delete': (ev)=>{ + //console.warn('delete',ev); + incr(ev.type); + T.assert('string'===typeof ev.data); + switch(ev.data){ + case 'jrnl': + T.assert(pglog.includeJournal); + pglog.jrnl = null; + break; + default:{ + const n = +ev.data; + T.assert( n>0, "Expecting positive db page number" ); + if( n < pglog.pages.length ){ + pglog.size = undefined; + } + pglog.pages[n] = undefined; + break; + } + } + }, + 'sync': (ev)=>{ + incr(ev.data ? 'xSync' : 'xFileControlSync'); + }, + 'write': (ev)=>{ + //console.warn('write',ev); + incr(ev.type); + T.assert(Array.isArray(ev.data)); + const key = ev.data[0], val = ev.data[1]; + T.assert('string'===typeof key); + switch( key ){ + case 'jrnl': + T.assert(pglog.includeJournal); + pglog.jrnl = val; + break; + case 'sz':{ + const sz = +val; + T.assert( sz>0, "Expecting a db page number" ); + if( sz < pglog.sz ){ + pglog.pages.length = sz / pglog.pages.length; + } + pglog.size = sz; + break; + } + default: + T.assert( +key>0, "Expecting a positive db page number" ); + pglog.pages[+key] = val; + if( pglog.decodePages ){ + T.assert( val instanceof Uint8Array ); + }else{ + T.assert( 'string'===typeof val ); + } + break; + } + } + } + }; + + kvvfs.listen(listener); + const dbFileRaw = 'file:'+filename+'?vfs=kvvfs&delete-on-close=1'; + const expOpt = { + name: filename, + //decodePages: true + }; + db = new DB(dbFileRaw); + db.exec(sqlSetup); + T.assert(db.selectObjects(sqlSelectSchema)?.length>0, + "Unexpected empty schema"); + db.close(); + debug("kvvfs listener counts:",counts); + T.assert( counts.open ); + T.assert( counts.close ); + T.assert( listener.includeJournal ? counts.delete : !counts.delete ); + T.assert( counts.write ); + T.assert( counts.xSync ); + T.assert( counts.xFileControlSync>=counts.xSync ); + T.assert( counts.open===counts.close ); + T.assert( pglog.includeJournal + ? (null===pglog.jrnl) + : (undefined===pglog.jrnl), + "Unexpected pglog.jrnl value: "+pglog.jrnl ); + if( 1 ){ + T.assert(undefined===pglog.pages[0], "Expecting empty slot 0"); + pglog.pages.shift(); + //debug("kvvfs listener pageLog", pglog); + } + const before = JSON.stringify(counts); + T.assert( kvvfs.unlisten(listener) ); + T.assert( !kvvfs.unlisten(listener) ); + db = new DB(dbFileRaw); + T.assert( db.selectObjects(sqlSelectSchema)?.length>0 ); + const exp = kvvfs.export(expOpt); + const expectRows = db.selectValue(sqlCount); + db.exec("delete from kvvfs"); + db.close(); + const after = JSON.stringify(counts); + T.assert( before===after, "Expecting no events after unlistening." ); + if( 0 ){ + exp = kvvfs.export(expOpt); + debug("Post-delete export:",exp); + } + if( 1 ){ + // Replace the storage with the pglog state... + const bogoExp = { + name: filename, + size: pglog.size, + timestamp: Date.now(), + pages: pglog.pages + }; + //debug("exp",exp); + //debug("bogoExp",bogoExp); + kvvfs.import(bogoExp, true); + //debug("Re-exported", kvvfs.export(expOpt)); + db = new DB(dbFileRaw); + // Failing on the next line despite exports looking good + T.assert(db.selectObjects(sqlSelectSchema)?.length>0, + "Empty schema on imported db"); + T.assert(expectRows===db.selectValue(sqlCount)); + db.close(); + } + }finally{ + db?.close?.(); + kvvfs.unlink(filename); + } + } + })/*kvvfs listeners */ + + .t({ + name: 'kvvfs vtab', + predicate: (sqlite3)=>!!sqlite3.kvvfs.create_module, + test: function(sqlite3){ + const kvvfs = sqlite3.kvvfs; + const db = new sqlite3.oo1.DB(); + const db2 = new sqlite3.oo1.DB('file:foo?vfs=kvvfs&delete-on-close=1'); + try{ + kvvfs.create_module(db); + let rc = db.selectObjects("select * from sqlite_kvvfs order by name"); + debug("sqlite_kvvfs vtab:", rc); + const nDb = rc.length; + rc = db.selectObject("select * from sqlite_kvvfs where name='foo'"); + T.assert(rc, "Expecting foo storage record") + .assert('foo'===rc.name, "Unexpected name") + .assert(1===rc.nRef, "Unexpected refcount"); + db2.close(); + rc = db.selectObjects("select * from sqlite_kvvfs"); + T.assert( !rc.filter((o)=>o.name==='foo').length, + "Expecting foo storage to be gone"); + debug("sqlite_kvvfs vtab:", rc); + T.assert( rc.length+1 === nDb, + "Unexpected storage count: got",rc.length,"expected",nDb); + }finally{ + db.close(); + db2.close(); + } + } + })/* kvvfs vtab */ + +//#if enable-see + .t({ + name: 'kvvfs SEE encryption in sessionStorage', + predicate: ()=>(!!globalThis.sessionStorage + || "sessionStorage is not available"), + test: function(sqlite3){ + const JDb = sqlite3.oo1.JsStorageDb; + T.seeBaseCheck(JDb, + (isInit)=>return {filename: "session"}, + ()=>JDb.clearStorage('session')); } })/*kvvfs with SEE*/ //#endif enable-see @@ -2915,7 +3497,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let countCommit = 0, countRollback = 0;; const db = new sqlite3.oo1.DB(':memory:',1 ? 'c' : 'ct'); let rc = capi.sqlite3_commit_hook(db, (p)=>{ - //console.debug("commit hook",arguments); + //debug("commit hook",arguments); ++countCommit; return (17 == p) ? 0 : capi.SQLITE_ERROR; }, 17); @@ -3315,71 +3897,14 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .t({ name: 'OPFS with SEE encryption', test: function(sqlite3){ - const dbFile = 'file:///sqlite3-see.edb'; - const dbCtor = sqlite3.oo1.OpfsDb; - const hexFoo = new Uint8Array([0x66,0x6f,0x6f]/*=="foo"*/); - let initDb = true; - const tryKey = function(keyKey, key, expectCount){ - let db; - //console.debug('tryKey()',arguments); - const ctoropt = { - filename: dbFile, - flags: 'c' - }; - try { - if (initDb) { - initDb = false; - const opt = { - ...ctoropt, - [keyKey]: key - }; - opt.filename += '?delete-before-open=1'; - db = new dbCtor(opt); - db.exec([ - "drop table if exists t;", - "create table t(a);" - ]); - db.close(); - // Ensure that it's actually encrypted... - let err; - try { - db = new dbCtor(ctoropt); - T.assert(db, 'db opened') /* opening is fine, but... */; - const rv = db.exec({ - sql:"select count(*) from sqlite_schema", - returnValue: 'resultRows' - }); - console.warn("(should not be reached) rv =",rv); - } catch (e) { - err = e; - } finally { - db.close() - } - T.assert(err, "Expecting an exception") - .assert(sqlite3.capi.SQLITE_NOTADB == err.resultCode, - "Expecting NOTADB"); - }/*initDb*/ - db = new dbCtor({ - ...ctoropt, - [keyKey]: key - }); - db.exec("insert into t(a) values (1),(2)"); - T.assert(expectCount === db.selectValue('select sum(a) from t')); - } finally { - if (db) db.close(); - } - }; - tryKey('textkey', 'foo', 3); - T.assert( !initDb ); - tryKey('textkey', 'foo', 6); - initDb = true; - tryKey('key', 'foo', 3); - T.assert( !initDb ); - tryKey('key', hexFoo, 6); - initDb = true; - tryKey('hexkey', hexFoo, 3); - T.assert( !initDb ); - tryKey('hexkey', hexFoo, 6); + T.seeBaseCheck( + sqlite3.oo1.OpfsDb, + function(isInit){ + const opt = {filename: 'file:///sqlite3-see.edb'}; + if( isInit ) opt.filename += '?delete-before-open=1'; + return opt; + }, + ()=>{}); } })/*OPFS with SEE*/ //#endif enable-see @@ -3584,69 +4109,11 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let poolUtil; const P1 = await inst(poolConfig).then(u=>poolUtil = u).catch(catcher); const dbFile = '/sqlite3-see.edb'; - const dbCtor = poolUtil.OpfsSAHPoolDb; - const hexFoo = new Uint8Array([0x66,0x6f,0x6f]/*=="foo"*/); - let initDb = true; - const tryKey = function(keyKey, key, expectCount){ - let db; - //console.debug('tryKey()',arguments); - const ctoropt = { - filename: dbFile, - flags: 'c' - }; - try { - if (initDb) { - initDb = false; - poolUtil.unlink(dbFile); - db = new dbCtor({ - ...ctoropt, - [keyKey]: key - }); - db.exec([ - "drop table if exists t;", - "create table t(a);" - ]); - db.close(); - // Ensure that it's actually encrypted... - let err; - try { - db = new dbCtor(ctoropt); - T.assert(db, 'db opened') /* opening is fine, but... */; - const rv = db.exec({ - sql:"select count(*) from sqlite_schema", - returnValue: 'resultRows' - }); - console.warn("(should not be reached) rv =",rv); - } catch (e) { - err = e; - } finally { - db.close() - } - T.assert(err, "Expecting an exception") - .assert(sqlite3.capi.SQLITE_NOTADB == err.resultCode, - "Expecting NOTADB"); - }/*initDb*/ - db = new dbCtor({ - ...ctoropt, - [keyKey]: key - }); - db.exec("insert into t(a) values (1),(2)"); - T.assert(expectCount === db.selectValue('select sum(a) from t')); - } finally { - if (db) db.close(); - } - }; - tryKey('textkey', 'foo', 3); - T.assert( !initDb ); - tryKey('textkey', 'foo', 6); - initDb = true; - tryKey('key', 'foo', 3); - T.assert( !initDb ); - tryKey('key', hexFoo, 6); - initDb = true; - tryKey('hexkey', hexFoo, 3); - T.assert( !initDb ); - tryKey('hexkey', hexFoo, 6); + T.seeBaseCheck( + poolUtil.OpfsSAHPoolDb, + (isInit)=>{return {filename: dbFile}}, + ()=>poolUtil.unlink(dbFile) + ); poolUtil.removeVfs(); } })/*opfs-sahpool with SEE*/ @@ -3715,44 +4182,54 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .t("Misc. stmt_...", function(sqlite3){ const db = new sqlite3.oo1.DB(); db.exec("create table t(a doggiebiscuits); insert into t(a) values(123)"); - const stmt = db.prepare("select a, a+1 from t"); - T.assert( stmt.isReadOnly() ) - .assert( 0===capi.sqlite3_stmt_isexplain(stmt) ) - .assert( 0===capi.sqlite3_stmt_explain(stmt, 1) ) - .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) ) - .assert( 0===capi.sqlite3_stmt_explain(stmt, 2) ) - .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) ) - .assert( 0===capi.sqlite3_stmt_explain(stmt, 0) ) - .assert( 0===capi.sqlite3_stmt_isexplain(stmt) ); - let n = 0; - while( capi.SQLITE_ROW === capi.sqlite3_step(stmt) ){ - ++n; - T.assert( 0!==capi.sqlite3_stmt_explain(stmt, 1), - "Because stmt is busy" ) - .assert( capi.sqlite3_stmt_busy(stmt) ) - .assert( stmt.isBusy() ) - .assert( 0!==capi.sqlite3_stmt_readonly(stmt) ) - .assert( true===stmt.isReadOnly() ); - const sv = capi.sqlite3_column_value(stmt, 0); - T.assert( 123===capi.sqlite3_value_int(sv) ) - .assert( "doggiebiscuits"===capi.sqlite3_column_decltype(stmt,0) ) - .assert( null===capi.sqlite3_column_decltype(stmt,1) ); - } - T.assert( 1===n ) - .assert( 0===capi.sqlite3_stmt_busy(stmt) ) - .assert( !stmt.isBusy() ); - - if( wasm.exports.sqlite3_column_origin_name ){ - log("Column metadata APIs enabled"); - T.assert( "t" === capi.sqlite3_column_table_name(stmt, 0)) - .assert("a" === capi.sqlite3_column_origin_name(stmt, 0)) - .assert("main" === capi.sqlite3_column_database_name(stmt, 0)) - }else{ - log("Column metadata APIs not enabled"); - } // column metadata APIs - - stmt.finalize(); - db.close(); + let stmt; + try{ + stmt = db.prepare("select a, a+1 from t"); + T.assert( stmt.isReadOnly() ) + .assert( 0===capi.sqlite3_stmt_isexplain(stmt) ) + .assert( 0===capi.sqlite3_stmt_explain(stmt, 1) ) + .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) ) + .assert( 0===capi.sqlite3_stmt_explain(stmt, 2) ) + .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) ) + .assert( 0===capi.sqlite3_stmt_explain(stmt, 0) ) + .assert( 0===capi.sqlite3_stmt_isexplain(stmt) ); + let n = 0; + while( capi.SQLITE_ROW === capi.sqlite3_step(stmt) ){ + ++n; + T.assert( 0!==capi.sqlite3_stmt_explain(stmt, 1), + "Because stmt is busy" ) + .assert( capi.sqlite3_stmt_busy(stmt) ) + .assert( stmt.isBusy() ) + .assert( 0!==capi.sqlite3_stmt_readonly(stmt) ) + .assert( true===stmt.isReadOnly() ); + const sv = capi.sqlite3_column_value(stmt, 0); + T.assert( 123===capi.sqlite3_value_int(sv) ) + .assert( "doggiebiscuits"===capi.sqlite3_column_decltype(stmt,0) ) + .assert( null===capi.sqlite3_column_decltype(stmt,1) ); + } + T.assert( 1===n ) + .assert( 0===capi.sqlite3_stmt_busy(stmt) ) + .assert( !stmt.isBusy() ); + + if( wasm.exports.sqlite3_column_origin_name ){ + log("Column metadata APIs enabled"); + T.assert( "t" === capi.sqlite3_column_table_name(stmt, 0)) + .assert("a" === capi.sqlite3_column_origin_name(stmt, 0)) + .assert("main" === capi.sqlite3_column_database_name(stmt, 0)) + }else{ + log("Column metadata APIs not enabled"); + } // column metadata APIs + stmt.finalize(); + stmt = null; + stmt = db.prepare("select ?1").bind(new Uint8Array([97,0,98,0,99])); + stmt.step(); + const sv = capi.sqlite3_column_value(stmt,0); + T.assert("a\0b\0c"===capi.sqlite3_value_text(sv), + "Expecting NULs to have survived."); + }finally{ + if(stmt) stmt.finalize(); + db.close(); + } }) //////////////////////////////////////////////////////////////////// diff --git a/main.mk b/main.mk index ac0fdb513..46d646d03 100644 --- a/main.mk +++ b/main.mk @@ -652,7 +652,7 @@ SRC = \ $(TOP)/src/sqliteInt.h \ $(TOP)/src/sqliteLimit.h \ $(TOP)/src/table.c \ - $(TOP)/src/tclsqlite.c \ + tclsqlite-ex.c \ $(TOP)/src/threads.c \ $(TOP)/src/tokenize.c \ $(TOP)/src/treeview.c \ @@ -952,25 +952,6 @@ FUZZDATA = \ # TESTOPTS = --verbose=file --output=test-out.txt -# -# Extra compiler options for various shell tools -# -# Note that some of these will only apply when embedding sqlite3.c -# into the shell, as these flags are not otherwise passed on to the -# library. -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 FUZZERSHELL_OPT = FUZZCHECK_OPT += -I$(TOP)/test FUZZCHECK_OPT += -I$(TOP)/ext/recover @@ -1428,15 +1409,15 @@ whereexpr.o: $(TOP)/src/whereexpr.c $(DEPS_OBJ_COMMON) window.o: $(TOP)/src/window.c $(DEPS_OBJ_COMMON) $(T.cc.sqlite) -c $(TOP)/src/window.c -tclsqlite.o: $(T.tcl.env.sh) $(TOP)/src/tclsqlite.c $(DEPS_OBJ_COMMON) +tclsqlite.o: $(T.tcl.env.sh) tclsqlite-ex.c $(DEPS_OBJ_COMMON) $(T.compile.tcl) -DUSE_TCL_STUBS=1 $$TCL_INCLUDE_SPEC \ - -c $(TOP)/src/tclsqlite.c + -c tclsqlite-ex.c -o tclsqlite.o -tclsqlite-shell.o: $(T.tcl.env.sh) $(TOP)/src/tclsqlite.c $(DEPS_OBJ_COMMON) - $(T.compile.tcl) -DTCLSH -o $@ -c $(TOP)/src/tclsqlite.c $$TCL_INCLUDE_SPEC +tclsqlite-shell.o: $(T.tcl.env.sh) tclsqlite-ex.c $(DEPS_OBJ_COMMON) + $(T.compile.tcl) -DTCLSH -o $@ -c tclsqlite-ex.c $$TCL_INCLUDE_SPEC -tclsqlite-stubs.o: $(T.tcl.env.sh) $(TOP)/src/tclsqlite.c $(DEPS_OBJ_COMMON) - $(T.compile.tcl) -DUSE_TCL_STUBS=1 -o $@ -c $(TOP)/src/tclsqlite.c $$TCL_INCLUDE_SPEC +tclsqlite-stubs.o: $(T.tcl.env.sh) tclsqlite-ex.c $(DEPS_OBJ_COMMON) + $(T.compile.tcl) -DUSE_TCL_STUBS=1 -o $@ -c tclsqlite-ex.c $$TCL_INCLUDE_SPEC # # STATIC_TCLSQLITE3 = 1 to statically link tclsqlite3, else @@ -1680,11 +1661,19 @@ install-tcl-0 install-tcl-: install-tcl: install-tcl-$(HAVE_TCL) install: install-tcl -tclsqlite3.c: sqlite3.c +TCLSQLITEEX = \ + $(TOP)/ext/qrf/qrf.h \ + $(TOP)/ext/qrf/qrf.c \ + $(TOP)/src/tclsqlite.c + +tclsqlite-ex.c: $(TCLSQLITEEX) $(TOP)/tool/mkcombo.tcl $(B.tclsh) + $(B.tclsh) $(TOP)/tool/mkcombo.tcl $(TCLSQLITEEX) -o $@ + +tclsqlite3.c: sqlite3.c tclsqlite-ex.c echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c cat sqlite3.c >>tclsqlite3.c echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c - cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c + cat tclsqlite-ex.c >>tclsqlite3.c # # $(CFLAGS.tclextension) = CFLAGS for the tclextension* targets. @@ -1801,13 +1790,13 @@ TESTFIXTURE_FLAGS += -DSQLITE_STRICT_SUBTYPE=1 TESTFIXTURE_SRC0 = $(TESTSRC2) $(libsqlite3.LIB) TESTFIXTURE_SRC1 = sqlite3.c -TESTFIXTURE_SRC = $(TESTSRC) $(TOP)/src/tclsqlite.c +TESTFIXTURE_SRC = $(TESTSRC) tclsqlite-ex.c TESTFIXTURE_SRC += $(TESTFIXTURE_SRC$(USE_AMALGAMATION)) testfixture$(T.exe): $(T.tcl.env.sh) has_tclsh85 $(TESTFIXTURE_SRC) $(T.link.tcl) -DSQLITE_NO_SYNC=1 $(TESTFIXTURE_FLAGS) \ -o $@ $(TESTFIXTURE_SRC) \ - $$TCL_LIB_SPEC $$TCL_INCLUDE_SPEC \ + $$TCL_LIB_SPEC $$TCL_INCLUDE_SPEC $$TCL_LIBS \ $(LDFLAGS.libsqlite3) coretestprogs: testfixture$(B.exe) sqlite3$(B.exe) @@ -1874,6 +1863,15 @@ mdevtest: srctree-check has_tclsh85 sdevtest: has_tclsh85 $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest $(TSTRNNR_OPTS) +retest: has_tclsh85 + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl retest + +errors: + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl errors + +status: + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl status -d 2 + # Like releasetest, except it omits srctree-check and verify-source so # that it can be used on a modified source tree. # @@ -1931,7 +1929,7 @@ shelltest: # sqlite3_analyzer.c.flags.0 = -DINCLUDE_SQLITE3_C=1 sqlite3_analyzer.c.flags.1 = -sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl \ +sqlite3_analyzer.c: sqlite3.c tclsqlite-ex.c $(TOP)/tool/spaceanal.tcl \ $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in $(B.tclsh) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in \ $(sqlite3_analyzer.c.flags.$(LINK_TOOLS_DYNAMICALLY)) \ @@ -1955,7 +1953,7 @@ sqlite3_analyzer$(T.exe): $(T.tcl.env.sh) sqlite3_analyzer.c \ # can cause the $@ to link to an out-of-tree libsqlite3.so, which may # or may not fail or otherwise cause confusion. -sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl \ +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 $(B.tclsh) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c @@ -1973,24 +1971,6 @@ sqlite3_expert$(T.exe): $(TOP)/ext/expert/sqlite3expert.h $(TOP)/ext/expert/sqli $(TOP)/ext/expert/expert.c sqlite3.c -o sqlite3_expert $(LDFLAGS.libsqlite3) xbin: sqlite3_expert$(T.exe) -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) - $(B.tclsh) $(TOP)/tool/mkccode.tcl $(TOP)/ext/repair/sqlite3_checker.c.in >$@ - -sqlite3_checker$(T.exe): $(T.tcl.env.sh) sqlite3_checker.c - $(T.link.tcl) sqlite3_checker.c -o $@ $$TCL_INCLUDE_SPEC \ - $$TCL_LIB_SPEC $(LDFLAGS.libsqlite3) -xbin: sqlite3_checker$(T.exe) - dbdump$(T.exe): $(TOP)/ext/misc/dbdump.c sqlite3.o $(T.link) -DDBDUMP_STANDALONE -o $@ \ $(TOP)/ext/misc/dbdump.c sqlite3.o $(LDFLAGS.libsqlite3) @@ -2020,6 +2000,10 @@ showshm$(T.exe): $(TOP)/tool/showshm.c $(T.link) -o $@ $(TOP)/tool/showshm.c $(LDFLAGS.configure) xbin: showshm$(T.exe) +showtmlog$(T.exe): $(TOP)/tool/showtmlog.c + $(T.link) -o $@ $(TOP)/tool/showtmlog.c $(LDFLAGS.configure) +xbin: showshm$(T.exe) + index_usage$(T.exe): $(TOP)/tool/index_usage.c sqlite3.o $(T.link) $(SHELL_OPT) -o $@ $(TOP)/tool/index_usage.c sqlite3.o \ $(LDFLAGS.libsqlite3) @@ -2118,16 +2102,19 @@ src-archives: sqlite-amalgamation.zip amalgamation-tarball sqlite-src.zip # Build a ZIP archive containing various command-line tools. # -tool-zip: testfixture$(T.exe) sqlite3$(T.exe) sqldiff$(T.exe) \ +tool-zip: sqlite3$(T.exe) sqldiff$(T.exe) \ sqlite3_analyzer$(T.exe) sqlite3_rsync$(T.exe) $(TOP)/tool/mktoolzip.tcl strip sqlite3$(T.exe) sqldiff$(T.exe) sqlite3_analyzer$(T.exe) sqlite3_rsync$(T.exe) - ./testfixture$(T.exe) $(TOP)/tool/mktoolzip.tcl + $(TCLSH_CMD) $(TOP)/tool/mktoolzip.tcl + snapshot-zip: testfixture$(T.exe) sqlite3$(T.exe) sqldiff$(T.exe) \ sqlite3_analyzer$(T.exe) sqlite3_rsync$(T.exe) $(TOP)/tool/mktoolzip.tcl strip sqlite3$(T.exe) sqldiff$(T.exe) sqlite3_analyzer$(T.exe) sqlite3_rsync$(T.exe) - ./testfixture$(T.exe) $(TOP)/tool/mktoolzip.tcl --snapshot + $(TCLSH_CMD) $(TOP)/tool/mktoolzip.tcl --snapshot + clean-tool-zip: rm -f sqlite-tools-*.zip + clean: clean-tool-zip # @@ -2352,6 +2339,8 @@ mptest: mptester$(T.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 \ diff --git a/make.bat b/make.bat new file mode 100644 index 000000000..2dc9b61c0 --- /dev/null +++ b/make.bat @@ -0,0 +1,2 @@ +@echo off +nmake /f Makefile.msc %* diff --git a/manifest b/manifest index 7be9b18d3..079aacfd7 100644 --- a/manifest +++ b/manifest @@ -1,14 +1,14 @@ -C Version\s3.51.2 -D 2026-01-09T17:27:48.405 +C Version\s3.52.0 +D 2026-03-06T16:01:44.367 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md 6bc480fc673fb4acbc4094e77edb326267dd460162d7723c7f30bee2d3d9e97d F Makefile.in 3ce07126d7e87c7464301482e161fdae6a51d0a2aa06b200b8f0000ef4d6163b F Makefile.linux-generic bd3e3cacd369821a6241d4ea1967395c962dfe3057e38cb0a435cee0e8b789d0 -F Makefile.msc d4459fad28b388063698cbb7a73bfce8684da998a844a04b21d4b9b10291196a -F README.md dae499194b75deed76a13a4a83c82493f2530331882d7dfe5754d63287d3f8f7 -F VERSION 53fb08d314af314f884da9b33cabad229928aac28b53984a2c38fd4d7dc608ab +F Makefile.msc 174764cb7e80c80f9003c46b3e388d74c68c8c40230208904b3af8fcabee5f4e +F README.md 3fa51fc7ababc32edd175ae8b2986c86d5ea120c1cb1e57c7f7849492d1405ec +F VERSION 74672bfd4c7826c0fc6f84762488a707c52e7d2d94af42ccb0edcc6c74311c41 F art/icon-243x273.gif 9750b734f82fdb3dc43127753d5e6fbf3b62c9f4e136c2fbf573b2f57ea87af5 F art/icon-80x90.gif 65509ce3e5f86a9cd64fe7fca2d23954199f31fe44c1e09e208c80fb83d87031 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 @@ -17,8 +17,8 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F art/sqlite370.svg 40b7e2fe8aac3add5d56dd86ab8d427a4eca5bcb3fe4f8946cb3794e1821d531 F auto.def 44a0d1bf09d78355fc88251ccbf8e64e6341fd89c11de68a01c3645e53a2bade F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.in 118aa2c4d49173672d065fdda19eb8a28642e2c684212d7a626d6db5e6762521 -F autoconf/Makefile.msc 9c1ca648062fd5a4a83ba7590c4422090cccd6399002af0346b7572f086c7483 +F autoconf/Makefile.in efab1f56c7961d301efc030baa4391b9faae38733d94385a3d56b54720a74aba +F autoconf/Makefile.msc e6c596e6e63ea17aa7a97eb8ded18cd984c8dd4203766644fbd57b8fcf720225 F autoconf/README.first f1d3876e9a7852c22f275a6f06814e64934cecbc0b5b9617d64849094c1fd136 F autoconf/README.txt b749816b8452b3af994dc6d607394bef3df1736d7e09359f1087de8439a52807 F autoconf/auto.def 3d994f3a9cc9b712dbce92a5708570ddcf3b988141b6eb738f2ed16127a9f0ac @@ -33,7 +33,7 @@ F autoconf/tea/teaish.tcl 81feb417e718ed75cdd7e2fdf6771f3da80dae97377a90c4d5b62b F autoconf/tea/teaish.test.tcl cfe94e1fb79dd078f650295be59843d470125e0cc3a17a1414c1fb8d77f4aea6 F autosetup/LICENSE 41a26aebdd2cd185d1e2b210f71b7ce234496979f6b35aef2cbf6b80cbed4ce4 F autosetup/README.autosetup a78ff8c4a3d2636a4268736672a74bf14a82f42687fcf0631a70c516075c031e -F autosetup/README.md ce0f95980a687bb861bd830b76bc4b48513567be5cf5ee7004f4f3439ffe3841 +F autosetup/README.md e8503618f6cb31b692383ca662f6eb9ef4345463f5e3c1fc84a5d6863c7058be F autosetup/autosetup b16e44924c197783df67366762dda985b45d49ebc4af15f4054e3ee0e3b65169 x F autosetup/autosetup-config.guess dfa101c5e8220e864d5e9c72a85e87110df60260d36cb951ad0a85d6d9eaa463 x F autosetup/autosetup-config.sub a38fb074d0dece01cf919e9fb534a26011608aa8fa606490864295328526cd73 x @@ -44,10 +44,10 @@ F autosetup/cc-lib.tcl 493c5935b5dd3bf9bd4eca89b07c8b1b1a9356d61783035144e21795f F autosetup/cc-shared.tcl 163eda58c14cd662fd8a504bd2ad8a716ef4db7015dc1de0095d5de8dd601a4b F autosetup/cc.tcl c0fcc50ca91deff8741e449ddad05bcd08268bc31177e613a6343bbd1fd3e45f F autosetup/find_tclconfig.tcl e64886ffe3b982d4df42cd28ed91fe0b5940c2c5785e126c1821baf61bc86a7e -F autosetup/jimsh0.c a57c16e65dcffc9c76e496757cb3f7fb47e01ecbd1631a0a5e01751fc856f049 +F autosetup/jimsh0.c 916bbdf8023fbda9937afae57d81a853d8c2ea00f2320aa27becbc33574f963d F autosetup/pkg-config.tcl 4e635bf39022ff65e0d5434339dd41503ea48fc53822c9c5bde88b02d3d952ba -F autosetup/proj.tcl 6fc14ef82b19b77a95788ffbcfad7989b4e3cb4ce96a21dcb5cf7312f362fba9 -F autosetup/sqlite-config.tcl 5d779fce20c11fde3fe99d157dcd5b5569d729b301141b8dfb8d5aacf9d48cba +F autosetup/proj.tcl ce301197f364f7ce2acabbbd84b43d19e917ec73653157ca134a06f32d322712 +F autosetup/sqlite-config.tcl 7463d59c9c5e86ca286ea16fdab943058beb9346110049eca435154795890f71 F autosetup/system.tcl 51d4be76cd9a9074704b584e5c9cbba616202c8468cf9ba8a4f8294a7ab1dba9 F autosetup/teaish/README.txt b40071e6f8506500a2f7f71d5fc69e0bf87b9d7678dd9da1e5b4d0acbf40b1ca F autosetup/teaish/core.tcl e014dd95900c7f9a34e8e0f460f47e94841059827bce8b4c49668b0c7ae3f1a0 @@ -56,14 +56,14 @@ F autosetup/teaish/tester.tcl 1799514c2652db49561b3386c5242b94534d1663f2cfac861a F configure 9a00b21dfd13757bbfb8d89b30660a89ec1f8f3a79402b8f9f9b6fc475c3303a x F contrib/sqlitecon.tcl eb4c6578e08dd353263958da0dc620f8400b869a50d06e271ab0be85a51a08d3 F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd -F doc/compile-for-unix.md c9dce1ddd4bf0d25efccc5c63eb047e78c01ce06a6ff29c73e0a8af4a0f4adbc -F doc/compile-for-windows.md f9e74d74da88f384edd5809f825035e071608f00f7f39c0e448df7b3982f979c +F doc/compile-for-unix.md c8f05bf9ff8c588c501515eb11642540572203e53d0b5eb5bf60983acdd47643 +F doc/compile-for-windows.md 36601c95fa4070eebfe757684271d17a7c4a586912ba706d0b5e7817e1df54ad F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f F doc/jsonb.md acd77fc3a709f51242655ad7803510c886aa8304202fa9cf2abc5f5c4e9d7ae5 -F doc/lemon.html 89ea833a6f71773ab1a9063fbb7fb9b32147bc0b1057b53ecab94a3b30c0aef5 +F doc/lemon.html 2085fda0a90a94fe92159a79dccc5c30d5a2313524887a31659cd66162f17233 F doc/pager-invariants.txt 83aa3a4724b2d7970cc3f3461f0295c46d4fc19a835a5781cbb35cb52feb0577 F doc/tcl-extension-testing.md b88861804fc1eaf83249f8e206334189b61e150c360e1b80d0dcf91af82354f5 -F doc/testrunner.md 5ee928637e03f136a25fef852c5ed975932e31927bd9b05a574424ae18c31019 +F doc/testrunner.md daffa0ebbbe397a73537ae1b19b3124d489ce5f89dfe570781d1f1ef1809597c F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56 F doc/vfs-shm.txt 1a55f3f0e7b6745931b117ba5c9df3640d7a0536f532ef0052563100f4416f86 @@ -71,7 +71,7 @@ F doc/wal-lock.md 7db0cd61e2000b545b78ce89b0c2a9a8dd8d64c097839258ac10d7c5c4156e F ext/README.md 6eb1ac267d917767952ed0ef63f55de003b6a5da433ce1fa389e1a9532e73132 F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 -F ext/expert/expert1.test 1d2da6606623b57bb47064e02140823ce1daecd4cacbf402c73ad3473d7f000c +F ext/expert/expert1.test d9dfbf7fb527cfd43049e30a6238ef02c94484041fa4461ed41acbc6435425d6 F ext/expert/sqlite3expert.c 546010043fbec93544f762de5161b3d553165859e6bd853c4b85c05f93484260 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c c395134bd6d4efa594a7d26578a1cb624c4027b79b4b5fcd44736c5ef1f5f725 @@ -79,9 +79,9 @@ F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c F ext/fts3/README.syntax b72477722e9b4fe43f8403227d790a1c94221bfad15c27863a4b36d1052e892b F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 4f02858ab845a97bedf06e6cc1fba49a81fe5e00a26df68d0ad0f00a5814fa70 +F ext/fts3/fts3.c 6cc7bbc307f27e7b6ee2e1d5ff63ffff4df3b42529dfe00eb34ddded417961b3 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h ed9b8bc5ed5be402069651e49d4855cb849af706cf3fe68548f58a2c21eefc7f +F ext/fts3/fts3Int.h fd6051f7aa4db93e05fdc703ef35faf79f78170419e809139109d7aef28f4170 F ext/fts3/fts3_aux.c 7eab82a9cf0830f6551ba3abfdbe73ed39e322a4d3940ee82fbf723674ecd9f3 F ext/fts3/fts3_expr.c 5c13796638d8192c388777166075cdc8bc4b6712024cd5b72c31acdbefce5984 F ext/fts3/fts3_hash.c d9dba473741445789330c7513d4f65737c92df23c3212784312931641814672a @@ -97,7 +97,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c c1de4ae28356ad98ccb8b2e3388a7fdcce7607b5523738c9afb6275dab765154 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c 2bee1c5828f6401adffd07ca64260aeb79d64138958273a56de8fa5e8759a0c1 +F ext/fts3/fts3_write.c d218b687fb55bce8c9340c6dbb368a10d94647cbe39801d85492d576a4e7da75 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674 @@ -108,21 +108,21 @@ F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a0 F ext/fts5/extract_api_docs.tcl 009cf59c77afa86d137b0cca3e3b1a5efbe2264faa2df233f9a7aa8563926d15 F ext/fts5/fts5.h ff5d3cc88b29e41612bfb29eb723e29e38973de62ca75ba3e8f94ccb67f5b5f2 F ext/fts5/fts5Int.h 8d98f8e180fe28d6067e240ed45b9011735d29d5cfb5bac194e1e376baa7c708 -F ext/fts5/fts5_aux.c da4a7a9a11ec15c6df0699d908915a209bcde48f0b04101461316b59f71abffb -F ext/fts5/fts5_buffer.c f1e6d0324d7c55329d340673befc26681a372a4d36086caa8d1ec7d7c53066c7 -F ext/fts5/fts5_config.c e7d8dd062b44a66cd77e5a0f74f23a2354cd1f3f8575afb967b2773c3384f7f8 -F ext/fts5/fts5_expr.c b8c32da1127bafaf10d6b4768b0dcb92285798524bed2d87a8686f99a8e8d259 -F ext/fts5/fts5_hash.c a6266cedd801ab7964fa9e74ebcdda6d30ec6a96107fa24148ec6b7b5b80f6e0 -F ext/fts5/fts5_index.c 4e94cec64da9a61f8763f033fee310d3ce22805e1452fd4190e3f972ec60dfb0 -F ext/fts5/fts5_main.c 42025174a556257287071e90516d3ab8115daf1dd525a301883544469a260014 +F ext/fts5/fts5_aux.c 042da27e97d38071312c111cf18f3cb7983b75ba5e724aa1c3164e61e90f428a +F ext/fts5/fts5_buffer.c dcc3f0352339fe79c9d8abbc1c2009bc3469206467880bf43558447ef4f846fb +F ext/fts5/fts5_config.c bfba970fe1e4eed18ee57c8d51458e226db9a960ddf775c5e50e3d76603a667e +F ext/fts5/fts5_expr.c 71d48e8cf0358deace4949276647d317ff7665db6db09f40b81e2e7fe6664c7c +F ext/fts5/fts5_hash.c d5871df92ce3fa210a650cf419ee916b87c29977e86084d06612edf772bff6f5 +F ext/fts5/fts5_index.c f8cfa37bb7397e5ede20242e4c9cb030bc8b4584ce3f23a5e2495038c0ae64bd +F ext/fts5/fts5_main.c 6889f1373c469d515e792fb3d783c2218e63c560433ebd66edc0f740ab086c1b F ext/fts5/fts5_storage.c 19bc7c4cbe1e6a2dd9849ef7d84b5ca1fcbf194cefc3e386b901e00e08bf05c2 -F ext/fts5/fts5_tcl.c 7fb5a3d3404099075aaa2457307cb459bbc257c0de3dbd52b1e80a5b503e0329 +F ext/fts5/fts5_tcl.c 2be6cc14f9448f720fd4418339cd202961a0801ea9424cb3d9de946f8f5a051c F ext/fts5/fts5_test_mi.c 4308d5658cb1f5eee5998dcbaac7d5bdf7a2ef43c8192ca6e0c843f856ccee26 -F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b -F ext/fts5/fts5_tokenize.c 49aea8cc400a690a6c4f83c4cedc67f4f8830c6789c4ee343404f62bcaebca7b +F ext/fts5/fts5_test_tok.c 6021033bd4f4feffe8579efb6e1f58156ed462256bf99a2acdbd629246529204 +F ext/fts5/fts5_tokenize.c cfc16dde905552fe238c0403670852e75c0330ba508a9fb4836c1f596618561d F ext/fts5/fts5_unicode2.c 536a6dae41d16edadd6a6b58c56e2ebbb133f0dfe757562a2edbcdc9b8362e50 F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80 -F ext/fts5/fts5_vocab.c 23e263ad94ac357cfffd19bd7e001c3f15c4420fb10fa35b5993142127e780e6 +F ext/fts5/fts5_vocab.c bebee4aabcd056a44b3731166433cfdecf17ece750c08cb58733216222bd39e2 F ext/fts5/fts5parse.y eb526940f892ade5693f22ffd6c4f2702543a9059942772526eac1fde256bb05 F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba F ext/fts5/test/fts5_common.tcl c5aa7cf7148b6dcffb5b61520ae18212baf169936af734ab265143f59db328fe @@ -168,6 +168,7 @@ F ext/fts5/test/fts5corrupt5.test 73985d4fe6d8f0d5d5c7bcf79ae7c6522c376cd6ad710a F ext/fts5/test/fts5corrupt6.test 2d72db743db7b5d9c9a6d0cfef24d799ed1aa5e8192b66c40e871a37ed9eed06 F ext/fts5/test/fts5corrupt7.test 814aab492d7a09abb5bfdd81cc66fc206d7f3868f9a3bae91876e02efc466fb3 F ext/fts5/test/fts5corrupt8.test 0b10750caf8aa23fa1c379ca4caf6130d41454505e4d5315590f4061eedcbe44 +F ext/fts5/test/fts5corrupt9.test 4253b9b59f33effac8b67da72ec34309c738aca2d5e8e2656bfbbd6a489a1dfe F ext/fts5/test/fts5corruptbig.test 9f95b40fa36e292feceab02b2ef06e21878bfa1ac7afefa138aae05518b51774 F ext/fts5/test/fts5delete.test 2a5008f8b1174ef41d1974e606928c20e4f9da77d9f8347aed818994d89cced4 F ext/fts5/test/fts5detail.test 54015e9c43ec4ba542cfb93268abdf280e0300f350efd08ee411284b03595cc4 @@ -198,9 +199,9 @@ F ext/fts5/test/fts5first.test bfd685b96905bf541d99d8644e0a7219d1d833455a08ab64e F ext/fts5/test/fts5full.test 97d263c1072f4a560929cca31e70f65d2ae232610e17e6affcf7e979df59547b F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e F ext/fts5/test/fts5hash.test fd3e0367fbf0b0944d6936fdb22696350f57b9871069c6766251578a103e8a14 -F ext/fts5/test/fts5integrity.test c423ce16fd1ccadcac7fc22f794226b2bb00f5a187c0ab1d9f8502521b1bae05 +F ext/fts5/test/fts5integrity.test 613efcebe16b2d7a4096f03bcfb164f79a000b3354420ceda4a6f3e035090789 F ext/fts5/test/fts5integrity2.test 4c3636615c0201232c44a8105d5cb14fd5499fd0ee3014d7ffd7e83aac76ece8 -F ext/fts5/test/fts5interrupt.test 20d04204d3e341b104c0c24a41596b6393a3a81eba1044c168db0e106f9ac92c +F ext/fts5/test/fts5interrupt.test af7834ac6c2e71c05aea42d92f272eef3655e89b7a14a5620a2cd9de35e2e8ea F ext/fts5/test/fts5join.test 48b7ed36956948c5b8456c8bcaa5b087808d99000675918a43c4f51a925f1514 F ext/fts5/test/fts5lastrowid.test f36298a1fb9f988bde060a274a7ce638faa9c38a31400f8d2d27ea9373e0c4a1 F ext/fts5/test/fts5leftjoin.test 1c14b51f4d1344a89e488160882f05a2246dd7e70c5cf077c8fb473e03c66338 @@ -284,14 +285,14 @@ F ext/intck/intck_common.tcl a61fd2697ae55b0a3d89847ca0b590c6e0d8ff64bebb70920d9 F ext/intck/intckbusy.test d5ed4ef85a4b1dc1dee2484bd14a4bb68529659cca743327df0c775f005fa387 F ext/intck/intckcorrupt.test f6c302792326fb3db9dcfc70b554c55369bc4b52882eaaf039cfe0b74c821029 F ext/intck/intckfault.test cff3f75dff74abb3edfcb13f6aa53f6436746ab64b09fe5e2028f051e985efab -F ext/intck/sqlite3intck.c b1c8a86f90fc00741d13314db9c58f7e2f92d1d19c5ad1c6904ec83a6bbd5c96 +F ext/intck/sqlite3intck.c 3c4a166645a1624731f63acd342e24e81e4ffd497116d94a427d72e6cc6caa69 F ext/intck/sqlite3intck.h 2b40c38e7063ab822c974c0bd4aed97dabb579ccfe2e180a4639bb3bbef0f1c9 F ext/intck/test_intck.c 4f9eaadaedccb9df1d26ba41116a0a8e5b0c5556dc3098c8ff68633adcccdea8 F ext/jni/GNUmakefile 8a94e3a1953b88cf117fb2a5380480feada8b4f5316f02572cab425030a720b4 -F ext/jni/README.md e3fbd47c774683539b7fdc95a667eb9cd6e64d8510f3ee327e7fa0c61c8aa787 +F ext/jni/README.md 1479c83dbe26125264a060ee6873531795a7082dbc0d43c4067683371331559f F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa -F ext/jni/src/c/sqlite3-jni.c 3d84a0176af779737ae977ba1c90d2ffe2537b8299c5d9f6552620493f12ac4b -F ext/jni/src/c/sqlite3-jni.h ac180ba9b21978727006c790d3006a95a2402a4c3ec7a0def92707ed89b79945 +F ext/jni/src/c/sqlite3-jni.c 6ccc50b0e98b8ef8fd6a8b837223e1c8ebb92bfe6b976128cc757c26257d994a +F ext/jni/src/c/sqlite3-jni.h df43024cced914c49485633d0f90168689e70577b3b17b0ecbdaf16e4a417bff F ext/jni/src/org/sqlite/jni/annotation/Experimental.java 8603498634e41d0f7c70f661f64e05df64376562ea8f126829fd1e0cdd47e82b F ext/jni/src/org/sqlite/jni/annotation/NotNull.java be6cc3e8e114485822331630097cc0f816377e8503af2fc02f9305ff2b353917 F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 56e3dee1f3f703a545dfdeddc1c3d64d1581172b1ad01ffcae95c18547fafd90 @@ -301,7 +302,7 @@ F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0e28a0df51368c7127e505f F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759 F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca -F ext/jni/src/org/sqlite/jni/capi/CApi.java 3d275f5f4fbdbe4fff15f4d42cf5ff559f9a4897e7373fa99f3b1dc9cf7f849c +F ext/jni/src/org/sqlite/jni/capi/CApi.java a9701cbe736b8bef5bc72ae465be0250e137f67bdb5a3ab62497972b6f51572d F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 1b3baf5b772f266e8baf8f35f0ddc5bd87fc8c4927ec69115c46fd6fba6701c3 F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab @@ -319,7 +320,7 @@ F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385 F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 3c0babc067d8560627a9ed1b07979f9d4393464e2282c2fca4832052e982c7bc F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615 F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java 9133bb7685901d2edf07801191284975e33b5583ce09dce1c05202ff91e7bb99 -F ext/jni/src/org/sqlite/jni/capi/Tester1.java 4c3d16fdf6e979f839b2ecdb14d0a0c04bd3d0e41500fc9e8110b588883b140b +F ext/jni/src/org/sqlite/jni/capi/Tester1.java 378d142435d220b20b7ce7343c62a03e853bb8c51e80447ee0f8ac5c37362d9a F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723 F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java c8bdf7848e6599115d601bcc9427ff902cb33129b9be32870ac6808e04b6ae56 F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 2ce069f3e007fdbbe1f4e507a5a407fc9679da31a0aa40985e6317ed4d5ec7b5 @@ -346,7 +347,7 @@ F ext/jni/src/org/sqlite/jni/test-script-interpreter.md d7987b432870d23f7c72a780 F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483 F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 326ffba29aab836a6ea189703c3d7fb573305fd93da2d14b0f9e9dcf314c8290 F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java e920f7a031e04975579240d4a07ac5e4a9d0f8de31b0aa7a4be753c98ae596c9 -F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java c82bc00c1988f86246a89f721d3c41f0d952f33f934aa6677ec87f7ca42519a0 +F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java ce8aef867e06a21685658eb0173768c355e9e1e2dfdc75f382643fa62c7ee779 F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 982538ddb4c0719ef87dfa664cd137b09890b546029a7477810bd64d4c47ee35 F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 08f92d52be2cec28a7b4555479cc54b7ebeeb94985256144eeb727154ec3f85b F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java a84e90c43724a69c2ecebd601bc8e5139f869b7d08cb705c77ef757dacdd0593 @@ -362,20 +363,20 @@ F ext/misc/base64.c 8dc0a08cee11722822858a62625f1b63e5d5f1adac1cf4492d5732b571e3 F ext/misc/base85.c ff54cc676c6ec86231f75ecc86ea45416fcb69751dfb79690d5f5da5f7d39867 F ext/misc/basexx.c 89ad6b76558efbceb627afd5e2ef1d84b2e96d9aaf9b7ecb20e3d00b51be6fcf F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a -F ext/misc/btreeinfo.c 8f5e6da2c82ec2f06ee0216e922370a436dafdbb06ffa7a552203515ff9e7ddf +F ext/misc/btreeinfo.c 13bc9e9f1c13cde370d0e4a6a2683e9f1926a4cead7fb72c71871b11a06d78a1 F ext/misc/cksumvfs.c 9d7d0cf1a8893ac5d48922bfe9f3f217b4a61a6265f559263a02bb2001259913 F ext/misc/closure.c 5559daf1daf742228431db929d1aa86dd535a4224cc634a81d2fd0d1e6ad7839 F ext/misc/completion.c c27b64fdd0943c1b7f152376599814cee2641f7d67a7bb9bd2b957c2a64a5591 F ext/misc/compress.c 8191118b9b73e7796c961790db62d35d9b0fb724b045e005a5713dc9e0795565 -F ext/misc/csv.c d9dab032420d81cdf0abc4b8523711d20a355704f82d958f963c86be48387dd9 +F ext/misc/csv.c e82124eabee0e692d7b90ab8b2c34fadbf7b375279f102567fa06e4da4b771bf F ext/misc/dbdump.c 678f1b9ae2317b4473f65d03132a2482c3f4b08920799ed80feedd2941a06680 -F ext/misc/decimal.c d4883de142f6dcd36eda23da40b55e2b51374e7b01eb54a7173940191389fc5e +F ext/misc/decimal.c e1da22eee70d7e3eaa99a6b761bc03c4d01d7ffa554bf3178b1f1f184932806c F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 606100185fb90d6a1eade1ed0414d53503c86820d8956a06e3b0a56291894f2b -F ext/misc/fileio.c d80268a5328fe839062a9d3103ea0fc7cacc6d42605959275675cb37867c84f7 -F ext/misc/fossildelta.c 2fc2dd4f34f478df674887db62586b1071c4cd3c9e73ee40f9ee669670e482d1 -F ext/misc/fuzzer.c 6b231352815304ba60d8e9ec2ee73d4918e74d9b76bda8940ba2b64e8777515e -F ext/misc/ieee754.c 176c061c94857b543313959289cb60cf777c999fd002f82b53d194b95e9f347a +F ext/misc/fileio.c 33165b3cd99f83dcd333a338eb51491f6b01c8d96cb6ae81f96a6a096834e030 +F ext/misc/fossildelta.c 86dfa83f85f7ccd640591d8a5c6865346d0c2ee6a949d78591eceb892f1cbfec +F ext/misc/fuzzer.c 684a4996b523ea89f495b38fd8a14a2ae00695089a88031366a4df6adc2c873b +F ext/misc/ieee754.c 2901d08a586d00a1d3c0fd89e03c57ee9e2b5f013b0daab9e49c7a48a9d5946b F ext/misc/memstat.c 43705d795090efb78c85c736b89251e743c291e23daaa8382fe7a0df2c6a283d F ext/misc/memtrace.c 7c0d115d2ef716ad0ba632c91e05bd119cb16c1aedf3bec9f06196ead2d5537b F ext/misc/mmapwarm.c a81af4aaec00f24f308e2f4c19bf1d88f3ac3ce848c36daa7a4cd38145c4080d @@ -387,21 +388,22 @@ F ext/misc/percentile.c 72e05a21db20a2fa85264b99515941f00ae698824c9db82d7edfbb16 F ext/misc/prefixes.c 82645f79229877afab08c8b08ca1e7fa31921280906b90a61c294e4f540cd2a6 F ext/misc/qpvtab.c fc189e127f68f791af90a487f4460ec91539a716daf45a0c357e963fd47cc06c F ext/misc/randomjson.c ef835fc64289e76ac4873b85fe12f9463a036168d7683cf2b773e36e6262c4ed -F ext/misc/regexp.c f1f7cfe90fc027b33d2b5ae7d6235eecce69c3aca71c9afce56fec62342c8b44 +F ext/misc/regexp.c 69bd45f6931bdc6801c1059b65a3e8b15ba88255e6abe387a34b653ce17e8908 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 F ext/misc/series.c 22c6d8f00cc1b5089b1b37392e9097e9df9a5db53be86daf9a7669d95bb179f4 -F ext/misc/sha1.c cb5002148c2661b5946f34561701e9105e9d339b713ec8ac057fd888b196dcb9 +F ext/misc/sha1.c 3030b5926b34bb09459200e100fae34e48c04077cf175381a7560f72bbf3d9cf F ext/misc/shathree.c fd22d70620f86a0467acfdd3acd8435d5cb54eb1e2d9ff36ae44e389826993df F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 F ext/misc/spellfix.c 693c8fd3293087fa821322967a97e59dfa24051e5d2ca7fa85790a4034db6fa4 F ext/misc/sqlar.c a6175790482328171da47095f87608b48a476d4fac78d8a9ff18b03a2454f634 -F ext/misc/sqlite3_stdio.c 0fe5a45bd332b30aef2b68c64edbe69e31e9c42365b0fa79ce95a034bca6fbb0 -F ext/misc/sqlite3_stdio.h f05eaf5e0258f0573910324a789a9586fc360a57678c57a6d63cfaa2245b6176 +F ext/misc/sqlite3_stdio.c e49c07050bf7bdc87866da7583beda236f2f8c462018a34b61785d99cbddedfd +F ext/misc/sqlite3_stdio.h 27a4ecea47e61bc9574ccdf2806f468afe23af2f95028c9b689bfa08ab1ce99f F ext/misc/stmt.c b090086cd6bd6281c21271d38d576eeffe662f0e6b67536352ce32bbaa438321 F ext/misc/stmtrand.c 59cffa5d8e158943ff1ce078956d8e208e8c04e67307e8f249dece2436dcb7fc F ext/misc/templatevtab.c 10f15b165b95423ddef593bc5dcb915ec4eb5e0f1066d585e5435a368b8bc22b +F ext/misc/tmstmpvfs.c 240caad4441328dc52bd2871f48811db46dff858d5598030e389176837a2f4df F ext/misc/totype.c ba11aac3c0b52c685bd25aa4e0f80c41c624fb1cc5ab763250e09ddc762bc3a8 F ext/misc/uint.c 327afc166058acf566f33a15bf47c869d2d3564612644d9ff81a23efc8b36039 F ext/misc/unionvtab.c 716d385256d5fb4beea31b0efede640807e423e85c9784d21d22f0cce010a785 @@ -410,16 +412,20 @@ F ext/misc/uuid.c 5bb2264c1b64d163efa46509544fd7500cb8769cb7c16dd52052da8d961505 F ext/misc/vfslog.c 3932ab932eeb2601dbc4447cb14d445aaa9fbe43b863ef5f014401c3420afd20 F ext/misc/vfsstat.c 0b23c0a69a2b63dc0ef0af44f9c1fc977300c480a1f7a9814500369d8211f56e F ext/misc/vfstrace.c 0e4b8b17ac0675ea90f6d168d8214687e06ca3efbc0060aad4814994d82b41fb -F ext/misc/vtablog.c 2d04386c2f5a3bb93bc9ae978f0b7dcd5a264e126abd640dd6d82aa9067cbd48 +F ext/misc/vtablog.c 402496fb38add7dd2c50f2a0ad20f83a9916ceab48dcd31e62ad621e663c83ac F ext/misc/vtshim.c e5bce24ab8c532f4fdc600148718fe1802cb6ed57417f1c1032d8961f72b0e8f F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f65b4fc0668 F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c -F ext/misc/zipfile.c 71d3fd3155ed5e738473e286e550cf0bcf346cc2fd63646eaf944e7b40531a1b +F ext/misc/zipfile.c c8ee04e1b349270b5df401ad732f5d7c387146e69b33c02fa90322760cc6fee0 F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee +F ext/qrf/README.md e6e0ce2700acf6fd06312b42726a8f08ca240f30e1b122bff87c71c602046352 +F ext/qrf/dev-notes.md e68a6d91ce4c7eb296ef2daadc2bb79c95c317ad15b9fafe40850c67b29c2430 +F ext/qrf/qrf.c cd48c23500c3b129be5e0627ce9d41b5df3c2d715525b00a6ccbd1f30689fb17 +F ext/qrf/qrf.h 2ac14b0aaacf44636d8c81051bfeab4afae50a98fbb2e10ff5aed0c28a87b2b2 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363 -F ext/rbu/rbu11.test 8584f80ef4be00e6beec4154f638847ffc40b5f2832ffadfbaf558ae40e50cb5 +F ext/rbu/rbu11.test 3b71377c018b05dd39c30ea2feb272a087eb0faeff1b7b4511144f87219c478e F ext/rbu/rbu12.test ec63aa7bfc3c65c1d774bf4357ed731723827d211d9d7cb0efa171bbaeeebaf4 F ext/rbu/rbu13.test 658edbc3325d79252a98b761fde95460e439f80e820ff29e10261e25f870b3b6 F ext/rbu/rbu14.test 05dac607a62f62102f4db92135979a8a4501143638060019aca08c753822cf39 @@ -460,7 +466,7 @@ F ext/rbu/rbuvacuum.test 542561741ff2b262e3694bc6012b44694ee62c545845319a06f3237 F ext/rbu/rbuvacuum2.test 1a9bd41f127be2826de2a65204df9118525a8af8d16e61e6bc63ba3ac0010a23 F ext/rbu/rbuvacuum3.test 3ce42695fdf21aaa3499e857d7d4253bc499ad759bcd6c9362042c13cd37d8de F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69eefaebb205 -F ext/rbu/sqlite3rbu.c c208f72f20784bf2f39244b6cdf8019724a706e1246be289e7621c42aad54695 +F ext/rbu/sqlite3rbu.c e99400d29d029936075e27495b269a2dcdceae3cf8c86b1d0869b4af487be3ab F ext/rbu/sqlite3rbu.h e3a5bf21e09ca93ce4e8740e00d6a853e90a697968ec0ea98f40826938bdb68e F ext/rbu/test_rbu.c 8b6e64e486c28c41ef29f6f4ea6be7b3091958987812784904f5e903f6b56418 F ext/recover/dbdata.c 10d3c56968a9af6853722a47280805ad1564714d79ea45ac6f7da14bb57fd137 @@ -482,17 +488,8 @@ F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e F ext/recover/sqlite3recover.c 56c216332ea91233d6d820d429f3384adbec9ecedda67aa98186b691d427cc57 F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959 F ext/recover/test_recover.c 3d0fb1df7823f5bc22a0b93955034d16a2dfa2eb1e443e9a0123a77f120599a3 -F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 -F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996 -F ext/repair/checkindex.c 7639b4f8928f82c10b950169e60cc45a7f6798df0b299771d17bef025736f657 -F ext/repair/sqlite3_checker.c.in 445118c5f7fea958b36fba1b2c464283e60ed4842039ddee3265f1698115ebf7 -F ext/repair/sqlite3_checker.tcl a9a2caa9660567257c177a91124d8c0dccdfa341e25c51e6da7f1fd9e601eafa -F ext/repair/test/README.md 34b2f542cf5be7bffe479242b33ee3492cea30711e447cc4a1a86cb5915f419e -F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc78249442da72ff3f8297398a69 -F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c335096108c12c01bddbadcec -F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 734aa36238bcd2dee91db5dba107d5fcbdb02396612811377a8ad50f1272b1c1 -F ext/rtree/geopoly.c f0573d5109fdc658a180db0db6eec86ab2a1cf5ce58ec66cbf3356167ea757eb +F ext/rtree/geopoly.c bd1971479184d559499ff3087c37f2823977d7b0ec80916141ae66f70345c88d F ext/rtree/rtree.c 9331997a76b88a9bc04e156bdfd6e2fe35c0aa93bc338ebc6aa0ae470fe4a852 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 F ext/rtree/rtree1.test e0608db762b2aadca0ecb6f97396cf66244490adc3ba88f2a292b27be3e1da3e @@ -535,14 +532,14 @@ F ext/session/changesetfuzz1.test 15b629004e58d5ffcc852e6842a603775bb64b1ce51254 F ext/session/session1.test 5d2502922d38a1579076863827342379a1609ca6bae78c40691a2be1ed1be2aa x F ext/session/session2.test ee83bb973b9ce17ccce4db931cdcdae65eb40bbb22089b2fe6aa4f6be3b9303f F ext/session/session3.test 2cc1629cfb880243aec1a7251145e07b78411d851b39b2aa1390704550db8e6a -F ext/session/session4.test 823f6f018fcbb8dacf61e2960f8b3b848d492b094f8b495eae1d9407d9ab7219 +F ext/session/session4.test ad0ddaaddb9a99dac433d83fc6674aae2af072b8f57e63a6b3f2d76f1a66e98c F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169 F ext/session/session6.test 35279f2ec45448cd2e24a61688219dc6cf7871757716063acf4a8b5455e1e926 F ext/session/session8.test 326f3273abf9d5d2d7d559eee8f5994c4ea74a5d935562454605e6607ee29904 F ext/session/session9.test 0c4a8fbe7a5031f50855f020f3408e1f07fd7859f1daa1629eadcec3422072d6 F ext/session/sessionA.test 1feeab0b8e03527f08f2f1defb442da25480138f F ext/session/sessionB.test c4fb7f8a688787111606e123a555f18ee04f65bb9f2a4bb2aa71d55ce4e6d02c -F ext/session/sessionC.test f8a5508bc059ae646e5ec9bdbca66ad24bc92fe99fda5790ac57e1f59fce2fdf +F ext/session/sessionC.test c3fade0a460d898fa42e9077b88e45c0d24ead3150268e145c8e19aeafc24ba1 F ext/session/sessionD.test 470ff917dc849e2eb78142ade63aaabd729d773833cff0ff01bca0eda68a21ce F ext/session/sessionE.test b2010949c9d7415306f64e3c2072ddabc4b8250c98478d3c0c4d064bce83111d F ext/session/sessionF.test d37ed800881e742c208df443537bf29aa49fd56eac520d0f0c6df3e6320f3401 @@ -572,11 +569,10 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795 F ext/session/sessionstat1.test 5e718d5888c0c49bbb33a7a4f816366db85f59f6a4f97544a806421b85dc2dec F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c b3de195ce668cace9b324599bf6255a70290cbfb5451e826e946f3aee6e64c54 +F ext/session/sqlite3session.c 6ebd02be470f36d41c4bd78927f39d507b62051ba025eacaed9936c769902a07 F ext/session/sqlite3session.h 7404723606074fcb2afdc6b72c206072cdb2b7d8ba097ca1559174a80bc26f7a -F ext/session/test_session.c 8766b5973a6323934cb51248f621c3dc87ad2a98f023c3cc280d79e7d78d36fb -F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile 3dc01e673c456d3b752674c9407276e8fef35dec1d304b3cc1de362f019b2a09 +F ext/session/test_session.c 190110e3bd9463717248dec1272b44fe9943e57b7646d0b4200dcf11e4dccee6 +F ext/wasm/GNUmakefile 79236447d750609aa6beda30feec1314180c5462a493ad94214122887232bfd4 F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a F ext/wasm/README.md 2e87804e12c98f1d194b7a06162a88441d33bb443efcfe00dc6565a780d2f259 F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff @@ -584,71 +580,68 @@ F ext/wasm/SQLTester/SQLTester.mjs 6b3c52ed36a5573ca4883176f326332a8d4c0cecf5efd F ext/wasm/SQLTester/SQLTester.run.mjs 57f2adb33f43f2784abbf8026c1bfd049d8013af1998e7dcb8b50c89ffc332e0 F ext/wasm/SQLTester/index.html 64f3435084c7d6139b08d1f2a713828a73f68de2ae6a3112cbb5980d991ba06f F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536 -F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core ef34398a903d0a2425fbbfbd4ed2cd596daea55b8515e2617c8dc7ad7c0767dd -F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras 9eae68943ce91ab145892b31370819c2103525240eb72e0fce53c498b8d8275a -F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b -F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 -F ext/wasm/api/README.md f4c0d67caaee21a77b8938c30b5f79667bfc9d0c95d01b51df77ea35ee773884 -F ext/wasm/api/extern-post-js.c-pp.js 205f55aacfc62c580985db5c790300779de3876a76a5c7e1bfb13e71c8b4506b +F ext/wasm/api/EXPORTED_FUNCTIONS.c-pp 7ba933e8f1290cc65459dd371c0c9a031d96bdf14d7a2244fa761d9775117b90 +F ext/wasm/api/README.md a905d5c6bfc3e2df875bd391d6d6b7b48d41b43bdee02ad115b47244781a7e81 +F ext/wasm/api/extern-post-js.c-pp.js d9f42ecbedc784c0d086bc37800e52946a14f7a21600b291daa3f963c314f930 F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 -F ext/wasm/api/post-js-footer.js 5bd7170b5e8ce7b62102702bbcf47ef7b3b49cd56ed40c043fd990aa715b74ee -F ext/wasm/api/post-js-header.js 79d078aec33d93b640a19c574b504d88bb2446432f38e2fbb3bb8e36da436e70 -F ext/wasm/api/pre-js.c-pp.js a876c6399dff29b6fe9e434036beb89889164cc872334e184291723ecc7cb072 -F ext/wasm/api/sqlite3-api-cleanup.js a3d6b9e449aefbb8bba283c2ba9477e2333a0eeb94a7a26b5bf952736f65a6dd -F ext/wasm/api/sqlite3-api-glue.c-pp.js d2b8263b3ce0cefc6c5a68d0a4d448a9770eda4bf9d9ded9d7eb0198e4ce4da1 -F ext/wasm/api/sqlite3-api-oo1.c-pp.js c4260f3fdc553c56ee530c20cc1119029067b503f0d6d7b472705536cb45aa1d -F ext/wasm/api/sqlite3-api-prologue.js 307583ff39a978c897c4ef4ce53fe231dce5c73dc84785969c81c1ab5960a293 +F ext/wasm/api/post-js-footer.js a50c1a2c4d008aede7b2aa1f18891a7ee71437c2f415b8aeb3db237ddce2935b +F ext/wasm/api/post-js-header.js d24bd0d065f3489c8b78ddf3ead6321e5d047187a162cd503c41700e03dd1f06 +F ext/wasm/api/pre-js.c-pp.js 9234ea680a2f6a2a177e8dcd934bdc5811a9f8409165433a252b87f4c07bba6f +F ext/wasm/api/sqlite3-api-glue.c-pp.js 9b33e3ee467791dec4fd1b444b12a8545dfbb6c8b28ac651c7bdc7661a3b5a5c +F ext/wasm/api/sqlite3-api-oo1.c-pp.js 45454631265d9ce82685f1a64e1650ee19c8e121c41db98a22b534c15e543cfa +F ext/wasm/api/sqlite3-api-prologue.js 1fefd40ab21e3dbf46f43b6fafb07f13eb13cc157a884f7c1134caf631ddb3f2 F ext/wasm/api/sqlite3-api-worker1.c-pp.js 1041dd645e8e821c082b628cd8d9acf70c667430f9d45167569633ffc7567938 -F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 -F ext/wasm/api/sqlite3-opfs-async-proxy.js 9654b565b346dc609b75d15337f20acfa7af7d9d558da1afeb9b6d8eaa404966 +F ext/wasm/api/sqlite3-license-version-header.js 98d90255a12d02214db634e041c8e7f2f133d9361a8ebf000ba9c9af4c6761cc +F ext/wasm/api/sqlite3-opfs-async-proxy.js 92d6d327a862f1627ff3e88e60fdfea9def06ad539d98929ba46490e64372736 F ext/wasm/api/sqlite3-vfs-helper.c-pp.js 3f828cc66758acb40e9c5b4dcfd87fd478a14c8fb7f0630264e6c7fa0e57515d -F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 26cb41d5a62f46a106b6371eb00fef02de3cdbfaa51338ba087a45f53028e0d0 -F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 418c33fe284739564daab3c7a7a88882fdd3c99137497900f98eddec1e409af5 -F ext/wasm/api/sqlite3-vtab-helper.c-pp.js 9097074724172e31e56ce20ccd7482259cf72a76124213cbc9469d757676da86 -F ext/wasm/api/sqlite3-wasm.c dd7fc1d535281f0d5d2732bb1b662d1d403a762f07b63c2ea5663053377b2804 -F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bda1c75bd674a92a0e27cc2f3d46dbbf21e422413f8046814515a0bd7409328a -F ext/wasm/api/sqlite3-worker1.c-pp.js 802d69ead8c38dc1be52c83afbfc77e757da8a91a2e159e7ed3ecda8b8dba2e7 -F ext/wasm/c-pp-lite.c 8fa0148e73782a86274db688c4730e2962cd675af329490493adddaf3322f16f -F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 +F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js 2ccf4322f42063aefc150972943e750c77f7926b866f1639d40eec05df075b6e +F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 1575ea6bbcf2da1e6df6892c17521a0c1c1c199a672e9090176ea0b88de48bd9 +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 88ce2078267a2d1af57525a32d896295f4a8db7664de0e17e82dc9ff006ed8d3 +F ext/wasm/api/sqlite3-vtab-helper.c-pp.js 366596d8ff73d4cefb938bbe95bc839d503c3fab6c8335ce4bf52f0d8a7dee81 +F ext/wasm/api/sqlite3-wasm.c 45bb20e19b245136711f9b78584371233975811b6560c29ed9b650e225417e29 +F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js aa9715f661fb700459a5a6cb1c32a4d6a770723b47aa9ac0e16c2cf87d622a66 +F ext/wasm/api/sqlite3-worker1.c-pp.js bd0655687090e3b1657268a6a9cacde1ea2a734079d194e16dbbed9083e51b38 +F ext/wasm/c-pp-lite.c f38254fba42561728c2e4764a7ba8d68700091e7c2f4418112868c0daba16783 +F ext/wasm/common/SqliteTestUtil.js dae753b95e72248c4395d8de8359e0d055cd9928488e8dd84aef89e46d23b32e F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 F ext/wasm/common/testing.css e97549bab24126c24e0daabfe2de9bb478fb0a69fdb2ddd0a73a992c091aad6f -F ext/wasm/common/whwasmutil.js 0d539324097fc83b953e9844267359ba0fd02286caa784ea2f597ced279ea640 +F ext/wasm/common/whwasmutil.js 831f07a0d9bb61713164871370811432e96d0f813806a4d2c783d3c77c2373a0 F ext/wasm/config.make.in c424ae1cc3c89274520ad312509d36c4daa34a3fce5d0c688e5f8f4365e1049a F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 F ext/wasm/demo-123.js c7b3cca50c55841c381a9ca4f9396e5bbdc6114273d0b10a43e378e32e7be5bf F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e -F ext/wasm/demo-jsstorage.js 42131ddfa18e817d0e39ac63745e9ea31553980a5ebd2222e04d4fac60c19837 -F ext/wasm/demo-worker1-promiser.c-pp.html 635cf90685805e21772a5f7a35d1ace80f98a9ef7c42ff04d7a125ddca7e5db8 +F ext/wasm/demo-jsstorage.js 467cb4126ff679ebcdb112d100d073af26b9808d0a0b52d66a40e28f59c5099b +F ext/wasm/demo-worker1-promiser.c-pp.html f73b0b98457e7fdad40d8353cb9b2919391da180f49549a86f3d58b4e5a010eb F ext/wasm/demo-worker1-promiser.c-pp.js f40ec65810048e368896be71461028bd10de01e24277208c59266edf23bb9f52 F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d -F ext/wasm/demo-worker1.js 08720227e98fa5b44761cf6e219269cee3e9dd0421d8d91459535da776950314 +F ext/wasm/demo-worker1.js fdfa90aa9d6b402bfed802cf1595fe4da6cc834ac38c8ff854bf1ee01f5ff9bb F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c95839afd8b05862f -F ext/wasm/fiddle/fiddle-worker.js 7798af02e672e088ff192716f80626c8895e19301a65b8af6d5d12b2d13d2451 +F ext/wasm/fiddle/fiddle-worker.js 6c72acac2d381480bc9f5eb538e3f2faf2c1f72dd4fcbd05d3b409818a9a8fd5 F ext/wasm/fiddle/fiddle.js 84fd75967e0af8b69d3dd849818342227d0f81d13db92e0dcbc63649b31a4893 -F ext/wasm/fiddle/index.html a27b8127ef9ecf19612da93b2a6a73bdb3777b5c56b5450bb7200a94bc108ff9 +F ext/wasm/fiddle/index.c-pp.html 72c7e5517217960b3809648429ea396a7cbad0ffb2c92f6a2f5703abecb27317 F ext/wasm/index-dist.html db23748044e286773f2768eec287669501703b5d5f72755e8db73607dc54d290 -F ext/wasm/index.html 54e27db740695ab2cb296e02d42c4c66b3f11b65797340d19fa6590f5b287da1 -F ext/wasm/jaccwabyt/jaccwabyt.js bbac67bc7a79dca34afe6215fd16b27768d84e22273507206f888c117e2ede7d -F ext/wasm/jaccwabyt/jaccwabyt.md 167fc0b624c9bc2c477846e336de9403842d81b1a24fc4d3b24317cb9eba734f -F ext/wasm/mkdist.sh 64d53f469c823ed311f6696f69cec9093f745e467334b34f5ceabdf9de3c5b28 x -F ext/wasm/mkwasmbuilds.c 1b53c4d2a1350c19a96a8cdfbda6a39baea9d2142bfe0cbef0ccb0e898787f47 +F ext/wasm/index.html 475bc283338749db4e3fbf24cf3f5aa020cc85a1fffb780d400a915fcb5f1756 +F ext/wasm/jaccwabyt/jaccwabyt.js 4e2b797dc170851c9c530c3567679f4aa509eec0fab73b466d945b00b356574b +F ext/wasm/jaccwabyt/jaccwabyt.md 6aa90fa1a973d0ad10d077088bea163b241d8470c75eafdef87620a1de1dea41 +F ext/wasm/mkdist.sh f8883b077a2ca47cf92e6f0ce305fbf72ca648c3501810125056c4b09c2d5554 x +F ext/wasm/mkwasmbuilds.c 0e9198eb90acae4bcf57cf62d7186f6af5aaac02efdb075a1aded33614b3805a F ext/wasm/module-symbols.html e54f42112e0aac2a31f850ab33e7f2630a2ea4f63496f484a12469a2501e07e2 F ext/wasm/scratchpad-wasmfs.html a3d7388f3c4b263676b58b526846e9d02dfcb4014ff29d3a5040935286af5b96 F ext/wasm/scratchpad-wasmfs.mjs 66034b9256b218de59248aad796760a1584c1dd842231505895eff00dbd57c63 F ext/wasm/speedtest1-wasmfs.html 0e9d335a9b5b5fafe6e1bc8dc0f0ca7e22e6eb916682a2d7c36218bb7d67379d F ext/wasm/speedtest1-wasmfs.mjs 60dd5842f6d2a70a6d0bef12633a11491bde6984aff75a37c2040980d8cbf36a F ext/wasm/speedtest1-worker.html 068d4190f304fa1c34e6501a1b3a4c32fe8d8dac93c2d0f53d667a1cb386eedc -F ext/wasm/speedtest1-worker.js 958a2d3c710bf8e82567277f656193a0248216db99a3c2c86966124b84309efb -F ext/wasm/speedtest1.html c90d63dfa795f0cb1ad188de587be9024b1ff73b4adc5fdf7efc0d781be94d03 +F ext/wasm/speedtest1-worker.js 8acad67bfd6aeeb799bd5ae007ea32af85a082a287d8877c5a10adf4bd7efd89 +F ext/wasm/speedtest1.html f32c66997eb0b036c4546e6302cd0673157912661df0b290ab65816f713feac6 F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c -F ext/wasm/tester1-worker.c-pp.html 883881eeac14eeeecc8ff22acf9fe0f18a97cacb48be08ebb0bae891ceded584 -F ext/wasm/tester1.c-pp.html 949920126dcf477925d8d540093d9cc374d3ab4c4ddee920c1dcadcf37917306 -F ext/wasm/tester1.c-pp.js 2b014884dadf28928fabcb688746ca87145673eef75e154486505a266203fc15 +F ext/wasm/tester1-worker.c-pp.html d0032241d0b24d996cf1c4dd0dde364189693af9b5c986e48af7d3d720fcd244 +F ext/wasm/tester1.c-pp.html 52d88fe2c6f21a046030a36410b4839b632f4424028197a45a3d5669ea724ddb +F ext/wasm/tester1.c-pp.js 6b946cd6d4da130dbae4a401057716d27117ca02cad2ea8c29ae9c46c675d618 F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e F ext/wasm/tests/opfs/concurrency/test.js d08889a5bb6e61937d0b8cbb78c9efbefbf65ad09f510589c779b7cc6a803a88 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -658,7 +651,8 @@ F ext/wasm/tests/opfs/sahpool/index.html be736567fd92d3ecb9754c145755037cbbd2bca F ext/wasm/tests/opfs/sahpool/sahpool-pausing.js f264925cfc82155de38cecb3d204c36e0f6991460fff0cb7c15079454679a4e2 F ext/wasm/tests/opfs/sahpool/sahpool-worker.js bd25a43fc2ab2d1bafd8f2854ad3943ef673f7c3be03e95ecf1612ff6e8e2a61 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk 00dd631c66c1f7922b2d691631163899eff1c3d1da780ff37a62f8d997b368e1 +F main.mk e1a03e9206f6a042a9147035915cb944e9242d570779bc3ccd7ed6a39df10cae +F make.bat a136fd0b1c93e89854a86d5f4edcf0386d211e5d5ec2434480f6eea436c7420c F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421 @@ -668,39 +662,39 @@ F mptest/multiwrite01.test dab5c5f8f9534971efce679152c5146da265222d F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 1b9c24374a85dfc7eb8fa7c4266ee0db4f9609cceecfc5481cd8307e5af04366 F sqlite3.pc.in e6dee284fba59ef500092fdc1843df3be8433323a3733c91da96690a50a5b398 -F src/alter.c fc7bbbeb9e89c7124bf5772ce474b333b7bdc18d6e080763211a40fde69fb1da +F src/alter.c fc36b19273ffe364aeb4d00ba04bda8798ad7a67fec7a035ee8ee56272e1bdbe F src/analyze.c 03bcfc083fc0cccaa9ded93604e1d4244ea245c17285d463ef6a60425fcb247d -F src/attach.c 9af61b63b10ee702b1594ecd24fb8cea0839cfdb6addee52fba26fa879f5db9d -F src/auth.c 54ab9c6c5803b47c0d45b76ce27eff22a03b4b1f767c5945a3a4eb13aa4c78dc +F src/attach.c 7cf07d4fa42b9fc8662237c60c40b730326c30aa90ae5fffc0b18b2d726ebf61 +F src/auth.c ebec42df26b34a62b6750d30d9c2c03554a1c522020182476f7729a439fef04f F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c e242d4496774dfc88fa278177dd23b607dce369ccafb3f61b41638eea2c9b399 F src/btmutex.c 30dada73a819a1ef5b7583786370dce1842e12e1ad941e4d05ac29695528daea -F src/btree.c cb5b8ceb9baa02a63a2f83dec09c4153e1cfbdf9c2adef5c62c26d2160eeb067 +F src/btree.c b744bf69d520534751c742cababe7ad28c3892f1e3a75242e75a20bca15a834a F src/btree.h e823c46d87f63d904d735a24b76146d19f51f04445ea561f71cc3382fd1307f0 F src/btreeInt.h 9c0f9ea5c9b5f4dcaea18111d43efe95f2ac276cd86d770dce10fd99ccc93886 -F src/build.c 611e07299d72ff04bbcb9e7109183467e30925d203c3e121ef9bb3cf6876289b +F src/build.c b993e4adef4c4cdfd7abf62e2676c467bb1923f25f40c3c7ab2a7bfbace3de7f F src/callback.c 3605bbf02bd7ed46c79cd48346db4a32fc51d67624400539c0532f4eead804ad -F src/carray.c ff6081a31878fc34df8fa1052a9cbf17ddc22652544dcb3e2326886ed1053b55 +F src/carray.c 3efe3982d5fb323334c29328a4e189ccaef6b95612a6084ad5fa124fd5db1179 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/date.c e19e0cfff9a41bfdd884c655755f6f00bca4c1a22272b56e0dd6667b7ea893a2 +F src/date.c 61e92f1f7e2e88e1cd91e91dc69eb2b2854e7877254470f9fabd776bfac922b8 F src/dbpage.c c9ea81c11727f27e02874611e92773e68e2a90a875ef2404b084564c235fd91f F src/dbstat.c 73362c0df0f40ad5523a6f5501224959d0976757b511299bf892313e79d14f5c -F src/delete.c 03a77ba20e54f0f42ebd8eddf15411ed6bdb06a2c472ac4b6b336521bf7cea42 -F src/expr.c 28b1cc3d2f147cc888703d5482f9581f17656d02abfa331c34370cb3350776be +F src/delete.c 901499bed747c3b4b2be45be1abe912ba50a3f6a40ba88cc006ccf279f2d0e97 +F src/expr.c 8c3b23cb35f43c2d0570c1058b9a269e561e769e09c81ba192992c95022c1939 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c 928ed2517e8732113d2b9821aa37af639688d752f4ea9ac6e0e393d713eeb76f -F src/func.c 0b802107498048d3dcac0b757720bcb8506507ce02159e213ab8161458eb293b +F src/fkey.c fb0f74c57d19a2d3f113f3476826919d68feda7ff334abfdb479a9a6353b9fcd +F src/func.c 6e7de3551ae0f8205006e5109f025223246edd20186d54d90746dee7c1c5c093 F src/global.c a19e4b1ca1335f560e9560e590fc13081e21f670643367f99cb9e8f9dc7d615b F src/hash.c 03c8c0f4be9e8bcb6de65aa26d34a61d48a9430747084a69f9469fbb00ea52ca F src/hash.h 46b92795a95bfefb210f52f0c316e9d7cdbcdd7e7fcfb0d8be796d3a5767cddf -F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 +F src/hwtime.h 21c2cf1f736e7b97502c3674d0c386db3f06870d6f10d0cf8174e2a4b8cb726e F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c dfd311b0ac2d4f6359e62013db67799757f4d2cc56cca5c10f4888acfbbfa3fd -F src/json.c fb031340edee159c07ad37dbe668ffe945ed86f525b0eb3822e4a67cbc498a72 +F src/json.c 8b6341a419150b28530cc21e3951b2238c35cdc312f11b2ca29017fe4b1dedc0 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c a3bc9a2522dc3b960e38b7582d1818f6245a49289387c2c7b19f27bfeabf1e81 -F src/main.c 65d11c17890966d271c925c6cc55e3ba50fa08374633cb99c0dee4719a20915a -F src/malloc.c 410e570b30c26cc36e3372577df50f7a96ee3eed5b2b161c6b6b48773c650c5e +F src/loadext.c 56a542244fbefc739a2ef57fac007c16b2aefdb4377f584e9547db2ce3e071f9 +F src/main.c 31a13302193fbd51279c7e69cdfa0320d0de7629f9151e0964c1d320e8bdd7a4 +F src/malloc.c 422f7e0498e1c9ef967f06283b6f2c0b16db6b905d8e06f6dbc8baaa3e4e6c5a F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 @@ -709,44 +703,44 @@ F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff F src/memdb.c a3feb427cdd4036ea2db0ba56d152f14c8212ca760ccb05fb7aa49ff6b897df3 F src/memjournal.c c283c6c95d940eb9dc70f1863eef3ee40382dbd35e5a1108026e7817c206e8a0 F src/msvc.h 80b35f95d93bf996ccb3e498535255f2ef1118c78764719a7cd15ab4106ccac9 -F src/mutex.c 06bcd9c3dbf2d9b21fcd182606c00fafb9bfe0287983c8e17acd13d2c81a2fa9 +F src/mutex.c 00b8cee206a67fd764d001f3a148494331d8d0b3b9c3974ecd69ff29bb444462 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 F src/mutex_unix.c f7ee5a2061a4c11815a2bf4fc0e2bfa6fb8d9dc89390eb613ca0cec32fc9a3d1 -F src/mutex_w32.c 28f8d480387db5b2ef5248705dd4e19db0cfc12c3ba426695a7d2c45c48e6885 +F src/mutex_w32.c e1d317d29cb623667d43de94714264d1e1871cc4bb39fa67dd17048e8138c739 F src/notify.c 57c2d1a2805d6dee32acd5d250d928ab94e02d76369ae057dee7d445fd64e878 F src/os.c 509452169d5ea739723e213b8e2481cf0e587f0e88579a912d200db5269f5f6d F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 -F src/os_kv.c fb7ba8d6204197357f1eb7e1c7450d09c10043bf7e99aba602f4aa46b8fb11a3 +F src/os_kv.c e7d96727db5b67e39d590a68cc61c86daf4c093c36c011a09ebfb521182ec28d F src/os_setup.h 8efc64eda6a6c2f221387eefc2e7e45fd5a3d5c8337a7a83519ba4fbd2957ae2 -F src/os_unix.c dcf7988ddbdd68619b821c9a722f9377abb46f1d26c9279eb5a50402fd43d749 -F src/os_win.c a89b501fc195085c7d6c9eec7f5bd782625e94bb2a96b000f4d009703df1083f -F src/os_win.h 4c247cdb6d407c75186c94a1e84d5a22cbae4adcec93fcae8d2bc1f956fd1f19 -F src/pager.c cd562b878ea1b44d021ba199abc9d3b54f6b3347500a9fed03f66d6000620945 +F src/os_unix.c fa5e09b4df35ad845440cad67b86908cfe1fd4c28c51915f82e23633d1992bf4 +F src/os_win.c 0d553b6e8b92c8eb85e7f1b4a8036fe8638c8b32c9ad8d9d72a861c10f81b4c5 +F src/os_win.h 5e168adf482484327195d10f9c3bce3520f598e04e07ffe62c9c5a8067c1037b +F src/pager.c fe34fd22ec251436985d7b6ebdd05bf238a17901c2cb23d3d28974dd2361a912 F src/pager.h 6137149346e6c8a3ddc1eeb40aee46381e9bc8b0fcc6dda8a1efde993c2275b8 -F src/parse.y 619c3e92a54686c5e47923688c4b9bf7ec534a4690db5677acc28b299c403250 +F src/parse.y 3b784d6083380a950e3b1b32ce5ddd303e8c7c209d8ab788df2c62aaf9ee8eb3 F src/pcache.c 588cc3c5ccaaadde689ed35ce5c5c891a1f7b1f4d1f56f6cf0143b74d8ee6484 -F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 +F src/pcache.h 092b758d2c5e4dabb30eae46d8dfad77c0f70b16bf3ff1943f7a232b0fe0d4ba F src/pcache1.c 131ca0daf4e66b4608d2945ae76d6ed90de3f60539afbd5ef9ec65667a5f2fcd F src/pragma.c ecec75795c1821520266e4f93fa8840cce48979af532db06f085e36a7813860f -F src/prepare.c 2af0b5c1ec787c8eebd21baa9d79caf4a4dc3a18e76ce2edbf2027d706bca37a -F src/printf.c 7297c2aeed4d90d80c5ba82920d9e57b7bfad04b3466be1d7e042db382fe296e +F src/prepare.c f6a6e28a281bd1d1da12f47d370a81af46159b40f73bf7fa0b276b664f9c8b7d +F src/printf.c 9cff219dba73b1aa9a8113e83e962f03f7bea8b6eb51cefb25bc468d5a69fb2d F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 5616fbcf3b833c7c705b24371828215ad0925d0c0073216c4f153348d5753f0a +F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c fa67ce1dd60744c315791085897c1571ffe5554bf3e0410942b5fc9d7e0a4b56 -F src/shell.c.in 2c7e751795f38bb1855c35b556419cab5b8ba22e0f6758f5a629338065d6b79f -F src/sqlite.h.in c0979f9ac1f5be887397dd2a0bb485636893a81b34d64df85123aae9650c42f2 +F src/select.c ffe199f025a0dd74670d2a77232bdea364a4d7b36f32c64a6572d39ba6a11576 +F src/shell.c.in d4e9ce266ca8f7364da6e86df011f8655beeb5f0d074d624215a2d8ce220a0ad +F src/sqlite.h.in 1f853f1d836af3e5a0b451521041d05658988a45f6978aaae08286e483fee5ac F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 -F src/sqlite3ext.h 7f236ca1b175ffe03316d974ef57df79b3938466c28d2f95caef5e08c57f3a52 -F src/sqliteInt.h 88f7fc9ce1630d9a5f7e0a8e1f3287cdc63882fba985c18e7eee1b9f457f59aa -F src/sqliteLimit.h fe70bd8983e5d317a264f2ea97473b359faf3ebb0827877a76813f5cf0cdc364 +F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca +F src/sqliteInt.h 1c7f23ab9d6efdf3dc434880b6320f158937284f6e2cebd2a024def0c749cb04 +F src/sqliteLimit.h 904a3f520362c7065c18165aaabd504fb13cc1b76cb411f38bd41ac219e4af1e F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c 3c604c49e6cf4211960a9ddb9505280fd22cde32175f40884c641c0f5a286036 +F src/tclsqlite.c 85b5a20df96016e5d1d8fdc68c8a4c279c5b93e2049b77cd806c2cc50b9d8c56 F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a -F src/test1.c 5d061afe479c7364842e0170be7220dea13389575fa6030d30b3e20bec4e1f75 +F src/test1.c 3e3b013f59ffcb57dce00c90d55907072d71d4e970cb0a590cb261efe11bae9c F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff F src/test3.c 432646f581d8af1bb495e58fc98234380250954f5d5535e507fc785eccc3987a F src/test4.c 0ac87fc13cdb334ab3a71823f99b6c32a6bebe5d603cd6a71d84c823d43a25a0 @@ -756,10 +750,10 @@ F src/test8.c 206d8f3cc73950d252906656e2646b5de0d580b07187b635fcb3edd8c2c5fbc0 F src/test9.c df9ddc7db6ef1b8cf745866ee229090779728bcbe660c7f297d3127ab21d92af F src/test_autoext.c 14d4bbd3d0bd1eec0f6d16b29e28cf1e2d0b020d454835f0721a5f68121ac10f F src/test_backup.c a2bfd90d2ff2511b8635507bdb30fa9b605ade19c16b533066cae3077f5bdb72 -F src/test_bestindex.c a9428931bec06de830b2630f57a7b1f2711761269f04df62b7aa1affcbce15bb +F src/test_bestindex.c d75fad21369d80910238032bcf8d9ca1f2bffda13c1ceec63bfbb7f704448b15 F src/test_blob.c 77b994e17f2c87055f44fd96c9a206c5a7155bae2cda2769af60c2f3582f962c F src/test_btree.c 28283787d32b8fa953eb77412ad0de2c9895260e4e5bd5a94b3c7411664f90d5 -F src/test_config.c 18aa596d37de1d5968c439fd58ebf38bc4d9c9d1db63621504e241fde375cecd +F src/test_config.c e02566c2c4ee2916324ce17123a798b47663cead2de546cfbd71d8cddb46bb26 F src/test_delete.c d0e8f6dc55cfc98a7c27c057fb88d512260564bf0b611482656c68b8f7f401ed F src/test_demovfs.c 3efa2adf4f21e10d95521721687d5ca047aea91fa62dd8cc22ac9e5a9c942383 F src/test_devsym.c 649434ed34d0b03fbd5a6b42df80f0f9a7e53f94dd1710aad5dd8831e91c4e86 @@ -779,7 +773,7 @@ F src/test_mutex.c dacae6790956c0d4e705aaed2090227792e291b0496cccd688e9994c1e21f F src/test_onefile.c f31e52e891c5fef6709b9fcef54ce660648a34172423a9cbdf4cbce3ba0049f4 F src/test_osinst.c 269039d9c0820a02ee928014c30860d57ee757ecda54df42e463d0ca1377b835 F src/test_pcache.c 496da3f7e2ca66aefbc36bbf22138b1eff43ba0dff175c228b760fa020a37bd0 -F src/test_quota.c 180e87437250bed7e17e4e61c106730939e39fec9be73d28961f27f579a92078 +F src/test_quota.c 5bb44452b9c6c248bb3c82d2466a20915aa6d12801f6c1784b6499aaa04d9811 F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d F src/test_rtree.c d844d746a3cc027247318b970025a927f14772339c991f40e7911583ea5ed0d9 F src/test_schema.c b06d3ddc3edc173c143878f3edb869dd200d57d918ae2f38820534f9a5e3d7d9 @@ -794,34 +788,34 @@ F src/test_vfs.c b4135c1308516adf0dfd494e6d6c33114e03732be899eace0502919b674586b F src/test_window.c 6d80e11fba89a1796525e6f0048ff0c7789aa2c6b0b11c80827dc1437bd8ea72 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c cb3294cf23c11106b50d9af6998a6c1bf389b52e15b17698c9fab97bbaa9b37f -F src/treeview.c 3ce7ac9835d2d70cc1c868b01b747ae8a062322e155701e58e3d62ca79aada7a -F src/trigger.c d5cf2541ff048f30b6a0507eb3d1ec4e695c53584e3b2298a5bf248714fe185e +F src/tokenize.c f297bbf02037639e7a93b37d9c6e4415b3de1273395ee8fa8183e741e1e7fb72 +F src/treeview.c feaa59f14db4f7b5aacca9c5ad5aeb562c1f98262c1ffd74371f4186ade91fc5 +F src/trigger.c 4bf3bfb3851d165e4404a9f9e69357345f3f7103378c07e07139fdd8aeb7bd20 F src/update.c 3e5e7ff66fa19ebe4d1b113d480639a24cc1175adbefabbd1a948a07f28e37cf F src/upsert.c 215328c3f91623c520ec8672c44323553f12caeb4f01b1090ebdca99fdf7b4f1 F src/utf.c 7267c3fb9e2467020507601af3354c2446c61f444387e094c779dccd5ca62165 -F src/util.c 36fb1150062957280777655976f3f9a75db236cb8207a0770ceae8d5ec17fcd3 -F src/vacuum.c 1bacdd0a81d2b5dc1c508fbf0d938c89fa78dd8d5b46ec92686d44030d4f4789 -F src/vdbe.c b44c366e83412d3b8c190feb1f029b7d02e1bd69252a57b32f195107f0d03964 -F src/vdbe.h be33bd7b17f2ec92939642416030491508c51071f6c14e27cd195983fec56b63 -F src/vdbeInt.h 2aaeb6df2938b181b4700a9328688a3986f2bba71e8b96f6a80671316618fa49 -F src/vdbeapi.c 869a0da5d855495055f4d35c6ada582f64ce995ce14b26ff9d336274d497266c -F src/vdbeaux.c 908d8a191aed444b2e4c920159249127f3ff67b94c56a16fad1dfdf9c7488f20 +F src/util.c eccfa8b3b414bb64c6543421c9fd10e5f07e103baae36427a273a9131527694c +F src/vacuum.c d3d35d8ae893d419ade5fa196d761a83bddcbb62137a1a157ae751ef38b26e82 +F src/vdbe.c 5328c99dd256ee8132383565a86e253543a85daccfd7477c52f20bac6b385a7f +F src/vdbe.h 966d0677a540b7ea6549b7c4e1312fc0d830fce3a235a58c801f2cc31cf5ecf9 +F src/vdbeInt.h 42488247a80cd9d300627833c6c85ace067ae5011a99e7614e2358130d62feea +F src/vdbeapi.c 6cdcbe5c7afa754c998e73d2d5d2805556268362914b952811bdfb9c78a37cf1 +F src/vdbeaux.c 396d38a62a357b807eabae0cae441fc89d2767a57ab08026b7072bf7aa2dd00c F src/vdbeblob.c b3f0640db9642fbdc88bd6ebcc83d6009514cafc98f062f675f2c8d505d82692 -F src/vdbemem.c 48e562ff27e6386eb8613207ac27d3d98c1f67fdc4775a1ab13759d2c2a1c021 +F src/vdbemem.c 317ec5e870ddb16951b606c9fe8be22baef22ecbe46f58fdefc259662238afb7 F src/vdbesort.c b69220f4ea9ffea5fdef34d968c60305444eea909252a81933b54c296d9cca70 F src/vdbetrace.c 49e689f751505839742f4a243a1a566e57d5c9eaf0d33bbaa26e2de3febf7b41 F src/vdbevtab.c fc46b9cbd759dc013f0b3724549cc0d71379183c667df3a5988f7e2f1bd485f3 F src/vtab.c 5437ce986db2f70e639ce8a3fe68dcdfe64b0f1abb14eaebecdabd5e0766cc68 F src/vxworks.h 9d18819c5235b49c2340a8a4d48195ec5d5afb637b152406de95a9436beeaeab -F src/wal.c 505a98fbc599a971d92cb90371cf54546c404cd61e04fd093e7b0c8ff978f9b6 +F src/wal.c 88d94fd15a75f6eda831fa32d1148a267ea37bf0a4b69829a73dfde06244b08f F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014 -F src/where.c 287324fe73a0ae8e55b3be89bb2fe4148e3a8394e1e2f10ed2113713a037d8a3 +F src/where.c 9f09ee7b260010138d5f9fb5f195b98051119eae3096a99d72ff16c83230f4af F src/whereInt.h 8d94cb116c9e06205c3d5ac87af065fc044f8cf08bfdccd94b6ea1c1308e65da -F src/wherecode.c 71c5c6804b7f882dec8ec858758accae02fcfca13df3cc720f1f258e663ec7c5 -F src/whereexpr.c 403a44eeec1a0f0914fccc6a59376b6924bc00ef6728fe6ffce4cf3051b320fc -F src/window.c 538195bbc75bb924e18e368fbd4ed731a3fe3f901351b44f6466ec486f53affe +F src/wherecode.c 783ecd30061c875c919a5163e4b55f9a0eccdaf7c9b17ad2908a1668a8766bc4 +F src/whereexpr.c e9f7185fba366d9365aa7a97329609e4cf00b3dd0400d069fbaa5187350c17c6 +F src/window.c c0a38cd32473e8e8e7bc435039f914a36ca42465506dc491c65870c01ddac9fb F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test 4d7a34d328e58ca2a2d78fd76c27614a41ca7ddf4312ded9c68c04f430b3b47d F test/affinity3.test 9b7d1133e11d5edd7805573c4ab6f3ba73b0b74a1f280d5b130d4bf3506a93ff @@ -836,12 +830,14 @@ F test/alter2.test 7e3d26ab409df52df887b366a63902c3429b935c41cb962fd58ffc25784f2 F test/alter3.test dcdd5f850f30656a45a0f05e41abfb52b74bbf6ccba165d0f7adf6b0116e4fd6 F test/alter4.test 37cafe164067a6590a0ee4cec780bddbbaa33dc50b11542dcfbe0e65626494fd F test/alterauth.test 63442ba61ceb0c1eeb63aac1f4f5cebfa509d352276059d27106ae256bafc959 -F test/alterauth2.test 48967abae0494d9a300d1c92473d99fcb66edfcc23579c89322f033f49410adc -F test/altercol.test b43fb5725332f4cf8cff0280605202c1672e808281accea60a066d2ccc5129e5 +F test/alterauth2.test 4b74fa8f184f4736497317feb587b65759eb87d87acfe3a8ef433d4d18bb002b +F test/altercol.test 3661c432aacb42bc2198dd4611bbb9c3b09fc73251b59edda046109103b8ac00 +F test/altercons.test ea18def4a0f26b9066da56095c9c480df705df4d02e4ae151708fae76f7e3884 +F test/altercons2.test 3c1f58312817df43aeada3b1827fdc3ce3fc50c6f49a95ef62cf4cbbae8583a0 F test/altercorrupt.test 2e1d705342cf9d7de884518ddbb053fd52d7e60d2b8869b7b63b2fda68435c12 F test/alterdropcol.test a653a3945f964d26845ec0cd0a8e74189f46de3119a984c5bc45457da392612e F test/alterdropcol2.test 527fce683b200d620f560f666c44ae33e22728e990a10a48a543280dfd4b4d41 -F test/alterfault.test 289067108947bedca27534edd4ff251bcd298cf84402d7b24eaa3749305418c6 +F test/alterfault.test 2bb3103954ea60f2e2777b1ae12e79ec3e1fd278f2b1398ad316c68835a62898 F test/alterlegacy.test f38c6d06cda39e1f7b955bbce57f2e3ef5b7cb566d3d1234502093e228c15811 F test/altermalloc.test 167a47de41b5c638f5f5c6efb59784002b196fff70f98d9b4ed3cd74a3fb80c9 F test/altermalloc2.test 17fb3724c4b004c469c27dc4ef181608aa644555fbd3f3236767584f73747c81 @@ -849,8 +845,8 @@ F test/altermalloc3.test 8040e486368403f2fdd6fc3998258b499bd4cc2f3ddbb5f8f874cd4 F test/alterqf.test 8ec03d776de9c391daa0078ea8f838903bdcfb11dfae4ba3576b48436834ccba F test/altertab.test 8a2712f9076da5012a002d0b5cc0a421398a5bf61c25bab41b77c427586a7a27 F test/altertab2.test 0889ba0700cc1cdb7bc7d25975aa61fece34f621de963d0886e2395716b38576 -F test/altertab3.test 471b8898d10bbc6488db9c23dc76811f405de6707d2d342b1b8b6fd1f13cd3c8 -F test/altertrig.test aacc980b657354fe2d3d4d3a004f07d04ccc1a93e5ef82d68a79088c274ddc6b +F test/altertab3.test 575e771e2f02b13eb98798dc92eabacd187d6dbcf596e70f11d699b0b6b5d0b2 +F test/altertrig.test b1590647076add5a47aea0f2236c609ca0bc8a7a2462463edd3e5882c7894802 F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f F test/analyze.test 2fb21d7d64748636384e6cb8998dbf83968caf644c07fcb4f76c18f2e7ede94b F test/analyze3.test c5156cef33f04b90a6b9e9d5d0bbc273a0fb44147d4508407bf1080811e2c6c8 @@ -867,6 +863,7 @@ F test/analyzeF.test 40b5cc3ad7b10e81020d7ca86f1417647ecfae7477cfd88acc5aa7ae106 F test/analyzeG.test 623be33038c49648872746c8dd8b23b5792c08fef173c55e82f1b12fca259852 F test/analyzer1.test b6a624ec0af92eec209e1328465b66937c8fdf2fb442a3fa45321ddb3700f4aa F test/atof1.test bd21c4a0e718ab1470de07a2a79f2544d7903be34feebcc80de04beee4807b00 +F test/atof2.test 12912add57230495450e2fc94cb8ad1c9f3277f8843a3bc27079cae45c9782a1 F test/atomic.test 065a453dde33c77ff586d91ccaa6ed419829d492dbb1a5694b8a09f3f9d7d061 F test/atomic2.test b6863b4aa552543874f80b42fb3063f1c8c2e3d8e56b6562f00a3cc347b5c1da F test/atrc.c c388fac43dbba05c804432a7135ae688b32e8f25818e9994ffba4b64cf60c27c @@ -880,7 +877,7 @@ F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 F test/auth3.test 76d20a7fa136d63bcfcf8bcb65c0b1455ed71078d81f22bcd0550d3eb18594ab F test/autoanalyze1.test b9cc3f32a990fa56669b668d237c6d53e983554ae80c0604992e18869a0b2dec F test/autoinc.test 9df9930966dbe92c55ef37a4d89112cfd537be0d0596d397177c12db9e581be0 -F test/autoindex1.test 65931519206bbec71948b11e125af0656435a0937973fe5fed70d776a712911f +F test/autoindex1.test 2523a76f30734742c3f4d948d0cbf3b6627f775e7833814f425a2e289ba58b22 F test/autoindex2.test 12ef578928102baaa0dc23ad397601a2f4ecb0df F test/autoindex3.test ca502c8050166ac6107a7b4fe4e951f4d3270a23a958af02b14f1b962b83c4b6 F test/autoindex4.test 3c2105e9172920e26f950ba3c5823e4972190e022c1e6f260ba476b0af24c593 @@ -888,7 +885,7 @@ F test/autoindex5.test 3fb938cbf4e7f3896563ce04e2a24b0bc653fc6245b4bf3268cd7b20f F test/autovacuum.test 00671369bbf96c6a49989a9425f5b78b94075d6a4b031e5e00000c2c32f365df F test/autovacuum2.test 76f7eb4fe6a6bf6d33a196a7141dba98886d2fb53a268d7feca285d5da4759d7 F test/autovacuum_ioerr2.test 8a367b224183ad801e0e24dcb7d1501f45f244b4 -F test/avfs.test 76f59743dc1f5fa533840d1818b420fe1ee45e21c0fd6bbac7942ba677903128 +F test/avfs.test 95bb8d04f8edad6dc9e600221d103f7e2cc3da398af84df215a3a819e560c45c F test/avtrans.test 7a6eae44763293024b137b53ff824d8500d754dbae060a8d940afbacfc1d4a15 F test/backcompat.test f2431465ed668f09fc3f6998e56e893a1506ccea6e8b6f409f085f759f431b48 F test/backup.test 3b08fd4af69f0fa786931103a31f4542b184aba16e239e5f22b18c3c2476697f @@ -908,13 +905,14 @@ F test/bestindex4.test 3039894f2dad50f3a68443dffad1b44c9b067ac03870102df1ce3d9a4 F test/bestindex5.test a0c90b2dad7836e80a01379e200e5f8ec9476d49b349af02c0dbff2fb75dc98d F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a6f99dd4 F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e -F test/bestindex8.test b63a4f171a2c83d481bb14c431a8b72e85d27b2ffdaa0435a95d58ca941678f9 +F test/bestindex8.test 4d8b1e8f30a7f405988ce4dbcc2b95c0775f0bed9ec08e0291a07e2f35f7e653 F test/bestindex9.test 1a4b93db117fd8abe74ae9be982f86aa72f01e60cd4ac541e6ede39673a451a0 F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572eef44c7f -F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce +F test/bestindexB.test 14db2f66ec9cc5064a74996033b74e5eec0fd2f3a327fbe34ff18de67e9d2671 F test/bestindexC.test 95b4a527b1a5d07951d731604a6d4cf7e5a806b39cea0e7819d4c9667e11c3fc F test/bestindexD.test 6a8f6f84990bcf17dfa59652a1f935beddb7afd96f8302830fbc86b0a13df3c3 F test/bestindexE.test 297f3ea8500a8f3c17d6f78e55bdfee089064c6144ee84a110bd005a03338f49 +F test/bestindexF.test 4e53d606cbde40a2254aa016d500c5b71766a4065b8541202d195a3d9fe11b1c F test/between.test e7587149796101cbe8d5f8abae8d2a7b87f04d8226610aa1091615005dcf4d54 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -949,7 +947,7 @@ F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4 F test/capi3c.test 31d3a6778f2d06f2d9222bd7660c41a516d1518a059b069e96ebbeadb5a490f7 F test/capi3d.test 8b778794af891b0dca3d900bd345fbc8ebd2aa2aae425a9dccdd10d5233dfbde F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe -F test/carray01.test 49e2aedfdf2c715bc002d2773cdc1217166679639542c79c8aa4115f06421407 +F test/carray01.test 17c1cf8287862b15dda949dba626fd5fee5c58471dcc1cae0341471c2ae7da01 F test/carray02.test 9d070b54f24a34d1f3b3c552ba34db0375a9d1c4219067416fb07d1595987c9d F test/carrayfault.test 108a7d83904fc267c448e27c13b2a857c700bd6ddaa2f1e2518be718b159cb6b F test/cast.test a2a3b32df86e3c0601ffa2e9f028a18796305d251801efea807092dbf374a040 @@ -967,7 +965,7 @@ F test/collate1.test 0890fa372753b59eba53832d37328af815f6b8e4b16761823180eeb62c8 F test/collate2.test 471c6f74573382b89b0f8b88a05256faa52f7964f9e4799e76708a3b1ece6ba4 F test/collate3.test 89defc49983ddfbf0a0555aca8c0521a676f56a5 F test/collate4.test c953715fb498b87163e3e73dd94356bff1f317bd -F test/collate5.test b1dfeff239ea69ee9225832553f423d37a6184eb730cee06f6846ab4e3c6dbef +F test/collate5.test 42daaf7799b04221206a219fb3c0f9efeede03e760f9562b8c0114b8df183fe3 F test/collate6.test 8be65a182abaac8011a622131486dafb8076e907 F test/collate7.test 8ec29d98f3ee4ccebce6e16ce3863fb6b8c7b868 F test/collate8.test cd9b3d3f999b8520ffaa7cc1647061fc5bab1334 @@ -1001,10 +999,10 @@ F test/corruptH.test 79801d97ec5c2f9f3c87739aa1ec2eb786f96454 F test/corruptI.test 9d8cbf6214e492abe9e822e759b9751ae336cec0a6fe3ff3b37bfbd8ff9c22ca F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4 F test/corruptK.test ac13504593d89d69690d45479547616ed12644d42b5cb7eeb2e759a76fc23dcb -F test/corruptL.test 652fc8ac0763a6fd3eb28b951d481924167b2d9936083bcc68253b2274a0c8fe +F test/corruptL.test f15de2b4729c0851ea89916a26766b094d74bac79f9f9f2b0191935aa3b344c9 F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 F test/corruptN.test a034bb217bebd8d007625dfb078e76ec3d53515052dbceb68bd47b2c27674d5c -F test/cost.test cc434a026b1e9d0d98137a147e24e5daf1b1ad09e9ff7da63b34c83ddd136d92 +F test/cost.test 3786cd1cc6d1ab416004a1e39387fb0db0c8e259f46b0bce62cbdc328f2c55a0 F test/count.test cd4bd531066e8d77ef8fe1e3fc8253d042072e117ccab214b290cf83f1602249 F test/countofview.test 4088e461a10ee33e69803c177a69aa1d7bba81a9ffc2df66d76465a22ca7fdfc F test/coveridxscan.test f35c7208dedc4f98e471c569df64c0f95a49f6e072d8dc7c8f99bdee2697de1b @@ -1035,7 +1033,7 @@ F test/dbfuzz.c fc566102f72c8af84ae8077b4faf7f056c571e6fa7a32e98b66e42b7505f47b6 F test/dbfuzz001.test 6c9a4622029d69dc38926f115864b055cb2f39badd25ec22cbfb130c8ba8e9c3 F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee F test/dbfuzz2.c 4b3c12de4d98b1b2d908ab03d217d4619e47c8b23d5e67f8a6f2b1bdee7cae23 -F test/dblwidth-a.sql eb4141518610e52f931a55a984310075e98dc31eee5a28ae806b1e35377be85a +F test/dblwidth-a.sql 59dd59aa78ce8fd8ab631a3816516831f4e947b143039257e6fe132c3cea4171 F test/dbpage.test 63fab1eb026bada121107e53436fa749bbf83281dc9dea17af422f7a5c0f289f F test/dbpagefault.test ea39de2ca86041a9c6df1135645180a76d0a8da93ac159e2fafe38e39636530b F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 @@ -1052,8 +1050,9 @@ F test/descidx2.test a0ba347037ff3b811f4c6ceca5fd0f9d5d72e74e59f2d9de346a9d2f6ad F test/descidx3.test 953c831df7ea219c73826dfbf2f6ee02d95040725aa88ccb4fa43d1a1999b926 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e F test/distinct.test 691c9e850b0d0b56b66e7e235453198cb4cf0760e324b7403d3c5abbeab0a014 -F test/distinct2.test 4d6316b6487a0aa5a90bee111575c957e2a5ba5a9be9156febe9533ce78876e8 +F test/distinct2.test a6af6a90b2c1eea64c3cc87ea7f8feb832053f7276fe3c212abacf646de4762a F test/distinctagg.test 40d7169ae5846caaf62c6e307d2ca3c333daf9b6f7cde888956a339a97afe85f +F test/dotcmd01.sql 0388a778912ed08436ae5c80e03389d8bd347fa724611193257a18c69692019d F test/e_blobbytes.test 4c01dfe4f12087b92b20705a3fdfded45dc4ed16d5a211fed4e1d2786ba68a52 F test/e_blobclose.test 692fc02a058476c2222a63d97e3f3b2b809c1842e5525ded7f854d540ac2e075 F test/e_blobopen.test 29f6055ee453b8e679fe9570c4d3acfedbef821622c5dad16875148c5952ef50 @@ -1063,7 +1062,7 @@ F test/e_createtable.test 31b9bcb6ac8876bc7ec342d86d9c231a84c62b442093a6651dfd0f F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e F test/e_droptrigger.test 235c610f8bf8ec44513e222b9085c7e49fad65ad0c1975ac2577109dd06fd8fa F test/e_dropview.test 74e405df7fa0f762e0c9445b166fe03955856532e2bb234c372f7c51228d75e7 -F test/e_expr.test 0a1e175caddc78b27306647cb4ce2362c55790190f8cdd178b75fd6262eb8f76 +F test/e_expr.test 9bdb347b78b9f4eff9153ea97797facc179a821898588471a70808b4471a69b0 F test/e_fkey.test feeba6238aeff9d809fb6236b351da8df4ae9bda89e088e54526b31a0cbfeec5 F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07 F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e @@ -1072,19 +1071,19 @@ F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8 F test/e_select.test 327a15f14068bbd6f647cedc67210f8680fcb2f05e481a0a855fccd2abfa1292 F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f F test/e_totalchanges.test c927f7499dc3aa28b9b556b7d6d115a2f0fe41f012b128d16bf1f3b30e9b41e4 -F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528 +F test/e_update.test 9f8bb82b8760ac66f2c9c2aadb78a418bd639d22f041d712570c9db56f20afda F test/e_uri.test 86564382132d9c453845eeb5293c7e375487b625900ab56c181a0464908417d8 F test/e_vacuum.test 89fc48e8beee2f9dfd6de1fbb2edea6542dae9121dc0fbe6313764169e742104 F test/e_wal.test db7c33642711cf3c7959714b5f012aca08cacfa78da0382f95e849eb3ba66aa4 F test/e_walauto.test 248af31e73c98df23476a22bdb815524c9dc3ba8 -F test/e_walckpt.test 28c371a6bb5e5fe7f31679c1df1763a19d19e8a0 +F test/e_walckpt.test 16e7d006e8687654ee59e7ad5a6d285ba23f0fe0eeb87f790afd6bc9cf1d1924 F test/e_walhook.test 01b494287ba9e60b70f6ebf3c6c62e0ffe01788e344a4846b08e5de0b344cb66 F test/emptytable.test a38110becbdfa6325cd65cb588dca658cd885f62 F test/enc.test b5503a87b31cea8a5084c6e447383f9ca08933bd2f29d97b6b6201081b2343eb F test/enc2.test 872afe58db772e7dfa1ad8e0759f8cc820e9efc8172d460fae83023101c2e435 F test/enc3.test 55ef64416d72975c66167310a51dc9fc544ba3ae4858b8d5ab22f4cb6500b087 F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 -F test/eqp.test 746db9fe11629a0d00328e1721cc2a2e4726d574b677ab14de35fd914f54cc82 +F test/eqp.test 1d653fe8d2612cd6764e5ea2f16dcf5f13a9f50448b9233bb1573804bccd7579 F test/eqp2.test 6e8996148de88f0e7670491e92e712a2920a369b4406f21a27c3c9b6a46b68dd F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9 F test/errofst1.test 6da78363739ba8991f498396ab331b5d64e7ab5c4172c12b5884683ef523ac53 @@ -1104,7 +1103,7 @@ F test/extension01.test 5de412c66276105901c370770175003381fdcb0c4da7054fa43cf4a3 F test/external_reader.test 6fdec43eeca23eb32faad1e95a4d1abc402bc8b3db70df12d6fc08a637f4a2b5 F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 F test/fallocate.test 37a62e396a68eeede8f8d2ecf23573a80faceb630788d314d0a073d862616717 -F test/filectrl.test 4b720117388cf6766d0b798e2dddd785953f8f371633b0c0084d2f34cf72336a +F test/filectrl.test 3344987f143f3cb9914895dc63d9e9518a535ef1b1438d22caf3ba2fa76cc6da F test/filefmt.test f393e80c4b8d493b7a7f8f3809a8425bbf4292af1f5140f01cb1427798a2bbd4 F test/filter1.test 590f8ba9a0cd0823b80d89ac75c5ce72276189cef9225d2436adaf1ee87f3727 F test/filter2.tcl 44e525497ce07382915f01bd29ffd0fa49dab3adb87253b5e5103ba8f93393e8 @@ -1123,7 +1122,8 @@ F test/fordelete.test ba98f14446b310f9c9d935b97ec748753d0144a28b356ba30d1f4f6958 F test/fork-test.c 9ac2e6423a1d38df3d6be0e8ac15608b545de21e2b19d9d876254c5931b63edb F test/format4.test eeae341953db8b6bda7f549044797c3278a6cc345d11ada81471671b654f8ef4 F test/fp-speed-1.c b37de94eba034e1703668816225f54510ec60fb0685406608cc707afe6b8234d -F test/fpconv1.test d5d8aa0c427533006c112fb1957cdd1ea68c1d0709470dabb9ca02c2e4c06ad8 +F test/fpconv1.test 63f352682fa65601a326563ad633086df6ab194e6ed5e7366786f38a525a7fd7 +F test/fptest01.sql 210562ad8d5a7895f26273dd3be56561a41bcb51d78a28a337af0f1ceaa3bb8d F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c F test/fts3.test 672a040ea57036fb4b6fdc09027c18d7d24ab654 F test/fts3_common.tcl dffad248f9ce090800e272017d2898005c28ee6314fc1dd5550643a02666907a @@ -1149,7 +1149,7 @@ F test/fts3aux1.test 1880eaa75c586cd10f53080479a2b819b3915ae7ce55c4e0ba8f1fe05ac F test/fts3aux2.test 2459e7fa3e22734aed237d1e2ae192f5541c4d8b218956ad2d90754977bf907f F test/fts3b.test c15c4a9d04e210d0be67e54ce6a87b927168fbf9c1e3faec8c1a732c366fd491 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 -F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c +F test/fts3comp1.test 73a53ada3d25bf242c4b2a24cfe9d39e658be56cfa74754279b9e6db776ed7ce F test/fts3conf.test c9cd45433b6787d48a43e84949aa2eb8b3b3d242bac7276731c1476290d31f29 F test/fts3corrupt.test 6732477c5ace050c5758a40a8b5706c8c0cccd416b9c558e0e15224805a40e57 F test/fts3corrupt2.test e318f0676e5e78d5a4b702637e2bb25265954c08a1b1e4aaf93c7880bb0c67d0 @@ -1199,7 +1199,7 @@ F test/fts3tok_err.test 52273cd193b9036282f7bacb43da78c6be87418d F test/fts3varint.test 0b84a3fd4eba8a39f3687523804d18f3b322e6d4539a55bf342079c3614f2ada F test/fts4aa.test 0e6bfd6a81695a39b23e448dda25d864e63dda75bde6949c45ddc95426c6c3f5 F test/fts4check.test f0ea5e5581951d8ef7a341eea14486daf6c5f516a2f3273b0d5e8cb8a6cd3bd2 -F test/fts4content.test 73bbb123420d2c46ef2fb3b24761e9acdb78b0877179d3a5d7d57aada08066f6 +F test/fts4content.test 7f441866207f5b1e76e0f18bde5d9925d1ee8f60388054613dd14a29a79f0bc4 F test/fts4docid.test e33c383cfbdff0284685604d256f347a18fdbf01 F test/fts4growth.test 289833c34ad45a5e6e6133b53b6a71647231fb89d36ddcb8d9c87211b6721d7f F test/fts4growth2.test 13ad4e76451af6e6906c95cdc725d01b00044269 @@ -1211,7 +1211,7 @@ F test/fts4merge.test 57d093660a5093ae6e9fbd2d17592a88b45bbd66db2703c4b640b28828 F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891 F test/fts4merge3.test 8d9ccb4a3d41c4c617a149d6c4b13ad02de797d0 F test/fts4merge4.test 66fce89934cd9508cbdc67de486558c34912ffb2e8ffe5c9a1bbb9b8a4408ba7 -F test/fts4merge5.test 69932d85cda8a1c4dcfb742865900ed8fbda51724b8cf9a45bbe226dfd06c596 +F test/fts4merge5.test 987af90c930e8555f74ab994f597431caec7f8defc52de7718655c32da07af9e F test/fts4min.test 1c11e4bde16674a0c795953509cbc3731a7d9cbd1ddc7f35467bf39d632d749f F test/fts4noti.test d5d933705b1b1516b67a5e3f8e514ecb19c6522fb3357bb744776d48427c2292 F test/fts4onepass.test d69ddc4ee3415e40b0c5d1d0408488a87614d4f63ba9c44f3e52db541d6b7cc7 @@ -1238,7 +1238,7 @@ F test/fuzz3.test 70ba57260364b83e964707b9d4b5625284239768ab907dd387c740c0370ce3 F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 34a025386f84d818cd3343e69e9d9083091af83153e226d71d4e1c126b5f1dd0 +F test/fuzzcheck.c 9096506277f33cc242eb59743c409c81306492b6ebb84571198f864e536ebe22 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1250,7 +1250,7 @@ F test/fuzzdata8.db 8f34ae00d8d5d4747dd80983cf46161065e4f78324dcff3c893506ff8db3 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc -F test/fuzzinvariants.c 3ddfec7f5b970b018f1a982532de905cf180e0c1e48cd653be9365d3e6177625 +F test/fuzzinvariants.c 6768bcd03290776cd982624729d2abee2e89e6aba62b4a2b839a98332725a167 F test/gcfault.test 4ea410ac161e685f17b19e1f606f58514a2850e806c65b846d05f60d436c5b0d F test/gencol1.test ceb3163b59cb77f4ad57ae4f01a143ce36b06fdd6a8dab1149235db89979ffd8 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 @@ -1261,6 +1261,8 @@ F test/hook.test 2d89bf9480646feb8093be3a58ea502d6521906779ed960de31dd9c4502c054 F test/hook2.test b9ff3b8c6519fb67f33192f1afe86e7782ee4ac8 F test/icu.test 8da7d52cd9722c82f33b0466ed915460cb03c23a38f18a9a2d3ff97da9a4a8c0 F test/ieee754.test 0d3ab84ab2069c9994c833a7cd820ee6037f0cf888e206a4a7fc05f735d5790a +F test/import01.sql 11a5f8325b8b04c66fe489d30558d462411432adf750cef1c0f7b06564691a8c +F test/imposter1.sql fc5ad0945bb19622688c7a1cd7dfd1cefa4b013bac9e2628c22b03c7309f021f F test/imposter1.test 5a20b2cdeb53e65fc57cdb10a33750bd4ef6259909eaf1972253b9e79f7a3fb2 F test/in.test edf979bff3244b9e47849e2b43886631354c8213791f42da92216f08012141af F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 @@ -1300,11 +1302,12 @@ F test/insert.test 97cfb30b83ca1622b9422a1e4c4831b4cb767cf5d654660945036d1e72067 F test/insert2.test 4d14b8f1b810a41995f6286b64a6943215d52208 F test/insert3.test 1b7db95a03ad9c5013fdf7d6722b6cd66ee55e30 F test/insert4.test 2bf81535a990c969665d66db51fcf76c23499b39893b5109f413d1de4ad34cd3 -F test/insert5.test 394f96728d1258f406fe5f5aeb0aaf29487c39a6 +F test/insert5.test 79f6b6efd0d3db5f4e3ff442300b7d9e7185adb345b29aacc3ea5a9c58ab9beb F test/insertfault.test ac63d14ea3b49c573673a572f4014b9117383a03e497c58f308b5c776e4a7f74 F test/instr.test 67ba309e9697c24a304e98a7c8f372456177dd4e32237d2a305e1e05f7bb79c2 F test/instrfault.test 95e28efade652e6d51ae11b377088fe523a581a07ec428009e152a4dd0e0f44c F test/intarray.test bb976b0b3df0ebb6a2eddfb61768280440e672beba5460ed49679ea984ccf440 +F test/intck01.sql f2d88bf41cdd64f2ed8c3d4f357cf520f017aa2986999ab9a62eb6506ef18106 F test/interrupt.test ac1ef50ec9ab8e4f0e17c47629f82539d4b22558904e321ed5abea2e6187da7a F test/interrupt2.test e4408ca770a6feafbadb0801e54a0dcd1a8d108d F test/intpkey.test 7d54711acf553cdd641a40e9c6cfc2bf1a76070074940c1b126442517054320f @@ -1317,7 +1320,7 @@ F test/ioerr4.test f130fe9e71008577b342b8874d52984bd04ede2c F test/ioerr5.test 5984da7bf74b6540aa356f2ab0c6ae68a6d12039a3d798a9ac6a100abc17d520 F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b F test/istrue.test e7f285bb70282625c258e866ce6337d4c762922f5a300e1b50f958aef6e7d9c9 -F test/join.test 2fcfd84640cfd9ff48f31b4b0d370c4d5498c355ae4384544668ca54d37ae186 +F test/join.test c706b382ed09ddc89eee7ad0ffd08d862655b0abc292a690d41d995c18c17b3f F test/join2.test f59d63264fb24784ae9c3bc9d867eb569cd6d442da5660f8852effe5c1938c27 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 @@ -1333,7 +1336,7 @@ F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 F test/joinH.test 1d2fc3190be68525fd9ce749b9468c40ba2930181e52fb5ee6f836051b38effb -F test/joinI.test fc7d24a2b1e444979b83bd92c30ebb975cebb5b9eae4442ce94969bd8d083053 +F test/joinI.test 249802b168ce96d8d57943ef9abafc1e36e28d91829f68bc2b6e87f2b4d33241 F test/journal1.test bc61a4228db11bffca118bd358ba4b868524bf080f3532749de6c539656e20fa F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test e5aeff93a7776cf644dbc48dec277655cff80a1cd24689036abc87869b120ea6 @@ -1346,13 +1349,14 @@ F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb9 F test/json/json-speed-check.sh 7d5898808ce7542762318306ae6075a30f5e7ee115c4a409f487e123afe91d88 x F test/json/jsonb-q1.txt 1e180fe6491efab307e318b22879e3a736ac9a96539bbde7911a13ee5b33abc7 F test/json101.test cf53254f0f0c1399a01b21fc58fee0e63a12a556be91b9ee9faccdb8b82c083c -F test/json102.test 9b2e5ada10845ff84853b3feaae2ce51ce7145ae458f74c6a6cecc6ef6ee3ae1 -F test/json103.test 355746a6b66aa438f214b4fae454b13068fad2444b5f693e0d538ad1c059b264 +F test/json102.test ea5c9811e408e115c8fc539548deef431fda4924c23cacd79dd4b783f4449f07 +F test/json103.test e626d109cd0bdb8282ec9bf755af3befa50e3e03a255362fc53433d31e1d66d4 F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 -F test/json105.test 043838b56e68f3252a0dcf5be1689016f6f3f05056f8dcfcdc9d074f4d932988 +F test/json105.test 9900caa21888289873bc6c14f5ee41213d28ac9b7ca3395f8afb73d540e80f66 F test/json106.test 4aed3afd16549045d198a8d9cea00deea96e1f2ecf55864dce96cac558b8abef F test/json107.test 59054e815c8f6b67d634d44ace421cf975828fb5651c4460aa66015c8e19d562 F test/json108.test 0a5f1e2d4b35a1bc33052563d2a5ede03052e2099e58cb424547656c898e0f49 +F test/json109.test 441cea5d73c24a1a34d284101740dfae5a082237c048c8a66b03aeebe5e3643e F test/json501.test b95e2d14988b682a5cadf079dd6162f0f85fb74cd59c6b1f1624110104a974eb F test/json502.test 4ef68e4f272dfb083d4cbceb4e9e51d67ec1186a185e0c13637c50a4dc2f9796 F test/jsonb01.test f4cdfb4cf5a0c940091b17675ed9583f45add0c938f07d65b0de0e19d3a9a101 @@ -1425,7 +1429,7 @@ F test/misc1.test e3e36262aff1bd9b8b9bf1eeb3af04adb3fc1e23f0a92dbff708bba9e939ac F test/misc2.test a1a3573cc02662becd967766021d6f16c54684d56df5f227481c7ef0d9df0bd0 F test/misc3.test 651b88bca19b8ff6a7b6af73dae00c3fd5b3ea5bee0c0d1d91abd4c4b4748718 F test/misc4.test 10cd6addb2fa9093df4751a1b92b50440175dd5468a6ec84d0386e78f087db0e -F test/misc5.test 02fcaf4d42405be02ec975e946270a50b0282dac98c78303ade0d1392839d2b8 +F test/misc5.test 0a5d7604e197f10ee471280bfcaaf8229f9d8e2eebfef2c8853222cbc1ea9cd5 F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 F test/misc7.test d595599972ec0b436985f0f02f243b68500ffc977b9b3194ec66c0866cfddcab F test/misc8.test 08d2380bc435486b12161521f225043ac2be26f02471c2c1ea4cac0b1548edbd @@ -1438,11 +1442,12 @@ F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93 F test/mmapcorrupt.test 470fb44fe92e99c1d23701d156f8c17865f5b027063c9119dcfdb842791f4465 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3 +F test/modeA.sql 3f2b5a7ce7074a52b2b7ec07b07dc1a08edba19e40bce9b4d65d3965413bbea3 F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7 F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4 -F test/mutex1.test 42cb5e244c3a77bb0ef2b967e06fa5e7ba7d32d90a9b20bed98f6f5ede185a25 +F test/mutex1.test 2cdc320a3320521d73b8090a04a2245c1e625e5f90672882517bf5fedcec8f13 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660 F test/nan.test 73ea63ab43668313e2f8cc9ef9e9a966672c7934f3ce76926fbe991235d07d91 F test/nockpt.test 3db354270fc63b6871eebd40285d4c55324fb27be629c958adbff6d7fcaa8e14 @@ -1452,13 +1457,13 @@ F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 F test/notify3.test 796c7b7157f55c93b4e672b724e9c923a6fc6aa72ac419379a623e2350472e22 F test/notnull.test a37b663d5bb728d66fc182016613fb8e4a0a4bbf3d75b8876a7527f7d4ed3f18 -F test/notnull2.test 5b7dd6e82c409b2d011ad6acf19ae4bf0816a9c69ccf600b529d7405d7c49874 +F test/notnull2.test c2c7b670fb8fa6ffe5f9cc08af88864fbb8237e28b56ad528e8dee921019c5fe F test/notnullfault.test fc4bb7845582a2b3db376001ef49118393b1b11abe0d24adb03db057ee2b73d5 F test/null.test b7ff206a1c60fe01aa2abd33ef9ea83c93727d993ca8a613de86e925c9f2bc6f F test/nulls1.test 7a5e4346ee4285034100b4cd20e6784f16a9d6c927e44ecdf10034086bbee9c9 F test/numcast.test 5d126f7f581432e86a90d1e35cac625164aec4a1 F test/numindex1.test 20a5450d4b056e48cd5db30e659f13347a099823 -F test/offset1.test 72cca52482cbd5bc687cfa67aa2566c859081b5a353fd2f9da9bbd3914dea1ef +F test/offset1.test c21e67d2d5ae8ed310243fbe84fc2f0dca49e9ffed3ea89110c0d5914c0de620 F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394 F test/optfuzz-db01.c 9f2fa80b8f84ebbf1f2e8b13421a4e0477fe300f6686fbd76cac1d2db66e0fdc F test/optfuzz-db01.txt 21f6bdeadc701cf11528276e2a55c70bfcb846ba42df327f979bd9e7b6ce7041 @@ -1507,6 +1512,12 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd +F test/qrf01.test abc3e558a75ae2678a3172051b39960dc6fd4b298b6d594afa50939759f4037f +F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92 +F test/qrf03.test e7efe46d204671726b4707585126cd78d107368de4a7d0c7b8d5157cdd8624ed +F test/qrf04.test 0894692c998d2401dcc33449c02051b503ecce0c94217be54fb007c82d2d1379 +F test/qrf05.test 8ade5bfa7ef0b448e531687203fa8ae9ef41f1d7e4c11d5ba0c4846af75b13d5 +F test/qrf06.test cd7d0f0e2904904ab88141630a8fff5718ef7e3cc23e5a9c519cf29bb0919d89 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a @@ -1519,8 +1530,9 @@ F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 F test/readonly.test 0d307c335b3421898cfe64a783a376138aa003849b6bff61ee2d21e805bc0051 -F test/recover.test c76d05f33f0271fba0f0752170e03b0ab5952dc61dcea7ab3ba40df03c4c42de -F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 +F test/recover.test 643139b911ac880a1e881d7621f02cfb546b608b8f2494d7d26fd5ed103b1ceb +F test/regexp1.sql de2b5b33b16b664d655b41e780f2efca38de3e5559fc254b4c9783ff0bea96b0 +F test/regexp1.test 0023eae4073265641b826a70d81ba34d4dd66ad71871a5b4a1b7cf500d5c0c51 F test/regexp2.test 64f9726b2ddc71aea06725fcad53231833d038d58b936d49083ace658b370a13 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d F test/reservebytes.test 6163640b5a5120c0dee6591481e673a0fa0bf0d12d4da7513bad692c1a49a162 @@ -1538,13 +1550,13 @@ F test/rowid.test d27191b5ce794c05bf61081e8b2c546a1844c1641321dcaf7fb785234256cc F test/rowvalue.test 93474d8e1c496e970bdcc3a7f54ac835adda667d2fd971957b4bce0c0b11707b F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b F test/rowvalue3.test 103e9a224ca0548dd0d67e439f39c5dd16de4200221a333927372408c025324c -F test/rowvalue4.test bac9326d1e886656650f67c0ec484eb5f452244a8209c6af508e9a862ace08ed +F test/rowvalue4.test 6e160977d44ee715e142f63ec0e339586c61f12bbbffacee369b1cdc0b7390f0 F test/rowvalue5.test 00740304ea6a53a8704640c7405690f0045d5d2a6b4b04dde7bccc14c3068ea7 F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087 F test/rowvalue7.test 06ec0aca725bf683313d03793aa2943bc7f45a901848c7056a9665b769c8fc38 F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0 F test/rowvalue9.test 7499a8fd7ca3a3f0e19d94e135355439aa2b596f86b775ca8de79672da2ca378 -F test/rowvalueA.test be8d6ad8b476eb24c151bb20bfd487e0d50c5e99618b7b0e656035069d2fc2cf +F test/rowvalueA.test 1c5ed13f3b0641452ae35e6488d6ecc16cefce99f2adf7c07c513530e2aac6b7 F test/rowvaluefault.test 963ae9cdaed30a85a29668dd514e639f3556cae903ee9f172ea972d511c54fff F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 @@ -1558,7 +1570,7 @@ F test/savepoint7.test 24c69af86d750c80d51cf6500fde9270717f2b6e5658f055b5e75af75 F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 F test/scanstatus.test b249328caf4d317e71058006872b8012598a5fa045b30bf24a81eeff650ab49e F test/scanstatus2.test d85d17f2b0b4c013dde95232f7beab749f11f0ef847f5ecffb9486d2f5ecf9f9 -F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431 +F test/schema.test e615575f2d756df4629596523f11d9322384ecf9f980e58c774cff80ff041c33 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5c9ce F test/schema4.test 3b26c9fa916abb6dadf894137adcf41b7796f7b9 @@ -1576,7 +1588,7 @@ F test/select5.test 8afc5e5dcdebc2be54472e73ebd9cd1adef1225fd15d37a1c62f969159f3 F test/select6.test da91e61d26b8dea4b61e4a862088dd6ab19998f7be22a16a5b0cfe806e597639 F test/select7.test b825420da8a0b5722fdb77f3369f6396a3d198c46e8787eb26ff9425d4ac9d27 F test/select8.test 8c8f5ae43894c891efc5755ed905467d1d67ad5d -F test/select9.test f7586b207ce2304ab80dc93d3146469a28fd4403621dd3a82d06644563d3c812 +F test/select9.test 108ceff733f31698fef41eb9a0c332f150c54e98be534ee38019a19943f3f5ae F test/selectA.test 1da8ce3884c326e11d2855baffb76436b0d7e044404af8a2a70d1399a4ff7e29 F test/selectB.test 954e4e49cf1f896d61794e440669e03a27ceea25 F test/selectC.test 38c530b0cc5728b793c3c11f52b52c70290d39822224acd39011c89c1853bd31 @@ -1600,16 +1612,17 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test ebe953d64c937ad42a0f33170ac0d2d2568faae26813fc7a95203756446d54aa -F test/shell2.test ab23f01ea2347e4b72bb2399af7ee82aa00f9c059141749f7c4064abca5ad728 +F test/shell1.test 2d658ceee13d9e4361d04d0ea16340ad17784ddf378fb6e9ca6d49c682cb4bae +F test/shell2.test dc541d2681503e55466a24d35a4cbf8ca5b90b8fcdef37fc4db07373a67d31d3 F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e -F test/shell4.test 03593fa7908a55f255916ffeda707cdf55680c777736e3da62b1d78cde0d684d -F test/shell5.test d17e7927ab8b7f720efbdd9b5d05fceb6c3c56c25917901b315400214bf24ef4 +F test/shell4.test e25580a792b7b54560c3a76b6968bd8189261f38979fe28e6bc6312c5db280db +F test/shell5.test a9cd2c8b62e125049ef500937674f47dd6787f0157ac0515aa554044a4dc3ea9 F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8 F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3 -F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871 +F test/shell8.test 38c9e4d7e85d2a3ecfacaa9f6cda4f7a81bf4fffb5f3f37f9cd76827c6883192 F test/shell9.test 8742a5b390cdcef6369f5aa223e415aa4255a4129ef249b177887dc635a87209 -F test/shellA.test 4ecff8b7b2c0122ba8174abfbcc4b0f59e44d80f2a911068f8cd4cfc6661032d +F test/shellA.test 05cdaafa1f79913654487ce3aefa038d4106245d58f52e02faf506140a76d480 +F test/shellB.test 7123d231158588401f332bf278754687b83ba5fc5b352ec8679fb19edfb4cc0a F test/shmlock.test 9f1f729a7fe2c46c88b156af819ac9b72c0714ac6f7246638a73c5752b5fd13c F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@ -1643,8 +1656,8 @@ F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c -F test/speedtest.md ee958457ae1b729d9715ae33c0320600000bf1d9ddea1a88dcf79f56729d6fad -F test/speedtest.tcl 6b66974d833d35a63d0e9ec344e0ffa92fbbfac83e173556f700a61cb3be96fc x +F test/speedtest.md ea0c85ebe0ecff8b45ba6cdb26e694871f469009a5a29dcfe634b055f05ab241 +F test/speedtest.tcl b06f6321ef90bb68f18f7b0e430e25203d9da79b80f8926986a0d5f21ac485fb x F test/speedtest1.c 6c01252e66f46de0b6b8d5316e03521e2151782104f3608c10262aa5dce85721 F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 @@ -1679,23 +1692,24 @@ F test/sync.test a619e407ede58a7b6e3e44375328628559fc9695a9c24c47cb5690a866b0031 F test/sync2.test 06152269ed73128782c450c355988fe8dd794d305833af75e1a5e79edd4dae47 F test/syscall.test a067468b43b8cb2305e9f9fe414e5f40c875bb5d2cba5f00b8154396e95fcf37 F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04 -F test/tabfunc01.test 56eeae736217204bb1d9f9ef38340d48058f809b64249217cf77ff4ba600cc21 +F test/tabfunc01.test cfa96a9a235c39fb0cae69928b989b28bfec108f62d2533486f76e32dcedfdfb F test/table.test e87294bf1c80bfd7792142b84ab32ea5beb4f3f71e535d7fb263a6b2068377bf F test/tableapi.test e37c33e6be2276e3a96bb54b00eea7f321277115d10e5b30fdb52a112b432750 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 -F test/tclsqlite.test 3f697424cfc1cdc9c076ec0cadb0e700f059400a3e3ce134b7d856fc9f880e1c +F test/tclsqlite.test 5d6c73bfe7006c85e2f7fb7db8638b521eb2043d5451aaacdac4851eab895443 F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cbd08 F test/tempdb2.test 353864e96fd3ae2f70773d0ffbf8b1fe48589b02c2ec05013b540879410c3440 F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900 F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 -F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl 463ae33b8bf75ac77451df19bd65e7c415c2e9891227c7c9e657d0a2d8e1074a +F test/temptrigfault.tes fc5918e64f3867156fefe7cfca9d8e1f495134a5229b2b511b0dc11c07f2eab4 +F test/temptrigger.test a00f258ed8d21a0e8fd4f322f15e8cfb5cef2e43655670e07a753e3fb4769d61 +F test/tester.tcl 2d943f60200e0a36bcd3f1f0baf181a751cd3604ef6b6bd4c8dc39b4e8a53116 F test/testloadext.c 862b848783eaed9985fbce46c65cd214664376b549fae252b364d5d1ef350a27 -F test/testrunner.tcl 60d7efa1816c5dfc37df3e3454b94b9042c0c8c50b27ae296d4a797cd309ace6 x -F test/testrunner_data.tcl c507a9afa911c03446ed90442ffd4a98aca02882c3d51bd1177c24795674def8 -F test/testrunner_estwork.tcl 7927a84327259a32854926f68a75292e33a61e7e052fdbfcb01f18696c99c724 +F test/testrunner.tcl 78d67079fc39caf2af3fd9d4c30bdac78dae7ec50b9fc802835e7a5189581e07 x +F test/testrunner_data.tcl 078e251983c8fc573567125147655f68132210f226c92922daf21fb913779717 +F test/testrunner_estwork.tcl 81e2ae10238f50540f42fbf2d94913052a99bfb494b69e546506323f195dcff9 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1744,7 +1758,7 @@ F test/tkt-8454a207b9.test ead80b7a01438ca1436cee029694a96c821346cf1e24f06de12f8 F test/tkt-868145d012.test a5f941107ece6a64410ca4755c6329b7eb57a356 F test/tkt-8c63ff0ec.test 258b7fc8d7e4e1cb5362c7d65c143528b9c4cbed F test/tkt-91e2e8ba6f.test 08c4f94ae07696b05c9b822da0b4e5337a2f54c5 -F test/tkt-99378177930f87bd.test 9d6cff39b50d062c813ae1cb0ebbd1b7acf81ecc23ae5d5215e5bb05667dc137 +F test/tkt-99378177930f87bd.test 1ee631d155f0d51a4547e9405ef35a3a9a32977352a37a10bcbbacc5e38356ad F test/tkt-9a8b09f8e6.test b2ef151d0984b2ebf237760dbeaa50724e5a0667 F test/tkt-9d68c883.test 16f7cb96781ba579bc2e19bb14b4ad609d9774b6 F test/tkt-9f2eb3abac.test cb6123ac695a08b4454c3792fbe85108f67fabf8 @@ -1792,7 +1806,7 @@ F test/tkt2213.test a9702175601a57b61aba095a233b001d6f362474 F test/tkt2251.test 5aab8c7898cd2df2a68fe19289cc29e8f5cf8c82 F test/tkt2285.test cca17be61cf600b397188e77e7143844d2b977e9 F test/tkt2332.test fc955609b958ca86dfa102832243370a0cc84070 -F test/tkt2339.test 73bd17818924cd2ac442e5fd9916b58565739450 +F test/tkt2339.test bad48bd064594aa7b4de23f6d59a72b0b0c4175fd917f4b66907584732d41652 F test/tkt2391.test ab7a11be7402da8b51a5be603425367aa0684567 F test/tkt2409.test be0d60e7d283f639dccea4b0b5e1cd3a4851fb5b F test/tkt2450.test 77ed94863f2049c1420288ddfea2d41e5e0971d6 @@ -1926,15 +1940,15 @@ F test/vacuum4.test 7ea76b769fffeb41f925303b04cbcf5a5bbeabe55e4c60ae754ff24eeeb7 F test/vacuum5.test 263b144d537e92ad8e9ca8a73cc6e1583f41cfd0dda9432b87f7806174a2f48c F test/vacuum6.test b137b04bf3392d3f5c3b8fda0ce85a6775a70ca112f6559f74ff52dc9ce042fd F test/vacuummem.test 4b30f5b95a9ff86e9d5c20741e50a898b2dc10b0962a3211571eb165357003fb -F test/values.test 0eda08a6ce6545f1ab012dff4cc72a7dd0fee2510f42444136bb2b2b5ed84bc0 +F test/values.test 0e037c50789ac2a308746567d07b53b2f6026c1bb3a435d1b099424600e64caf F test/valuesfault.test 2ef23ed965e3bd08e268cdc38a0d11653390ddbbe1e8e2e98d16f55edd30f6e8 F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 F test/view.test 3c23d7a068e9e4a0c4e6907498042772adea725f0630c3d9638ffd4e5a08b92b F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456 -F test/vt02.c 5b44ac67b1a283fedecf2d6e2ceda61e7a157f01d44dcb4490dcb1e87d057060 -F test/vt100-a.sql 631eeab18c5adb531bab79aecf64eee3934b42c75a309ee395c814717a6a7651 +F test/vt02.c c2faf56d74470d569cd00741acb3f1719ee95d668f84ef58acc3872635789680 +F test/vt100-a.sql a3e188a118ca78c08b41681a4db6d0f353e554ceb33f1573b1872d16e2d30596 F test/vtab1.test 09a72330d0f31eda2ffaa828b06a6b917fb86250ee72de0301570af725774c07 F test/vtab2.test 14d4ab26cee13ba6cf5c5601b158e4f57552d3b055cdd9406cf7f711e9c84082 F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e @@ -1975,7 +1989,7 @@ F test/wal_common.tcl 204d1721ac13c5e0c7fae6380315b5ab7f4e8423f580d826c5e9df1995 F test/walbak.test 018d4e5a3d45c6298d11b99f09a8ef6876527946 F test/walbig.test f437473a16cfb314867c6b5d1dbcd519e73e3434 F test/walblock.test 6bb472e82730e7e4e81395e907a01d8cfc2bd9e1f01f8a9184ca572e2955a4bf -F test/walckptnoop.test b13a2c3140f2c913cfd422d9a224544757d04b8b14ab4c267ab9910467c0b9be +F test/walckptnoop.test 5f6123750f40cb86633a7e014f9fb805d0eb494b811840086dc72e554e68c7c1 F test/walcksum.test 50e204500eed9c691b6045e467bb2923f49aa93d8adf315e2be135fdb202c1c2 F test/walcrash.test 21038858cc552077b0522f50b0fa87e38139306a F test/walcrash2.test a0edab4e5390f03b99a790de89aad15d6ec70b36 @@ -1990,6 +2004,7 @@ F test/waloverwrite.test dad2f26567f1b45174e54fbf9a8dc1cb876a7f03 F test/walpersist.test 8d78a1ec91299163451417b451a2bac3481f8eb9f455b1ca507a6625c927ca6e F test/walprotocol.test 1b3f922125e341703f6e946d77fdc564d38fb3e07a9385cfdc6c99cac1ecf878 F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db868eebc131 +F test/walrestart.test 5168c0c2414d1971d8dec949c1070a0144cf15402361ba0d0e6a8054f5598a64 F test/walro.test 78a84bc0fdae1385c06b017215c426b6845734d6a5a3ac75c918dd9b801b1b9d F test/walro2.test 33955a6fd874dd9724005e17f77fef89d334b3171454a1256fe4941a96766cdc F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68 @@ -2004,7 +2019,7 @@ F test/walslow.test 0c51843836c9dcf40a5ac05aa781bfb977b396ee2c872d92bd48b79d5dd9 F test/walthread.test d562f51a61191ccfab64940df7aa1cef87c902fa5ab742590ef7f859dfe6a44b F test/walvfs.test e1a6ad0f3c78e98b55c3d5f0889cf366cc0d0a1cb2bccb44ac9ec67384adc4a1 F test/where.test 5087c72d26fd075a1644c8512be9fe18de9bf2d2b0754f7fd9b74a1c6540c4fc -F test/where2.test 52237a8cb27ebbf6583469429bb5733d1b94ac37d09c3dbd0f487952ed0ab3f8 +F test/where2.test 1dbff4ab847068a52b927a0c1a7cf7faed4c8cae081fbcdac9b052a3a209aefa F test/where3.test 4ccb156ae33de86414a52775a6f590a9d60ba2cbc7a93a24fa331b7bcf5b6030 F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8 F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2 @@ -2022,7 +2037,7 @@ F test/whereG.test 875d020ac0a47828b31e36c54f1bf0cf81c9ea43b257bc21286eca1fe9a48 F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 F test/whereI.test c4bb7e2ca56d49bd8ab5c7bd085b8b83e353922b46904d68aefb3c7468643581 F test/whereJ.test fc05e374cc9f2dc204148d6c06822c380ad388895fe97a6d335b94a26a08aecf -F test/whereK.test 0270ab7f04ba5436fb9156d31d642a1c82727f4c4bfe5ba90d435c78cf44684a +F test/whereK.test 4fb96b078f2ecedc467fa53177787378ff659539e415a4256cae7ae4e2a804b2 F test/whereL.test cb115604cc9bd61acbc99a1f1df0eb1ea7a7875a77fef25ba9282f01d10283e1 F test/whereM.test 0dbc9998783458ddcf3cc078ca7c2951d8b2677d472ecf0028f449ed327c0250 F test/whereN.test 63a3584b71acfb6963416de82f26c6b1644abc5ca6080c76546b9246734c8803 @@ -2034,7 +2049,7 @@ F test/wherelimit3.test 22d73e046870cf8bbe15573eda6b432b07ebe64a88711f9f849c6b36 F test/widetab1.test c296a98e123762de79917350e45fa33fdf88577a2571eb3a64c8bf7e44ef74d1 F test/win32heap.test 1ec2ce646aee491ec23bfcdfd005b33c79f13bf91467966f374a76ffe7c7e85f F test/win32lock.test e56d7a9b6cf9d5f3867c2dd19ff36c5326881e4038c6867610ecb3a9868ea4eb -F test/win32longpath.test 0f9837039b306735c13521c5f25b6ed42937b600dace58e28a3d2f8baf429b6a +F test/win32longpath.test 2641e3a5dbb59f49456f6caf78c0acd6ec7dbba27cb56363bab8fbfe93995caa F test/win32nolock.test 95854dc0206b8a95e4aee15a76acc082767b38f079b2e24676aed6cbb0f32798 F test/window1.test b46d28b9698559e66aa4adafd8074b940faee498bf0c4fbdb62548bfcccc67e7 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 @@ -2059,7 +2074,7 @@ F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387 F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c31660a7c F test/windowpushd.test c420e2265f0e09a0e798d0513a660d71b51602088d81b3dbd038918ee1339dcc -F test/with1.test 1ee171d7c306ab8b0771f3511d870f56c735607729836585bbceb1fc2f47e0b1 +F test/with1.test 31db84788e0429885b63995149fab57d32e26196b752a3a926249ae74c0adddd F test/with2.test 181674a6cc86a601ca2ac052741cdfad5b529e07e870435d2f6cdb92d589ff17 F test/with3.test e30369ea27aa27eb1bda4c5e510c8a9f782c8afd2ab99d1a02b8a7f25a5d3e65 F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205 @@ -2078,13 +2093,13 @@ F test/writecrash.test 13520af28f376bfc8c0bcd130efc1fff20bb165198e8b94cf153f1f75 F test/zeroblob.test 7b74cefc7b281dfa2b07cd237987fbe94b4a2037a7771e9e83f2d5f608b1d99e F test/zeroblobfault.test 861d8191a0d944dfebb3cb4d2c5b4e46a5a119eaec5a63dd996c2389f8063441 F test/zerodamage.test 9c41628db7e8d9e8a0181e59ea5f189df311a9f6ce99cc376dc461f66db6f8dc -F test/zipfile.test c52db63e31a66ae4245affa3e4e65e302442a87e5fd5f2ad29060bc849a83480 -F test/zipfile2.test a577e0775e32ef8972e7d5e9a45bc071a5ae061b5b965a08c9c4b709ad036a25 +F test/zipfile.test a3fcfc43115e4226fdddadd43bdf31c8ca805ad08dad435634f1633d8f5840d9 +F test/zipfile2.test 21afaffcf4f7769df38bf16e4a9c4dfa6ba1b0f5b695f844ec61fafb92db0db7 F test/zipfilefault.test 44d4d7a7f7cca7521d569d7f71026b241d65a6b1757aa409c1a168827edbbc2c F tool/GetFile.cs 47852aa0d806fe47ed1ac5138bdce7f000fe87aaa7f28107d0cb1e26682aeb44 F tool/GetTclKit.bat d84033c6a93dfe735d247f48ba00292a1cc284dcf69963e5e672444e04534bbf F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91 -F tool/build-all-msvc.bat 1960a7a3e5d8176c4329e31476f6e3dfa9543675355fa9020a569f4452628458 x +F tool/build-all-msvc.bat 1ee9dbadcc07fc23268025854e97b392bcbad72376b47aee7b22f3797a4f2c87 x F tool/build-shell.sh 369c4b171cc877ad974fef691e4da782b4c1e99fe8f4361316c735f64d49280f F tool/buildtclext.tcl d09b753d7858314104eeaf5f4def85d35784470279809e47a633f142226f2b3f F tool/cg_anno.tcl c1f875f5a4c9caca3d59937b16aff716f8b1883935f1b4c9ae23124705bc8099 x @@ -2093,7 +2108,7 @@ F tool/cktclsh.sh 6075eef9c6b9ba4b38fef2ca2a66d25f2311bd3c610498d18a9b01f861629c F tool/cp.tcl 9a0d663ad45828de13763ee7ca0200f31f56c6d742cf104a56ae80e027c242d8 F tool/custom.txt 24ed55e71c5edae0067ba159bbf09240d58b160331f7716e95816cd3aa0ba5c4 F tool/dbhash.c 5da0c61032d23d74f2ab84ffc5740f0e8abec94f2c45c0b4306be7eb3ae96df0 -F tool/dbtotxt.c ca48d34eaca6d6b6e4bd6a7be2b72caf34475869054240244c60fa7e69a518d6 +F tool/dbtotxt.c cfeb957571735af345f253ba8417256031fa0dddf79468eefad184262d17211e F tool/dbtotxt.md c9a57af8739957ef36d2cfad5c4b1443ff3688ed33e4901ee200c8b651f43f3c F tool/emcc.sh.in 41a049468c8155433e37e656ba5bae063a000768b1d627025f277732c4e7c4a4 F tool/enlargedb.c 3e8b2612b985cfa7e3e8800031ee191b43ae80de96abb5abbd5eada62651ee21 @@ -2106,24 +2121,25 @@ F tool/genfkey.README e550911fa984c8255ebed2ef97824125d83806eb5232582700de949edf F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a5a4f F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/index_usage.c f62a0c701b2c7ff2f3e21d206f093c123f222dbf07136a10ffd1ca15a5c706c5 -F tool/lemon.c 8f6c122e5727cb0e5f302b8efc91489b1947a8d98206d7a1b1cfc0ed685b6e7c -F tool/lempar.c bdffd3b233a4e4e78056c9c01fadd2bb3fe902435abde3bce3d769fdf0d5cca2 +F tool/lemon.c 3fdc16b23f1ea0c91c049b518fc3f75c71843dbfe2b447fcb3cd92d9e4f219f8 +F tool/lempar.c b57e1780bf8098dd4a9a5bba537f994276ea825a420f6165153e5894dc2dfb07 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c 63412f9790e5e8538fbde0b4f6db154aaaf80f7a10a01e3c94d14b773a8dd5a6 F tool/logest.c c34e5944318415de513d29a6098df247a9618c96d83c38d4abd88641fe46e669 F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkamalzip.tcl 8aa5ebe7973c8b8774062d34e15fea9815c4cc2ceea3a9b184695f005910876a -F tool/mkautoconfamal.sh 647dada5e34c466bef62a4408e1c99a7e5e1922805479dd57944f33f9803f2f8 +F tool/mkautoconfamal.sh 06fbe090b81c24e592c1f22b404334f805ba74d482a9260f2ac81e6f3d3386d8 F tool/mkccode.tcl c42a8f8cf78f92e83795d5447460dbce7aaf78a3bbf9082f1507dc71a3665f3c x +F tool/mkcombo.tcl 2a5189b219c4a495e1ff7fc980bd568d3cfb82ae9d50c84e77f7a161e96fc132 F tool/mkctimec.tcl 3fb5cad05922f5da61262cb6bcd5868a34e94a49ca8833ae2d7796e7df075576 x -F tool/mkkeywordhash.c 6b0be901c47f9ad42215fc995eb2f4384ac49213b1fba395102ec3e999acf559 +F tool/mkkeywordhash.c 82d5af1d0e677900739fba59155cddac172d8c712c2d91ab73d6e6bcb30060f0 F tool/mkmsvcmin.tcl d76c45efda1cce2d4005bcea7b8a22bb752e3256009f331120fb4fecb14ebb7a F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef F tool/mkopcodeh.tcl 2b4e6967a670ef21bf53a164964c35c6163277d002a4c6f56fa231d68c88d023 F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl 3801ce32f8c55fe63a3b279f231fb26c2c1a2ea9a09d2dd599239d87a609acec -F tool/mkshellc.tcl bab0a72a68384181a5706712dfdf6815f6526446d4e8aacace2de5e80cda91b2 +F tool/mkshellc.tcl da6918b128e928a8f0d663519e14829153e59465bd5eb596442e99fa10a411b7 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mksqlite3c-noext.tcl 351c55256213154cabb051a3c870ef9f4487de905015141ae50dc7578a901b84 F tool/mksqlite3c.tcl 7a268139158e5deef27a370bc2f8db6ccf100c1ad7ac5e5b23743c0fd354f609 @@ -2135,26 +2151,27 @@ F tool/mkvsix.tcl 67b40996a50f985a573278eea32fc5a5eb6110bdf14d33f1d8086e48c69e54 F tool/mkwinarm64ec.tcl 171f79234fa53552a129b360356df5599fdab15239caffb3d29c571292728033 F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c6115285845 F tool/omittest-msvc.tcl d6b8f501ac1d7798c4126065030f89812379012cad98a1735d6d7221492abc08 -F tool/omittest.tcl bec70ef0e16255c8d9eb06ecd7edf823c07a60a836186cdbce3528fb34b67995 +F tool/omittest.tcl 436b7072e00e25e9b77145a9f67aa8e0eeabd186168827435fd03f8f981aac32 F tool/opcodesum.tcl 740ed206ba8c5040018988129abbf3089a0ccf4a F tool/pagesig.c f98909b4168d9cac11a2de7f031adea0e2f3131faa7515a72807c03ec58eafeb F tool/replace.tcl 511c61acfe563dfb58675efb4628bb158a13d48ff8322123ac447e9d25a82d9a F tool/restore_jrnl.tcl 1079ecba47cc82fa82115b81c1f68097ab1f956f357ee8da5fc4b2589af6bd98 F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5 -F tool/showdb.c 3956d71e5193162609a60e8c9edfcf09274c00cfea2b1d221261427adb2b5cca +F tool/showdb.c 1faa3661d2d634f206c76794cb21d89d3ea9082d07d5e983be0f025e40f21320 F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818 F tool/showlocks.c 9cc5e66d4ebbf2d194f39db2527ece92077e86ae627ddd233ee48e16e8142564 F tool/showshm.c a0ab6ec32dd1f11218ca2a4018f8fb875b59414801ab8ceed8b2e69b7b45a809 F tool/showstat4.c b706fcbc4cd1a6e4a73ac32549afc4b460479d650402d64b23e8d813516e8de4 +F tool/showtmlog.c 2e9da6c4b4767113a0ad5ddabd4337ea100d38ff9c7fee260f9ccdefb2ffdc23 F tool/showwal.c 11eca547980a066b081f512636151233350ac679f29ecf4ebfce7f4530230b3d F tool/soak1.tcl a3892082ed1079671565c044e93b55c3c7f38829aedf53cc597c65d23ffdaddf F tool/spaceanal.tcl 1f83962090a6b60e1d7bf92495d643e622bef9fe82ea3f2d22350dcbce9a12d0 F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d40618 x F tool/split-sqlite3c.tcl 4969fd642dad0ea483e4e104163021d92baf98f6a8eac981fe48525f9b873430 -F tool/sqldiff.c 134be7866be19f8beb32043d5aea5657f01aaeae2df8d33d758ff722c78666b9 +F tool/sqldiff.c 847edc1e0d1e1feb652d3d6128e504456deaf254ab9ad3e7cebd4317d2037182 F tool/sqlite3_analyzer.c.in 14f02cb5ec3c264cd6107d1f1dad77092b1cf440fc196c30b69ae87b56a1a43b -F tool/sqlite3_rsync.c d0e58a1e49fe2192c3ee0b697aed182d502bebfe5b4b406ba6b2baa52a04ecbe -F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 +F tool/sqlite3_rsync.c f510a8b230e1c5b0f62842acd0e94ff15d2f77a00ae782f7d20f9e39919fa19b +F tool/sqltclsh.c.in c103c6fc7d42bce611f9d4596774d60b7ef3d0b291a1f58c9e6184e458b89296 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 F tool/src-verify.c 6c655d9a8d6b30f3648fc78a79bf3838ed68f8543869d380c43ea9f17b3b8501 F tool/srcck1.c 559e703c6cca1d70398bdba1d7f91036c1a71adf718a1aaa6401a562ccaed154 @@ -2171,10 +2188,12 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 10e11b9c539a8be50fd93bdf7cf5afe97d9757ce8577cac58426a1b218063e47 -R ec4161915fdee7c9c786931b31f64669 +F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c +P 5fa49c4d592778fb82c4e25c77cf0442d3dc23cc7f8d91d25952c722af866930 +R b4a00b0f2cf0b157145ad751c36ae742 +T +sym-major-release * T +sym-release * -T +sym-version-3.51.2 * +T +sym-version-3.52.0 * U drh -Z 6e3a819551f499011e2275a586e8d608 +Z ee8b8689818d3dfbd1a3f7d6f78a36ce # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index c644e06f9..e0b8607c1 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,4 +1,5 @@ -branch branch-3.51 +branch trunk +tag trunk tag release -tag branch-3.51 -tag version-3.51.2 +tag major-release +tag version-3.52.0 diff --git a/manifest.uuid b/manifest.uuid index 75ccd2e60..55fe12eaa 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b270f8339eb13b504d0b2ba154ebca966b7dde08e40c3ed7d559749818cb2075 +557aeb43869d3585137b17690cb3b64f7de6921774daae9e56403c3717dceab6 diff --git a/src/alter.c b/src/alter.c index a7255e75e..fb5a37935 100644 --- a/src/alter.c +++ b/src/alter.c @@ -491,7 +491,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ /* Look up the table being altered. */ assert( pParse->pNewTable==0 ); assert( sqlite3BtreeHoldsAllMutexes(db) ); - if( db->mallocFailed ) goto exit_begin_add_column; + if( NEVER(db->mallocFailed) ) goto exit_begin_add_column; pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); if( !pTab ) goto exit_begin_add_column; @@ -563,7 +563,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ ** Or, if pTab is not a view or virtual table, zero is returned. */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) -static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ +static int isRealTable(Parse *pParse, Table *pTab, int iOp){ const char *zType = 0; #ifndef SQLITE_OMIT_VIEW if( IsView(pTab) ){ @@ -576,9 +576,12 @@ static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ } #endif if( zType ){ + const char *azMsg[] = { + "rename columns of", "drop column from", "edit constraints of" + }; + assert( iOp>=0 && iOpzName + azMsg[iOp], zType, pTab->zName ); return 1; } @@ -1049,6 +1052,25 @@ static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){ return pBest; } +/* +** Set the error message of the context passed as the first argument to +** the result of formatting zFmt using printf() style formatting. +*/ +static void errorMPrintf(sqlite3_context *pCtx, const char *zFmt, ...){ + sqlite3 *db = sqlite3_context_db_handle(pCtx); + char *zErr = 0; + va_list ap; + va_start(ap, zFmt); + zErr = sqlite3VMPrintf(db, zFmt, ap); + va_end(ap); + if( zErr ){ + sqlite3_result_error(pCtx, zErr, -1); + sqlite3DbFree(db, zErr); + }else{ + sqlite3_result_error_nomem(pCtx); + } +} + /* ** An error occurred while parsing or otherwise processing a database ** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an @@ -1346,8 +1368,8 @@ static int renameResolveTrigger(Parse *pParse){ sqlite3SelectPrep(pParse, pStep->pSelect, &sNC); if( pParse->nErr ) rc = pParse->rc; } - if( rc==SQLITE_OK && pStep->zTarget ){ - SrcList *pSrc = sqlite3TriggerStepSrc(pParse, pStep); + if( rc==SQLITE_OK && pStep->pSrc ){ + SrcList *pSrc = sqlite3SrcListDup(db, pStep->pSrc, 0); if( pSrc ){ Select *pSel = sqlite3SelectNew( pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0 @@ -1375,10 +1397,10 @@ static int renameResolveTrigger(Parse *pParse){ pSel->pSrc = 0; sqlite3SelectDelete(db, pSel); } - if( pStep->pFrom ){ + if( ALWAYS(pStep->pSrc) ){ int i; - for(i=0; ipFrom->nSrc && rc==SQLITE_OK; i++){ - SrcItem *p = &pStep->pFrom->a[i]; + for(i=0; ipSrc->nSrc && rc==SQLITE_OK; i++){ + SrcItem *p = &pStep->pSrc->a[i]; if( p->fg.isSubquery ){ assert( p->u4.pSubq!=0 ); sqlite3SelectPrep(pParse, p->u4.pSubq->pSelect, 0); @@ -1447,13 +1469,13 @@ static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){ sqlite3WalkExpr(pWalker, pUpsert->pUpsertWhere); sqlite3WalkExpr(pWalker, pUpsert->pUpsertTargetWhere); } - if( pStep->pFrom ){ + if( pStep->pSrc ){ int i; - SrcList *pFrom = pStep->pFrom; - for(i=0; inSrc; i++){ - if( pFrom->a[i].fg.isSubquery ){ - assert( pFrom->a[i].u4.pSubq!=0 ); - sqlite3WalkSelect(pWalker, pFrom->a[i].u4.pSubq->pSelect); + SrcList *pSrc = pStep->pSrc; + for(i=0; inSrc; i++){ + if( pSrc->a[i].fg.isSubquery ){ + assert( pSrc->a[i].u4.pSubq!=0 ); + sqlite3WalkSelect(pWalker, pSrc->a[i].u4.pSubq->pSelect); } } } @@ -1624,8 +1646,8 @@ static void renameColumnFunc( if( rc!=SQLITE_OK ) goto renameColumnFunc_done; for(pStep=sParse.pNewTrigger->step_list; pStep; pStep=pStep->pNext){ - if( pStep->zTarget ){ - Table *pTarget = sqlite3LocateTable(&sParse, 0, pStep->zTarget, zDb); + if( pStep->pSrc ){ + Table *pTarget = sqlite3LocateTableItem(&sParse, 0, &pStep->pSrc->a[0]); if( pTarget==pTab ){ if( pStep->pUpsert ){ ExprList *pUpsertSet = pStep->pUpsert->pUpsertSet; @@ -1637,7 +1659,6 @@ static void renameColumnFunc( } } - /* Find tokens to edit in UPDATE OF clause */ if( sParse.pTriggerTab==pTab ){ renameColumnIdlistNames(&sParse, &sCtx,sParse.pNewTrigger->pColumns,zOld); @@ -1839,13 +1860,10 @@ static void renameTableFunc( if( rc==SQLITE_OK ){ renameWalkTrigger(&sWalker, pTrigger); for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){ - if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){ - renameTokenFind(&sParse, &sCtx, pStep->zTarget); - } - if( pStep->pFrom ){ + if( pStep->pSrc ){ int i; - for(i=0; ipFrom->nSrc; i++){ - SrcItem *pItem = &pStep->pFrom->a[i]; + for(i=0; ipSrc->nSrc; i++){ + SrcItem *pItem = &pStep->pSrc->a[i]; if( 0==sqlite3_stricmp(pItem->zName, zOld) ){ renameTokenFind(&sParse, &sCtx, pItem->zName); } @@ -2092,6 +2110,57 @@ static void renameTableTest( #endif } + +/* +** Return the number of bytes until the end of the next non-whitespace and +** non-comment token. For the purpose of this function, a "(" token includes +** all of the bytes through and including the matching ")", or until the +** first illegal token, whichever comes first. +** +** Write the token type into *piToken. +** +** The value returned is the number of bytes in the token itself plus +** the number of bytes of leading whitespace and comments skipped plus +** all bytes through the next matching ")" if the token is TK_LP. +** +** Example: (Note: '.' used in place of '*' in the example z[] text) +** +** ,--------- *piToken := TK_RP +** v +** z[] = " /.comment./ --comment\n (two three four) five" +** | | +** |<-------------------------------------->| +** | +** `--- return value +*/ +static int getConstraintToken(const u8 *z, int *piToken){ + int iOff = 0; + int t = 0; + do { + iOff += sqlite3GetToken(&z[iOff], &t); + }while( t==TK_SPACE || t==TK_COMMENT ); + + *piToken = t; + + if( t==TK_LP ){ + int nNest = 1; + while( nNest>0 ){ + iOff += sqlite3GetToken(&z[iOff], &t); + if( t==TK_LP ){ + nNest++; + }else if( t==TK_RP ){ + t = TK_LP; + nNest--; + }else if( t==TK_ILLEGAL ){ + break; + } + } + } + + *piToken = t; + return iOff; +} + /* ** The implementation of internal UDF sqlite_drop_column(). ** @@ -2136,15 +2205,24 @@ static void dropColumnFunc( goto drop_column_done; } - pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); if( iColnCol-1 ){ RenameToken *pEnd; + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zCnName); zEnd = (const char*)pEnd->t.z; }else{ + int eTok; assert( IsOrdinaryTable(pTab) ); + assert( iCol!=0 ); + /* Point pCol->t.z at the "," immediately preceding the definition of + ** the column being dropped. To do this, start at the name of the + ** previous column, and tokenize until the next ",". */ + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol-1].zCnName); + do { + pCol->t.z += getConstraintToken((const u8*)pCol->t.z, &eTok); + }while( eTok!=TK_COMMA ); + pCol->t.z--; zEnd = (const char*)&zSql[pTab->u.tab.addColOffset]; - while( ALWAYS(pCol->t.z[0]!=0) && pCol->t.z[0]!=',' ) pCol->t.z--; } zNew = sqlite3MPrintf(db, "%.*s%s", pCol->t.z-zSql, zSql, zEnd); @@ -2313,6 +2391,651 @@ void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ sqlite3SrcListDelete(db, pSrc); } +/* +** Return the number of bytes of leading whitespace/comments in string z[]. +*/ +static int getWhitespace(const u8 *z){ + int nRet = 0; + while( 1 ){ + int t = 0; + int n = sqlite3GetToken(&z[nRet], &t); + if( t!=TK_SPACE && t!=TK_COMMENT ) break; + nRet += n; + } + return nRet; +} + + +/* +** Argument z points into the body of a constraint - specifically the +** second token of the constraint definition. For a named constraint, +** z points to the first token past the CONSTRAINT keyword. For an +** unnamed NOT NULL constraint, z points to the first byte past the NOT +** keyword. +** +** Return the number of bytes until the end of the constraint. +*/ +static int getConstraint(const u8 *z){ + int iOff = 0; + int t = 0; + + /* Now, the current constraint proceeds until the next occurence of one + ** of the following tokens: + ** + ** CONSTRAINT, PRIMARY, NOT, UNIQUE, CHECK, DEFAULT, + ** COLLATE, REFERENCES, FOREIGN, GENERATED, AS, RP, or COMMA + ** + ** Also exit the loop if ILLEGAL turns up. + */ + while( 1 ){ + int n = getConstraintToken(&z[iOff], &t); + if( t==TK_CONSTRAINT || t==TK_PRIMARY || t==TK_NOT || t==TK_UNIQUE + || t==TK_CHECK || t==TK_DEFAULT || t==TK_COLLATE || t==TK_REFERENCES + || t==TK_FOREIGN || t==TK_RP || t==TK_COMMA || t==TK_ILLEGAL + || t==TK_AS || t==TK_GENERATED + ){ + break; + } + iOff += n; + } + + return iOff; +} + +/* +** Compare two constraint names. +** +** Summary: *pRes := zQuote != zCmp +** +** Details: +** Compare the (possibly quoted) constraint name zQuote[0..nQuote-1] +** against zCmp[]. Write zero into *pRes if they are the same and +** non-zero if they differ. Normally return SQLITE_OK, except if there +** is an OOM, set the OOM error condition on ctx and return SQLITE_NOMEM. +*/ +static int quotedCompare( + sqlite3_context *ctx, /* Function context on which to report errors */ + int t, /* Token type */ + const u8 *zQuote, /* Possibly quoted text. Not zero-terminated. */ + int nQuote, /* Length of zQuote in bytes */ + const u8 *zCmp, /* Zero-terminated, unquoted name to compare against */ + int *pRes /* OUT: Set to 0 if equal, non-zero if unequal */ +){ + char *zCopy = 0; /* De-quoted, zero-terminated copy of zQuote[] */ + + if( t==TK_ILLEGAL ){ + *pRes = 1; + return SQLITE_OK; + } + zCopy = sqlite3MallocZero(nQuote+1); + if( zCopy==0 ){ + sqlite3_result_error_nomem(ctx); + return SQLITE_NOMEM_BKPT; + } + memcpy(zCopy, zQuote, nQuote); + sqlite3Dequote(zCopy); + *pRes = sqlite3_stricmp((const char*)zCopy, (const char*)zCmp); + sqlite3_free(zCopy); + return SQLITE_OK; +} + +/* +** zSql[] is a CREATE TABLE statement, supposedly. Find the offset +** into zSql[] of the first character past the first "(" and write +** that offset into *piOff and return SQLITE_OK. Or, if not found, +** set the SQLITE_CORRUPT error code and return SQLITE_ERROR. +*/ +static int skipCreateTable(sqlite3_context *ctx, const u8 *zSql, int *piOff){ + int iOff = 0; + + if( zSql==0 ) return SQLITE_ERROR; + + /* Jump past the "CREATE TABLE" bit. */ + while( 1 ){ + int t = 0; + iOff += sqlite3GetToken(&zSql[iOff], &t); + if( t==TK_LP ) break; + if( t==TK_ILLEGAL ){ + sqlite3_result_error_code(ctx, SQLITE_CORRUPT_BKPT); + return SQLITE_ERROR; + } + } + + *piOff = iOff; + return SQLITE_OK; +} + +/* +** Internal SQL function sqlite3_drop_constraint(): Given an input +** CREATE TABLE statement, return a revised CREATE TABLE statement +** with a constraint removed. Two forms, depending on the datatype +** of argv[2]: +** +** sqlite_drop_constraint(SQL, INT) -- Omit NOT NULL from the INT-th column +** sqlite_drop_constraint(SQL, TEXT) -- OMIT constraint with name TEXT +** +** In the first case, the left-most column is 0. +*/ +static void dropConstraintFunc( + sqlite3_context *ctx, + int NotUsed, + sqlite3_value **argv +){ + const u8 *zSql = sqlite3_value_text(argv[0]); + const u8 *zCons = 0; + int iNotNull = -1; + int ii; + int iOff = 0; + int iStart = 0; + int iEnd = 0; + char *zNew = 0; + int t = 0; + sqlite3 *db; + UNUSED_PARAMETER(NotUsed); + + if( zSql==0 ) return; + + /* Jump past the "CREATE TABLE" bit. */ + if( skipCreateTable(ctx, zSql, &iOff) ) return; + + if( sqlite3_value_type(argv[1])==SQLITE_INTEGER ){ + iNotNull = sqlite3_value_int(argv[1]); + }else{ + zCons = sqlite3_value_text(argv[1]); + } + + /* Search for the named constraint within column definitions. */ + for(ii=0; iEnd==0; ii++){ + + /* Now parse the column or table constraint definition. Search + ** for the token CONSTRAINT if this is a DROP CONSTRAINT command, or + ** NOT in the right column if this is a DROP NOT NULL. */ + while( 1 ){ + iStart = iOff; + iOff += getConstraintToken(&zSql[iOff], &t); + if( t==TK_CONSTRAINT && (zCons || iNotNull==ii) ){ + /* Check if this is the constraint we are searching for. */ + int nTok = 0; + int cmp = 1; + + /* Skip past any whitespace. */ + iOff += getWhitespace(&zSql[iOff]); + + /* Compare the next token - which may be quoted - with the name of + ** the constraint being dropped. */ + nTok = getConstraintToken(&zSql[iOff], &t); + if( zCons ){ + if( quotedCompare(ctx, t, &zSql[iOff], nTok, zCons, &cmp) ) return; + } + iOff += nTok; + + /* The next token is usually the first token of the constraint + ** definition. This is enough to tell the type of the constraint - + ** TK_NOT means it is a NOT NULL, TK_CHECK a CHECK constraint etc. + ** + ** There is also the chance that the next token is TK_CONSTRAINT + ** (or TK_DEFAULT or TK_COLLATE), for example if a table has been + ** created as follows: + ** + ** CREATE TABLE t1(cols, CONSTRAINT one CONSTRAINT two NOT NULL); + ** + ** In this case, allow the "CONSTRAINT one" bit to be dropped by + ** this command if that is what is requested, or to advance to + ** the next iteration of the loop with &zSql[iOff] still pointing + ** to the CONSTRAINT keyword. */ + nTok = getConstraintToken(&zSql[iOff], &t); + if( t==TK_CONSTRAINT || t==TK_DEFAULT || t==TK_COLLATE + || t==TK_COMMA || t==TK_RP || t==TK_GENERATED || t==TK_AS + ){ + t = TK_CHECK; + }else{ + iOff += nTok; + iOff += getConstraint(&zSql[iOff]); + } + + if( cmp==0 || (iNotNull>=0 && t==TK_NOT) ){ + if( t!=TK_NOT && t!=TK_CHECK ){ + errorMPrintf(ctx, "constraint may not be dropped: %s", zCons); + return; + } + iEnd = iOff; + break; + } + + }else if( t==TK_NOT && iNotNull==ii ){ + iEnd = iOff + getConstraint(&zSql[iOff]); + break; + }else if( t==TK_RP || t==TK_ILLEGAL ){ + iEnd = -1; + break; + }else if( t==TK_COMMA ){ + break; + } + } + } + + /* If the constraint has not been found it is an error. */ + if( iEnd<=0 ){ + if( zCons ){ + errorMPrintf(ctx, "no such constraint: %s", zCons); + }else{ + /* SQLite follows postgres in that a DROP NOT NULL on a column that is + ** not NOT NULL is not an error. So just return the original SQL here. */ + sqlite3_result_text(ctx, (const char*)zSql, -1, SQLITE_TRANSIENT); + } + }else{ + + /* Figure out if an extra space should be inserted after the constraint + ** is removed. And if an additional comma preceding the constraint + ** should be removed. */ + const char *zSpace = " "; + iEnd += getWhitespace(&zSql[iEnd]); + sqlite3GetToken(&zSql[iEnd], &t); + if( t==TK_RP || t==TK_COMMA ){ + zSpace = ""; + if( zSql[iStart-1]==',' ) iStart--; + } + + db = sqlite3_context_db_handle(ctx); + zNew = sqlite3MPrintf(db, "%.*s%s%s", iStart, zSql, zSpace, &zSql[iEnd]); + sqlite3_result_text(ctx, zNew, -1, SQLITE_DYNAMIC); + } +} + +/* +** Internal SQL function: +** +** sqlite_add_constraint(SQL, CONSTRAINT-TEXT, ICOL) +** +** SQL is a CREATE TABLE statement. Return a modified version of +** SQL that adds CONSTRAINT-TEXT at the end of the ICOL-th column +** definition. (The left-most column defintion is 0.) +*/ +static void addConstraintFunc( + sqlite3_context *ctx, + int NotUsed, + sqlite3_value **argv +){ + const u8 *zSql = sqlite3_value_text(argv[0]); + const char *zCons = (const char*)sqlite3_value_text(argv[1]); + int iCol = sqlite3_value_int(argv[2]); + int iOff = 0; + int ii; + char *zNew = 0; + int t = 0; + sqlite3 *db; + UNUSED_PARAMETER(NotUsed); + + if( skipCreateTable(ctx, zSql, &iOff) ) return; + + for(ii=0; ii<=iCol || (iCol<0 && t!=TK_RP); ii++){ + iOff += getConstraintToken(&zSql[iOff], &t); + while( 1 ){ + int nTok = getConstraintToken(&zSql[iOff], &t); + if( t==TK_COMMA || t==TK_RP ) break; + if( t==TK_ILLEGAL ){ + sqlite3_result_error_code(ctx, SQLITE_CORRUPT_BKPT); + return; + } + iOff += nTok; + } + } + + iOff += getWhitespace(&zSql[iOff]); + + db = sqlite3_context_db_handle(ctx); + if( iCol<0 ){ + zNew = sqlite3MPrintf(db, "%.*s, %s%s", iOff, zSql, zCons, &zSql[iOff]); + }else{ + zNew = sqlite3MPrintf(db, "%.*s %s%s", iOff, zSql, zCons, &zSql[iOff]); + } + sqlite3_result_text(ctx, zNew, -1, SQLITE_DYNAMIC); +} + +/* +** Find a column named pCol in table pTab. If successful, set output +** parameter *piCol to the index of the column in the table and return +** SQLITE_OK. Otherwise, set *piCol to -1 and return an SQLite error +** code. +*/ +static int alterFindCol(Parse *pParse, Table *pTab, Token *pCol, int *piCol){ + sqlite3 *db = pParse->db; + char *zName = sqlite3NameFromToken(db, pCol); + int rc = SQLITE_NOMEM; + int iCol = -1; + + if( zName ){ + iCol = sqlite3ColumnIndex(pTab, zName); + if( iCol<0 ){ + sqlite3ErrorMsg(pParse, "no such column: %s", zName); + rc = SQLITE_ERROR; + }else{ + rc = SQLITE_OK; + } + } + +#ifndef SQLITE_OMIT_AUTHORIZATION + if( rc==SQLITE_OK ){ + const char *zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; + const char *zCol = pTab->aCol[iCol].zCnName; + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, zCol) ){ + pTab = 0; + } + } +#endif + + sqlite3DbFree(db, zName); + *piCol = iCol; + return rc; +} + + +/* +** Find the table named by the first entry in source list pSrc. If successful, +** return a pointer to the Table structure and set output variable (*pzDb) +** to point to the name of the database containin the table (i.e. "main", +** "temp" or the name of an attached database). +** +** If the table cannot be located, return NULL. The value of the two output +** parameters is undefined in this case. +*/ +static Table *alterFindTable( + Parse *pParse, /* Parsing context */ + SrcList *pSrc, /* Name of the table to look for */ + int *piDb, /* OUT: write the iDb here */ + const char **pzDb, /* OUT: write name of schema here */ + int bAuth /* Do ALTER TABLE authorization checks if true */ +){ + sqlite3 *db = pParse->db; + Table *pTab = 0; + assert( sqlite3BtreeHoldsAllMutexes(db) ); + pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); + if( pTab ){ + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + *pzDb = db->aDb[iDb].zDbSName; + *piDb = iDb; + + if( SQLITE_OK!=isRealTable(pParse, pTab, 2) + || SQLITE_OK!=isAlterableTable(pParse, pTab) + ){ + pTab = 0; + } + } +#ifndef SQLITE_OMIT_AUTHORIZATION + if( pTab && bAuth ){ + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, *pzDb, pTab->zName, 0) ){ + pTab = 0; + } + } +#endif + sqlite3SrcListDelete(db, pSrc); + return pTab; +} + +/* +** Generate bytecode for one of: +** +** (1) ALTER TABLE pSrc DROP CONSTRAINT pCons +** (2) ALTER TABLE pSrc ALTER pCol DROP NOT NULL +** +** One of pCons and pCol must be NULL and the other non-null. +*/ +void sqlite3AlterDropConstraint( + Parse *pParse, /* Parsing context */ + SrcList *pSrc, /* The table being altered */ + Token *pCons, /* Name of the constraint to drop */ + Token *pCol /* Name of the column from which to remove the NOT NULL */ +){ + sqlite3 *db = pParse->db; + Table *pTab = 0; + int iDb = 0; + const char *zDb = 0; + char *zArg = 0; + + assert( (pCol==0)!=(pCons==0) ); + assert( pSrc->nSrc==1 ); + pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, pCons!=0); + if( !pTab ) return; + + if( pCons ){ + zArg = sqlite3MPrintf(db, "%.*Q", pCons->n, pCons->z); + }else{ + int iCol; + if( alterFindCol(pParse, pTab, pCol, &iCol) ) return; + zArg = sqlite3MPrintf(db, "%d", iCol); + } + + /* Edit the SQL for the named table. */ + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_drop_constraint(sql, %s) " + "WHERE type='table' AND tbl_name=%Q COLLATE nocase" + , zDb, zArg, pTab->zName + ); + sqlite3DbFree(db, zArg); + + /* Finally, reload the database schema. */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); +} + +/* +** The implementation of SQL function sqlite_fail(MSG). This takes a single +** argument, and returns it as an error message with the error code set to +** SQLITE_CONSTRAINT. +*/ +static void failConstraintFunc( + sqlite3_context *ctx, + int NotUsed, + sqlite3_value **argv +){ + const char *zText = (const char*)sqlite3_value_text(argv[0]); + int err = sqlite3_value_int(argv[1]); + (void)NotUsed; + sqlite3_result_error(ctx, zText, -1); + sqlite3_result_error_code(ctx, err); +} + +/* +** Buffer pCons, which is nCons bytes in size, contains the text of a +** NOT NULL or CHECK constraint that will be inserted into a CREATE TABLE +** statement. If successful, this function returns the size of the buffer in +** bytes not including any trailing whitespace or "--" style comments. Or, +** if an OOM occurs, it returns 0 and sets db->mallocFailed to true. +** +** C-style comments at the end are preserved. "--" style comments are +** removed because the comment terminator might be \000, and we are about +** to insert the pCons[] text into the middle of a larger string, and that +** will have the effect of removing the comment terminator and messing up +** the syntax. +*/ +static int alterRtrimConstraint( + sqlite3 *db, /* used to record OOM error */ + const char *pCons, /* Buffer containing constraint */ + int nCons /* Size of pCons in bytes */ +){ + u8 *zTmp = (u8*)sqlite3MPrintf(db, "%.*s", nCons, pCons); + int iOff = 0; + int iEnd = 0; + + if( zTmp==0 ) return 0; + + while( 1 ){ + int t = 0; + int nToken = sqlite3GetToken(&zTmp[iOff], &t); + if( t==TK_ILLEGAL ) break; + if( t!=TK_SPACE && (t!=TK_COMMENT || zTmp[iOff]!='-') ){ + iEnd = iOff+nToken; + } + iOff += nToken; + } + + sqlite3DbFree(db, zTmp); + return iEnd; +} + +/* +** Prepare a statement of the form: +** +** ALTER TABLE pSrc ALTER pCol SET NOT NULL +*/ +void sqlite3AlterSetNotNull( + Parse *pParse, /* Parsing context */ + SrcList *pSrc, /* Name of the table being altered */ + Token *pCol, /* Name of the column to add a NOT NULL constraint to */ + Token *pFirst /* The NOT token of the NOT NULL constraint text */ +){ + Table *pTab = 0; + int iCol = 0; + int iDb = 0; + const char *zDb = 0; + const char *pCons = 0; + int nCons = 0; + + /* Look up the table being altered. */ + assert( pSrc->nSrc==1 ); + pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 0); + if( !pTab ) return; + + /* Find the column being altered. */ + if( alterFindCol(pParse, pTab, pCol, &iCol) ){ + return; + } + + /* Find the length in bytes of the constraint definition */ + pCons = pFirst->z; + nCons = alterRtrimConstraint(pParse->db, pCons, pParse->sLastToken.z - pCons); + + /* Search for a constraint violation. Throw an exception if one is found. */ + sqlite3NestedParse(pParse, + "SELECT sqlite_fail('constraint failed', %d) " + "FROM %Q.%Q AS x WHERE x.%.*s IS NULL", + SQLITE_CONSTRAINT, zDb, pTab->zName, (int)pCol->n, pCol->z + ); + + /* Edit the SQL for the named table. */ + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_add_constraint(sqlite_drop_constraint(sql, %d), %.*Q, %d) " + "WHERE type='table' AND tbl_name=%Q COLLATE nocase" + , zDb, iCol, nCons, pCons, iCol, pTab->zName + ); + + /* Finally, reload the database schema. */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); +} + +/* +** Implementation of internal SQL function: +** +** sqlite_find_constraint(SQL, CONSTRAINT-NAME) +** +** This function returns true if the SQL passed as the first argument is a +** CREATE TABLE that contains a constraint with the name CONSTRAINT-NAME, +** or false otherwise. +*/ +static void findConstraintFunc( + sqlite3_context *ctx, + int NotUsed, + sqlite3_value **argv +){ + const u8 *zSql = 0; + const u8 *zCons = 0; + int iOff = 0; + int t = 0; + + (void)NotUsed; + zSql = sqlite3_value_text(argv[0]); + zCons = sqlite3_value_text(argv[1]); + + if( zSql==0 || zCons==0 ) return; + while( t!=TK_LP && t!=TK_ILLEGAL ){ + iOff += sqlite3GetToken(&zSql[iOff], &t); + } + + while( 1 ){ + iOff += getConstraintToken(&zSql[iOff], &t); + if( t==TK_CONSTRAINT ){ + int nTok = 0; + int cmp = 0; + iOff += getWhitespace(&zSql[iOff]); + nTok = getConstraintToken(&zSql[iOff], &t); + if( quotedCompare(ctx, t, &zSql[iOff], nTok, zCons, &cmp) ) return; + if( cmp==0 ){ + sqlite3_result_int(ctx, 1); + return; + } + }else if( t==TK_ILLEGAL ){ + break; + } + } + + sqlite3_result_int(ctx, 0); +} + +/* +** Generate bytecode to implement: +** +** ALTER TABLE pSrc ADD [CONSTRAINT pName] CHECK(pExpr) +** +** Any "ON CONFLICT" text that occurs after the "CHECK(...)", up +** until pParse->sLastToken, is included as part of the new constraint. +*/ +void sqlite3AlterAddConstraint( + Parse *pParse, /* Parse context */ + SrcList *pSrc, /* Table to add constraint to */ + Token *pFirst, /* First token of new constraint */ + Token *pName, /* Name of new constraint. NULL if name omitted. */ + const char *pExpr, /* Text of CHECK expression */ + int nExpr /* Size of pExpr in bytes */ +){ + Table *pTab = 0; /* Table identified by pSrc */ + int iDb = 0; /* Which schema does pTab live in */ + const char *zDb = 0; /* Name of the schema in which pTab lives */ + const char *pCons = 0; /* Text of the constraint */ + int nCons; /* Bytes of text to use from pCons[] */ + + /* Look up the table being altered. */ + assert( pSrc->nSrc==1 ); + pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 1); + if( !pTab ) return; + + /* If this new constraint has a name, check that it is not a duplicate of + ** an existing constraint. It is an error if it is. */ + if( pName ){ + char *zName = sqlite3NameFromToken(pParse->db, pName); + + sqlite3NestedParse(pParse, + "SELECT sqlite_fail('constraint %q already exists', %d) " + "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " + "WHERE type='table' AND tbl_name=%Q COLLATE nocase " + "AND sqlite_find_constraint(sql, %Q)", + zName, SQLITE_ERROR, zDb, pTab->zName, zName + ); + sqlite3DbFree(pParse->db, zName); + } + + /* Search for a constraint violation. Throw an exception if one is found. */ + sqlite3NestedParse(pParse, + "SELECT sqlite_fail('constraint failed', %d) " + "FROM %Q.%Q WHERE (%.*s) IS NOT TRUE", + SQLITE_CONSTRAINT, zDb, pTab->zName, nExpr, pExpr + ); + + /* Edit the SQL for the named table. */ + pCons = pFirst->z; + nCons = alterRtrimConstraint(pParse->db, pCons, pParse->sLastToken.z - pCons); + + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_add_constraint(sql, %.*Q, -1) " + "WHERE type='table' AND tbl_name=%Q COLLATE nocase" + , zDb, nCons, pCons, pTab->zName + ); + + /* Finally, reload the database schema. */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); +} + /* ** Register built-in functions used to help implement ALTER TABLE */ @@ -2323,6 +3046,10 @@ void sqlite3AlterFunctions(void){ INTERNAL_FUNCTION(sqlite_rename_test, 7, renameTableTest), INTERNAL_FUNCTION(sqlite_drop_column, 3, dropColumnFunc), INTERNAL_FUNCTION(sqlite_rename_quotefix,2, renameQuotefixFunc), + INTERNAL_FUNCTION(sqlite_drop_constraint,2, dropConstraintFunc), + INTERNAL_FUNCTION(sqlite_fail, 2, failConstraintFunc), + INTERNAL_FUNCTION(sqlite_add_constraint, 3, addConstraintFunc), + INTERNAL_FUNCTION(sqlite_find_constraint,2, findConstraintFunc), }; sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); } diff --git a/src/attach.c b/src/attach.c index 085e1b0ec..f27c1e6be 100644 --- a/src/attach.c +++ b/src/attach.c @@ -596,7 +596,7 @@ int sqlite3FixTriggerStep( if( sqlite3WalkSelect(&pFix->w, pStep->pSelect) || sqlite3WalkExpr(&pFix->w, pStep->pWhere) || sqlite3WalkExprList(&pFix->w, pStep->pExprList) - || sqlite3FixSrcList(pFix, pStep->pFrom) + || sqlite3FixSrcList(pFix, pStep->pSrc) ){ return 1; } diff --git a/src/auth.c b/src/auth.c index 9ec2e7d04..1088f844a 100644 --- a/src/auth.c +++ b/src/auth.c @@ -78,7 +78,7 @@ int sqlite3_set_authorizer( sqlite3_mutex_enter(db->mutex); db->xAuth = (sqlite3_xauth)xAuth; db->pAuthArg = pArg; - if( db->xAuth ) sqlite3ExpirePreparedStatements(db, 1); + sqlite3ExpirePreparedStatements(db, 1); sqlite3_mutex_leave(db->mutex); return SQLITE_OK; } diff --git a/src/btree.c b/src/btree.c index a931b0d12..7e73c7fd7 100644 --- a/src/btree.c +++ b/src/btree.c @@ -3673,6 +3673,30 @@ static SQLITE_NOINLINE int btreeBeginTrans( } #endif +#ifdef SQLITE_EXPERIMENTAL_PRAGMA_20251114 + /* If both a read and write transaction will be opened by this call, + ** then issue a file-control as if the following pragma command had + ** been evaluated: + ** + ** PRAGMA experimental_pragma_20251114 = 1|2 + ** + ** where the RHS is "1" if wrflag is 1 (RESERVED lock), or "2" if wrflag + ** is 2 (EXCLUSIVE lock). Ignore any result or error returned by the VFS. + ** + ** WARNING: This code will likely remain part of SQLite only temporarily - + ** it exists to allow users to experiment with certain types of blocking + ** locks in custom VFS implementations. It MAY BE REMOVED AT ANY TIME. */ + if( pBt->pPage1==0 && wrflag ){ + sqlite3_file *fd = sqlite3PagerFile(pPager); + char *aFcntl[3] = {0,0,0}; + aFcntl[1] = "experimental_pragma_20251114"; + assert( wrflag==1 || wrflag==2 ); + aFcntl[2] = (wrflag==1 ? "1" : "2"); + sqlite3OsFileControlHint(fd, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); + sqlite3_free(aFcntl[0]); + } +#endif + /* Call lockBtree() until either pBt->pPage1 is populated or ** lockBtree() returns something other than SQLITE_OK. lockBtree() ** may return SQLITE_OK but leave pBt->pPage1 set to 0 if after @@ -5120,7 +5144,7 @@ static int accessPayload( getCellInfo(pCur); aPayload = pCur->info.pPayload; - assert( offset+amt <= pCur->info.nPayload ); + assert( (u64)offset+(u64)amt <= (u64)pCur->info.nPayload ); assert( aPayload > pPage->aData ); if( (uptr)(aPayload - pPage->aData) > (pBt->usableSize - pCur->info.nLocal) ){ @@ -5677,7 +5701,7 @@ int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes){ assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - if( pCur->eState==CURSOR_VALID ){ + if( NEVER(pCur->eState==CURSOR_VALID) ){ *pRes = 0; return SQLITE_OK; } @@ -9758,7 +9782,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ }while( rc==SQLITE_OK && nOut>0 ); if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ - Pgno pgnoNew; + Pgno pgnoNew = 0; /* Prevent harmless static-analyzer warning */ MemPage *pNew = 0; rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); put4byte(pPgnoOut, pgnoNew); diff --git a/src/build.c b/src/build.c index de890c2e9..c45194145 100644 --- a/src/build.c +++ b/src/build.c @@ -486,6 +486,7 @@ Table *sqlite3LocateTableItem( const char *zDb; if( p->fg.fixedSchema ){ int iDb = sqlite3SchemaToIndex(pParse->db, p->u4.pSchema); + assert( iDb>=0 && iDbdb->nDb ); zDb = pParse->db->aDb[iDb].zDbSName; }else{ assert( !p->fg.isSubquery ); @@ -2059,8 +2060,8 @@ void sqlite3ChangeCookie(Parse *pParse, int iDb){ ** The estimate is conservative. It might be larger that what is ** really needed. */ -static int identLength(const char *z){ - int n; +static i64 identLength(const char *z){ + i64 n; for(n=0; *z; n++, z++){ if( *z=='"' ){ n++; } } @@ -2570,13 +2571,14 @@ void sqlite3MarkAllShadowTablesOf(sqlite3 *db, Table *pTab){ ** restored to its original value prior to this routine returning. */ int sqlite3ShadowTableName(sqlite3 *db, const char *zName){ - char *zTail; /* Pointer to the last "_" in zName */ + const char *zTail; /* Pointer to the last "_" in zName */ Table *pTab; /* Table that zName is a shadow of */ + char *zCopy; zTail = strrchr(zName, '_'); if( zTail==0 ) return 0; - *zTail = 0; - pTab = sqlite3FindTable(db, zName, 0); - *zTail = '_'; + zCopy = sqlite3DbStrNDup(db, zName, (int)(zTail-zName)); + pTab = zCopy ? sqlite3FindTable(db, zCopy, 0) : 0; + sqlite3DbFree(db, zCopy); if( pTab==0 ) return 0; if( !IsVirtual(pTab) ) return 0; return sqlite3IsShadowTableOf(db, pTab, zName); @@ -2729,6 +2731,7 @@ void sqlite3EndTable( convertToWithoutRowidTable(pParse, p); } iDb = sqlite3SchemaToIndex(db, p->pSchema); + assert( iDb>=0 && iDb<=db->nDb ); #ifndef SQLITE_OMIT_CHECK /* Resolve names in all CHECK constraint expressions. @@ -3024,6 +3027,7 @@ void sqlite3CreateView( sqlite3TwoPartName(pParse, pName1, pName2, &pName); iDb = sqlite3SchemaToIndex(db, p->pSchema); + assert( iDb>=0 && iDbnDb ); sqlite3FixInit(&sFix, pParse, iDb, "view", pName); if( sqlite3FixSelect(&sFix, pSelect) ) goto create_view_fail; @@ -4620,6 +4624,7 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ goto exit_drop_index; } iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); + assert( iDb>=0 && iDbnDb ); #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_DROP_INDEX; diff --git a/src/carray.c b/src/carray.c index 154d107dd..ff0691a85 100644 --- a/src/carray.c +++ b/src/carray.c @@ -79,6 +79,7 @@ struct carray_bind { int nData; /* Number of elements */ int mFlags; /* Control flags */ void (*xDel)(void*); /* Destructor for aData */ + void *pDel; /* Alternative argument to xDel() */ }; @@ -411,7 +412,7 @@ static sqlite3_module carrayModule = { static void carrayBindDel(void *pPtr){ carray_bind *p = (carray_bind*)pPtr; if( p->xDel!=SQLITE_STATIC ){ - p->xDel(p->aData); + p->xDel(p->pDel); } sqlite3_free(p); } @@ -419,14 +420,26 @@ static void carrayBindDel(void *pPtr){ /* ** Invoke this interface in order to bind to the single-argument ** version of CARRAY(). +** +** pStmt The prepared statement to which to bind +** idx The index of the parameter of pStmt to which to bind +** aData The data to be bound +** nData The number of elements in aData +** mFlags One of SQLITE_CARRAY_xxxx indicating datatype of aData +** xDestroy Destructor for pDestroy or aData if pDestroy==NULL. +** pDestroy Invoke xDestroy on this pointer if not NULL +** +** The destructor is called pDestroy if pDestroy!=NULL, or against +** aData if pDestroy==NULL. */ -SQLITE_API int sqlite3_carray_bind( +SQLITE_API int sqlite3_carray_bind_v2( sqlite3_stmt *pStmt, int idx, void *aData, int nData, int mFlags, - void (*xDestroy)(void*) + void (*xDestroy)(void*), + void *pDestroy ){ carray_bind *pNew = 0; int i; @@ -503,20 +516,38 @@ SQLITE_API int sqlite3_carray_bind( memcpy(pNew->aData, aData, sz); } pNew->xDel = sqlite3_free; + pNew->pDel = pNew->aData; }else{ pNew->aData = aData; pNew->xDel = xDestroy; + pNew->pDel = pDestroy; } return sqlite3_bind_pointer(pStmt, idx, pNew, "carray-bind", carrayBindDel); carray_bind_error: if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){ - xDestroy(aData); + xDestroy(pDestroy); } sqlite3_free(pNew); return rc; } +/* +** Invoke this interface in order to bind to the single-argument +** version of CARRAY(). Same as sqlite3_carray_bind_v2() with the +** pDestroy parameter set to NULL. +*/ +SQLITE_API int sqlite3_carray_bind( + sqlite3_stmt *pStmt, + int idx, + void *aData, + int nData, + int mFlags, + void (*xDestroy)(void*) +){ + return sqlite3_carray_bind_v2(pStmt,idx,aData,nData,mFlags,xDestroy,aData); +} + /* ** Invoke this routine to register the carray() function. */ diff --git a/src/date.c b/src/date.c index 5e7ae6f1f..58a7ce544 100644 --- a/src/date.c +++ b/src/date.c @@ -429,7 +429,7 @@ static int parseDateOrTime( return 0; }else if( sqlite3StrICmp(zDate,"now")==0 && sqlite3NotPureFunc(context) ){ return setDateTimeToCurrent(context, p); - }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8)>0 ){ + }else if( sqlite3AtoF(zDate, &r)>0 ){ setRawDateNumber(p, r); return 0; }else if( (sqlite3StrICmp(zDate,"subsec")==0 @@ -875,7 +875,7 @@ static int parseModifier( ** date is already on the appropriate weekday, this is a no-op. */ if( sqlite3_strnicmp(z, "weekday ", 8)==0 - && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8)>0 + && sqlite3AtoF(&z[8], &r)>0 && r>=0.0 && r<7.0 && (n=(int)r)==r ){ sqlite3_int64 Z; computeYMD_HMS(p); @@ -946,9 +946,11 @@ static int parseModifier( case '8': case '9': { double rRounder; - int i; + int i, rx; int Y,M,D,h,m,x; const char *z2 = z; + char *zCopy; + sqlite3 *db = sqlite3_context_db_handle(pCtx); char z0 = z[0]; for(n=1; z[n]; n++){ if( z[n]==':' ) break; @@ -958,7 +960,11 @@ static int parseModifier( if( n==6 && getDigits(&z[1], "50f", &Y)==1 ) break; } } - if( sqlite3AtoF(z, &r, n, SQLITE_UTF8)<=0 ){ + zCopy = sqlite3DbStrNDup(db, z, n); + if( zCopy==0 ) break; + rx = sqlite3AtoF(zCopy, &r)<=0; + sqlite3DbFree(db, zCopy); + if( rx ){ assert( rc==1 ); break; } @@ -1778,7 +1784,7 @@ static void datedebugFunc( char *zJson; zJson = sqlite3_mprintf( "{iJD:%lld,Y:%d,M:%d,D:%d,h:%d,m:%d,tz:%d," - "s:%.3f,validJD:%d,validYMS:%d,validHMS:%d," + "s:%.3f,validJD:%d,validYMD:%d,validHMS:%d," "nFloor:%d,rawS:%d,isError:%d,useSubsec:%d," "isUtc:%d,isLocal:%d}", x.iJD, x.Y, x.M, x.D, x.h, x.m, x.tz, diff --git a/src/delete.c b/src/delete.c index 8fac7c2f3..d4d2337c7 100644 --- a/src/delete.c +++ b/src/delete.c @@ -86,7 +86,7 @@ static int vtabIsReadOnly(Parse *pParse, Table *pTab){ ** * Only allow DELETE, INSERT, or UPDATE of non-SQLITE_VTAB_INNOCUOUS ** virtual tables if PRAGMA trusted_schema=ON. */ - if( pParse->pToplevel!=0 + if( (pParse->pToplevel!=0 || (pParse->prepFlags & SQLITE_PREPARE_FROM_DDL)) && pTab->u.vtab.p->eVtabRisk > ((pParse->db->flags & SQLITE_TrustedSchema)!=0) ){ @@ -924,7 +924,6 @@ void sqlite3GenerateRowIndexDelete( &iPartIdxLabel, pPrior, r1); sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1, pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn); - sqlite3VdbeChangeP5(v, 1); /* Cause IdxDelete to error if no entry found */ sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); pPrior = pIdx; } diff --git a/src/expr.c b/src/expr.c index fdc05366c..d486e48e3 100644 --- a/src/expr.c +++ b/src/expr.c @@ -935,34 +935,22 @@ Expr *sqlite3ExprAlloc( int dequote /* True to dequote */ ){ Expr *pNew; - int nExtra = 0; - int iValue = 0; + int nExtra = pToken ? pToken->n+1 : 0; assert( db!=0 ); - if( pToken ){ - if( op!=TK_INTEGER || pToken->z==0 - || sqlite3GetInt32(pToken->z, &iValue)==0 ){ - nExtra = pToken->n+1; /* tag-20240227-a */ - assert( iValue>=0 ); - } - } pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)+nExtra); if( pNew ){ memset(pNew, 0, sizeof(Expr)); pNew->op = (u8)op; pNew->iAgg = -1; - if( pToken ){ - if( nExtra==0 ){ - pNew->flags |= EP_IntValue|EP_Leaf|(iValue?EP_IsTrue:EP_IsFalse); - pNew->u.iValue = iValue; - }else{ - pNew->u.zToken = (char*)&pNew[1]; - assert( pToken->z!=0 || pToken->n==0 ); - if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); - pNew->u.zToken[pToken->n] = 0; - if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ - sqlite3DequoteExpr(pNew); - } + if( nExtra ){ + assert( pToken!=0 ); + pNew->u.zToken = (char*)&pNew[1]; + assert( pToken->z!=0 || pToken->n==0 ); + if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); + pNew->u.zToken[pToken->n] = 0; + if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ + sqlite3DequoteExpr(pNew); } } #if SQLITE_MAX_EXPR_DEPTH>0 @@ -987,6 +975,24 @@ Expr *sqlite3Expr( return sqlite3ExprAlloc(db, op, &x, 0); } +/* +** Allocate an expression for a 32-bit signed integer literal. +*/ +Expr *sqlite3ExprInt32(sqlite3 *db, int iVal){ + Expr *pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)); + if( pNew ){ + memset(pNew, 0, sizeof(Expr)); + pNew->op = TK_INTEGER; + pNew->iAgg = -1; + pNew->flags = EP_IntValue|EP_Leaf|(iVal?EP_IsTrue:EP_IsFalse); + pNew->u.iValue = iVal; +#if SQLITE_MAX_EXPR_DEPTH>0 + pNew->nHeight = 1; +#endif + } + return pNew; +} + /* ** Attach subtrees pLeft and pRight to the Expr node pRoot. ** @@ -1149,7 +1155,7 @@ Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ ){ sqlite3ExprDeferredDelete(pParse, pLeft); sqlite3ExprDeferredDelete(pParse, pRight); - return sqlite3Expr(db, TK_INTEGER, "0"); + return sqlite3ExprInt32(db, 0); }else{ return sqlite3PExpr(pParse, TK_AND, pLeft, pRight); } @@ -1274,7 +1280,9 @@ void sqlite3ExprFunctionUsable( ){ assert( !IN_RENAME_OBJECT ); assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 ); - if( ExprHasProperty(pExpr, EP_FromDDL) ){ + if( ExprHasProperty(pExpr, EP_FromDDL) + || pParse->prepFlags & SQLITE_PREPARE_FROM_DDL + ){ if( (pDef->funcFlags & SQLITE_FUNC_DIRECT)!=0 || (pParse->db->flags & SQLITE_TrustedSchema)==0 ){ @@ -1970,9 +1978,7 @@ Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int flags){ pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags); pNew->iLimit = 0; pNew->iOffset = 0; - pNew->selFlags = p->selFlags & ~(u32)SF_UsesEphemeral; - pNew->addrOpenEphm[0] = -1; - pNew->addrOpenEphm[1] = -1; + pNew->selFlags = p->selFlags; pNew->nSelectRow = p->nSelectRow; pNew->pWith = sqlite3WithDup(db, p->pWith); #ifndef SQLITE_OMIT_WINDOWFUNC @@ -2624,7 +2630,7 @@ static int exprIsConst(Parse *pParse, Expr *p, int initFlag){ /* ** Walk an expression tree. Return non-zero if the expression is constant -** and 0 if it involves variables or function calls. +** or return zero if the expression involves variables or function calls. ** ** For the purposes of this function, a double-quoted string (ex: "abc") ** is considered a variable but a single-quoted string (ex: 'abc') is @@ -3414,6 +3420,7 @@ int sqlite3FindInIndex( */ u32 savedNQueryLoop = pParse->nQueryLoop; int rMayHaveNull = 0; + int bloomOk = (inFlags & IN_INDEX_MEMBERSHIP)!=0; eType = IN_INDEX_EPH; if( inFlags & IN_INDEX_LOOP ){ pParse->nQueryLoop = 0; @@ -3421,7 +3428,13 @@ int sqlite3FindInIndex( *prRhsHasNull = rMayHaveNull = ++pParse->nMem; } assert( pX->op==TK_IN ); - sqlite3CodeRhsOfIN(pParse, pX, iTab); + if( !bloomOk + && ExprUseXSelect(pX) + && (pX->x.pSelect->selFlags & SF_ClonedRhsIn)!=0 + ){ + bloomOk = 1; + } + sqlite3CodeRhsOfIN(pParse, pX, iTab, bloomOk); if( rMayHaveNull ){ sqlite3SetHasNullFlag(v, iTab, rMayHaveNull); } @@ -3579,7 +3592,8 @@ static int findCompatibleInRhsSubrtn( void sqlite3CodeRhsOfIN( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The IN operator */ - int iTab /* Use this cursor number */ + int iTab, /* Use this cursor number */ + int allowBloom /* True to allow the use of a Bloom filter */ ){ int addrOnce = 0; /* Address of the OP_Once instruction at top */ int addr; /* Address of OP_OpenEphemeral instruction */ @@ -3701,7 +3715,10 @@ void sqlite3CodeRhsOfIN( sqlite3SelectDestInit(&dest, SRT_Set, iTab); dest.zAffSdst = exprINAffinity(pParse, pExpr); pSelect->iLimit = 0; - if( addrOnce && OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ){ + if( addrOnce + && allowBloom + && OptimizationEnabled(pParse->db, SQLITE_BloomFilter) + ){ int regBloom = ++pParse->nMem; addrBloom = sqlite3VdbeAddOp2(v, OP_Blob, 10000, regBloom); VdbeComment((v, "Bloom filter")); @@ -3922,7 +3939,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ || (pLeft->u.iValue!=1 && pLeft->u.iValue!=0) ){ sqlite3 *db = pParse->db; - pLimit = sqlite3Expr(db, TK_INTEGER, "0"); + pLimit = sqlite3ExprInt32(db, 0); if( pLimit ){ pLimit->affExpr = SQLITE_AFF_NUMERIC; pLimit = sqlite3PExpr(pParse, TK_NE, @@ -3933,7 +3950,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ } }else{ /* If there is no pre-existing limit add a limit of 1 */ - pLimit = sqlite3Expr(pParse->db, TK_INTEGER, "1"); + pLimit = sqlite3ExprInt32(pParse->db, 1); pSel->pLimit = sqlite3PExpr(pParse, TK_LIMIT, pLimit, 0); } pSel->iLimit = 0; @@ -4194,8 +4211,9 @@ static void sqlite3ExprCodeIN( if( ExprHasProperty(pExpr, EP_Subrtn) ){ const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); assert( pOp->opcode==OP_Once || pParse->nErr ); - if( pOp->opcode==OP_Once && pOp->p3>0 ){ /* tag-202407032019 */ - assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ); + if( pOp->p3>0 ){ /* tag-202407032019 */ + assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) + || pParse->nErr ); sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse, rLhs, nVector); VdbeCoverage(v); } @@ -4285,7 +4303,7 @@ static void sqlite3ExprCodeIN( static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){ if( ALWAYS(z!=0) ){ double value; - sqlite3AtoF(z, &value, sqlite3Strlen30(z), SQLITE_UTF8); + sqlite3AtoF(z, &value); assert( !sqlite3IsNaN(value) ); /* The new AtoF never returns NaN */ if( negateFlag ) value = -value; sqlite3VdbeAddOp4Dup8(v, OP_Real, 0, iMem, 0, (u8*)&value, P4_REAL); @@ -7386,7 +7404,10 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ if( pIEpr==0 ) break; if( NEVER(!ExprUseYTab(pExpr)) ) break; for(i=0; inSrc; i++){ - if( pSrcList->a[0].iCursor==pIEpr->iDataCur ) break; + if( pSrcList->a[i].iCursor==pIEpr->iDataCur ){ + testcase( i>0 ); + break; + } } if( i>=pSrcList->nSrc ) break; if( NEVER(pExpr->pAggInfo!=0) ) break; /* Resolved by outer context */ diff --git a/src/fkey.c b/src/fkey.c index f1117a884..59edd8810 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -688,6 +688,7 @@ FKey *sqlite3FkReferences(Table *pTab){ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ if( p ){ TriggerStep *pStep = p->step_list; + sqlite3SrcListDelete(dbMem, pStep->pSrc); sqlite3ExprDelete(dbMem, pStep->pWhere); sqlite3ExprListDelete(dbMem, pStep->pExprList); sqlite3SelectDelete(dbMem, pStep->pSelect); @@ -907,6 +908,7 @@ void sqlite3FkCheck( if( !IsOrdinaryTable(pTab) ) return; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb>=00 && iDbnDb ); zDb = db->aDb[iDb].zDbSName; /* Loop through all the foreign key constraints for which pTab is the @@ -1354,14 +1356,14 @@ static Trigger *fkActionTrigger( pTrigger = (Trigger *)sqlite3DbMallocZero(db, sizeof(Trigger) + /* struct Trigger */ - sizeof(TriggerStep) + /* Single step in trigger program */ - nFrom + 1 /* Space for pStep->zTarget */ + sizeof(TriggerStep) /* Single step in trigger program */ ); if( pTrigger ){ pStep = pTrigger->step_list = (TriggerStep *)&pTrigger[1]; - pStep->zTarget = (char *)&pStep[1]; - memcpy((char *)pStep->zTarget, zFrom, nFrom); - + pStep->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + if( pStep->pSrc ){ + pStep->pSrc->a[0].zName = sqlite3DbStrNDup(db, zFrom, nFrom); + } pStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE); pStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); diff --git a/src/func.c b/src/func.c index 6dac7195a..d9d8f59ad 100644 --- a/src/func.c +++ b/src/func.c @@ -466,7 +466,7 @@ static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ sqlite3_result_error_nomem(context); return; } - sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); + sqlite3AtoF(zBuf, &r); sqlite3_free(zBuf); } sqlite3_result_double(context, r); @@ -1104,7 +1104,7 @@ void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue, int bEscape){ sqlite3_str_appendf(pStr, "%!0.15g", r1); zVal = sqlite3_str_value(pStr); if( zVal ){ - sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8); + sqlite3AtoF(zVal, &r2); if( r1!=r2 ){ sqlite3_str_reset(pStr); sqlite3_str_appendf(pStr, "%!0.20e", r1); @@ -1201,7 +1201,7 @@ static void unistrFunc( } i = j = 0; while( ifuncFlags |= flags; pDef->funcFlags &= ~SQLITE_FUNC_UNSAFE; } diff --git a/src/hwtime.h b/src/hwtime.h index f808fa40e..cf637edaf 100644 --- a/src/hwtime.h +++ b/src/hwtime.h @@ -16,17 +16,19 @@ #ifndef SQLITE_HWTIME_H #define SQLITE_HWTIME_H -/* -** The following routine only works on Pentium-class (or newer) processors. -** It uses the RDTSC opcode to read the cycle count value out of the -** processor and returns that value. This can be used for high-res -** profiling. -*/ -#if !defined(__STRICT_ANSI__) && \ - (defined(__GNUC__) || defined(_MSC_VER)) && \ - (defined(i386) || defined(__i386__) || defined(_M_IX86)) +#if defined(_MSC_VER) && defined(_WIN32) + + #include "windows.h" + #include - #if defined(__GNUC__) + __inline sqlite3_uint64 sqlite3Hwtime(void){ + LARGE_INTEGER tm; + QueryPerformanceCounter(&tm); + return (sqlite3_uint64)tm.QuadPart; + } + +#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && \ + (defined(i386) || defined(__i386__) || defined(_M_IX86)) __inline__ sqlite_uint64 sqlite3Hwtime(void){ unsigned int lo, hi; @@ -34,17 +36,6 @@ return (sqlite_uint64)hi << 32 | lo; } - #elif defined(_MSC_VER) - - __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ - __asm { - rdtsc - ret ; return value at EDX:EAX - } - } - - #endif - #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) __inline__ sqlite_uint64 sqlite3Hwtime(void){ @@ -52,6 +43,14 @@ __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return (sqlite_uint64)hi << 32 | lo; } + +#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && defined(__aarch64__) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + sqlite3_uint64 cnt; + __asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (cnt)); + return cnt; + } #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) diff --git a/src/json.c b/src/json.c index d0d3c53a2..795d3ed73 100644 --- a/src/json.c +++ b/src/json.c @@ -328,7 +328,10 @@ struct JsonString { #define JSON_SQL 0x02 /* Result is always SQL */ #define JSON_ABPATH 0x03 /* Allow abbreviated JSON path specs */ #define JSON_ISSET 0x04 /* json_set(), not json_insert() */ -#define JSON_BLOB 0x08 /* Use the BLOB output format */ +#define JSON_AINS 0x08 /* json_array_insert(), not json_insert() */ +#define JSON_BLOB 0x10 /* Use the BLOB output format */ + +#define JSON_INSERT_TYPE(X) (((X)&0xC)>>2) /* A parsed JSON value. Lifecycle: @@ -374,6 +377,7 @@ struct JsonParse { #define JEDIT_REPL 2 /* Overwrite if exists */ #define JEDIT_INS 3 /* Insert if not exists */ #define JEDIT_SET 4 /* Insert or overwrite */ +#define JEDIT_AINS 5 /* array_insert() */ /* ** Maximum nesting depth of JSON for this implementation. @@ -2870,7 +2874,8 @@ static int jsonLabelCompare( */ #define JSON_LOOKUP_ERROR 0xffffffff #define JSON_LOOKUP_NOTFOUND 0xfffffffe -#define JSON_LOOKUP_PATHERROR 0xfffffffd +#define JSON_LOOKUP_NOTARRAY 0xfffffffd +#define JSON_LOOKUP_PATHERROR 0xfffffffc #define JSON_LOOKUP_ISERROR(x) ((x)>=JSON_LOOKUP_PATHERROR) /* Forward declaration */ @@ -2899,7 +2904,7 @@ static u32 jsonLookupStep(JsonParse*,u32,const char*,u32); static u32 jsonCreateEditSubstructure( JsonParse *pParse, /* The original JSONB that is being edited */ JsonParse *pIns, /* Populate this with the blob data to insert */ - const char *zTail /* Tail of the path that determins substructure */ + const char *zTail /* Tail of the path that determines substructure */ ){ static const u8 emptyObject[] = { JSONB_ARRAY, JSONB_OBJECT }; int rc; @@ -2934,9 +2939,9 @@ static u32 jsonCreateEditSubstructure( ** Return one of the JSON_LOOKUP error codes if problems are seen. ** ** This routine will also modify the blob. If pParse->eEdit is one of -** JEDIT_DEL, JEDIT_REPL, JEDIT_INS, or JEDIT_SET, then changes might be -** made to the selected value. If an edit is performed, then the return -** value does not necessarily point to the select element. If an edit +** JEDIT_DEL, JEDIT_REPL, JEDIT_INS, JEDIT_SET, or JEDIT_AINS, then changes +** might be made to the selected value. If an edit is performed, then the +** return value does not necessarily point to the select element. If an edit ** is performed, the return value is only useful for detecting error ** conditions. */ @@ -2962,6 +2967,13 @@ static u32 jsonLookupStep( jsonBlobEdit(pParse, iRoot, sz, 0, 0); }else if( pParse->eEdit==JEDIT_INS ){ /* Already exists, so json_insert() is a no-op */ + }else if( pParse->eEdit==JEDIT_AINS ){ + /* json_array_insert() */ + if( zPath[-1]!=']' ){ + return JSON_LOOKUP_NOTARRAY; + }else{ + jsonBlobEdit(pParse, iRoot, 0, pParse->aIns, pParse->nIns); + } }else{ /* json_set() or json_replace() */ jsonBlobEdit(pParse, iRoot, sz, pParse->aIns, pParse->nIns); @@ -3033,6 +3045,10 @@ static u32 jsonLookupStep( JsonParse ix; /* Header of the label to be inserted */ testcase( pParse->eEdit==JEDIT_INS ); testcase( pParse->eEdit==JEDIT_SET ); + testcase( pParse->eEdit==JEDIT_AINS ); + if( pParse->eEdit==JEDIT_AINS && sqlite3_strglob("*]",&zPath[i])!=0 ){ + return JSON_LOOKUP_NOTARRAY; + } memset(&ix, 0, sizeof(ix)); ix.db = pParse->db; jsonBlobAppendNode(&ix, rawKey?JSONB_TEXTRAW:JSONB_TEXT5, nKey, 0); @@ -3060,28 +3076,32 @@ static u32 jsonLookupStep( return rc; } }else if( zPath[0]=='[' ){ + u64 kk = 0; x = pParse->aBlob[iRoot] & 0x0f; if( x!=JSONB_ARRAY ) return JSON_LOOKUP_NOTFOUND; n = jsonbPayloadSize(pParse, iRoot, &sz); - k = 0; i = 1; while( sqlite3Isdigit(zPath[i]) ){ - k = k*10 + zPath[i] - '0'; + if( kk<0xffffffff ) kk = kk*10 + zPath[i] - '0'; + /* ^^^^^^^^^^--- Allow kk to be bigger than any JSON array so that + ** we get NOTFOUND instead of PATHERROR, without overflowing kk. */ i++; } if( i<2 || zPath[i]!=']' ){ if( zPath[1]=='#' ){ - k = jsonbArrayCount(pParse, iRoot); + kk = jsonbArrayCount(pParse, iRoot); i = 2; if( zPath[2]=='-' && sqlite3Isdigit(zPath[3]) ){ - unsigned int nn = 0; + u64 nn = 0; i = 3; do{ - nn = nn*10 + zPath[i] - '0'; + if( nn<0xffffffff ) nn = nn*10 + zPath[i] - '0'; + /* ^^^^^^^^^^--- Allow nn to be bigger than any JSON array to + ** get NOTFOUND instead of PATHERROR, without overflowing nn. */ i++; }while( sqlite3Isdigit(zPath[i]) ); - if( nn>k ) return JSON_LOOKUP_NOTFOUND; - k -= nn; + if( nn>kk ) return JSON_LOOKUP_NOTFOUND; + kk -= nn; } if( zPath[i]!=']' ){ return JSON_LOOKUP_PATHERROR; @@ -3093,21 +3113,22 @@ static u32 jsonLookupStep( j = iRoot+n; iEnd = j+sz; while( jdelta ) jsonAfterEditSizeAdjust(pParse, iRoot); return rc; } - k--; + kk--; n = jsonbPayloadSize(pParse, j, &sz); if( n==0 ) return JSON_LOOKUP_ERROR; j += n+sz; } if( j>iEnd ) return JSON_LOOKUP_ERROR; - if( k>0 ) return JSON_LOOKUP_NOTFOUND; + if( kk>0 ) return JSON_LOOKUP_NOTFOUND; if( pParse->eEdit>=JEDIT_INS ){ JsonParse v; testcase( pParse->eEdit==JEDIT_INS ); + testcase( pParse->eEdit==JEDIT_AINS ); testcase( pParse->eEdit==JEDIT_SET ); rc = jsonCreateEditSubstructure(pParse, &v, &zPath[i+1]); if( !JSON_LOOKUP_ISERROR(rc) @@ -3245,7 +3266,7 @@ static void jsonReturnFromBlob( to_double: z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); if( z==0 ) goto returnfromblob_oom; - rc = sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); + rc = sqlite3AtoF(z, &r); sqlite3DbFree(db, z); if( rc<=0 ) goto returnfromblob_malformed; sqlite3_result_double(pCtx, r); @@ -3432,9 +3453,15 @@ static int jsonFunctionArgToBlob( */ static char *jsonBadPathError( sqlite3_context *ctx, /* The function call containing the error */ - const char *zPath /* The path with the problem */ + const char *zPath, /* The path with the problem */ + int rc /* Maybe JSON_LOOKUP_NOTARRAY */ ){ - char *zMsg = sqlite3_mprintf("bad JSON path: %Q", zPath); + char *zMsg; + if( rc==(int)JSON_LOOKUP_NOTARRAY ){ + zMsg = sqlite3_mprintf("not an array element: %Q", zPath); + }else{ + zMsg = sqlite3_mprintf("bad JSON path: %Q", zPath); + } if( ctx==0 ) return zMsg; if( zMsg ){ sqlite3_result_error(ctx, zMsg, -1); @@ -3451,13 +3478,13 @@ static char *jsonBadPathError( ** and return the result. ** ** The specific operation is determined by eEdit, which can be one -** of JEDIT_INS, JEDIT_REPL, or JEDIT_SET. +** of JEDIT_INS, JEDIT_REPL, JEDIT_SET, or JEDIT_AINS. */ static void jsonInsertIntoBlob( sqlite3_context *ctx, int argc, sqlite3_value **argv, - int eEdit /* JEDIT_INS, JEDIT_REPL, or JEDIT_SET */ + int eEdit /* JEDIT_INS, JEDIT_REPL, JEDIT_SET, JEDIT_AINS */ ){ int i; u32 rc = 0; @@ -3509,7 +3536,7 @@ static void jsonInsertIntoBlob( if( rc==JSON_LOOKUP_ERROR ){ sqlite3_result_error(ctx, "malformed JSON", -1); }else{ - jsonBadPathError(ctx, zPath); + jsonBadPathError(ctx, zPath, rc); } return; } @@ -3951,7 +3978,7 @@ static void jsonArrayLengthFunc( if( i==JSON_LOOKUP_NOTFOUND ){ /* no-op */ }else if( i==JSON_LOOKUP_PATHERROR ){ - jsonBadPathError(ctx, zPath); + jsonBadPathError(ctx, zPath, 0); }else{ sqlite3_result_error(ctx, "malformed JSON", -1); } @@ -4056,7 +4083,7 @@ static void jsonExtractFunc( j = jsonLookupStep(p, 0, jx.zBuf, 0); jsonStringReset(&jx); }else{ - jsonBadPathError(ctx, zPath); + jsonBadPathError(ctx, zPath, 0); goto json_extract_error; } if( jnBlob ){ @@ -4091,7 +4118,7 @@ static void jsonExtractFunc( sqlite3_result_error(ctx, "malformed JSON", -1); goto json_extract_error; }else{ - jsonBadPathError(ctx, zPath); + jsonBadPathError(ctx, zPath, 0); goto json_extract_error; } } @@ -4420,7 +4447,7 @@ static void jsonRemoveFunc( if( rc==JSON_LOOKUP_NOTFOUND ){ continue; /* No-op */ }else if( rc==JSON_LOOKUP_PATHERROR ){ - jsonBadPathError(ctx, zPath); + jsonBadPathError(ctx, zPath, rc); }else{ sqlite3_result_error(ctx, "malformed JSON", -1); } @@ -4432,7 +4459,7 @@ static void jsonRemoveFunc( return; json_remove_patherror: - jsonBadPathError(ctx, zPath); + jsonBadPathError(ctx, zPath, 0); json_remove_done: jsonParseFree(p); @@ -4476,16 +4503,18 @@ static void jsonSetFunc( int argc, sqlite3_value **argv ){ - int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); - int bIsSet = (flags&JSON_ISSET)!=0; + int eInsType = JSON_INSERT_TYPE(flags); + static const char *azInsType[] = { "insert", "set", "array_insert" }; + static const u8 aEditType[] = { JEDIT_INS, JEDIT_SET, JEDIT_AINS }; if( argc<1 ) return; + assert( eInsType>=0 && eInsType<=2 ); if( (argc&1)==0 ) { - jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); + jsonWrongNumArgs(ctx, azInsType[eInsType]); return; } - jsonInsertIntoBlob(ctx, argc, argv, bIsSet ? JEDIT_SET : JEDIT_INS); + jsonInsertIntoBlob(ctx, argc, argv, aEditType[eInsType]); } /* @@ -4510,7 +4539,7 @@ static void jsonTypeFunc( zPath = (const char*)sqlite3_value_text(argv[1]); if( zPath==0 ) goto json_type_done; if( zPath[0]!='$' ){ - jsonBadPathError(ctx, zPath); + jsonBadPathError(ctx, zPath, 0); goto json_type_done; } i = jsonLookupStep(p, 0, zPath+1, 0); @@ -4518,7 +4547,7 @@ static void jsonTypeFunc( if( i==JSON_LOOKUP_NOTFOUND ){ /* no-op */ }else if( i==JSON_LOOKUP_PATHERROR ){ - jsonBadPathError(ctx, zPath); + jsonBadPathError(ctx, zPath, 0); }else{ sqlite3_result_error(ctx, "malformed JSON", -1); } @@ -4774,12 +4803,11 @@ static void jsonArrayStep( } static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ - int flags; pStr->pCtx = ctx; jsonAppendChar(pStr, ']'); - flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); if( pStr->eErr ){ jsonReturnString(pStr, 0, 0); return; @@ -4800,6 +4828,9 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); jsonStringTrimOneChar(pStr); } + }else if( flags & JSON_BLOB ){ + static const u8 emptyArray = 0x0b; + sqlite3_result_blob(ctx, &emptyArray, 1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC); } @@ -4896,12 +4927,11 @@ static void jsonObjectStep( } static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ - int flags; jsonAppendChar(pStr, '}'); pStr->pCtx = ctx; - flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); if( pStr->eErr ){ jsonReturnString(pStr, 0, 0); return; @@ -4922,6 +4952,9 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); jsonStringTrimOneChar(pStr); } + }else if( flags & JSON_BLOB ){ + static const unsigned char emptyObject = 0x0c; + sqlite3_result_blob(ctx, &emptyObject, 1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC); } @@ -5422,7 +5455,7 @@ static int jsonEachFilter( if( zRoot==0 ) return SQLITE_OK; if( zRoot[0]!='$' ){ sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot, 0); jsonEachCursorReset(p); return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; } @@ -5440,7 +5473,7 @@ static int jsonEachFilter( return SQLITE_OK; } sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot, 0); jsonEachCursorReset(p); return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; } @@ -5530,6 +5563,8 @@ void sqlite3RegisterJsonFunctions(void){ JFUNCTION(jsonb, 1,1,0, 0,1,0, jsonRemoveFunc), JFUNCTION(json_array, -1,0,1, 1,0,0, jsonArrayFunc), JFUNCTION(jsonb_array, -1,0,1, 1,1,0, jsonArrayFunc), + JFUNCTION(json_array_insert, -1,1,1, 1,0,JSON_AINS, jsonSetFunc), + JFUNCTION(jsonb_array_insert,-1,1,0, 1,1,JSON_AINS, jsonSetFunc), JFUNCTION(json_array_length, 1,1,0, 0,0,0, jsonArrayLengthFunc), JFUNCTION(json_array_length, 2,1,0, 0,0,0, jsonArrayLengthFunc), JFUNCTION(json_error_position,1,1,0, 0,0,0, jsonErrorFunc), diff --git a/src/loadext.c b/src/loadext.c index c5177715e..55325edd7 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -522,7 +522,17 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_setlk_timeout, /* Version 3.51.0 and later */ sqlite3_set_errmsg, - sqlite3_db_status64 + sqlite3_db_status64, + /* Version 3.52.0 and later */ + sqlite3_str_truncate, + sqlite3_str_free, +#ifdef SQLITE_ENABLE_CARRAY + sqlite3_carray_bind, + sqlite3_carray_bind_v2 +#else + 0, + 0 +#endif }; /* True if x is the directory separator character @@ -624,33 +634,42 @@ static int sqlite3LoadExtension( ** entry point name "sqlite3_extension_init" was not found, then ** construct an entry point name "sqlite3_X_init" where the X is ** replaced by the lowercase value of every ASCII alphabetic - ** character in the filename after the last "/" upto the first ".", - ** and eliding the first three characters if they are "lib". + ** character in the filename after the last "/" up to the first ".", + ** and skipping the first three characters if they are "lib". ** Examples: ** ** /usr/local/lib/libExample5.4.3.so ==> sqlite3_example_init ** C:/lib/mathfuncs.dll ==> sqlite3_mathfuncs_init + ** + ** If that still finds no entry point, repeat a second time but this + ** time include both alphabetic and numeric characters up to the first + ** ".". Example: + ** + ** /usr/local/lib/libExample5.4.3.so ==> sqlite3_example5_init */ if( xInit==0 && zProc==0 ){ int iFile, iEntry, c; int ncFile = sqlite3Strlen30(zFile); + int cnt = 0; zAltEntry = sqlite3_malloc64(ncFile+30); if( zAltEntry==0 ){ sqlite3OsDlClose(pVfs, handle); return SQLITE_NOMEM_BKPT; } - memcpy(zAltEntry, "sqlite3_", 8); - for(iFile=ncFile-1; iFile>=0 && !DirSep(zFile[iFile]); iFile--){} - iFile++; - if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; - for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ - if( sqlite3Isalpha(c) ){ - zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; + do{ + memcpy(zAltEntry, "sqlite3_", 8); + for(iFile=ncFile-1; iFile>=0 && !DirSep(zFile[iFile]); iFile--){} + iFile++; + if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; + for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ + if( sqlite3Isalpha(c) || (cnt && sqlite3Isdigit(c)) ){ + zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; + } } - } - memcpy(zAltEntry+iEntry, "_init", 6); - zEntry = zAltEntry; - xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); + memcpy(zAltEntry+iEntry, "_init", 6); + zEntry = zAltEntry; + xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); + }while( xInit==0 && (++cnt)<2 ); } if( xInit==0 ){ if( pzErrMsg ){ diff --git a/src/main.c b/src/main.c index 6efe538d4..b44ac8dca 100644 --- a/src/main.c +++ b/src/main.c @@ -971,6 +971,14 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ rc = setupLookaside(db, pBuf, sz, cnt); break; } + case SQLITE_DBCONFIG_FP_DIGITS: { + int nIn = va_arg(ap, int); + int *pOut = va_arg(ap, int*); + if( nIn>3 && nIn<24 ) db->nFpDigit = (u8)nIn; + if( pOut ) *pOut = db->nFpDigit; + rc = SQLITE_OK; + break; + } default: { static const struct { int op; /* The opcode */ @@ -2526,6 +2534,9 @@ void *sqlite3_wal_hook( sqlite3_mutex_leave(db->mutex); return pRet; #else + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(xCallback); + UNUSED_PARAMETER(pArg); return 0; #endif } @@ -2541,6 +2552,11 @@ int sqlite3_wal_checkpoint_v2( int *pnCkpt /* OUT: Total number of frames checkpointed */ ){ #ifdef SQLITE_OMIT_WAL + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(zDb); + UNUSED_PARAMETER(eMode); + UNUSED_PARAMETER(pnLog); + UNUSED_PARAMETER(pnCkpt); return SQLITE_OK; #else int rc; /* Return code */ @@ -2554,11 +2570,12 @@ int sqlite3_wal_checkpoint_v2( if( pnLog ) *pnLog = -1; if( pnCkpt ) *pnCkpt = -1; + assert( SQLITE_CHECKPOINT_NOOP==-1 ); assert( SQLITE_CHECKPOINT_PASSIVE==0 ); assert( SQLITE_CHECKPOINT_FULL==1 ); assert( SQLITE_CHECKPOINT_RESTART==2 ); assert( SQLITE_CHECKPOINT_TRUNCATE==3 ); - if( eModeSQLITE_CHECKPOINT_TRUNCATE ){ + if( eModeSQLITE_CHECKPOINT_TRUNCATE ){ /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint ** mode: */ return SQLITE_MISUSE_BKPT; @@ -2922,6 +2939,7 @@ static const int aHardLimit[] = { SQLITE_MAX_VARIABLE_NUMBER, /* IMP: R-38091-32352 */ SQLITE_MAX_TRIGGER_DEPTH, SQLITE_MAX_WORKER_THREADS, + SQLITE_MAX_PARSER_DEPTH, }; /* @@ -2936,6 +2954,9 @@ static const int aHardLimit[] = { #if SQLITE_MAX_SQL_LENGTH>SQLITE_MAX_LENGTH # error SQLITE_MAX_SQL_LENGTH must not be greater than SQLITE_MAX_LENGTH #endif +#if SQLITE_MAX_SQL_LENGTH>2147482624 /* 1024 less than 2^31 */ +# error SQLITE_MAX_SQL_LENGTH must not be greater than 2147482624 +#endif #if SQLITE_MAX_COMPOUND_SELECT<2 # error SQLITE_MAX_COMPOUND_SELECT must be at least 2 #endif @@ -2991,6 +3012,7 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ assert( aHardLimit[SQLITE_LIMIT_SQL_LENGTH]==SQLITE_MAX_SQL_LENGTH ); assert( aHardLimit[SQLITE_LIMIT_COLUMN]==SQLITE_MAX_COLUMN ); assert( aHardLimit[SQLITE_LIMIT_EXPR_DEPTH]==SQLITE_MAX_EXPR_DEPTH ); + assert( aHardLimit[SQLITE_LIMIT_PARSER_DEPTH]==SQLITE_MAX_PARSER_DEPTH ); assert( aHardLimit[SQLITE_LIMIT_COMPOUND_SELECT]==SQLITE_MAX_COMPOUND_SELECT); assert( aHardLimit[SQLITE_LIMIT_VDBE_OP]==SQLITE_MAX_VDBE_OP ); assert( aHardLimit[SQLITE_LIMIT_FUNCTION_ARG]==SQLITE_MAX_FUNCTION_ARG ); @@ -3000,7 +3022,7 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ assert( aHardLimit[SQLITE_LIMIT_VARIABLE_NUMBER]==SQLITE_MAX_VARIABLE_NUMBER); assert( aHardLimit[SQLITE_LIMIT_TRIGGER_DEPTH]==SQLITE_MAX_TRIGGER_DEPTH ); assert( aHardLimit[SQLITE_LIMIT_WORKER_THREADS]==SQLITE_MAX_WORKER_THREADS ); - assert( SQLITE_LIMIT_WORKER_THREADS==(SQLITE_N_LIMIT-1) ); + assert( SQLITE_LIMIT_PARSER_DEPTH==(SQLITE_N_LIMIT-1) ); if( limitId<0 || limitId>=SQLITE_N_LIMIT ){ @@ -3364,7 +3386,7 @@ static int openDatabase( db = sqlite3MallocZero( sizeof(sqlite3) ); if( db==0 ) goto opendb_out; if( isThreadsafe -#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS +#if defined(SQLITE_THREAD_MISUSE_WARNINGS) || sqlite3GlobalConfig.bCoreMutex #endif ){ @@ -3385,6 +3407,7 @@ static int openDatabase( db->aDb = db->aDbStatic; db->lookaside.bDisable = 1; db->lookaside.sz = 0; + db->nFpDigit = 17; assert( sizeof(db->aLimit)==sizeof(aHardLimit) ); memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit)); @@ -3830,6 +3853,12 @@ int sqlite3_collation_needed16( */ void *sqlite3_get_clientdata(sqlite3 *db, const char *zName){ DbClientData *p; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zName || !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); for(p=db->pDbData; p; p=p->pNext){ if( strcmp(p->zName, zName)==0 ){ @@ -4903,6 +4932,7 @@ const char *sqlite3_filename_journal(const char *zFilename){ } const char *sqlite3_filename_wal(const char *zFilename){ #ifdef SQLITE_OMIT_WAL + UNUSED_PARAMETER(zFilename); return 0; #else zFilename = sqlite3_filename_journal(zFilename); diff --git a/src/malloc.c b/src/malloc.c index 9a635e716..942934644 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -289,27 +289,6 @@ static void mallocWithAlarm(int n, void **pp){ *pp = p; } -/* -** Maximum size of any single memory allocation. -** -** This is not a limit on the total amount of memory used. This is -** a limit on the size parameter to sqlite3_malloc() and sqlite3_realloc(). -** -** The upper bound is slightly less than 2GiB: 0x7ffffeff == 2,147,483,391 -** This provides a 256-byte safety margin for defense against 32-bit -** signed integer overflow bugs when computing memory allocation sizes. -** Paranoid applications might want to reduce the maximum allocation size -** further for an even larger safety margin. 0x3fffffff or 0x0fffffff -** or even smaller would be reasonable upper bounds on the size of a memory -** allocations for most applications. -*/ -#ifndef SQLITE_MAX_ALLOCATION_SIZE -# define SQLITE_MAX_ALLOCATION_SIZE 2147483391 -#endif -#if SQLITE_MAX_ALLOCATION_SIZE>2147483391 -# error Maximum size for SQLITE_MAX_ALLOCATION_SIZE is 2147483391 -#endif - /* ** Allocate memory. This routine is like sqlite3_malloc() except that it ** assumes the memory subsystem has already been initialized. @@ -533,8 +512,7 @@ void *sqlite3Realloc(void *pOld, u64 nBytes){ sqlite3_free(pOld); /* IMP: R-26507-47431 */ return 0; } - if( nBytes>=0x7fffff00 ){ - /* The 0x7ffff00 limit term is explained in comments on sqlite3Malloc() */ + if( nBytes>SQLITE_MAX_ALLOCATION_SIZE ){ return 0; } nOld = sqlite3MallocSize(pOld); diff --git a/src/mutex.c b/src/mutex.c index 62e09cb4f..21b47e511 100644 --- a/src/mutex.c +++ b/src/mutex.c @@ -27,23 +27,28 @@ static SQLITE_WSD int mutexIsInit = 0; #ifndef SQLITE_MUTEX_OMIT -#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS +#ifdef SQLITE_THREAD_MISUSE_WARNINGS /* -** This block (enclosed by SQLITE_ENABLE_MULTITHREADED_CHECKS) contains +** This block (enclosed by SQLITE_THREAD_MISUSE_WARNINGS) contains ** the implementation of a wrapper around the system default mutex ** implementation (sqlite3DefaultMutex()). ** ** Most calls are passed directly through to the underlying default ** mutex implementation. Except, if a mutex is configured by calling ** sqlite3MutexWarnOnContention() on it, then if contention is ever -** encountered within xMutexEnter() a warning is emitted via sqlite3_log(). +** encountered within xMutexEnter() then a warning is emitted via +** sqlite3_log(). Furthermore, if SQLITE_THREAD_MISUSE_ABORT is +** defined then abort() is called after the sqlite3_log() warning. ** -** This type of mutex is used as the database handle mutex when testing -** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. +** This type of mutex is used on the database handle mutex when testing +** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. A failure +** indicates that the app ought to be using SQLITE_OPEN_FULLMUTEX or +** similar because it is trying to use the same database handle from +** two different connections at the same time. */ /* -** Type for all mutexes used when SQLITE_ENABLE_MULTITHREADED_CHECKS +** Type for all mutexes used when SQLITE_THREAD_MISUSE_WARNINGS ** is defined. Variable CheckMutex.mutex is a pointer to the real mutex ** allocated by the system mutex implementation. Variable iType is usually set ** to the type of mutex requested - SQLITE_MUTEX_RECURSIVE, SQLITE_MUTEX_FAST @@ -79,11 +84,12 @@ static int checkMutexNotheld(sqlite3_mutex *p){ */ static int checkMutexInit(void){ pGlobalMutexMethods = sqlite3DefaultMutex(); - return SQLITE_OK; + return pGlobalMutexMethods->xMutexInit(); } static int checkMutexEnd(void){ + int rc = pGlobalMutexMethods->xMutexEnd(); pGlobalMutexMethods = 0; - return SQLITE_OK; + return rc; } /* @@ -160,6 +166,9 @@ static void checkMutexEnter(sqlite3_mutex *p){ sqlite3_log(SQLITE_MISUSE, "illegal multi-threaded access to database connection" ); +#if SQLITE_THREAD_MISUSE_ABORT + abort(); +#endif } pGlobalMutexMethods->xMutexEnter(pCheck->mutex); } @@ -211,7 +220,7 @@ void sqlite3MutexWarnOnContention(sqlite3_mutex *p){ pCheck->iType = SQLITE_MUTEX_WARNONCONTENTION; } } -#endif /* ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS */ +#endif /* ifdef SQLITE_THREAD_MISUSE_WARNINGS */ /* ** Initialize the mutex system. @@ -228,7 +237,7 @@ int sqlite3MutexInit(void){ sqlite3_mutex_methods *pTo = &sqlite3GlobalConfig.mutex; if( sqlite3GlobalConfig.bCoreMutex ){ -#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS +#ifdef SQLITE_THREAD_MISUSE_WARNINGS pFrom = multiThreadedCheckMutex(); #else pFrom = sqlite3DefaultMutex(); diff --git a/src/mutex_w32.c b/src/mutex_w32.c index 7eb5b50be..20e41b161 100644 --- a/src/mutex_w32.c +++ b/src/mutex_w32.c @@ -129,11 +129,7 @@ static int winMutexInit(void){ if( InterlockedCompareExchange(&winMutex_lock, 1, 0)==0 ){ int i; for(i=0; itrace = 1; #endif #endif -#if SQLITE_OS_WINRT - InitializeCriticalSectionEx(&p->mutex, 0, 0); -#else InitializeCriticalSection(&p->mutex); -#endif } break; } diff --git a/src/os_kv.c b/src/os_kv.c index c2d1f9b7a..1fd1c8e8c 100644 --- a/src/os_kv.c +++ b/src/os_kv.c @@ -21,7 +21,7 @@ ** Debugging logic */ -/* SQLITE_KV_TRACE() is used for tracing calls to kvstorage routines. */ +/* SQLITE_KV_TRACE() is used for tracing calls to kvrecord routines. */ #if 0 #define SQLITE_KV_TRACE(X) printf X #else @@ -35,7 +35,6 @@ #define SQLITE_KV_LOG(X) #endif - /* ** Forward declaration of objects used by this VFS implementation */ @@ -43,6 +42,11 @@ typedef struct KVVfsFile KVVfsFile; /* A single open file. There are only two files represented by this ** VFS - the database and the rollback journal. +** +** Maintenance reminder: if this struct changes in any way, the JSON +** rendering of its structure must be updated in +** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary +** compatibility concerns, so it does not need an iVersion member. */ struct KVVfsFile { sqlite3_file base; /* IO methods */ @@ -92,7 +96,7 @@ static int kvvfsCurrentTime(sqlite3_vfs*, double*); static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); static sqlite3_vfs sqlite3OsKvvfsObject = { - 1, /* iVersion */ + 2, /* iVersion */ sizeof(KVVfsFile), /* szOsFile */ 1024, /* mxPathname */ 0, /* pNext */ @@ -168,23 +172,37 @@ static sqlite3_io_methods kvvfs_jrnl_io_methods = { /* Forward declarations for the low-level storage engine */ -static int kvstorageWrite(const char*, const char *zKey, const char *zData); -static int kvstorageDelete(const char*, const char *zKey); -static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); -#define KVSTORAGE_KEY_SZ 32 +#ifndef SQLITE_WASM +/* In WASM builds these are implemented in JS. */ +static int kvrecordWrite(const char*, const char *zKey, const char *zData); +static int kvrecordDelete(const char*, const char *zKey); +static int kvrecordRead(const char*, const char *zKey, char *zBuf, int nBuf); +#endif +#ifndef KVRECORD_KEY_SZ +#define KVRECORD_KEY_SZ 32 +#endif /* Expand the key name with an appropriate prefix and put the result ** in zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least -** KVSTORAGE_KEY_SZ bytes. +** KVRECORD_KEY_SZ bytes. */ -static void kvstorageMakeKey( +static void kvrecordMakeKey( const char *zClass, const char *zKeyIn, char *zKeyOut ){ - sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn); + assert( zKeyIn ); + assert( zKeyOut ); + assert( zClass ); + sqlite3_snprintf(KVRECORD_KEY_SZ, zKeyOut, "kvvfs-%s-%s", + zClass, zKeyIn); } +#ifndef SQLITE_WASM +/* In WASM builds do not define APIs which use fopen(), fwrite(), +** and the like because those APIs are a portability issue for +** WASM. +*/ /* Write content into a key. zClass is the particular namespace of the ** underlying key/value store to use - either "local" or "session". ** @@ -192,14 +210,14 @@ static void kvstorageMakeKey( ** ** Return the number of errors. */ -static int kvstorageWrite( +static int kvrecordWrite( const char *zClass, const char *zKey, const char *zData ){ FILE *fd; - char zXKey[KVSTORAGE_KEY_SZ]; - kvstorageMakeKey(zClass, zKey, zXKey); + char zXKey[KVRECORD_KEY_SZ]; + kvrecordMakeKey(zClass, zKey, zXKey); fd = fopen(zXKey, "wb"); if( fd ){ SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey, @@ -217,9 +235,9 @@ static int kvstorageWrite( ** namespace given by zClass. If the key does not previously exist, ** this routine is a no-op. */ -static int kvstorageDelete(const char *zClass, const char *zKey){ - char zXKey[KVSTORAGE_KEY_SZ]; - kvstorageMakeKey(zClass, zKey, zXKey); +static int kvrecordDelete(const char *zClass, const char *zKey){ + char zXKey[KVRECORD_KEY_SZ]; + kvrecordMakeKey(zClass, zKey, zXKey); unlink(zXKey); SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey)); return 0; @@ -240,7 +258,7 @@ static int kvstorageDelete(const char *zClass, const char *zKey){ ** zero-terminates zBuf at zBuf[0] and returns the size of the data ** without reading it. */ -static int kvstorageRead( +static int kvrecordRead( const char *zClass, const char *zKey, char *zBuf, @@ -248,8 +266,8 @@ static int kvstorageRead( ){ FILE *fd; struct stat buf; - char zXKey[KVSTORAGE_KEY_SZ]; - kvstorageMakeKey(zClass, zKey, zXKey); + char zXKey[KVRECORD_KEY_SZ]; + kvrecordMakeKey(zClass, zKey, zXKey); if( access(zXKey, R_OK)!=0 || stat(zXKey, &buf)!=0 || !S_ISREG(buf.st_mode) @@ -281,6 +299,8 @@ static int kvstorageRead( return (int)n; } } +#endif /* #ifndef SQLITE_WASM */ + /* ** An internal level of indirection which enables us to replace the @@ -288,17 +308,27 @@ static int kvstorageRead( ** Maintenance reminder: if this struct changes in any way, the JSON ** rendering of its structure must be updated in ** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary -** compatibility concerns, so it does not need an iVersion -** member. +** compatibility concerns, so it does not need an iVersion member. */ typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; struct sqlite3_kvvfs_methods { - int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf); - int (*xWrite)(const char *zClass, const char *zKey, const char *zData); - int (*xDelete)(const char *zClass, const char *zKey); + int (*xRcrdRead)(const char*, const char *zKey, char *zBuf, int nBuf); + int (*xRcrdWrite)(const char*, const char *zKey, const char *zData); + int (*xRcrdDelete)(const char*, const char *zKey); const int nKeySize; + const int nBufferSize; +#ifndef SQLITE_WASM +# define MAYBE_CONST const +#else +# define MAYBE_CONST +#endif + MAYBE_CONST sqlite3_vfs * pVfs; + MAYBE_CONST sqlite3_io_methods *pIoDb; + MAYBE_CONST sqlite3_io_methods *pIoJrnl; +#undef MAYBE_CONST }; + /* ** This object holds the kvvfs I/O methods which may be swapped out ** for JavaScript-side implementations in WASM builds. In such builds @@ -313,10 +343,20 @@ struct sqlite3_kvvfs_methods { const #endif sqlite3_kvvfs_methods sqlite3KvvfsMethods = { -kvstorageRead, -kvstorageWrite, -kvstorageDelete, -KVSTORAGE_KEY_SZ +#ifndef SQLITE_WASM + .xRcrdRead = kvrecordRead, + .xRcrdWrite = kvrecordWrite, + .xRcrdDelete = kvrecordDelete, +#else + .xRcrdRead = 0, + .xRcrdWrite = 0, + .xRcrdDelete = 0, +#endif + .nKeySize = KVRECORD_KEY_SZ, + .nBufferSize = SQLITE_KVOS_SZ, + .pVfs = &sqlite3OsKvvfsObject, + .pIoDb = &kvvfs_db_io_methods, + .pIoJrnl = &kvvfs_jrnl_io_methods }; /****** Utility subroutines ************************************************/ @@ -343,7 +383,10 @@ KVSTORAGE_KEY_SZ ** of hexadecimal and base-26 numbers, it is always clear where ** one stops and the next begins. */ -static int kvvfsEncode(const char *aData, int nData, char *aOut){ +#ifndef SQLITE_WASM +static +#endif +int kvvfsEncode(const char *aData, int nData, char *aOut){ int i, j; const unsigned char *a = (const unsigned char*)aData; for(i=j=0; izClass, "sz", zData, sizeof(zData)-1); + sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "sz", zData, + sizeof(zData)-1); return strtoll(zData, 0, 0); } static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){ char zData[50]; sqlite3_snprintf(sizeof(zData), zData, "%lld", sz); - return sqlite3KvvfsMethods.xWrite(pFile->zClass, "sz", zData); + return sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, "sz", zData); } /****** sqlite3_io_methods methods ******************************************/ @@ -491,10 +539,13 @@ static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){ static int kvvfsClose(sqlite3_file *pProtoFile){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; - SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, + SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, pFile->isJournal ? "journal" : "db")); sqlite3_free(pFile->aJrnl); sqlite3_free(pFile->aData); +#ifdef SQLITE_WASM + memset(pFile, 0, sizeof(*pFile)); +#endif return SQLITE_OK; } @@ -503,24 +554,30 @@ static int kvvfsClose(sqlite3_file *pProtoFile){ */ static int kvvfsReadJrnl( sqlite3_file *pProtoFile, - void *zBuf, - int iAmt, + void *zBuf, + int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; assert( pFile->isJournal ); SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); if( pFile->aJrnl==0 ){ - int szTxt = kvstorageRead(pFile->zClass, "jrnl", 0, 0); + int rc; + int szTxt = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "jrnl", + 0, 0); char *aTxt; if( szTxt<=4 ){ return SQLITE_IOERR; } aTxt = sqlite3_malloc64( szTxt+1 ); if( aTxt==0 ) return SQLITE_NOMEM; - kvstorageRead(pFile->zClass, "jrnl", aTxt, szTxt+1); - kvvfsDecodeJournal(pFile, aTxt, szTxt); + rc = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "jrnl", + aTxt, szTxt+1); + if( rc>=0 ){ + kvvfsDecodeJournal(pFile, aTxt, szTxt); + } sqlite3_free(aTxt); + if( rc ) return rc; if( pFile->aJrnl==0 ) return SQLITE_IOERR; } if( iOfst+iAmt>pFile->nJrnl ){ @@ -535,8 +592,8 @@ static int kvvfsReadJrnl( */ static int kvvfsReadDb( sqlite3_file *pProtoFile, - void *zBuf, - int iAmt, + void *zBuf, + int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; @@ -560,8 +617,8 @@ static int kvvfsReadDb( pgno = 1; } sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); - got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, - aData, SQLITE_KVOS_SZ-1); + got = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, zKey, + aData, SQLITE_KVOS_SZ-1); if( got<0 ){ n = 0; }else{ @@ -593,8 +650,8 @@ static int kvvfsReadDb( */ static int kvvfsWriteJrnl( sqlite3_file *pProtoFile, - const void *zBuf, - int iAmt, + const void *zBuf, + int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; @@ -621,14 +678,15 @@ static int kvvfsWriteJrnl( */ static int kvvfsWriteDb( sqlite3_file *pProtoFile, - const void *zBuf, - int iAmt, + const void *zBuf, + int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; unsigned int pgno; char zKey[30]; char *aData = pFile->aData; + int rc; SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); assert( iAmt>=512 && iAmt<=65536 ); assert( (iAmt & (iAmt-1))==0 ); @@ -637,13 +695,13 @@ static int kvvfsWriteDb( pgno = 1 + iOfst/iAmt; sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); kvvfsEncode(zBuf, iAmt, aData); - if( sqlite3KvvfsMethods.xWrite(pFile->zClass, zKey, aData) ){ - return SQLITE_IOERR; - } - if( iOfst+iAmt > pFile->szDb ){ - pFile->szDb = iOfst + iAmt; + rc = sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, zKey, aData); + if( 0==rc ){ + if( iOfst+iAmt > pFile->szDb ){ + pFile->szDb = iOfst + iAmt; + } } - return SQLITE_OK; + return rc; } /* @@ -653,7 +711,7 @@ static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size)); assert( size==0 ); - sqlite3KvvfsMethods.xDelete(pFile->zClass, "jrnl"); + sqlite3KvvfsMethods.xRcrdDelete(pFile->zClass, "jrnl"); sqlite3_free(pFile->aJrnl); pFile->aJrnl = 0; pFile->nJrnl = 0; @@ -662,7 +720,7 @@ static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){ static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; if( pFile->szDb>size - && pFile->szPage>0 + && pFile->szPage>0 && (size % pFile->szPage)==0 ){ char zKey[50]; @@ -672,7 +730,7 @@ static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){ pgnoMax = 2 + pFile->szDb/pFile->szPage; while( pgno<=pgnoMax ){ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); - sqlite3KvvfsMethods.xDelete(pFile->zClass, zKey); + sqlite3KvvfsMethods.xRcrdDelete(pFile->zClass, zKey); pgno++; } pFile->szDb = size; @@ -704,7 +762,7 @@ static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){ }while( n>0 ); zOut[i++] = ' '; kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]); - i = sqlite3KvvfsMethods.xWrite(pFile->zClass, "jrnl", zOut); + i = sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, "jrnl", zOut); sqlite3_free(zOut); return i ? SQLITE_IOERR : SQLITE_OK; } @@ -818,33 +876,32 @@ static int kvvfsOpen( KVVfsFile *pFile = (KVVfsFile*)pProtoFile; if( zName==0 ) zName = ""; SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName)); - if( strcmp(zName, "local")==0 - || strcmp(zName, "session")==0 - ){ - pFile->isJournal = 0; - pFile->base.pMethods = &kvvfs_db_io_methods; - }else - if( strcmp(zName, "local-journal")==0 - || strcmp(zName, "session-journal")==0 - ){ + assert(!pFile->zClass); + assert(!pFile->aData); + assert(!pFile->aJrnl); + assert(!pFile->nJrnl); + assert(!pFile->base.pMethods); + pFile->szPage = -1; + pFile->szDb = -1; + if( 0==sqlite3_strglob("*-journal", zName) ){ pFile->isJournal = 1; pFile->base.pMethods = &kvvfs_jrnl_io_methods; + if( 0==strcmp("session-journal",zName) ){ + pFile->zClass = "session"; + }else if( 0==strcmp("local-journal",zName) ){ + pFile->zClass = "local"; + } }else{ - return SQLITE_CANTOPEN; + pFile->isJournal = 0; + pFile->base.pMethods = &kvvfs_db_io_methods; } - if( zName[0]=='s' ){ - pFile->zClass = "session"; - }else{ - pFile->zClass = "local"; + if( !pFile->zClass ){ + pFile->zClass = zName; } pFile->aData = sqlite3_malloc64(SQLITE_KVOS_SZ); if( pFile->aData==0 ){ return SQLITE_NOMEM; } - pFile->aJrnl = 0; - pFile->nJrnl = 0; - pFile->szPage = -1; - pFile->szDb = -1; return SQLITE_OK; } @@ -854,13 +911,17 @@ static int kvvfsOpen( ** returning. */ static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + int rc /* The JS impl can fail with OOM in argument conversion */; if( strcmp(zPath, "local-journal")==0 ){ - sqlite3KvvfsMethods.xDelete("local", "jrnl"); + rc = sqlite3KvvfsMethods.xRcrdDelete("local", "jrnl"); }else if( strcmp(zPath, "session-journal")==0 ){ - sqlite3KvvfsMethods.xDelete("session", "jrnl"); + rc = sqlite3KvvfsMethods.xRcrdDelete("session", "jrnl"); } - return SQLITE_OK; + else{ + rc = 0; + } + return rc; } /* @@ -868,27 +929,48 @@ static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ ** is available, or false otherwise. */ static int kvvfsAccess( - sqlite3_vfs *pProtoVfs, - const char *zPath, - int flags, + sqlite3_vfs *pProtoVfs, + const char *zPath, + int flags, int *pResOut ){ SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath)); +#if 0 && defined(SQLITE_WASM) + /* + ** This is not having the desired effect in the JS bindings. + ** It's ostensibly the same logic as the #else block, but + ** it's not behaving that way. + ** + ** In JS we map all zPaths to Storage objects, and -journal files + ** are mapped to the storage for the main db (which is is exactly + ** what the mapping of "local-journal" -> "local" is doing). + */ + const char *zKey = (0==sqlite3_strglob("*-journal", zPath)) + ? "jrnl" : "sz"; + *pResOut = + sqlite3KvvfsMethods.xRcrdRead(zPath, zKey, 0, 0)>0; +#else if( strcmp(zPath, "local-journal")==0 ){ - *pResOut = sqlite3KvvfsMethods.xRead("local", "jrnl", 0, 0)>0; + *pResOut = + sqlite3KvvfsMethods.xRcrdRead("local", "jrnl", 0, 0)>0; }else if( strcmp(zPath, "session-journal")==0 ){ - *pResOut = sqlite3KvvfsMethods.xRead("session", "jrnl", 0, 0)>0; + *pResOut = + sqlite3KvvfsMethods.xRcrdRead("session", "jrnl", 0, 0)>0; }else if( strcmp(zPath, "local")==0 ){ - *pResOut = sqlite3KvvfsMethods.xRead("local", "sz", 0, 0)>0; + *pResOut = + sqlite3KvvfsMethods.xRcrdRead("local", "sz", 0, 0)>0; }else if( strcmp(zPath, "session")==0 ){ - *pResOut = sqlite3KvvfsMethods.xRead("session", "sz", 0, 0)>0; + *pResOut = + sqlite3KvvfsMethods.xRcrdRead("session", "sz", 0, 0)>0; }else { *pResOut = 0; } + /*all current JS tests avoid triggering: assert( *pResOut == 0 ); */ +#endif SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut)); return SQLITE_OK; } @@ -899,9 +981,9 @@ static int kvvfsAccess( ** of at least (INST_MAX_PATHNAME+1) bytes. */ static int kvvfsFullPathname( - sqlite3_vfs *pVfs, - const char *zPath, - int nOut, + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, char *zOut ){ size_t nPath; @@ -924,7 +1006,7 @@ static void *kvvfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){ } /* -** Populate the buffer pointed to by zBufOut with nByte bytes of +** Populate the buffer pointed to by zBufOut with nByte bytes of ** random data. */ static int kvvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ @@ -933,7 +1015,7 @@ static int kvvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ } /* -** Sleep for nMicro microseconds. Return the number of microseconds +** Sleep for nMicro microseconds. Return the number of microseconds ** actually slept. */ static int kvvfsSleep(sqlite3_vfs *pVfs, int nMicro){ @@ -961,7 +1043,7 @@ static int kvvfsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ #endif /* SQLITE_OS_KV || SQLITE_OS_UNIX */ #if SQLITE_OS_KV -/* +/* ** This routine is called initialize the KV-vfs as the default VFS. */ int sqlite3_os_init(void){ diff --git a/src/os_unix.c b/src/os_unix.c index d73d89924..2f75829c8 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5190,7 +5190,7 @@ static int unixShmMap( } /* Map the requested memory region into this processes address space. */ - apNew = (char **)sqlite3_realloc( + apNew = (char **)sqlite3_realloc64( pShmNode->apRegion, nReqRegion*sizeof(char *) ); if( !apNew ){ diff --git a/src/os_win.c b/src/os_win.c index a6b25f2e8..7583ecc1f 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -43,7 +43,7 @@ ** Are most of the Win32 ANSI APIs available (i.e. with certain exceptions ** based on the sub-platform)? */ -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(SQLITE_WIN32_NO_ANSI) +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_ANSI) # define SQLITE_WIN32_HAS_ANSI #endif @@ -51,7 +51,7 @@ ** Are most of the Win32 Unicode APIs available (i.e. with certain exceptions ** based on the sub-platform)? */ -#if (SQLITE_OS_WINCE || SQLITE_OS_WINNT || SQLITE_OS_WINRT) && \ +#if (SQLITE_OS_WINCE || SQLITE_OS_WINNT) && \ !defined(SQLITE_WIN32_NO_WIDE) # define SQLITE_WIN32_HAS_WIDE #endif @@ -190,16 +190,7 @@ */ #if SQLITE_WIN32_FILEMAPPING_API && \ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) -/* -** Two of the file mapping APIs are different under WinRT. Figure out which -** set we need. -*/ -#if SQLITE_OS_WINRT -WINBASEAPI HANDLE WINAPI CreateFileMappingFromApp(HANDLE, \ - LPSECURITY_ATTRIBUTES, ULONG, ULONG64, LPCWSTR); -WINBASEAPI LPVOID WINAPI MapViewOfFileFromApp(HANDLE, ULONG, ULONG64, SIZE_T); -#else #if defined(SQLITE_WIN32_HAS_ANSI) WINBASEAPI HANDLE WINAPI CreateFileMappingA(HANDLE, LPSECURITY_ATTRIBUTES, \ DWORD, DWORD, DWORD, LPCSTR); @@ -211,7 +202,6 @@ WINBASEAPI HANDLE WINAPI CreateFileMappingW(HANDLE, LPSECURITY_ATTRIBUTES, \ #endif /* defined(SQLITE_WIN32_HAS_WIDE) */ WINBASEAPI LPVOID WINAPI MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, SIZE_T); -#endif /* SQLITE_OS_WINRT */ /* ** These file mapping APIs are common to both Win32 and WinRT. @@ -502,7 +492,7 @@ static LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; ** This function is not available on Windows CE or WinRT. */ -#if SQLITE_OS_WINCE || SQLITE_OS_WINRT +#if SQLITE_OS_WINCE # define osAreFileApisANSI() 1 #endif @@ -517,7 +507,7 @@ static struct win_syscall { sqlite3_syscall_ptr pCurrent; /* Current value of the system call */ sqlite3_syscall_ptr pDefault; /* Default value */ } aSyscall[] = { -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE { "AreFileApisANSI", (SYSCALL)AreFileApisANSI, 0 }, #else { "AreFileApisANSI", (SYSCALL)0, 0 }, @@ -556,7 +546,7 @@ static struct win_syscall { #define osCreateFileA ((HANDLE(WINAPI*)(LPCSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[4].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) { "CreateFileW", (SYSCALL)CreateFileW, 0 }, #else { "CreateFileW", (SYSCALL)0, 0 }, @@ -565,7 +555,7 @@ static struct win_syscall { #define osCreateFileW ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[5].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \ +#if defined(SQLITE_WIN32_HAS_ANSI) && \ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) && \ SQLITE_WIN32_CREATEFILEMAPPINGA { "CreateFileMappingA", (SYSCALL)CreateFileMappingA, 0 }, @@ -576,8 +566,8 @@ static struct win_syscall { #define osCreateFileMappingA ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ DWORD,DWORD,DWORD,LPCSTR))aSyscall[6].pCurrent) -#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ - (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) +#if (SQLITE_OS_WINCE || defined(SQLITE_WIN32_HAS_WIDE)) && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) { "CreateFileMappingW", (SYSCALL)CreateFileMappingW, 0 }, #else { "CreateFileMappingW", (SYSCALL)0, 0 }, @@ -586,7 +576,7 @@ static struct win_syscall { #define osCreateFileMappingW ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ DWORD,DWORD,DWORD,LPCWSTR))aSyscall[7].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) { "CreateMutexW", (SYSCALL)CreateMutexW, 0 }, #else { "CreateMutexW", (SYSCALL)0, 0 }, @@ -672,7 +662,7 @@ static struct win_syscall { #define osGetDiskFreeSpaceA ((BOOL(WINAPI*)(LPCSTR,LPDWORD,LPDWORD,LPDWORD, \ LPDWORD))aSyscall[18].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) { "GetDiskFreeSpaceW", (SYSCALL)GetDiskFreeSpaceW, 0 }, #else { "GetDiskFreeSpaceW", (SYSCALL)0, 0 }, @@ -689,7 +679,7 @@ static struct win_syscall { #define osGetFileAttributesA ((DWORD(WINAPI*)(LPCSTR))aSyscall[20].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) { "GetFileAttributesW", (SYSCALL)GetFileAttributesW, 0 }, #else { "GetFileAttributesW", (SYSCALL)0, 0 }, @@ -706,11 +696,7 @@ static struct win_syscall { #define osGetFileAttributesExW ((BOOL(WINAPI*)(LPCWSTR,GET_FILEEX_INFO_LEVELS, \ LPVOID))aSyscall[22].pCurrent) -#if !SQLITE_OS_WINRT { "GetFileSize", (SYSCALL)GetFileSize, 0 }, -#else - { "GetFileSize", (SYSCALL)0, 0 }, -#endif #define osGetFileSize ((DWORD(WINAPI*)(HANDLE,LPDWORD))aSyscall[23].pCurrent) @@ -723,7 +709,7 @@ static struct win_syscall { #define osGetFullPathNameA ((DWORD(WINAPI*)(LPCSTR,DWORD,LPSTR, \ LPSTR*))aSyscall[24].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) { "GetFullPathNameW", (SYSCALL)GetFullPathNameW, 0 }, #else { "GetFullPathNameW", (SYSCALL)0, 0 }, @@ -758,16 +744,10 @@ static struct win_syscall { #define osGetProcAddressA ((FARPROC(WINAPI*)(HMODULE, \ LPCSTR))aSyscall[27].pCurrent) -#if !SQLITE_OS_WINRT { "GetSystemInfo", (SYSCALL)GetSystemInfo, 0 }, -#else - { "GetSystemInfo", (SYSCALL)0, 0 }, -#endif - #define osGetSystemInfo ((VOID(WINAPI*)(LPSYSTEM_INFO))aSyscall[28].pCurrent) { "GetSystemTime", (SYSCALL)GetSystemTime, 0 }, - #define osGetSystemTime ((VOID(WINAPI*)(LPSYSTEMTIME))aSyscall[29].pCurrent) #if !SQLITE_OS_WINCE @@ -787,7 +767,7 @@ static struct win_syscall { #define osGetTempPathA ((DWORD(WINAPI*)(DWORD,LPSTR))aSyscall[31].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) { "GetTempPathW", (SYSCALL)GetTempPathW, 0 }, #else { "GetTempPathW", (SYSCALL)0, 0 }, @@ -795,11 +775,7 @@ static struct win_syscall { #define osGetTempPathW ((DWORD(WINAPI*)(DWORD,LPWSTR))aSyscall[32].pCurrent) -#if !SQLITE_OS_WINRT { "GetTickCount", (SYSCALL)GetTickCount, 0 }, -#else - { "GetTickCount", (SYSCALL)0, 0 }, -#endif #define osGetTickCount ((DWORD(WINAPI*)(VOID))aSyscall[33].pCurrent) @@ -812,7 +788,7 @@ static struct win_syscall { #define osGetVersionExA ((BOOL(WINAPI*)( \ LPOSVERSIONINFOA))aSyscall[34].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ +#if defined(SQLITE_WIN32_HAS_WIDE) && \ SQLITE_WIN32_GETVERSIONEX { "GetVersionExW", (SYSCALL)GetVersionExW, 0 }, #else @@ -827,20 +803,12 @@ static struct win_syscall { #define osHeapAlloc ((LPVOID(WINAPI*)(HANDLE,DWORD, \ SIZE_T))aSyscall[36].pCurrent) -#if !SQLITE_OS_WINRT { "HeapCreate", (SYSCALL)HeapCreate, 0 }, -#else - { "HeapCreate", (SYSCALL)0, 0 }, -#endif #define osHeapCreate ((HANDLE(WINAPI*)(DWORD,SIZE_T, \ SIZE_T))aSyscall[37].pCurrent) -#if !SQLITE_OS_WINRT { "HeapDestroy", (SYSCALL)HeapDestroy, 0 }, -#else - { "HeapDestroy", (SYSCALL)0, 0 }, -#endif #define osHeapDestroy ((BOOL(WINAPI*)(HANDLE))aSyscall[38].pCurrent) @@ -858,16 +826,12 @@ static struct win_syscall { #define osHeapSize ((SIZE_T(WINAPI*)(HANDLE,DWORD, \ LPCVOID))aSyscall[41].pCurrent) -#if !SQLITE_OS_WINRT { "HeapValidate", (SYSCALL)HeapValidate, 0 }, -#else - { "HeapValidate", (SYSCALL)0, 0 }, -#endif #define osHeapValidate ((BOOL(WINAPI*)(HANDLE,DWORD, \ LPCVOID))aSyscall[42].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE { "HeapCompact", (SYSCALL)HeapCompact, 0 }, #else { "HeapCompact", (SYSCALL)0, 0 }, @@ -883,7 +847,7 @@ static struct win_syscall { #define osLoadLibraryA ((HMODULE(WINAPI*)(LPCSTR))aSyscall[44].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ +#if defined(SQLITE_WIN32_HAS_WIDE) && \ !defined(SQLITE_OMIT_LOAD_EXTENSION) { "LoadLibraryW", (SYSCALL)LoadLibraryW, 0 }, #else @@ -892,15 +856,11 @@ static struct win_syscall { #define osLoadLibraryW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[45].pCurrent) -#if !SQLITE_OS_WINRT { "LocalFree", (SYSCALL)LocalFree, 0 }, -#else - { "LocalFree", (SYSCALL)0, 0 }, -#endif #define osLocalFree ((HLOCAL(WINAPI*)(HLOCAL))aSyscall[46].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE { "LockFile", (SYSCALL)LockFile, 0 }, #else { "LockFile", (SYSCALL)0, 0 }, @@ -922,8 +882,7 @@ static struct win_syscall { LPOVERLAPPED))aSyscall[48].pCurrent) #endif -#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && \ - (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) +#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 { "MapViewOfFile", (SYSCALL)MapViewOfFile, 0 }, #else { "MapViewOfFile", (SYSCALL)0, 0 }, @@ -951,20 +910,12 @@ static struct win_syscall { #define osSetEndOfFile ((BOOL(WINAPI*)(HANDLE))aSyscall[53].pCurrent) -#if !SQLITE_OS_WINRT { "SetFilePointer", (SYSCALL)SetFilePointer, 0 }, -#else - { "SetFilePointer", (SYSCALL)0, 0 }, -#endif #define osSetFilePointer ((DWORD(WINAPI*)(HANDLE,LONG,PLONG, \ DWORD))aSyscall[54].pCurrent) -#if !SQLITE_OS_WINRT { "Sleep", (SYSCALL)Sleep, 0 }, -#else - { "Sleep", (SYSCALL)0, 0 }, -#endif #define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent) @@ -973,7 +924,7 @@ static struct win_syscall { #define osSystemTimeToFileTime ((BOOL(WINAPI*)(const SYSTEMTIME*, \ LPFILETIME))aSyscall[56].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE { "UnlockFile", (SYSCALL)UnlockFile, 0 }, #else { "UnlockFile", (SYSCALL)0, 0 }, @@ -1011,15 +962,6 @@ static struct win_syscall { #define osWriteFile ((BOOL(WINAPI*)(HANDLE,LPCVOID,DWORD,LPDWORD, \ LPOVERLAPPED))aSyscall[61].pCurrent) -#if SQLITE_OS_WINRT - { "CreateEventExW", (SYSCALL)CreateEventExW, 0 }, -#else - { "CreateEventExW", (SYSCALL)0, 0 }, -#endif - -#define osCreateEventExW ((HANDLE(WINAPI*)(LPSECURITY_ATTRIBUTES,LPCWSTR, \ - DWORD,DWORD))aSyscall[62].pCurrent) - /* ** For WaitForSingleObject(), MSDN says: ** @@ -1029,7 +971,7 @@ static struct win_syscall { { "WaitForSingleObject", (SYSCALL)WaitForSingleObject, 0 }, #define osWaitForSingleObject ((DWORD(WINAPI*)(HANDLE, \ - DWORD))aSyscall[63].pCurrent) + DWORD))aSyscall[62].pCurrent) #if !SQLITE_OS_WINCE { "WaitForSingleObjectEx", (SYSCALL)WaitForSingleObjectEx, 0 }, @@ -1038,69 +980,12 @@ static struct win_syscall { #endif #define osWaitForSingleObjectEx ((DWORD(WINAPI*)(HANDLE,DWORD, \ - BOOL))aSyscall[64].pCurrent) - -#if SQLITE_OS_WINRT - { "SetFilePointerEx", (SYSCALL)SetFilePointerEx, 0 }, -#else - { "SetFilePointerEx", (SYSCALL)0, 0 }, -#endif - -#define osSetFilePointerEx ((BOOL(WINAPI*)(HANDLE,LARGE_INTEGER, \ - PLARGE_INTEGER,DWORD))aSyscall[65].pCurrent) + BOOL))aSyscall[63].pCurrent) -#if SQLITE_OS_WINRT - { "GetFileInformationByHandleEx", (SYSCALL)GetFileInformationByHandleEx, 0 }, -#else - { "GetFileInformationByHandleEx", (SYSCALL)0, 0 }, -#endif - -#define osGetFileInformationByHandleEx ((BOOL(WINAPI*)(HANDLE, \ - FILE_INFO_BY_HANDLE_CLASS,LPVOID,DWORD))aSyscall[66].pCurrent) - -#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) - { "MapViewOfFileFromApp", (SYSCALL)MapViewOfFileFromApp, 0 }, -#else - { "MapViewOfFileFromApp", (SYSCALL)0, 0 }, -#endif - -#define osMapViewOfFileFromApp ((LPVOID(WINAPI*)(HANDLE,ULONG,ULONG64, \ - SIZE_T))aSyscall[67].pCurrent) - -#if SQLITE_OS_WINRT - { "CreateFile2", (SYSCALL)CreateFile2, 0 }, -#else - { "CreateFile2", (SYSCALL)0, 0 }, -#endif - -#define osCreateFile2 ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD,DWORD, \ - LPCREATEFILE2_EXTENDED_PARAMETERS))aSyscall[68].pCurrent) - -#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_LOAD_EXTENSION) - { "LoadPackagedLibrary", (SYSCALL)LoadPackagedLibrary, 0 }, -#else - { "LoadPackagedLibrary", (SYSCALL)0, 0 }, -#endif - -#define osLoadPackagedLibrary ((HMODULE(WINAPI*)(LPCWSTR, \ - DWORD))aSyscall[69].pCurrent) - -#if SQLITE_OS_WINRT - { "GetTickCount64", (SYSCALL)GetTickCount64, 0 }, -#else - { "GetTickCount64", (SYSCALL)0, 0 }, -#endif - -#define osGetTickCount64 ((ULONGLONG(WINAPI*)(VOID))aSyscall[70].pCurrent) - -#if SQLITE_OS_WINRT { "GetNativeSystemInfo", (SYSCALL)GetNativeSystemInfo, 0 }, -#else - { "GetNativeSystemInfo", (SYSCALL)0, 0 }, -#endif #define osGetNativeSystemInfo ((VOID(WINAPI*)( \ - LPSYSTEM_INFO))aSyscall[71].pCurrent) + LPSYSTEM_INFO))aSyscall[64].pCurrent) #if defined(SQLITE_WIN32_HAS_ANSI) { "OutputDebugStringA", (SYSCALL)OutputDebugStringA, 0 }, @@ -1108,7 +993,7 @@ static struct win_syscall { { "OutputDebugStringA", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[72].pCurrent) +#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[65].pCurrent) #if defined(SQLITE_WIN32_HAS_WIDE) { "OutputDebugStringW", (SYSCALL)OutputDebugStringW, 0 }, @@ -1116,20 +1001,11 @@ static struct win_syscall { { "OutputDebugStringW", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[73].pCurrent) +#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[66].pCurrent) { "GetProcessHeap", (SYSCALL)GetProcessHeap, 0 }, -#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[74].pCurrent) - -#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) - { "CreateFileMappingFromApp", (SYSCALL)CreateFileMappingFromApp, 0 }, -#else - { "CreateFileMappingFromApp", (SYSCALL)0, 0 }, -#endif - -#define osCreateFileMappingFromApp ((HANDLE(WINAPI*)(HANDLE, \ - LPSECURITY_ATTRIBUTES,ULONG,ULONG64,LPCWSTR))aSyscall[75].pCurrent) +#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[67].pCurrent) /* ** NOTE: On some sub-platforms, the InterlockedCompareExchange "function" @@ -1144,25 +1020,25 @@ static struct win_syscall { { "InterlockedCompareExchange", (SYSCALL)InterlockedCompareExchange, 0 }, #define osInterlockedCompareExchange ((LONG(WINAPI*)(LONG \ - SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[76].pCurrent) + SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[68].pCurrent) #endif /* defined(InterlockedCompareExchange) */ -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID { "UuidCreate", (SYSCALL)UuidCreate, 0 }, #else { "UuidCreate", (SYSCALL)0, 0 }, #endif -#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[77].pCurrent) +#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[69].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID { "UuidCreateSequential", (SYSCALL)UuidCreateSequential, 0 }, #else { "UuidCreateSequential", (SYSCALL)0, 0 }, #endif #define osUuidCreateSequential \ - ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[78].pCurrent) + ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[70].pCurrent) #if !defined(SQLITE_NO_SYNC) && SQLITE_MAX_MMAP_SIZE>0 { "FlushViewOfFile", (SYSCALL)FlushViewOfFile, 0 }, @@ -1171,7 +1047,7 @@ static struct win_syscall { #endif #define osFlushViewOfFile \ - ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[79].pCurrent) + ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[71].pCurrent) /* ** If SQLITE_ENABLE_SETLK_TIMEOUT is defined, we require CreateEvent() @@ -1188,7 +1064,7 @@ static struct win_syscall { #define osCreateEvent ( \ (HANDLE(WINAPI*) (LPSECURITY_ATTRIBUTES,BOOL,BOOL,LPCSTR)) \ - aSyscall[80].pCurrent \ + aSyscall[72].pCurrent \ ) /* @@ -1205,7 +1081,7 @@ static struct win_syscall { { "CancelIo", (SYSCALL)0, 0 }, #endif -#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent) +#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[73].pCurrent) #if defined(SQLITE_WIN32_HAS_WIDE) && defined(_WIN32) { "GetModuleHandleW", (SYSCALL)GetModuleHandleW, 0 }, @@ -1213,7 +1089,7 @@ static struct win_syscall { { "GetModuleHandleW", (SYSCALL)0, 0 }, #endif -#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[82].pCurrent) +#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[74].pCurrent) #ifndef _WIN32 { "getenv", (SYSCALL)getenv, 0 }, @@ -1221,7 +1097,7 @@ static struct win_syscall { { "getenv", (SYSCALL)0, 0 }, #endif -#define osGetenv ((const char *(*)(const char *))aSyscall[83].pCurrent) +#define osGetenv ((const char *(*)(const char *))aSyscall[75].pCurrent) #ifndef _WIN32 { "getcwd", (SYSCALL)getcwd, 0 }, @@ -1229,7 +1105,7 @@ static struct win_syscall { { "getcwd", (SYSCALL)0, 0 }, #endif -#define osGetcwd ((char*(*)(char*,size_t))aSyscall[84].pCurrent) +#define osGetcwd ((char*(*)(char*,size_t))aSyscall[76].pCurrent) #ifndef _WIN32 { "readlink", (SYSCALL)readlink, 0 }, @@ -1237,7 +1113,7 @@ static struct win_syscall { { "readlink", (SYSCALL)0, 0 }, #endif -#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[85].pCurrent) +#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[77].pCurrent) #ifndef _WIN32 { "lstat", (SYSCALL)lstat, 0 }, @@ -1245,7 +1121,7 @@ static struct win_syscall { { "lstat", (SYSCALL)0, 0 }, #endif -#define osLstat ((int(*)(const char*,struct stat*))aSyscall[86].pCurrent) +#define osLstat ((int(*)(const char*,struct stat*))aSyscall[78].pCurrent) #ifndef _WIN32 { "__errno", (SYSCALL)__errno, 0 }, @@ -1253,7 +1129,7 @@ static struct win_syscall { { "__errno", (SYSCALL)0, 0 }, #endif -#define osErrno (*((int*(*)(void))aSyscall[87].pCurrent)()) +#define osErrno (*((int*(*)(void))aSyscall[79].pCurrent)()) #ifndef _WIN32 { "cygwin_conv_path", (SYSCALL)cygwin_conv_path, 0 }, @@ -1262,7 +1138,7 @@ static struct win_syscall { #endif #define osCygwin_conv_path ((size_t(*)(unsigned int, \ - const void *, void *, size_t))aSyscall[88].pCurrent) + const void *, void *, size_t))aSyscall[80].pCurrent) }; /* End of the overrideable system calls */ @@ -1366,10 +1242,10 @@ int sqlite3_win32_compact_heap(LPUINT pnLargest){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE if( (nLargest=osHeapCompact(hHeap, SQLITE_WIN32_HEAP_FLAGS))==0 ){ DWORD lastErrno = osGetLastError(); if( lastErrno==NO_ERROR ){ @@ -1482,28 +1358,11 @@ void sqlite3_win32_write_debug(const char *zBuf, int nBuf){ } #endif /* _WIN32 */ -/* -** The following routine suspends the current thread for at least ms -** milliseconds. This is equivalent to the Win32 Sleep() interface. -*/ -#if SQLITE_OS_WINRT -static HANDLE sleepObj = NULL; -#endif - void sqlite3_win32_sleep(DWORD milliseconds){ -#if SQLITE_OS_WINRT - if ( sleepObj==NULL ){ - sleepObj = osCreateEventExW(NULL, NULL, CREATE_EVENT_MANUAL_RESET, - SYNCHRONIZE); - } - assert( sleepObj!=NULL ); - osWaitForSingleObjectEx(sleepObj, milliseconds, FALSE); -#else osSleep(milliseconds); -#endif } -#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ +#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && \ SQLITE_THREADSAFE>0 DWORD sqlite3Win32Wait(HANDLE hObject){ DWORD rc; @@ -1527,7 +1386,7 @@ DWORD sqlite3Win32Wait(HANDLE hObject){ #if !SQLITE_WIN32_GETVERSIONEX # define osIsNT() (1) -#elif SQLITE_OS_WINCE || SQLITE_OS_WINRT || !defined(SQLITE_WIN32_HAS_ANSI) +#elif SQLITE_OS_WINCE || !defined(SQLITE_WIN32_HAS_ANSI) # define osIsNT() (1) #elif !defined(SQLITE_WIN32_HAS_WIDE) # define osIsNT() (0) @@ -1540,13 +1399,7 @@ DWORD sqlite3Win32Wait(HANDLE hObject){ ** based on the NT kernel. */ int sqlite3_win32_is_nt(void){ -#if SQLITE_OS_WINRT - /* - ** NOTE: The WinRT sub-platform is always assumed to be based on the NT - ** kernel. - */ - return 1; -#elif SQLITE_WIN32_GETVERSIONEX +#if SQLITE_WIN32_GETVERSIONEX if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){ #if defined(SQLITE_WIN32_HAS_ANSI) OSVERSIONINFOA sInfo; @@ -1588,7 +1441,7 @@ static void *winMemMalloc(int nBytes){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif assert( nBytes>=0 ); @@ -1610,7 +1463,7 @@ static void winMemFree(void *pPrior){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif if( !pPrior ) return; /* Passing NULL to HeapFree is undefined. */ @@ -1631,7 +1484,7 @@ static void *winMemRealloc(void *pPrior, int nBytes){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif assert( nBytes>=0 ); @@ -1659,7 +1512,7 @@ static int winMemSize(void *p){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, p) ); #endif if( !p ) return 0; @@ -1689,7 +1542,7 @@ static int winMemInit(void *pAppData){ assert( pWinMemData->magic1==WINMEM_MAGIC1 ); assert( pWinMemData->magic2==WINMEM_MAGIC2 ); -#if !SQLITE_OS_WINRT && SQLITE_WIN32_HEAP_CREATE +#if SQLITE_WIN32_HEAP_CREATE if( !pWinMemData->hHeap ){ DWORD dwInitialSize = SQLITE_WIN32_HEAP_INIT_SIZE; DWORD dwMaximumSize = (DWORD)sqlite3GlobalConfig.nHeap; @@ -1722,7 +1575,7 @@ static int winMemInit(void *pAppData){ #endif assert( pWinMemData->hHeap!=0 ); assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif return SQLITE_OK; @@ -1740,7 +1593,7 @@ static void winMemShutdown(void *pAppData){ if( pWinMemData->hHeap ){ assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif if( pWinMemData->bOwned ){ @@ -2121,17 +1974,6 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ char *zOut = 0; if( osIsNT() ){ -#if SQLITE_OS_WINRT - WCHAR zTempWide[SQLITE_WIN32_MAX_ERRMSG_CHARS+1]; - dwLen = osFormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - lastErrno, - 0, - zTempWide, - SQLITE_WIN32_MAX_ERRMSG_CHARS, - 0); -#else LPWSTR zTempWide = NULL; dwLen = osFormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | @@ -2142,16 +1984,13 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ (LPWSTR) &zTempWide, 0, 0); -#endif if( dwLen > 0 ){ /* allocate a buffer and convert to UTF8 */ sqlite3BeginBenignMalloc(); zOut = winUnicodeToUtf8(zTempWide); sqlite3EndBenignMalloc(); -#if !SQLITE_OS_WINRT /* free the system buffer allocated by FormatMessage */ osLocalFree(zTempWide); -#endif } } #ifdef SQLITE_WIN32_HAS_ANSI @@ -2812,7 +2651,6 @@ static int winHandleUnlock(HANDLE h, int iOff, int nByte){ static int winHandleSeek(HANDLE h, sqlite3_int64 iOffset){ int rc = SQLITE_OK; /* Return value */ -#if !SQLITE_OS_WINRT LONG upperBits; /* Most sig. 32 bits of new offset */ LONG lowerBits; /* Least sig. 32 bits of new offset */ DWORD dwRet; /* Value returned by SetFilePointer() */ @@ -2834,20 +2672,7 @@ static int winHandleSeek(HANDLE h, sqlite3_int64 iOffset){ rc = SQLITE_IOERR_SEEK; } } -#else - /* This implementation works for WinRT. */ - LARGE_INTEGER x; /* The new offset */ - BOOL bRet; /* Value returned by SetFilePointerEx() */ - - x.QuadPart = iOffset; - bRet = osSetFilePointerEx(h, x, 0, FILE_BEGIN); - - if(!bRet){ - rc = SQLITE_IOERR_SEEK; - } -#endif - - OSTRACE(("SEEK file=%p, offset=%lld rc=%s\n", h, iOffset, sqlite3ErrName(rc))); + OSTRACE(("SEEK file=%p, offset=%lld rc=%s\n", h, iOffset,sqlite3ErrName(rc))); return rc; } @@ -3148,17 +2973,6 @@ static int winHandleTruncate(HANDLE h, sqlite3_int64 nByte){ */ static int winHandleSize(HANDLE h, sqlite3_int64 *pnByte){ int rc = SQLITE_OK; - -#if SQLITE_OS_WINRT - FILE_STANDARD_INFO info; - BOOL b; - b = osGetFileInformationByHandleEx(h, FileStandardInfo, &info, sizeof(info)); - if( b ){ - *pnByte = info.EndOfFile.QuadPart; - }else{ - rc = SQLITE_IOERR_FSTAT; - } -#else DWORD upperBits = 0; DWORD lowerBits = 0; @@ -3168,8 +2982,6 @@ static int winHandleSize(HANDLE h, sqlite3_int64 *pnByte){ if( lowerBits==INVALID_FILE_SIZE && osGetLastError()!=NO_ERROR ){ rc = SQLITE_IOERR_FSTAT; } -#endif - return rc; } @@ -3368,20 +3180,6 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ assert( pSize!=0 ); SimulateIOError(return SQLITE_IOERR_FSTAT); OSTRACE(("SIZE file=%p, pSize=%p\n", pFile->h, pSize)); - -#if SQLITE_OS_WINRT - { - FILE_STANDARD_INFO info; - if( osGetFileInformationByHandleEx(pFile->h, FileStandardInfo, - &info, sizeof(info)) ){ - *pSize = info.EndOfFile.QuadPart; - }else{ - pFile->lastErrno = osGetLastError(); - rc = winLogError(SQLITE_IOERR_FSTAT, pFile->lastErrno, - "winFileSize", pFile->zPath); - } - } -#else { DWORD upperBits; DWORD lowerBits; @@ -3396,7 +3194,6 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ "winFileSize", pFile->zPath); } } -#endif OSTRACE(("SIZE file=%p, pSize=%p, *pSize=%lld, rc=%s\n", pFile->h, pSize, *pSize, sqlite3ErrName(rc))); return rc; @@ -4358,20 +4155,6 @@ static int winHandleOpen( ** TODO: retry-on-ioerr. */ if( osIsNT() ){ -#if SQLITE_OS_WINRT - CREATEFILE2_EXTENDED_PARAMETERS extendedParameters; - memset(&extendedParameters, 0, sizeof(extendedParameters)); - extendedParameters.dwSize = sizeof(extendedParameters); - extendedParameters.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; - extendedParameters.dwFileFlags = flag_overlapped; - extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS; - h = osCreateFile2((LPCWSTR)zConverted, - (GENERIC_READ | (bReadonly ? 0 : GENERIC_WRITE)),/* dwDesiredAccess */ - FILE_SHARE_READ | FILE_SHARE_WRITE, /* dwShareMode */ - OPEN_ALWAYS, /* dwCreationDisposition */ - &extendedParameters - ); -#else h = osCreateFileW((LPCWSTR)zConverted, /* lpFileName */ (GENERIC_READ | (bReadonly ? 0 : GENERIC_WRITE)), /* dwDesiredAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, /* dwShareMode */ @@ -4380,7 +4163,6 @@ static int winHandleOpen( FILE_ATTRIBUTE_NORMAL|flag_overlapped, NULL ); -#endif }else{ /* Due to pre-processor directives earlier in this file, ** SQLITE_WIN32_HAS_ANSI is always defined if osIsNT() is false. */ @@ -4848,9 +4630,7 @@ static int winShmMap( HANDLE hMap = NULL; /* file-mapping handle */ void *pMap = 0; /* Mapped memory region */ -#if SQLITE_OS_WINRT - hMap = osCreateFileMappingFromApp(hShared, NULL, protect, nByte, NULL); -#elif defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) hMap = osCreateFileMappingW(hShared, NULL, protect, 0, nByte, NULL); #elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA hMap = osCreateFileMappingA(hShared, NULL, protect, 0, nByte, NULL); @@ -4862,15 +4642,9 @@ static int winShmMap( if( hMap ){ int iOffset = pShmNode->nRegion*szRegion; int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; -#if SQLITE_OS_WINRT - pMap = osMapViewOfFileFromApp(hMap, flags, - iOffset - iOffsetShift, szRegion + iOffsetShift - ); -#else pMap = osMapViewOfFile(hMap, flags, 0, iOffset - iOffsetShift, szRegion + iOffsetShift ); -#endif OSTRACE(("SHM-MAP-MAP pid=%lu, region=%d, offset=%d, size=%d, rc=%s\n", osGetCurrentProcessId(), pShmNode->nRegion, iOffset, szRegion, pMap ? "ok" : "failed")); @@ -5003,9 +4777,7 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ flags |= FILE_MAP_WRITE; } #endif -#if SQLITE_OS_WINRT - pFd->hMap = osCreateFileMappingFromApp(pFd->h, NULL, protect, nMap, NULL); -#elif defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) pFd->hMap = osCreateFileMappingW(pFd->h, NULL, protect, (DWORD)((nMap>>32) & 0xffffffff), (DWORD)(nMap & 0xffffffff), NULL); @@ -5025,11 +4797,7 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ } assert( (nMap % winSysInfo.dwPageSize)==0 ); assert( sizeof(SIZE_T)==sizeof(sqlite3_int64) || nMap<=0xffffffff ); -#if SQLITE_OS_WINRT - pNew = osMapViewOfFileFromApp(pFd->hMap, flags, 0, (SIZE_T)nMap); -#else pNew = osMapViewOfFile(pFd->hMap, flags, 0, 0, (SIZE_T)nMap); -#endif if( pNew==NULL ){ osCloseHandle(pFd->hMap); pFd->hMap = NULL; @@ -5364,7 +5132,6 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ } #endif -#if !SQLITE_OS_WINRT && defined(_WIN32) else if( osIsNT() ){ char *zMulti; LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) ); @@ -5418,7 +5185,6 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ } } #endif /* SQLITE_WIN32_HAS_ANSI */ -#endif /* !SQLITE_OS_WINRT */ /* ** Check to make sure the temporary directory ends with an appropriate @@ -5593,13 +5359,6 @@ static int winOpen( memset(pFile, 0, sizeof(winFile)); pFile->h = INVALID_HANDLE_VALUE; -#if SQLITE_OS_WINRT - if( !zUtf8Name && !sqlite3_temp_directory ){ - sqlite3_log(SQLITE_ERROR, - "sqlite3_temp_directory variable should be set for WinRT"); - } -#endif - /* If the second argument to this function is NULL, generate a ** temporary file name to use */ @@ -5682,31 +5441,6 @@ static int winOpen( #endif if( osIsNT() ){ -#if SQLITE_OS_WINRT - CREATEFILE2_EXTENDED_PARAMETERS extendedParameters; - extendedParameters.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS); - extendedParameters.dwFileAttributes = - dwFlagsAndAttributes & FILE_ATTRIBUTE_MASK; - extendedParameters.dwFileFlags = dwFlagsAndAttributes & FILE_FLAG_MASK; - extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS; - extendedParameters.lpSecurityAttributes = NULL; - extendedParameters.hTemplateFile = NULL; - do{ - h = osCreateFile2((LPCWSTR)zConverted, - dwDesiredAccess, - dwShareMode, - dwCreationDisposition, - &extendedParameters); - if( h!=INVALID_HANDLE_VALUE ) break; - if( isReadWrite ){ - int rc2; - sqlite3BeginBenignMalloc(); - rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); - sqlite3EndBenignMalloc(); - if( rc2==SQLITE_OK && isRO ) break; - } - }while( winRetryIoerr(&cnt, &lastErrno) ); -#else do{ h = osCreateFileW((LPCWSTR)zConverted, dwDesiredAccess, @@ -5723,7 +5457,6 @@ static int winOpen( if( rc2==SQLITE_OK && isRO ) break; } }while( winRetryIoerr(&cnt, &lastErrno) ); -#endif } #ifdef SQLITE_WIN32_HAS_ANSI else{ @@ -5860,25 +5593,7 @@ static int winDelete( } if( osIsNT() ){ do { -#if SQLITE_OS_WINRT - WIN32_FILE_ATTRIBUTE_DATA sAttrData; - memset(&sAttrData, 0, sizeof(sAttrData)); - if ( osGetFileAttributesExW(zConverted, GetFileExInfoStandard, - &sAttrData) ){ - attr = sAttrData.dwFileAttributes; - }else{ - lastErrno = osGetLastError(); - if( lastErrno==ERROR_FILE_NOT_FOUND - || lastErrno==ERROR_PATH_NOT_FOUND ){ - rc = SQLITE_IOERR_DELETE_NOENT; /* Already gone? */ - }else{ - rc = SQLITE_ERROR; - } - break; - } -#else attr = osGetFileAttributesW(zConverted); -#endif if ( attr==INVALID_FILE_ATTRIBUTES ){ lastErrno = osGetLastError(); if( lastErrno==ERROR_FILE_NOT_FOUND @@ -6001,6 +5716,7 @@ static int winAccess( attr = sAttrData.dwFileAttributes; } }else{ + if( noRetry ) lastErrno = osGetLastError(); winLogIoerr(cnt, __LINE__); if( lastErrno!=ERROR_FILE_NOT_FOUND && lastErrno!=ERROR_PATH_NOT_FOUND ){ sqlite3_free(zConverted); @@ -6169,7 +5885,7 @@ static int winFullPathnameNoMutex( int nFull, /* Size of output buffer in bytes */ char *zFull /* Output buffer */ ){ -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE int nByte; void *zConverted; char *zOut; @@ -6258,7 +5974,7 @@ static int winFullPathnameNoMutex( } #endif /* __CYGWIN__ */ -#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && defined(_WIN32) +#if SQLITE_OS_WINCE && defined(_WIN32) SimulateIOError( return SQLITE_ERROR ); /* WinCE has no concept of a relative pathname, or so I am told. */ /* WinRT has no way to convert a relative path to an absolute one. */ @@ -6277,7 +5993,7 @@ static int winFullPathnameNoMutex( return SQLITE_OK; #endif -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE #if defined(_WIN32) /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this @@ -6409,11 +6125,7 @@ static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){ return 0; } if( osIsNT() ){ -#if SQLITE_OS_WINRT - h = osLoadPackagedLibrary((LPCWSTR)zConverted, 0); -#else h = osLoadLibraryW((LPCWSTR)zConverted); -#endif } #ifdef SQLITE_WIN32_HAS_ANSI else{ @@ -6495,23 +6207,16 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ DWORD pid = osGetCurrentProcessId(); xorMemory(&e, (unsigned char*)&pid, sizeof(DWORD)); } -#if SQLITE_OS_WINRT - { - ULONGLONG cnt = osGetTickCount64(); - xorMemory(&e, (unsigned char*)&cnt, sizeof(ULONGLONG)); - } -#else { DWORD cnt = osGetTickCount(); xorMemory(&e, (unsigned char*)&cnt, sizeof(DWORD)); } -#endif /* SQLITE_OS_WINRT */ { LARGE_INTEGER i; osQueryPerformanceCounter(&i); xorMemory(&e, (unsigned char*)&i, sizeof(LARGE_INTEGER)); } -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID { UUID id; memset(&id, 0, sizeof(UUID)); @@ -6521,7 +6226,7 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ osUuidCreateSequential(&id); xorMemory(&e, (unsigned char*)&id, sizeof(UUID)); } -#endif /* !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID */ +#endif /* !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID */ return e.nXor>nBuf ? nBuf : e.nXor; #endif /* defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS) */ } @@ -6752,15 +6457,16 @@ int sqlite3_os_init(void){ /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==89 ); + assert( ArraySize(aSyscall)==81 ); + assert( strcmp(aSyscall[0].zName,"AreFileApisANSI")==0 ); + assert( strcmp(aSyscall[20].zName,"GetFileAttributesA")==0 ); + assert( strcmp(aSyscall[40].zName,"HeapReAlloc")==0 ); + assert( strcmp(aSyscall[60].zName,"WideCharToMultiByte")==0 ); + assert( strcmp(aSyscall[80].zName,"cygwin_conv_path")==0 ); /* get memory map allocation granularity */ memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); -#if SQLITE_OS_WINRT - osGetNativeSystemInfo(&winSysInfo); -#else osGetSystemInfo(&winSysInfo); -#endif assert( winSysInfo.dwAllocationGranularity>0 ); assert( winSysInfo.dwPageSize>0 ); @@ -6784,17 +6490,9 @@ int sqlite3_os_init(void){ } int sqlite3_os_end(void){ -#if SQLITE_OS_WINRT - if( sleepObj!=NULL ){ - osCloseHandle(sleepObj); - sleepObj = NULL; - } -#endif - #ifndef SQLITE_OMIT_WAL winBigLock = 0; #endif - return SQLITE_OK; } diff --git a/src/os_win.h b/src/os_win.h index a0845f003..696486c19 100644 --- a/src/os_win.h +++ b/src/os_win.h @@ -58,14 +58,6 @@ # define SQLITE_OS_WINCE 0 #endif -/* -** Determine if we are dealing with WinRT, which provides only a subset of -** the full Win32 API. -*/ -#if !defined(SQLITE_OS_WINRT) -# define SQLITE_OS_WINRT 0 -#endif - /* ** For WinCE, some API function parameters do not appear to be declared as ** volatile. @@ -80,7 +72,7 @@ ** For some Windows sub-platforms, the _beginthreadex() / _endthreadex() ** functions are not available (e.g. those not using MSVC, Cygwin, etc). */ -#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ +#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && \ SQLITE_THREADSAFE>0 && !defined(__CYGWIN__) # define SQLITE_OS_WIN_THREADS 1 #else diff --git a/src/pager.c b/src/pager.c index cbaef186a..61b391d6b 100644 --- a/src/pager.c +++ b/src/pager.c @@ -813,6 +813,8 @@ int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); if( iRead ) return 0; /* Case (4) */ } +#else + UNUSED_PARAMETER(pgno); #endif assert( pPager->fd->pMethods->xDeviceCharacteristics!=0 ); if( (pPager->fd->pMethods->xDeviceCharacteristics(pPager->fd) @@ -1233,17 +1235,17 @@ static int jrnlBufferSize(Pager *pPager){ */ #ifdef SQLITE_CHECK_PAGES /* -** Return a 32-bit hash of the page data for pPage. +** Return a 64-bit hash of the page data for pPage. */ -static u32 pager_datahash(int nByte, unsigned char *pData){ - u32 hash = 0; +static u64 pager_datahash(int nByte, unsigned char *pData){ + u64 hash = 0; int i; for(i=0; ipPager->pageSize, (unsigned char *)pPage->pData); } static void pager_set_pagehash(PgHdr *pPage){ @@ -4192,6 +4194,8 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,a); pPager->pWal = 0; } +#else + UNUSED_PARAMETER(db); #endif pager_reset(pPager); if( MEMDB ){ diff --git a/src/parse.y b/src/parse.y index 617eb7303..f5a6bed14 100644 --- a/src/parse.y +++ b/src/parse.y @@ -21,9 +21,11 @@ */ } -// Function used to enlarge the parser stack, if needed -%realloc parserStackRealloc -%free sqlite3_free +// Setup for the parser stack +%stack_size 50 // Initial stack size +%stack_size_limit parserStackSizeLimit // Function returning max stack size +%realloc parserStackRealloc // realloc() for the stack +%free parserStackFree // free() for the stack // All token codes are small integers with #defines that begin with "TK_" %token_prefix TK_ @@ -49,7 +51,7 @@ } } %stack_overflow { - sqlite3OomFault(pParse->db); + if( pParse->nErr==0 ) sqlite3ErrorMsg(pParse, "Recursion limit"); } // The name of the generated procedure that implements the parser @@ -583,8 +585,23 @@ cmd ::= select(X). { ** sqlite3_realloc() that includes a call to sqlite3FaultSim() to facilitate ** testing. */ - static void *parserStackRealloc(void *pOld, sqlite3_uint64 newSize){ - return sqlite3FaultSim(700) ? 0 : sqlite3_realloc(pOld, newSize); + static void *parserStackRealloc( + void *pOld, /* Prior allocation */ + sqlite3_uint64 newSize, /* Requested new alloation size */ + Parse *pParse /* Parsing context */ + ){ + void *p = sqlite3FaultSim(700) ? 0 : sqlite3_realloc(pOld, newSize); + if( p==0 ) sqlite3OomFault(pParse->db); + return p; + } + static void parserStackFree(void *pOld, Parse *pParse){ + (void)pParse; + sqlite3_free(pOld); + } + + /* Return an integer that is the maximum allowed stack size */ + static int parserStackSizeLimit(Parse *pParse){ + return pParse->db->aLimit[SQLITE_LIMIT_PARSER_DEPTH]; } } @@ -816,19 +833,36 @@ fullname(A) ::= nm(X) DOT nm(Y). { %type xfullname {SrcList*} %destructor xfullname {sqlite3SrcListDelete(pParse->db, $$);} -xfullname(A) ::= nm(X). - {A = sqlite3SrcListAppend(pParse,0,&X,0); /*A-overwrites-X*/} -xfullname(A) ::= nm(X) DOT nm(Y). - {A = sqlite3SrcListAppend(pParse,0,&X,&Y); /*A-overwrites-X*/} -xfullname(A) ::= nm(X) DOT nm(Y) AS nm(Z). { - A = sqlite3SrcListAppend(pParse,0,&X,&Y); /*A-overwrites-X*/ - if( A ) A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z); +xfullname(A) ::= nm(X). { + A = sqlite3SrcListAppend(pParse,0,&X,0); + if( IN_RENAME_OBJECT && A ) sqlite3RenameTokenMap(pParse, A->a[0].zName, &X); } -xfullname(A) ::= nm(X) AS nm(Z). { - A = sqlite3SrcListAppend(pParse,0,&X,0); /*A-overwrites-X*/ - if( A ) A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z); +xfullname(A) ::= nm(X) DOT nm(Y). { + A = sqlite3SrcListAppend(pParse,0,&X,&Y); + if( IN_RENAME_OBJECT && A ) sqlite3RenameTokenMap(pParse, A->a[0].zName, &Y); +} +xfullname(A) ::= nm(X) AS nm(Z). { + A = sqlite3SrcListAppend(pParse,0,&X,0); + if( A ){ + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, A->a[0].zName, &X); + }else{ + A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z); + } + } +} +xfullname(A) ::= nm(X) DOT nm(Y) AS nm(Z). { + A = sqlite3SrcListAppend(pParse,0,&X,&Y); + if( A ){ + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, A->a[0].zName, &Y); + }else{ + A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z); + } + } } + %type joinop {int} joinop(X) ::= COMMA|JOIN. { X = JT_INNER; } joinop(X) ::= JOIN_KW(A) JOIN. @@ -1159,7 +1193,12 @@ expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). { term(A) ::= NULL|FLOAT|BLOB(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} term(A) ::= STRING(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} term(A) ::= INTEGER(X). { - A = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &X, 1); + int iValue; + if( sqlite3GetInt32(X.z, &iValue)==0 ){ + A = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &X, 0); + }else{ + A = sqlite3ExprInt32(pParse->db, iValue); + } if( A ) A->w.iOfst = (int)(X.z - pParse->zTail); } expr(A) ::= VARIABLE(X). { @@ -1341,43 +1380,67 @@ expr(A) ::= expr(A) likeop(OP) expr(Y) ESCAPE expr(E). [LIKE_KW] { if( A ) A->flags |= EP_InfixFunc; } -expr(A) ::= expr(A) ISNULL|NOTNULL(E). {A = sqlite3PExpr(pParse,@E,A,0);} -expr(A) ::= expr(A) NOT NULL. {A = sqlite3PExpr(pParse,TK_NOTNULL,A,0);} - %include { - /* A routine to convert a binary TK_IS or TK_ISNOT expression into a - ** unary TK_ISNULL or TK_NOTNULL expression. */ - static void binaryToUnaryIfNull(Parse *pParse, Expr *pY, Expr *pA, int op){ - sqlite3 *db = pParse->db; - if( pA && pY && pY->op==TK_NULL && !IN_RENAME_OBJECT ){ - pA->op = (u8)op; - sqlite3ExprDelete(db, pA->pRight); - pA->pRight = 0; + /* Create a TK_ISNULL or TK_NOTNULL expression, perhaps optimized to + ** to TK_TRUEFALSE, if possible */ + static Expr *sqlite3PExprIsNull( + Parse *pParse, /* Parsing context */ + int op, /* TK_ISNULL or TK_NOTNULL */ + Expr *pLeft /* Operand */ + ){ + Expr *p = pLeft; + assert( op==TK_ISNULL || op==TK_NOTNULL ); + assert( pLeft!=0 ); + while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ + p = p->pLeft; + assert( p!=0 ); + } + switch( p->op ){ + case TK_INTEGER: + case TK_STRING: + case TK_FLOAT: + case TK_BLOB: + sqlite3ExprDeferredDelete(pParse, pLeft); + return sqlite3ExprInt32(pParse->db, op==TK_NOTNULL); + default: + break; } + return sqlite3PExpr(pParse, op, pLeft, 0); + } + + /* Create a TK_IS or TK_ISNOT operator, perhaps optimized to + ** TK_ISNULL or TK_NOTNULL or TK_TRUEFALSE. */ + static Expr *sqlite3PExprIs( + Parse *pParse, /* Parsing context */ + int op, /* TK_IS or TK_ISNOT */ + Expr *pLeft, /* Left operand */ + Expr *pRight /* Right operand */ + ){ + if( pRight && pRight->op==TK_NULL ){ + sqlite3ExprDeferredDelete(pParse, pRight); + return sqlite3PExprIsNull(pParse, op==TK_IS ? TK_ISNULL : TK_NOTNULL, pLeft); + } + return sqlite3PExpr(pParse, op, pLeft, pRight); } } -// expr1 IS expr2 -// expr1 IS NOT expr2 +expr(A) ::= expr(A) ISNULL|NOTNULL(E). {A = sqlite3PExprIsNull(pParse,@E,A);} +expr(A) ::= expr(A) NOT NULL. {A = sqlite3PExprIsNull(pParse,TK_NOTNULL,A);} + +// expr1 IS expr2 same as expr1 IS NOT DISTINCT FROM expr2 +// expr1 IS NOT expr2 same as expr1 IS DISTINCT FROM expr2 // -// If expr2 is NULL then code as TK_ISNULL or TK_NOTNULL. If expr2 -// is any other expression, code as TK_IS or TK_ISNOT. -// expr(A) ::= expr(A) IS expr(Y). { - A = sqlite3PExpr(pParse,TK_IS,A,Y); - binaryToUnaryIfNull(pParse, Y, A, TK_ISNULL); + A = sqlite3PExprIs(pParse, TK_IS, A, Y); } expr(A) ::= expr(A) IS NOT expr(Y). { - A = sqlite3PExpr(pParse,TK_ISNOT,A,Y); - binaryToUnaryIfNull(pParse, Y, A, TK_NOTNULL); + A = sqlite3PExprIs(pParse, TK_ISNOT, A, Y); } expr(A) ::= expr(A) IS NOT DISTINCT FROM expr(Y). { - A = sqlite3PExpr(pParse,TK_IS,A,Y); - binaryToUnaryIfNull(pParse, Y, A, TK_ISNULL); + A = sqlite3PExprIs(pParse, TK_IS, A, Y); } expr(A) ::= expr(A) IS DISTINCT FROM expr(Y). { - A = sqlite3PExpr(pParse,TK_ISNOT,A,Y); - binaryToUnaryIfNull(pParse, Y, A, TK_NOTNULL); + A = sqlite3PExprIs(pParse, TK_ISNOT, A, Y); } expr(A) ::= NOT(B) expr(X). @@ -1707,28 +1770,13 @@ when_clause(A) ::= WHEN expr(X). { A = X; } %type trigger_cmd_list {TriggerStep*} %destructor trigger_cmd_list {sqlite3DeleteTriggerStep(pParse->db, $$);} trigger_cmd_list(A) ::= trigger_cmd_list(A) trigger_cmd(X) SEMI. { - assert( A!=0 ); A->pLast->pNext = X; A->pLast = X; } trigger_cmd_list(A) ::= trigger_cmd(A) SEMI. { - assert( A!=0 ); A->pLast = A; } -// Disallow qualified table names on INSERT, UPDATE, and DELETE statements -// within a trigger. The table to INSERT, UPDATE, or DELETE is always in -// the same database as the table that the trigger fires on. -// -%type trnm {Token} -trnm(A) ::= nm(A). -trnm(A) ::= nm DOT nm(X). { - A = X; - sqlite3ErrorMsg(pParse, - "qualified table names are not allowed on INSERT, UPDATE, and DELETE " - "statements within triggers"); -} - // Disallow the INDEX BY and NOT INDEXED clauses on UPDATE and DELETE // statements within triggers. We make a specific error message for this // since it is an exception to the default grammar rules. @@ -1751,17 +1799,17 @@ tridxby ::= NOT INDEXED. { %destructor trigger_cmd {sqlite3DeleteTriggerStep(pParse->db, $$);} // UPDATE trigger_cmd(A) ::= - UPDATE(B) orconf(R) trnm(X) tridxby SET setlist(Y) from(F) where_opt(Z) scanpt(E). - {A = sqlite3TriggerUpdateStep(pParse, &X, F, Y, Z, R, B.z, E);} + UPDATE(B) orconf(R) xfullname(X) tridxby SET setlist(Y) from(F) where_opt(Z) scanpt(E). + {A = sqlite3TriggerUpdateStep(pParse, X, F, Y, Z, R, B.z, E);} // INSERT trigger_cmd(A) ::= scanpt(B) insert_cmd(R) INTO - trnm(X) idlist_opt(F) select(S) upsert(U) scanpt(Z). { - A = sqlite3TriggerInsertStep(pParse,&X,F,S,R,U,B,Z);/*A-overwrites-R*/ + xfullname(X) idlist_opt(F) select(S) upsert(U) scanpt(Z). { + A = sqlite3TriggerInsertStep(pParse,X,F,S,R,U,B,Z);/*A-overwrites-R*/ } // DELETE -trigger_cmd(A) ::= DELETE(B) FROM trnm(X) tridxby where_opt(Y) scanpt(E). - {A = sqlite3TriggerDeleteStep(pParse, &X, Y, B.z, E);} +trigger_cmd(A) ::= DELETE(B) FROM xfullname(X) tridxby where_opt(Y) scanpt(E). + {A = sqlite3TriggerDeleteStep(pParse, X, Y, B.z, E);} // SELECT trigger_cmd(A) ::= scanpt(B) select(X) scanpt(E). @@ -1831,22 +1879,42 @@ cmd ::= ANALYZE nm(X) dbnm(Y). {sqlite3Analyze(pParse, &X, &Y);} cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). { sqlite3AlterRenameTable(pParse,X,&Z); } -cmd ::= ALTER TABLE add_column_fullname - ADD kwcolumn_opt columnname(Y) carglist. { + +// The ALTER TABLE ADD COLUMN command. This is broken into two sections so +// that sqlite3AlterBeginAddColumn() is called before parsing the various +// constraints and so on (carglist) attached to the new column definition. +cmd ::= alter_add(Y) carglist. { Y.n = (int)(pParse->sLastToken.z-Y.z) + pParse->sLastToken.n; sqlite3AlterFinishAddColumn(pParse, &Y); } -cmd ::= ALTER TABLE fullname(X) DROP kwcolumn_opt nm(Y). { - sqlite3AlterDropColumn(pParse, X, &Y); -} - -add_column_fullname ::= fullname(X). { +alter_add(A) ::= ALTER TABLE fullname(X) ADD kwcolumn_opt nm(Y) typetoken(Z). { disableLookaside(pParse); sqlite3AlterBeginAddColumn(pParse, X); + sqlite3AddColumn(pParse, Y, Z); + A = Y; +} + +cmd ::= ALTER TABLE fullname(X) DROP kwcolumn_opt nm(Y). { + sqlite3AlterDropColumn(pParse, X, &Y); } cmd ::= ALTER TABLE fullname(X) RENAME kwcolumn_opt nm(Y) TO nm(Z). { sqlite3AlterRenameColumn(pParse, X, &Y, &Z); } +cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Y). { + sqlite3AlterDropConstraint(pParse, X, &Y, 0); +} +cmd ::= ALTER TABLE fullname(X) ALTER kwcolumn_opt nm(Y) DROP NOT NULL. { + sqlite3AlterDropConstraint(pParse, X, 0, &Y); +} +cmd ::= ALTER TABLE fullname(X) ALTER kwcolumn_opt nm(Y) SET NOT(Z) NULL onconf. { + sqlite3AlterSetNotNull(pParse, X, &Y, &Z); +} +cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT(Y) nm(Z) CHECK LP(A) expr RP(B) onconf. { + sqlite3AlterAddConstraint(pParse, X, &Y, &Z, A.z+1, (B.z-A.z-1)); +} +cmd ::= ALTER TABLE fullname(X) ADD CHECK(Y) LP(A) expr RP(B) onconf. { + sqlite3AlterAddConstraint(pParse, X, &Y, 0, A.z+1, (B.z-A.z-1)); +} kwcolumn_opt ::= . kwcolumn_opt ::= COLUMNKW. diff --git a/src/pcache.h b/src/pcache.h index f945dab1a..dafb59390 100644 --- a/src/pcache.h +++ b/src/pcache.h @@ -29,10 +29,10 @@ struct PgHdr { PCache *pCache; /* PRIVATE: Cache that owns this page */ PgHdr *pDirty; /* Transient list of dirty sorted by pgno */ Pager *pPager; /* The pager this page is part of */ - Pgno pgno; /* Page number for this page */ #ifdef SQLITE_CHECK_PAGES - u32 pageHash; /* Hash of page content */ + u64 pageHash; /* Hash of page content */ #endif + Pgno pgno; /* Page number for this page */ u16 flags; /* PGHDR flags defined below */ /********************************************************************** diff --git a/src/prepare.c b/src/prepare.c index 539360b74..be9e496f1 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -33,7 +33,8 @@ static void corruptSchema( static const char *azAlterType[] = { "rename", "drop column", - "add column" + "add column", + "drop constraint" }; *pData->pzErrMsg = sqlite3MPrintf(db, "error in %s %s after %s: %s", azObj[0], azObj[1], diff --git a/src/printf.c b/src/printf.c index f75ed3b8a..d9f3c229d 100644 --- a/src/printf.c +++ b/src/printf.c @@ -549,7 +549,7 @@ void sqlite3_str_vappendf( }else{ iRound = precision+1; } - sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 26 : 16); + sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 20 : 16); if( s.isSpecial ){ if( s.isSpecial==2 ){ bufpt = flag_zeropad ? "null" : "NaN"; @@ -1228,6 +1228,14 @@ int sqlite3_str_length(sqlite3_str *p){ return p ? p->nChar : 0; } +/* Truncate the text of the string to be no more than N bytes. */ +void sqlite3_str_truncate(sqlite3_str *p, int N){ + if( p!=0 && N>=0 && (u32)NnChar ){ + p->nChar = N; + p->zText[p->nChar] = 0; + } +} + /* Return the current value for p */ char *sqlite3_str_value(sqlite3_str *p){ if( p==0 || p->nChar==0 ) return 0; @@ -1248,6 +1256,17 @@ void sqlite3_str_reset(StrAccum *p){ p->zText = 0; } +/* +** Destroy a dynamically allocate sqlite3_str object and all +** of its content, all in one call. +*/ +void sqlite3_str_free(sqlite3_str *p){ + if( p ){ + sqlite3_str_reset(p); + sqlite3_free(p); + } +} + /* ** Initialize a string accumulator. ** diff --git a/src/resolve.c b/src/resolve.c index 16c193ca2..5b6d6c9c5 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -936,7 +936,7 @@ static int exprProbability(Expr *p){ double r = -1.0; if( p->op!=TK_FLOAT ) return -1; assert( !ExprHasProperty(p, EP_IntValue) ); - sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8); + sqlite3AtoF(p->u.zToken, &r); assert( r>=0.0 ); if( r>1.0 ) return -1; return (int)(r*134217728.0); @@ -1656,10 +1656,8 @@ static int resolveCompoundOrderBy( /* Convert the ORDER BY term into an integer column number iCol, ** taking care to preserve the COLLATE clause if it exists. */ if( !IN_RENAME_OBJECT ){ - Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); + Expr *pNew = sqlite3ExprInt32(db, iCol); if( pNew==0 ) return 1; - pNew->flags |= EP_IntValue; - pNew->u.iValue = iCol; if( pItem->pExpr==pE ){ pItem->pExpr = pNew; }else{ @@ -2013,10 +2011,6 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } #endif - /* The ORDER BY and GROUP BY clauses may not refer to terms in - ** outer queries - */ - sNC.pNext = 0; sNC.ncFlags |= NC_AllowAgg|NC_AllowWin; /* If this is a converted compound query, move the ORDER BY clause from @@ -2079,6 +2073,14 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ return WRC_Abort; } + /* If the SELECT statement contains ON clauses that were moved into + ** the WHERE clause, go through and verify that none of the terms + ** in the ON clauses reference tables to the right of the ON clause. */ + if( (p->selFlags & SF_OnToWhere) ){ + sqlite3SelectCheckOnClauses(pParse, p); + if( pParse->nErr ) return WRC_Abort; + } + /* Advance to the next term of the compound */ p = p->pPrior; diff --git a/src/select.c b/src/select.c index bc17ecf84..e8e9f36a8 100644 --- a/src/select.c +++ b/src/select.c @@ -21,7 +21,7 @@ */ typedef struct DistinctCtx DistinctCtx; struct DistinctCtx { - u8 isTnct; /* 0: Not distinct. 1: DISTICT 2: DISTINCT and ORDER BY */ + u8 isTnct; /* 0: Not distinct. 1: DISTINCT 2: DISTINCT and ORDER BY */ u8 eTnctType; /* One of the WHERE_DISTINCT_* operators */ int tabTnct; /* Ephemeral table used for DISTINCT processing */ int addrTnct; /* Address of OP_OpenEphemeral opcode for tabTnct */ @@ -151,8 +151,6 @@ Select *sqlite3SelectNew( pNew->iLimit = 0; pNew->iOffset = 0; pNew->selId = ++pParse->nSelect; - pNew->addrOpenEphm[0] = -1; - pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = 0; if( pSrc==0 ) pSrc = sqlite3DbMallocZero(pParse->db, SZ_SRCLIST_1); pNew->pSrc = pSrc; @@ -661,6 +659,10 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ pRight->fg.isOn = 1; p->selFlags |= SF_OnToWhere; } + + if( IsVirtual(pRightTab) && joinType==EP_OuterON && pRight->u1.pFuncArg ){ + p->selFlags |= SF_OnToWhere; + } } return 0; } @@ -1300,29 +1302,6 @@ static void selectInnerLoop( } switch( eDest ){ - /* In this mode, write each query result to the key of the temporary - ** table iParm. - */ -#ifndef SQLITE_OMIT_COMPOUND_SELECT - case SRT_Union: { - int r1; - r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1); - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); - sqlite3ReleaseTempReg(pParse, r1); - break; - } - - /* Construct a record from the query result, but instead of - ** saving that record, use it as a key to delete elements from - ** the temporary table iParm. - */ - case SRT_Except: { - sqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nResultCol); - break; - } -#endif /* SQLITE_OMIT_COMPOUND_SELECT */ - /* Store the result as data using a unique key. */ case SRT_Fifo: @@ -2435,8 +2414,8 @@ void sqlite3SubqueryColumnTypes( } } if( zType ){ - const i64 k = sqlite3Strlen30(zType); - n = sqlite3Strlen30(pCol->zCnName); + const i64 k = strlen(zType); + n = strlen(pCol->zCnName); pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+k+2); pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); if( pCol->zCnName ){ @@ -2609,9 +2588,9 @@ static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){ ** function is responsible for ensuring that this structure is eventually ** freed. */ -static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ +static KeyInfo *multiSelectByMergeKeyInfo(Parse *pParse, Select *p, int nExtra){ ExprList *pOrderBy = p->pOrderBy; - int nOrderBy = ALWAYS(pOrderBy!=0) ? pOrderBy->nExpr : 0; + int nOrderBy = (pOrderBy!=0) ? pOrderBy->nExpr : 0; sqlite3 *db = pParse->db; KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1); if( pRet ){ @@ -2744,7 +2723,7 @@ static void generateWithRecursiveQuery( regCurrent = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol); if( pOrderBy ){ - KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1); + KeyInfo *pKeyInfo = multiSelectByMergeKeyInfo(pParse, p, 1); sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iQueue, pOrderBy->nExpr+2, 0, (char*)pKeyInfo, P4_KEYINFO); destQueue.pOrderBy = pOrderBy; @@ -2753,8 +2732,28 @@ static void generateWithRecursiveQuery( } VdbeComment((v, "Queue table")); if( iDistinct ){ - p->addrOpenEphm[0] = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iDistinct, 0); - p->selFlags |= SF_UsesEphemeral; + /* Generate an ephemeral table used to enforce distinctness on the + ** output of the recursive part of the CTE. + */ + KeyInfo *pKeyInfo; /* Collating sequence for the result set */ + CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ + + assert( p->pNext==0 ); + assert( p->pEList!=0 ); + nCol = p->pEList->nExpr; + pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCol, 1); + if( pKeyInfo ){ + for(i=0, apColl=pKeyInfo->aColl; idb->pDfltColl; + } + } + sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iDistinct, nCol, 0, + (void*)pKeyInfo, P4_KEYINFO); + }else{ + assert( pParse->nErr>0 ); + } } /* Detach the ORDER BY clause from the compound SELECT */ @@ -2829,7 +2828,7 @@ static void generateWithRecursiveQuery( #endif /* SQLITE_OMIT_CTE */ /* Forward references */ -static int multiSelectOrderBy( +static int multiSelectByMerge( Parse *pParse, /* Parsing context */ Select *p, /* The right-most of SELECTs to be coded */ SelectDest *pDest /* What to do with query results */ @@ -2978,12 +2977,26 @@ static int multiSelect( generateWithRecursiveQuery(pParse, p, &dest); }else #endif - - /* Compound SELECTs that have an ORDER BY clause are handled separately. - */ if( p->pOrderBy ){ - return multiSelectOrderBy(pParse, p, pDest); + /* If the compound has an ORDER BY clause, then always use the merge + ** algorithm. */ + return multiSelectByMerge(pParse, p, pDest); + }else if( p->op!=TK_ALL ){ + /* If the compound is EXCEPT, INTERSECT, or UNION (anything other than + ** UNION ALL) then also always use the merge algorithm. However, the + ** multiSelectByMerge() routine requires that the compound have an + ** ORDER BY clause, and it doesn't right now. So invent one first. */ + Expr *pOne = sqlite3ExprInt32(db, 1); + p->pOrderBy = sqlite3ExprListAppend(pParse, 0, pOne); + if( pParse->nErr ) goto multi_select_end; + assert( p->pOrderBy!=0 ); + p->pOrderBy->a[0].u.x.iOrderByCol = 1; + return multiSelectByMerge(pParse, p, pDest); }else{ + /* For a UNION ALL compound without ORDER BY, simply run the left + ** query, then run the right query */ + int addr = 0; + int nLimit = 0; /* Initialize to suppress harmless compiler warning */ #ifndef SQLITE_OMIT_EXPLAIN if( pPrior->pPrior==0 ){ @@ -2991,300 +3004,49 @@ static int multiSelect( ExplainQueryPlan((pParse, 1, "LEFT-MOST SUBQUERY")); } #endif - - /* Generate code for the left and right SELECT statements. - */ - switch( p->op ){ - case TK_ALL: { - int addr = 0; - int nLimit = 0; /* Initialize to suppress harmless compiler warning */ - assert( !pPrior->pLimit ); - pPrior->iLimit = p->iLimit; - pPrior->iOffset = p->iOffset; - pPrior->pLimit = p->pLimit; - TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); - rc = sqlite3Select(pParse, pPrior, &dest); - pPrior->pLimit = 0; - if( rc ){ - goto multi_select_end; - } - p->pPrior = 0; - p->iLimit = pPrior->iLimit; - p->iOffset = pPrior->iOffset; - if( p->iLimit ){ - addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); - VdbeComment((v, "Jump ahead if LIMIT reached")); - if( p->iOffset ){ - sqlite3VdbeAddOp3(v, OP_OffsetLimit, - p->iLimit, p->iOffset+1, p->iOffset); - } - } - ExplainQueryPlan((pParse, 1, "UNION ALL")); - TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); - rc = sqlite3Select(pParse, p, &dest); - testcase( rc!=SQLITE_OK ); - pDelete = p->pPrior; - p->pPrior = pPrior; - p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); - if( p->pLimit - && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse) - && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) - ){ - p->nSelectRow = sqlite3LogEst((u64)nLimit); - } - if( addr ){ - sqlite3VdbeJumpHere(v, addr); - } - break; - } - case TK_EXCEPT: - case TK_UNION: { - int unionTab; /* Cursor number of the temp table holding result */ - u8 op = 0; /* One of the SRT_ operations to apply to self */ - int priorOp; /* The SRT_ operation to apply to prior selects */ - Expr *pLimit; /* Saved values of p->nLimit */ - int addr; - int emptyBypass = 0; /* IfEmpty opcode to bypass RHS */ - SelectDest uniondest; - - - testcase( p->op==TK_EXCEPT ); - testcase( p->op==TK_UNION ); - priorOp = SRT_Union; - if( dest.eDest==priorOp ){ - /* We can reuse a temporary table generated by a SELECT to our - ** right. - */ - assert( p->pLimit==0 ); /* Not allowed on leftward elements */ - unionTab = dest.iSDParm; - }else{ - /* We will need to create our own temporary table to hold the - ** intermediate results. - */ - unionTab = pParse->nTab++; - assert( p->pOrderBy==0 ); - addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0); - assert( p->addrOpenEphm[0] == -1 ); - p->addrOpenEphm[0] = addr; - findRightmost(p)->selFlags |= SF_UsesEphemeral; - assert( p->pEList ); - } - - - /* Code the SELECT statements to our left - */ - assert( !pPrior->pOrderBy ); - sqlite3SelectDestInit(&uniondest, priorOp, unionTab); - TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION left...\n")); - rc = sqlite3Select(pParse, pPrior, &uniondest); - if( rc ){ - goto multi_select_end; - } - - /* Code the current SELECT statement - */ - if( p->op==TK_EXCEPT ){ - op = SRT_Except; - emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, unionTab); - VdbeCoverage(v); - }else{ - assert( p->op==TK_UNION ); - op = SRT_Union; - } - p->pPrior = 0; - pLimit = p->pLimit; - p->pLimit = 0; - uniondest.eDest = op; - ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", - sqlite3SelectOpName(p->op))); - TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION right...\n")); - rc = sqlite3Select(pParse, p, &uniondest); - testcase( rc!=SQLITE_OK ); - assert( p->pOrderBy==0 ); - pDelete = p->pPrior; - p->pPrior = pPrior; - p->pOrderBy = 0; - if( p->op==TK_UNION ){ - p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); - } - if( emptyBypass ) sqlite3VdbeJumpHere(v, emptyBypass); - sqlite3ExprDelete(db, p->pLimit); - p->pLimit = pLimit; - p->iLimit = 0; - p->iOffset = 0; - - /* Convert the data in the temporary table into whatever form - ** it is that we currently need. - */ - assert( unionTab==dest.iSDParm || dest.eDest!=priorOp ); - assert( p->pEList || db->mallocFailed ); - if( dest.eDest!=priorOp && db->mallocFailed==0 ){ - int iCont, iBreak, iStart; - iBreak = sqlite3VdbeMakeLabel(pParse); - iCont = sqlite3VdbeMakeLabel(pParse); - computeLimitRegisters(pParse, p, iBreak); - sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v); - iStart = sqlite3VdbeCurrentAddr(v); - selectInnerLoop(pParse, p, unionTab, - 0, 0, &dest, iCont, iBreak); - sqlite3VdbeResolveLabel(v, iCont); - sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v); - sqlite3VdbeResolveLabel(v, iBreak); - sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0); - } - break; - } - default: assert( p->op==TK_INTERSECT ); { - int tab1, tab2; - int iCont, iBreak, iStart; - Expr *pLimit; - int addr, iLimit, iOffset; - SelectDest intersectdest; - int r1; - int emptyBypass; - - /* INTERSECT is different from the others since it requires - ** two temporary tables. Hence it has its own case. Begin - ** by allocating the tables we will need. - */ - tab1 = pParse->nTab++; - tab2 = pParse->nTab++; - assert( p->pOrderBy==0 ); - - addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0); - assert( p->addrOpenEphm[0] == -1 ); - p->addrOpenEphm[0] = addr; - findRightmost(p)->selFlags |= SF_UsesEphemeral; - assert( p->pEList ); - - /* Code the SELECTs to our left into temporary table "tab1". - */ - sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); - TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT left...\n")); - rc = sqlite3Select(pParse, pPrior, &intersectdest); - if( rc ){ - goto multi_select_end; - } - - /* Initialize LIMIT counters before checking to see if the LHS - ** is empty, in case the jump is taken */ - iBreak = sqlite3VdbeMakeLabel(pParse); - computeLimitRegisters(pParse, p, iBreak); - emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, tab1); VdbeCoverage(v); - - /* Code the current SELECT into temporary table "tab2" - */ - addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0); - assert( p->addrOpenEphm[1] == -1 ); - p->addrOpenEphm[1] = addr; - - /* Disable prior SELECTs and the LIMIT counters during the computation - ** of the RHS select */ - pLimit = p->pLimit; - iLimit = p->iLimit; - iOffset = p->iOffset; - p->pPrior = 0; - p->pLimit = 0; - p->iLimit = 0; - p->iOffset = 0; - - intersectdest.iSDParm = tab2; - ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", - sqlite3SelectOpName(p->op))); - TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT right...\n")); - rc = sqlite3Select(pParse, p, &intersectdest); - testcase( rc!=SQLITE_OK ); - pDelete = p->pPrior; - p->pPrior = pPrior; - if( p->nSelectRow>pPrior->nSelectRow ){ - p->nSelectRow = pPrior->nSelectRow; - } - sqlite3ExprDelete(db, p->pLimit); - - /* Reinstate the LIMIT counters prior to running the final intersect */ - p->pLimit = pLimit; - p->iLimit = iLimit; - p->iOffset = iOffset; - - /* Generate code to take the intersection of the two temporary - ** tables. - */ - if( rc ) break; - assert( p->pEList ); - sqlite3VdbeAddOp1(v, OP_Rewind, tab1); - r1 = sqlite3GetTempReg(pParse); - iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1); - iCont = sqlite3VdbeMakeLabel(pParse); - sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); - VdbeCoverage(v); - sqlite3ReleaseTempReg(pParse, r1); - selectInnerLoop(pParse, p, tab1, - 0, 0, &dest, iCont, iBreak); - sqlite3VdbeResolveLabel(v, iCont); - sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v); - sqlite3VdbeResolveLabel(v, iBreak); - sqlite3VdbeAddOp2(v, OP_Close, tab2, 0); - sqlite3VdbeJumpHere(v, emptyBypass); - sqlite3VdbeAddOp2(v, OP_Close, tab1, 0); - break; - } - } - - #ifndef SQLITE_OMIT_EXPLAIN - if( p->pNext==0 ){ - ExplainQueryPlanPop(pParse); - } - #endif - } - if( pParse->nErr ) goto multi_select_end; - - /* Compute collating sequences used by - ** temporary tables needed to implement the compound select. - ** Attach the KeyInfo structure to all temporary tables. - ** - ** This section is run by the right-most SELECT statement only. - ** SELECT statements to the left always skip this part. The right-most - ** SELECT might also skip this part if it has no ORDER BY clause and - ** no temp tables are required. - */ - if( p->selFlags & SF_UsesEphemeral ){ - int i; /* Loop counter */ - KeyInfo *pKeyInfo; /* Collating sequence for the result set */ - Select *pLoop; /* For looping through SELECT statements */ - CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ - int nCol; /* Number of columns in result set */ - - assert( p->pNext==0 ); - assert( p->pEList!=0 ); - nCol = p->pEList->nExpr; - pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); - if( !pKeyInfo ){ - rc = SQLITE_NOMEM_BKPT; + assert( !pPrior->pLimit ); + pPrior->iLimit = p->iLimit; + pPrior->iOffset = p->iOffset; + pPrior->pLimit = sqlite3ExprDup(db, p->pLimit, 0); + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); + rc = sqlite3Select(pParse, pPrior, &dest); + sqlite3ExprDelete(db, pPrior->pLimit); + pPrior->pLimit = 0; + if( rc ){ goto multi_select_end; } - for(i=0, apColl=pKeyInfo->aColl; ipDfltColl; - } + p->pPrior = 0; + p->iLimit = pPrior->iLimit; + p->iOffset = pPrior->iOffset; + if( p->iLimit ){ + addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); + VdbeComment((v, "Jump ahead if LIMIT reached")); + if( p->iOffset ){ + sqlite3VdbeAddOp3(v, OP_OffsetLimit, + p->iLimit, p->iOffset+1, p->iOffset); + } + } + ExplainQueryPlan((pParse, 1, "UNION ALL")); + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); + rc = sqlite3Select(pParse, p, &dest); + testcase( rc!=SQLITE_OK ); + pDelete = p->pPrior; + p->pPrior = pPrior; + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + if( p->pLimit + && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse) + && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) + ){ + p->nSelectRow = sqlite3LogEst((u64)nLimit); } - - for(pLoop=p; pLoop; pLoop=pLoop->pPrior){ - for(i=0; i<2; i++){ - int addr = pLoop->addrOpenEphm[i]; - if( addr<0 ){ - /* If [0] is unused then [1] is also unused. So we can - ** always safely abort as soon as the first unused slot is found */ - assert( pLoop->addrOpenEphm[1]<0 ); - break; - } - sqlite3VdbeChangeP2(v, addr, nCol); - sqlite3VdbeChangeP4(v, addr, (char*)sqlite3KeyInfoRef(pKeyInfo), - P4_KEYINFO); - pLoop->addrOpenEphm[i] = -1; - } + if( addr ){ + sqlite3VdbeJumpHere(v, addr); + } +#ifndef SQLITE_OMIT_EXPLAIN + if( p->pNext==0 ){ + ExplainQueryPlanPop(pParse); } - sqlite3KeyInfoUnref(pKeyInfo); +#endif } multi_select_end: @@ -3316,8 +3078,8 @@ void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){ ** Code an output subroutine for a coroutine implementation of a ** SELECT statement. ** -** The data to be output is contained in pIn->iSdst. There are -** pIn->nSdst columns to be output. pDest is where the output should +** The data to be output is contained in an array of pIn->nSdst registers +** starting at register pIn->iSdst. pDest is where the output should ** be sent. ** ** regReturn is the number of the register holding the subroutine @@ -3346,6 +3108,8 @@ static int generateOutputSubroutine( int iContinue; int addr; + assert( pIn->eDest==SRT_Coroutine ); + addr = sqlite3VdbeCurrentAddr(v); iContinue = sqlite3VdbeMakeLabel(pParse); @@ -3367,23 +3131,60 @@ static int generateOutputSubroutine( */ codeOffset(v, p->iOffset, iContinue); - assert( pDest->eDest!=SRT_Exists ); - assert( pDest->eDest!=SRT_Table ); switch( pDest->eDest ){ /* Store the result as data using a unique key. */ + case SRT_Fifo: + case SRT_DistFifo: + case SRT_Table: case SRT_EphemTab: { int r1 = sqlite3GetTempReg(pParse); int r2 = sqlite3GetTempReg(pParse); + int iParm = pDest->iSDParm; + testcase( pDest->eDest==SRT_Table ); + testcase( pDest->eDest==SRT_EphemTab ); + testcase( pDest->eDest==SRT_Fifo ); + testcase( pDest->eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r1); - sqlite3VdbeAddOp2(v, OP_NewRowid, pDest->iSDParm, r2); - sqlite3VdbeAddOp3(v, OP_Insert, pDest->iSDParm, r1, r2); +#if !defined(SQLITE_ENABLE_NULL_TRIM) && defined(SQLITE_DEBUG) + /* A destination of SRT_Table and a non-zero iSDParm2 parameter means + ** that this is an "UPDATE ... FROM" on a virtual table or view. In this + ** case set the p5 parameter of the OP_MakeRecord to OPFLAG_NOCHNG_MAGIC. + ** This does not affect operation in any way - it just allows MakeRecord + ** to process OPFLAG_NOCHANGE values without an assert() failing. */ + if( pDest->eDest==SRT_Table && pDest->iSDParm2 ){ + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); + } +#endif +#ifndef SQLITE_OMIT_CTE + if( pDest->eDest==SRT_DistFifo ){ + /* If the destination is DistFifo, then cursor (iParm+1) is open + ** on an ephemeral index that is used to enforce uniqueness on the + ** total result. At this point, we are processing the setup portion + ** of the recursive CTE using the merge algorithm, so the results are + ** guaranteed to be unique anyhow. But we still need to populate the + ** (iParm+1) cursor for use by the subsequent recursive phase. + */ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm+1, r1, + pIn->iSdst, pIn->nSdst); + } +#endif + sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2); + sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3ReleaseTempReg(pParse, r2); sqlite3ReleaseTempReg(pParse, r1); break; } + /* If any row exist in the result set, record that fact and abort. + */ + case SRT_Exists: { + sqlite3VdbeAddOp2(v, OP_Integer, 1, pDest->iSDParm); + /* The LIMIT clause will terminate the loop for us */ + break; + } + #ifndef SQLITE_OMIT_SUBQUERY /* If we are creating a set for an "expr IN (SELECT ...)". */ @@ -3430,9 +3231,51 @@ static int generateOutputSubroutine( break; } +#ifndef SQLITE_OMIT_CTE + /* Write the results into a priority queue that is order according to + ** pDest->pOrderBy (in pSO). pDest->iSDParm (in iParm) is the cursor for an + ** index with pSO->nExpr+2 columns. Build a key using pSO for the first + ** pSO->nExpr columns, then make sure all keys are unique by adding a + ** final OP_Sequence column. The last column is the record as a blob. + */ + case SRT_DistQueue: + case SRT_Queue: { + int nKey; + int r1, r2, r3, ii; + ExprList *pSO; + int iParm = pDest->iSDParm; + pSO = pDest->pOrderBy; + assert( pSO ); + nKey = pSO->nExpr; + r1 = sqlite3GetTempReg(pParse); + r2 = sqlite3GetTempRange(pParse, nKey+2); + r3 = r2+nKey+1; + + sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r3); + if( pDest->eDest==SRT_DistQueue ){ + sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r3); + } + for(ii=0; iiiSdst + pSO->a[ii].u.x.iOrderByCol - 1, + r2+ii); + } + sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey); + sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, r2, nKey+2); + sqlite3ReleaseTempReg(pParse, r1); + sqlite3ReleaseTempRange(pParse, r2, nKey+2); + break; + } +#endif /* SQLITE_OMIT_CTE */ + + /* Ignore the output */ + case SRT_Discard: { + break; + } + /* If none of the above, then the result destination must be - ** SRT_Output. This routine is never called with any other - ** destination other than the ones handled above or SRT_Output. + ** SRT_Output. ** ** For SRT_Output, results are stored in a sequence of registers. ** Then the OP_ResultRow opcode is used to cause sqlite3_step() to @@ -3460,8 +3303,9 @@ static int generateOutputSubroutine( } /* -** Alternative compound select code generator for cases when there -** is an ORDER BY clause. +** Generate code for a compound SELECT statement using a merge +** algorithm. The compound must have an ORDER BY clause for this +** to work. ** ** We assume a query of the following form: ** @@ -3478,7 +3322,7 @@ static int generateOutputSubroutine( ** ** outB: Move the output of the selectB coroutine into the output ** of the compound query. (Only generated for UNION and -** UNION ALL. EXCEPT and INSERTSECT never output a row that +** UNION ALL. EXCEPT and INTERSECT never output a row that ** appears only in B.) ** ** AltB: Called when there is data from both coroutines and Au.x.iOrderByCol==i ) break; } if( j==nOrderBy ){ - Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); + Expr *pNew = sqlite3ExprInt32(db, i); if( pNew==0 ) return SQLITE_NOMEM_BKPT; - pNew->flags |= EP_IntValue; - pNew->u.iValue = i; p->pOrderBy = pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew); if( pOrderBy ) pOrderBy->a[nOrderBy++].u.x.iOrderByCol = (u16)i; } @@ -3628,26 +3468,29 @@ static int multiSelectOrderBy( } /* Compute the comparison permutation and keyinfo that is used with - ** the permutation used to determine if the next - ** row of results comes from selectA or selectB. Also add explicit - ** collations to the ORDER BY clause terms so that when the subqueries - ** to the right and the left are evaluated, they use the correct - ** collation. + ** the permutation to determine if the next row of results comes + ** from selectA or selectB. Also add literal collations to the + ** ORDER BY clause terms so that when selectA and selectB are + ** evaluated, they use the correct collation. */ aPermute = sqlite3DbMallocRawNN(db, sizeof(u32)*(nOrderBy + 1)); if( aPermute ){ struct ExprList_item *pItem; + int bKeep = 0; aPermute[0] = nOrderBy; for(i=1, pItem=pOrderBy->a; i<=nOrderBy; i++, pItem++){ assert( pItem!=0 ); assert( pItem->u.x.iOrderByCol>0 ); assert( pItem->u.x.iOrderByCol<=p->pEList->nExpr ); aPermute[i] = pItem->u.x.iOrderByCol - 1; + if( aPermute[i]!=(u32)i-1 ) bKeep = 1; + } + if( bKeep==0 ){ + sqlite3DbFreeNN(db, aPermute); + aPermute = 0; } - pKeyMerge = multiSelectOrderByKeyInfo(pParse, p, 1); - }else{ - pKeyMerge = 0; } + pKeyMerge = multiSelectByMergeKeyInfo(pParse, p, 1); /* Allocate a range of temporary registers and the KeyInfo needed ** for the logic that removes duplicate result rows when the @@ -3726,7 +3569,7 @@ static int multiSelectOrderBy( */ addrSelectA = sqlite3VdbeCurrentAddr(v) + 1; addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrA, 0, addrSelectA); - VdbeComment((v, "left SELECT")); + VdbeComment((v, "SUBR: next-A")); pPrior->iLimit = regLimitA; ExplainQueryPlan((pParse, 1, "LEFT")); sqlite3Select(pParse, pPrior, &destA); @@ -3738,7 +3581,7 @@ static int multiSelectOrderBy( */ addrSelectB = sqlite3VdbeCurrentAddr(v) + 1; addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrB, 0, addrSelectB); - VdbeComment((v, "right SELECT")); + VdbeComment((v, "SUBR: next-B")); savedLimit = p->iLimit; savedOffset = p->iOffset; p->iLimit = regLimitB; @@ -3752,7 +3595,7 @@ static int multiSelectOrderBy( /* Generate a subroutine that outputs the current row of the A ** select as the next output row of the compound select. */ - VdbeNoopComment((v, "Output routine for A")); + VdbeNoopComment((v, "SUBR: out-A")); addrOutA = generateOutputSubroutine(pParse, p, &destA, pDest, regOutA, regPrev, pKeyDup, labelEnd); @@ -3761,7 +3604,7 @@ static int multiSelectOrderBy( ** select as the next output row of the compound select. */ if( op==TK_ALL || op==TK_UNION ){ - VdbeNoopComment((v, "Output routine for B")); + VdbeNoopComment((v, "SUBR: out-B")); addrOutB = generateOutputSubroutine(pParse, p, &destB, pDest, regOutB, regPrev, pKeyDup, labelEnd); @@ -3774,10 +3617,12 @@ static int multiSelectOrderBy( if( op==TK_EXCEPT || op==TK_INTERSECT ){ addrEofA_noB = addrEofA = labelEnd; }else{ - VdbeNoopComment((v, "eof-A subroutine")); + VdbeNoopComment((v, "SUBR: eof-A")); addrEofA = sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); + VdbeComment((v, "out-B")); addrEofA_noB = sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd); VdbeCoverage(v); + VdbeComment((v, "next-B")); sqlite3VdbeGoto(v, addrEofA); p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); } @@ -3789,17 +3634,20 @@ static int multiSelectOrderBy( addrEofB = addrEofA; if( p->nSelectRow > pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow; }else{ - VdbeNoopComment((v, "eof-B subroutine")); + VdbeNoopComment((v, "SUBR: eof-B")); addrEofB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); + VdbeComment((v, "out-A")); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, labelEnd); VdbeCoverage(v); + VdbeComment((v, "next-A")); sqlite3VdbeGoto(v, addrEofB); } /* Generate code to handle the case of AB */ - VdbeNoopComment((v, "A-gt-B subroutine")); addrAgtB = sqlite3VdbeCurrentAddr(v); if( op==TK_ALL || op==TK_UNION ){ sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); + VdbeComment((v, "out-B")); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); + VdbeComment((v, "next-B")); + sqlite3VdbeGoto(v, labelCmpr); + }else{ + addrAgtB++; /* Just do next-B. Might as well use the next-B call + ** in the next code block */ } - sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); - sqlite3VdbeGoto(v, labelCmpr); /* This code runs once to initialize everything. */ sqlite3VdbeJumpHere(v, addr1); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA_noB); VdbeCoverage(v); + VdbeComment((v, "next-A")); + /* v--- Also the A>B case for EXCEPT and INTERSECT */ sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); + VdbeComment((v, "next-B")); /* Implement the main merge loop */ + if( aPermute!=0 ){ + sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); + } sqlite3VdbeResolveLabel(v, labelCmpr); - sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy, (char*)pKeyMerge, P4_KEYINFO); - sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); - sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); VdbeCoverage(v); + if( aPermute!=0 ){ + sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); + } + sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); + VdbeCoverageIf(v, op==TK_ALL); + VdbeCoverageIf(v, op==TK_UNION); + VdbeCoverageIf(v, op==TK_EXCEPT); + VdbeCoverageIf(v, op==TK_INTERSECT); /* Jump to the this point in order to terminate the query. */ @@ -4756,7 +4616,7 @@ static int flattenSubquery( } pSubitem->fg.jointype |= jointype; - /* Now begin substituting subquery result set expressions for + /* Begin substituting subquery result set expressions for ** references to the iParent in the outer query. ** ** Example: @@ -4768,7 +4628,7 @@ static int flattenSubquery( ** We look at every expression in the outer query and every place we see ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10". */ - if( pSub->pOrderBy && (pParent->selFlags & SF_NoopOrderBy)==0 ){ + if( pSub->pOrderBy ){ /* At this point, any non-zero iOrderByCol values indicate that the ** ORDER BY column expression is identical to the iOrderByCol'th ** expression returned by SELECT statement pSub. Since these values @@ -4776,9 +4636,9 @@ static int flattenSubquery( ** zero them before transferring the ORDER BY clause. ** ** Not doing this may cause an error if a subsequent call to this - ** function attempts to flatten a compound sub-query into pParent - ** (the only way this can happen is if the compound sub-query is - ** currently part of pSub->pSrc). See ticket [d11a6e908f]. */ + ** function attempts to flatten a compound sub-query into pParent. + ** See ticket [d11a6e908f]. + */ ExprList *pOrderBy = pSub->pOrderBy; for(i=0; inExpr; i++){ pOrderBy->a[i].u.x.iOrderByCol = 0; @@ -5396,6 +5256,16 @@ static int pushDownWhereTerms( x.pEList = pSubq->pEList; x.pCList = findLeftmostExprlist(pSubq); pNew = substExpr(&x, pNew); + assert( pNew!=0 || pParse->nErr!=0 ); + if( pParse->nErr==0 && pNew->op==TK_IN && ExprUseXSelect(pNew) ){ + assert( pNew->x.pSelect!=0 ); + pNew->x.pSelect->selFlags |= SF_ClonedRhsIn; + assert( pWhere!=0 ); + assert( pWhere->op==TK_IN ); + assert( ExprUseXSelect(pWhere) ); + assert( pWhere->x.pSelect!=0 ); + pWhere->x.pSelect->selFlags |= SF_ClonedRhsIn; + } #ifndef SQLITE_OMIT_WINDOWFUNC if( pSubq->pWin && 0==pushDownWindowCheck(pParse, pSubq, pNew) ){ /* Restriction 6c has prevented push-down in this case */ @@ -5630,14 +5500,14 @@ int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ ** SELECT * FROM (SELECT ... FROM t1 EXCEPT SELECT ... FROM t2) ** ORDER BY ... COLLATE ... ** -** This transformation is necessary because the multiSelectOrderBy() routine +** This transformation is necessary because the multiSelectByMerge() routine ** above that generates the code for a compound SELECT with an ORDER BY clause ** uses a merge algorithm that requires the same collating sequence on the ** result columns as on the ORDER BY clause. See ticket ** http://sqlite.org/src/info/6709574d2a ** ** This transformation is only needed for EXCEPT, INTERSECT, and UNION. -** The UNION ALL operator works fine with multiSelectOrderBy() even when +** The UNION ALL operator works fine with multiSelectByMerge() even when ** there are COLLATE terms in the ORDER BY. */ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ @@ -6183,7 +6053,7 @@ static int selectExpander(Walker *pWalker, Select *p){ } #ifndef SQLITE_OMIT_VIRTUALTABLE else if( ALWAYS(IsVirtual(pTab)) - && pFrom->fg.fromDDL + && (pFrom->fg.fromDDL || (pParse->prepFlags & SQLITE_PREPARE_FROM_DDL)) && ALWAYS(pTab->u.vtab.p!=0) && pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0) ){ @@ -7136,7 +7006,7 @@ static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){ && pExpr->pAggInfo==0 ){ sqlite3 *db = pWalker->pParse->db; - Expr *pNew = sqlite3Expr(db, TK_INTEGER, "1"); + Expr *pNew = sqlite3ExprInt32(db, 1); if( pNew ){ Expr *pWhere = pS->pWhere; SWAP(Expr, *pNew, *pExpr); @@ -7487,7 +7357,6 @@ static SQLITE_NOINLINE void existsToJoin( ExprSetProperty(pWhere, EP_IntValue); assert( p->pWhere!=0 ); pSub->pSrc->a[0].fg.fromExists = 1; - pSub->pSrc->a[0].fg.jointype |= JT_CROSS; p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc); if( pSubWhere ){ p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSubWhere); @@ -7515,6 +7384,7 @@ typedef struct CheckOnCtx CheckOnCtx; struct CheckOnCtx { SrcList *pSrc; /* SrcList for this context */ int iJoin; /* Cursor numbers must be =< than this */ + int bFuncArg; /* True for table-function arg */ CheckOnCtx *pParent; /* Parent context */ }; @@ -7566,7 +7436,9 @@ static int selectCheckOnClausesExpr(Walker *pWalker, Expr *pExpr){ if( iTab>=pSrc->a[0].iCursor && iTab<=pSrc->a[pSrc->nSrc-1].iCursor ){ if( pCtx->iJoin && iTab>pCtx->iJoin ){ sqlite3ErrorMsg(pWalker->pParse, - "ON clause references tables to its right"); + "%s references tables to its right", + (pCtx->bFuncArg ? "table-function argument" : "ON clause") + ); return WRC_Abort; } break; @@ -7601,9 +7473,10 @@ static int selectCheckOnClausesSelect(Walker *pWalker, Select *pSelect){ ** Check all ON clauses in pSelect to verify that they do not reference ** columns to the right. */ -static void selectCheckOnClauses(Parse *pParse, Select *pSelect){ +void sqlite3SelectCheckOnClauses(Parse *pParse, Select *pSelect){ Walker w; CheckOnCtx sCtx; + int ii; assert( pSelect->selFlags & SF_OnToWhere ); assert( pSelect->pSrc!=0 && pSelect->pSrc->nSrc>=2 ); memset(&w, 0, sizeof(w)); @@ -7613,8 +7486,46 @@ static void selectCheckOnClauses(Parse *pParse, Select *pSelect){ w.u.pCheckOnCtx = &sCtx; memset(&sCtx, 0, sizeof(sCtx)); sCtx.pSrc = pSelect->pSrc; - sqlite3WalkExprNN(&w, pSelect->pWhere); + sqlite3WalkExpr(&w, pSelect->pWhere); pSelect->selFlags &= ~SF_OnToWhere; + + /* Check for any table-function args that are attached to virtual tables + ** on the RHS of an outer join. They are subject to the same constraints + ** as ON clauses. */ + sCtx.bFuncArg = 1; + for(ii=0; iipSrc->nSrc; ii++){ + SrcItem *pItem = &pSelect->pSrc->a[ii]; + if( pItem->fg.isTabFunc + && (pItem->fg.jointype & JT_OUTER) + ){ + sCtx.iJoin = pItem->iCursor; + sqlite3WalkExprList(&w, pItem->u1.pFuncArg); + } + } +} + +/* +** If p2 exists and p1 and p2 have the same number of terms, then change +** every term of p1 to have the same sort order as p2 and return true. +** +** If p2 is NULL or p1 and p2 are different lengths, then make no changes +** and return false. +** +** p1 must be non-NULL. +*/ +static int sqlite3CopySortOrder(ExprList *p1, ExprList *p2){ + assert( p1 ); + if( p2 && p1->nExpr==p2->nExpr ){ + int ii; + for(ii=0; iinExpr; ii++){ + u8 sortFlags; + sortFlags = p2->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; + p1->a[ii].fg.sortFlags = sortFlags; + } + return 1; + }else{ + return 0; + } } /* @@ -7712,8 +7623,7 @@ int sqlite3Select( assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_Queue ); if( IgnorableDistinct(pDest) ){ - assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union || - pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard || + assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Discard || pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); /* All of these destinations are also able to ignore the ORDER BY clause */ if( p->pOrderBy ){ @@ -7729,7 +7639,6 @@ int sqlite3Select( p->pOrderBy = 0; } p->selFlags &= ~(u32)SF_Distinct; - p->selFlags |= SF_NoopOrderBy; } sqlite3SelectPrep(pParse, p, 0); if( pParse->nErr ){ @@ -7744,18 +7653,6 @@ int sqlite3Select( } #endif - /* If the SELECT statement contains ON clauses that were moved into - ** the WHERE clause, go through and verify that none of the terms - ** in the ON clauses reference tables to the right of the ON clause. - ** Do this now, after name resolution, but before query flattening - */ - if( p->selFlags & SF_OnToWhere ){ - selectCheckOnClauses(pParse, p); - if( pParse->nErr ){ - goto select_end; - } - } - /* If the SF_UFSrcCheck flag is set, then this function is being called ** as part of populating the temp table for an UPDATE...FROM statement. ** In this case, it is an error if the target object (pSrc->a[0]) name @@ -8269,7 +8166,8 @@ int sqlite3Select( ** BY and DISTINCT, and an index or separate temp-table for the other. */ if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct - && sqlite3ExprListCompare(sSort.pOrderBy, pEList, -1)==0 + && sqlite3CopySortOrder(pEList, sSort.pOrderBy) + && sqlite3ExprListCompare(pEList, sSort.pOrderBy, -1)==0 && OptimizationEnabled(db, SQLITE_GroupByOrder) #ifndef SQLITE_OMIT_WINDOWFUNC && p->pWin==0 @@ -8483,21 +8381,10 @@ int sqlite3Select( ** but not actually sorted. Either way, record the fact that the ** ORDER BY and GROUP BY clauses are the same by setting the orderByGrp ** variable. */ - if( sSort.pOrderBy && pGroupBy->nExpr==sSort.pOrderBy->nExpr ){ - int ii; - /* The GROUP BY processing doesn't care whether rows are delivered in - ** ASC or DESC order - only that each group is returned contiguously. - ** So set the ASC/DESC flags in the GROUP BY to match those in the - ** ORDER BY to maximize the chances of rows being delivered in an - ** order that makes the ORDER BY redundant. */ - for(ii=0; iinExpr; ii++){ - u8 sortFlags; - sortFlags = sSort.pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; - pGroupBy->a[ii].fg.sortFlags = sortFlags; - } - if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){ - orderByGrp = 1; - } + if( sqlite3CopySortOrder(pGroupBy, sSort.pOrderBy) + && sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 + ){ + orderByGrp = 1; } }else{ assert( 0==sqlite3LogEst(1) ); diff --git a/src/shell.c.in b/src/shell.c.in index bd4483ff7..ef30194fa 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file contains code to implement the "sqlite" command line +** This file contains code to implement the "sqlite3" command line ** utility for accessing SQLite databases. */ #if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) @@ -31,14 +31,6 @@ typedef unsigned short int u16; # include SHELL_STRINGIFY(SQLITE_CUSTOM_INCLUDE) #endif -/* -** Determine if we are dealing with WinRT, which provides only a subset of -** the full Win32 API. -*/ -#if !defined(SQLITE_OS_WINRT) -# define SQLITE_OS_WINRT 0 -#endif - /* ** If SQLITE_SHELL_FIDDLE is defined then the shell is modified ** somewhat for use as a WASM module in a web browser. This flag @@ -47,6 +39,10 @@ typedef unsigned short int u16; ** and this build mode rewires the user input subsystem to account for ** that. */ +#if defined(SQLITE_SHELL_FIDDLE) +# undef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif /* ** Warning pragmas copied from msvc.h in the core. @@ -108,6 +104,7 @@ typedef unsigned char u8; #include #ifndef _WIN32 # include +# include #endif #if !defined(_WIN32) && !defined(WIN32) @@ -177,9 +174,6 @@ typedef unsigned char u8; #endif #if defined(_WIN32) || defined(WIN32) -# if SQLITE_OS_WINRT -# define SQLITE_OMIT_POPEN 1 -# else # include # include # define isatty(h) _isatty(h) @@ -194,7 +188,6 @@ typedef unsigned char u8; # endif # undef pclose # define pclose _pclose -# endif #else /* Make sure isatty() has a prototype. */ extern int isatty(int); @@ -225,9 +218,6 @@ typedef unsigned char u8; #define IsAlpha(X) isalpha((unsigned char)X) #if defined(_WIN32) || defined(WIN32) -#if SQLITE_OS_WINRT -#include -#endif #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include @@ -239,6 +229,8 @@ extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); INCLUDE ../ext/misc/sqlite3_stdio.h INCLUDE ../ext/misc/sqlite3_stdio.c +INCLUDE ../ext/qrf/qrf.h +INCLUDE ../ext/qrf/qrf.c /* Use console I/O package as a direct INCLUDE. */ #define SQLITE_INTERNAL_LINKAGE static @@ -252,11 +244,67 @@ INCLUDE ../ext/misc/sqlite3_stdio.c # define SQLITE_CIO_NO_FLUSH #endif -#define eputz(z) sqlite3_fputs(z,stderr) -#define sputz(fp,z) sqlite3_fputs(z,fp) +/* +** Output routines that are able to redirect to memory rather than +** doing actually I/O. +** Works like. +** -------------- +** cli_printf(FILE*, const char*, ...); fprintf() +** cli_puts(const char*, FILE*); fputs() +** cli_vprintf(FILE*, const char*, va_list); vfprintf() +** +** These are just thin wrappers with the following added semantics: +** If the file-scope variable cli_output_capture is not NULL, and +** if the FILE* argument is stdout or stderr, then rather than +** writing to stdout/stdout, append the text to the cli_output_capture +** variable. +** +** The cli_exit(int) routine works like exit() except that it +** first dumps any capture output to stdout. +*/ +static sqlite3_str *cli_output_capture = 0; +static int cli_printf(FILE *out, const char *zFormat, ...){ + va_list ap; + int rc; + va_start(ap,zFormat); + if( cli_output_capture && (out==stdout || out==stderr) ){ + sqlite3_str_vappendf(cli_output_capture, zFormat, ap); + rc = 1; + }else{ + rc = sqlite3_vfprintf(out, zFormat, ap); + } + va_end(ap); + return rc; +} +static int cli_puts(const char *zText, FILE *out){ + if( cli_output_capture && (out==stdout || out==stderr) ){ + sqlite3_str_appendall(cli_output_capture, zText); + return 1; + } + return sqlite3_fputs(zText, out); +} +#if 0 /* Not currently used - available if we need it later */ +static int cli_vprintf(FILE *out, const char *zFormat, va_list ap){ + if( cli_output_capture && (out==stdout || out==stderr) ){ + sqlite3_str_vappendf(cli_output_capture, zFormat, ap); + return 1; + }else{ + return sqlite3_vfprintf(out, zFormat, ap); + } +} +#endif +static void cli_exit(int rc){ + if( cli_output_capture ){ + char *z = sqlite3_str_finish(cli_output_capture); + sqlite3_fputs(z, stdout); + fflush(stdout); + } + exit(rc); +} + -/* True if the timer is enabled */ -static int enableTimer = 0; +#define eputz(z) cli_puts(z,stderr) +#define sputz(fp,z) cli_puts(z,fp) /* A version of strcmp() that works with NULL values */ static int cli_strcmp(const char *a, const char *b){ @@ -274,7 +322,7 @@ static int cli_strncmp(const char *a, const char *b, size_t n){ ** Unix epoch (1970-01-01T00:00:00Z) */ static sqlite3_int64 timeOfDay(void){ -#if defined(_WIN64) +#if defined(_WIN64) && _WIN32_WINNT >= _WIN32_WINNT_WIN8 sqlite3_uint64 t; FILETIME tm; GetSystemTimePreciseAsFileTime(&tm); @@ -302,152 +350,6 @@ static sqlite3_int64 timeOfDay(void){ #endif } -#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) -#include -#include - -/* VxWorks does not support getrusage() as far as we can determine */ -#if defined(_WRS_KERNEL) || defined(__RTP__) -struct rusage { - struct timeval ru_utime; /* user CPU time used */ - struct timeval ru_stime; /* system CPU time used */ -}; -#define getrusage(A,B) memset(B,0,sizeof(*B)) -#endif - - -/* Saved resource information for the beginning of an operation */ -static struct rusage sBegin; /* CPU time at start */ -static sqlite3_int64 iBegin; /* Wall-clock time at start */ - -/* -** Begin timing an operation -*/ -static void beginTimer(void){ - if( enableTimer ){ - getrusage(RUSAGE_SELF, &sBegin); - iBegin = timeOfDay(); - } -} - -/* Return the difference of two time_structs in seconds */ -static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ - return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + - (double)(pEnd->tv_sec - pStart->tv_sec); -} - -/* -** Print the timing results. -*/ -static void endTimer(FILE *out){ - if( enableTimer ){ - sqlite3_int64 iEnd = timeOfDay(); - struct rusage sEnd; - getrusage(RUSAGE_SELF, &sEnd); - sqlite3_fprintf(out, "Run Time: real %.6f user %.6f sys %.6f\n", - (iEnd - iBegin)*0.000001, - timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), - timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); - } -} - -#define BEGIN_TIMER beginTimer() -#define END_TIMER(X) endTimer(X) -#define HAS_TIMER 1 - -#elif (defined(_WIN32) || defined(WIN32)) - -/* Saved resource information for the beginning of an operation */ -static HANDLE hProcess; -static FILETIME ftKernelBegin; -static FILETIME ftUserBegin; -static sqlite3_int64 ftWallBegin; -typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, - LPFILETIME, LPFILETIME); -static GETPROCTIMES getProcessTimesAddr = NULL; - -/* -** Check to see if we have timer support. Return 1 if necessary -** support found (or found previously). -*/ -static int hasTimer(void){ - if( getProcessTimesAddr ){ - return 1; - } else { -#if !SQLITE_OS_WINRT - /* GetProcessTimes() isn't supported in WIN95 and some other Windows - ** versions. See if the version we are running on has it, and if it - ** does, save off a pointer to it and the current process handle. - */ - hProcess = GetCurrentProcess(); - if( hProcess ){ - HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); - if( NULL != hinstLib ){ - getProcessTimesAddr = - (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); - if( NULL != getProcessTimesAddr ){ - return 1; - } - FreeLibrary(hinstLib); - } - } -#endif - } - return 0; -} - -/* -** Begin timing an operation -*/ -static void beginTimer(void){ - if( enableTimer && getProcessTimesAddr ){ - FILETIME ftCreation, ftExit; - getProcessTimesAddr(hProcess,&ftCreation,&ftExit, - &ftKernelBegin,&ftUserBegin); - ftWallBegin = timeOfDay(); - } -} - -/* Return the difference of two FILETIME structs in seconds */ -static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ - sqlite_int64 i64Start = *((sqlite_int64 *) pStart); - sqlite_int64 i64End = *((sqlite_int64 *) pEnd); - return (double) ((i64End - i64Start) / 10000000.0); -} - -/* -** Print the timing results. -*/ -static void endTimer(FILE *out){ - if( enableTimer && getProcessTimesAddr){ - FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; - sqlite3_int64 ftWallEnd = timeOfDay(); - getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); -#ifdef _WIN64 - /* microsecond precision on 64-bit windows */ - sqlite3_fprintf(out, "Run Time: real %.6f user %f sys %f\n", - (ftWallEnd - ftWallBegin)*0.000001, - timeDiff(&ftUserBegin, &ftUserEnd), - timeDiff(&ftKernelBegin, &ftKernelEnd)); -#else - /* millisecond precisino on 32-bit windows */ - sqlite3_fprintf(out, "Run Time: real %.3f user %.3f sys %.3f\n", - (ftWallEnd - ftWallBegin)*0.000001, - timeDiff(&ftUserBegin, &ftUserEnd), - timeDiff(&ftKernelBegin, &ftKernelEnd)); -#endif - } -} - -#define BEGIN_TIMER beginTimer() -#define END_TIMER(X) endTimer(X) -#define HAS_TIMER hasTimer() - -#else -#define BEGIN_TIMER -#define END_TIMER(X) /*no-op*/ -#define HAS_TIMER 0 -#endif /* ** Used to prevent warnings about unused parameters @@ -472,12 +374,17 @@ static int bail_on_error = 0; static int stdin_is_interactive = 1; /* -** On Windows systems we need to know if standard output is a console -** in order to show that UTF-16 translation is done in the sign-on -** banner. The following variable is true if it is the console. +** Treat stdout like a TTY if true. */ static int stdout_is_console = 1; +/* +** Use this value as the width of the output device. Or, figure it +** out at runtime if the value is negative. Or use a default width +** if this value is zero. +*/ +static int stdout_tty_width = -1; + /* ** The following is the open SQLite database. We make a pointer ** to this database a static variable so that it can be accessed @@ -506,6 +413,14 @@ static char mainPrompt[PROMPT_LEN_MAX]; /* Continuation prompt. default: " ...> " */ static char continuePrompt[PROMPT_LEN_MAX]; +/* +** Write I/O traces to the following stream. +*/ +#ifdef SQLITE_ENABLE_IOTRACE +static FILE *iotrace = 0; +#endif + + /* This is variant of the standard-library strncpy() routine with the ** one change that the destination string is always zero-terminated, even ** if there is no zero-terminator in the first n-1 characters of the source @@ -615,7 +530,7 @@ static char *dynamicContinuePrompt(void){ /* Indicate out-of-memory and exit. */ static void shell_out_of_memory(void){ eputz("Error: out of memory\n"); - exit(1); + cli_exit(1); } /* Check a pointer to see if it is NULL. If it is NULL, exit with an @@ -625,13 +540,6 @@ static void shell_check_oom(const void *p){ if( p==0 ) shell_out_of_memory(); } -/* -** Write I/O traces to the following stream. -*/ -#ifdef SQLITE_ENABLE_IOTRACE -static FILE *iotrace = 0; -#endif - /* ** This routine works like printf in that its first argument is a ** format string and subsequent arguments are values to be substituted @@ -646,349 +554,67 @@ static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ va_start(ap, zFormat); z = sqlite3_vmprintf(zFormat, ap); va_end(ap); - sqlite3_fprintf(iotrace, "%s", z); + cli_printf(iotrace, "%s", z); sqlite3_free(z); } #endif -/* 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 */ -} aUWidth[] = { - /* {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, 0x01038}, {0, 0x01039}, {1, 0x0103a}, {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 cli_wcwidth(int c){ - int iFirst, iLast; - - /* Fast path for common characters */ - if( c<=0x300 ) return 1; - - /* The general case */ - iFirst = 0; - iLast = sizeof(aUWidth)/sizeof(aUWidth[0]) - 1; - while( iFirst c ){ - iLast = iMid - 1; - }else{ - return aUWidth[iMid].w; - } - } - if( aUWidth[iLast].iFirst > c ) return aUWidth[iFirst].w; - return aUWidth[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. +** Compute a string length that is limited to what can be stored in +** lower 30 bits of a 32-bit signed integer. */ -static int decodeUtf8(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; +static int strlen30(const char *z){ + size_t n; + if( z==0 ) return 0; + n = strlen(z); + return n>0x3fffffff ? 0x3fffffff : (int)n; } - -#if 0 /* NOT USED */ /* -** Return the width, in display columns, of a UTF-8 string. -** -** Each normal character counts as 1. Zero-width characters count -** as zero, and double-width characters count as 2. +** Return open FILE * if zFile exists, can be opened for read +** and is an ordinary file or a character stream source. +** Otherwise return 0. */ -int cli_wcswidth(const char *z){ - const unsigned char *a = (const unsigned char*)z; - int n = 0; - int i = 0; - unsigned char c; - while( (c = a[i])!=0 ){ - if( c>=0xc0 ){ - int u; - int len = decodeUtf8(&a[i], &u); - i += len; - n += cli_wcwidth(u); - }else if( c>=' ' ){ - n++; - i++; - }else{ - i++; - } +static FILE * openChrSource(const char *zFile){ +#if defined(_WIN32) || defined(WIN32) + struct __stat64 x = {0}; +# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) + /* On Windows, open first, then check the stream nature. This order + ** is necessary because _stat() and sibs, when checking a named pipe, + ** effectively break the pipe as its supplier sees it. */ + FILE *rv = sqlite3_fopen(zFile, "rb"); + if( rv==0 ) return 0; + if( _fstat64(_fileno(rv), &x) != 0 + || !STAT_CHR_SRC(x.st_mode)){ + fclose(rv); + rv = 0; + } + return rv; +#else + struct stat x = {0}; + int rc = stat(zFile, &x); +# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode)) + if( rc!=0 ) return 0; + if( STAT_CHR_SRC(x.st_mode) ){ + return sqlite3_fopen(zFile, "rb"); + }else{ + return 0; } - return n; -} #endif - -/* -** 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 isVt100(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; +#undef STAT_CHR_SRC } /* -** Output string zUtf to stdout as w characters. If w is negative, -** then right-justify the text. W is the width in UTF-8 characters, not -** in bytes. This is different from the %*.*s specification in printf -** since with %*.*s the width is measured in bytes, not characters. -** -** Take into account zero-width and double-width Unicode characters. -** In other words, a zero-width character does not count toward the -** the w limit. A double-width character counts as two. +** This routine reads a line of text from FILE in, stores +** the text in memory obtained from malloc() and returns a pointer +** to the text. NULL is returned at end of file, or if malloc() +** fails, or if the length of the line is longer than about a gigabyte. ** -** w should normally be a small number. A couple hundred at most. This -** routine caps w at 100 million to avoid integer overflow issues. +** If zLine is not NULL then it is a malloced buffer returned from +** a previous call to this routine that may be reused. */ -static void utf8_width_print(FILE *out, 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; - if( w<-mxW ){ - w = -mxW; - }else if( w>mxW ){ - w= mxW; - } - aw = w<0 ? -w : w; - if( zUtf==0 ) zUtf = ""; - while( (c = a[i])!=0 ){ - if( (c&0xc0)==0xc0 ){ - int u; - int len = decodeUtf8(a+i, &u); - int x = cli_wcwidth(u); - if( x+n>aw ){ - break; - } - i += len; - n += x; - }else if( c==0x1b && (k = isVt100(&a[i]))>0 ){ - i += k; - }else if( n>=aw ){ - break; - }else{ - n++; - i++; - } - } - if( n>=aw ){ - sqlite3_fprintf(out, "%.*s", i, zUtf); - }else if( w<0 ){ - sqlite3_fprintf(out, "%*s%s", aw-n, "", zUtf); - }else{ - sqlite3_fprintf(out, "%s%*s", zUtf, aw-n, ""); - } -} - - -/* -** Determines if a string is a number of not. -*/ -static int isNumber(const char *z, int *realnum){ - if( *z=='-' || *z=='+' ) z++; - if( !IsDigit(*z) ){ - return 0; - } - z++; - if( realnum ) *realnum = 0; - while( IsDigit(*z) ){ z++; } - if( *z=='.' ){ - z++; - if( !IsDigit(*z) ) return 0; - while( IsDigit(*z) ){ z++; } - if( realnum ) *realnum = 1; - } - if( *z=='e' || *z=='E' ){ - z++; - if( *z=='+' || *z=='-' ) z++; - if( !IsDigit(*z) ) return 0; - while( IsDigit(*z) ){ z++; } - if( realnum ) *realnum = 1; - } - return *z==0; -} - -/* -** Compute a string length that is limited to what can be stored in -** lower 30 bits of a 32-bit signed integer. -*/ -static int strlen30(const char *z){ - const char *z2 = z; - while( *z2 ){ z2++; } - return 0x3fffffff & (int)(z2 - z); -} - -/* -** Return the length of a string in characters. Multibyte UTF8 characters -** count as a single character for single-width characters, or as two -** characters for double-width characters. -*/ -static int strlenChar(const char *z){ - int n = 0; - while( *z ){ - if( (0x80&z[0])==0 ){ - n++; - z++; - }else{ - int u = 0; - int len = decodeUtf8((const u8*)z, &u); - z += len; - n += cli_wcwidth(u); - } - } - return n; -} - -/* -** Return open FILE * if zFile exists, can be opened for read -** and is an ordinary file or a character stream source. -** Otherwise return 0. -*/ -static FILE * openChrSource(const char *zFile){ -#if defined(_WIN32) || defined(WIN32) - struct __stat64 x = {0}; -# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) - /* On Windows, open first, then check the stream nature. This order - ** is necessary because _stat() and sibs, when checking a named pipe, - ** effectively break the pipe as its supplier sees it. */ - FILE *rv = sqlite3_fopen(zFile, "rb"); - if( rv==0 ) return 0; - if( _fstat64(_fileno(rv), &x) != 0 - || !STAT_CHR_SRC(x.st_mode)){ - fclose(rv); - rv = 0; - } - return rv; -#else - struct stat x = {0}; - int rc = stat(zFile, &x); -# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode)) - if( rc!=0 ) return 0; - if( STAT_CHR_SRC(x.st_mode) ){ - return sqlite3_fopen(zFile, "rb"); - }else{ - return 0; - } -#endif -#undef STAT_CHR_SRC -} - -/* -** This routine reads a line of text from FILE in, stores -** the text in memory obtained from malloc() and returns a pointer -** to the text. NULL is returned at end of file, or if malloc() -** fails, or if the length of the line is longer than about a gigabyte. -** -** If zLine is not NULL then it is a malloced buffer returned from -** a previous call to this routine that may be reused. -*/ -static char *local_getline(char *zLine, FILE *in){ - int nLine = zLine==0 ? 0 : 100; +static char *local_getline(char *zLine, FILE *in){ + int nLine = zLine==0 ? 0 : 100; int n = 0; while( 1 ){ @@ -1305,7 +931,7 @@ static void shellDtostr( char z[400]; if( n<1 ) n = 1; if( n>350 ) n = 350; - sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r); + sprintf(z, "%#+.*e", n, r); sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); } @@ -1463,31 +1089,24 @@ struct ExpertInfo { }; #endif -/* A single line in the EQP output */ -typedef struct EQPGraphRow EQPGraphRow; -struct EQPGraphRow { - int iEqpId; /* ID for this row */ - int iParentId; /* ID of the parent row */ - EQPGraphRow *pNext; /* Next row in sequence */ - char zText[1]; /* Text to display for this row */ -}; +/* All the parameters that determine how to render query results. +*/ +typedef struct Mode { + u8 autoExplain; /* Automatically turn on .explain mode */ + u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */ + u8 autoEQPtrace; /* autoEQP is in trace mode */ + u8 scanstatsOn; /* True to display scan stats before each finalize */ + u8 bAutoScreenWidth; /* Using the TTY to determine screen width */ + u8 mFlags; /* MFLG_ECHO, MFLG_CRLF, etc. */ + u8 eMode; /* One of the MODE_ values */ + sqlite3_qrf_spec spec; /* Spec to be passed into QRF */ +} Mode; -/* All EQP output is collected into an instance of the following */ -typedef struct EQPGraph EQPGraph; -struct EQPGraph { - EQPGraphRow *pRow; /* Linked list of all rows of the EQP output */ - EQPGraphRow *pLast; /* Last element of the pRow list */ - char zPrefix[100]; /* Graph prefix */ -}; +/* Flags for Mode.mFlags */ +#define MFLG_ECHO 0x01 /* Echo inputs to output */ +#define MFLG_CRLF 0x02 /* Use CR/LF output line endings */ +#define MFLG_HDR 0x04 /* .header used to change headers on/off */ -/* Parameters affecting columnar mode result display (defaulting together) */ -typedef struct ColModeOpts { - int iWrap; /* In columnar modes, wrap lines reaching this limit */ - u8 bQuote; /* Quote results for .mode box and table */ - u8 bWordWrap; /* In columnar modes, wrap at word boundaries */ -} ColModeOpts; -#define ColModeOpts_default { 60, 0, 0 } -#define ColModeOpts_default_qbox { 60, 1, 0 } /* ** State information about the database connection is contained in an @@ -1496,11 +1115,6 @@ typedef struct ColModeOpts { typedef struct ShellState ShellState; struct ShellState { sqlite3 *db; /* The database */ - u8 autoExplain; /* Automatically turn on .explain mode */ - u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */ - u8 autoEQPtest; /* autoEQP is in test mode */ - u8 autoEQPtrace; /* autoEQP is in trace mode */ - u8 scanstatsOn; /* True to display scan stats before each finalize */ u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */ u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */ u8 nEqpLevel; /* Depth of the EQP output graph */ @@ -1508,48 +1122,44 @@ struct ShellState { u8 bSafeMode; /* True to prohibit unsafe operations */ u8 bSafeModePersist; /* The long-term value of bSafeMode */ u8 eRestoreState; /* See comments above doAutoDetectRestore() */ - u8 crlfMode; /* Do NL-to-CRLF translations when enabled (maybe) */ - u8 eEscMode; /* Escape mode for text output */ - ColModeOpts cmOpts; /* Option values affecting columnar mode output */ unsigned statsOn; /* True to display memory stats before each finalize */ unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */ + u8 nPopOutput; /* Revert .output settings when reaching zero */ + u8 nPopMode; /* Revert .mode settings when reaching zero */ + u8 enableTimer; /* Enable the timer. 2: permanently 1: only once */ int inputNesting; /* Track nesting level of .read and other redirects */ - int outCount; /* Revert to stdout when reaching zero */ - int cnt; /* Number of records displayed so far */ + double prevTimer; /* Last reported timer value */ + double tmProgress; /* --timeout option for .progress */ i64 lineno; /* Line number of last line read from in */ + const char *zInFile; /* Name of the input file */ int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */ FILE *in; /* Read commands from this stream */ FILE *out; /* Write results here */ FILE *traceOut; /* Output for sqlite3_trace() */ int nErr; /* Number of errors seen */ - int mode; /* An output mode setting */ - int modePrior; /* Saved mode */ - int cMode; /* temporary output mode for the current query */ - int normalMode; /* Output mode before ".explain on" */ int writableSchema; /* True if PRAGMA writable_schema=ON */ - int showHeader; /* True to show column names in List or Column mode */ int nCheck; /* Number of ".check" commands run */ unsigned nProgress; /* Number of progress callbacks encountered */ unsigned mxProgress; /* Maximum progress callbacks before failing */ unsigned flgProgress; /* Flags for the progress callback */ unsigned shellFlgs; /* Various flags */ - unsigned priorShFlgs; /* Saved copy of flags */ + unsigned nTestRun; /* Number of test cases run */ + unsigned nTestErr; /* Number of test cases that failed */ sqlite3_int64 szMax; /* --maxsize argument to .open */ char *zDestTable; /* Name of destination table when MODE_Insert */ char *zTempFile; /* Temporary file that might need deleting */ + char *zErrPrefix; /* Alternative error message prefix */ char zTestcase[30]; /* Name of current test case */ - char colSeparator[20]; /* Column separator character for several modes */ - char rowSeparator[20]; /* Row separator character for MODE_Ascii */ - char colSepPrior[20]; /* Saved column separator */ - char rowSepPrior[20]; /* Saved row separator */ - int *colWidth; /* Requested width of each column in columnar modes */ - int *actualWidth; /* Actual width of each column */ - int nWidth; /* Number of slots in colWidth[] and actualWidth[] */ - char nullValue[20]; /* The text to print when a NULL comes back from - ** the database */ char outfile[FILENAME_MAX]; /* Filename for *out */ sqlite3_stmt *pStmt; /* Current statement if any. */ FILE *pLog; /* Write log output here */ + Mode mode; /* Current display mode */ + Mode modePrior; /* Backup */ + struct SavedMode { /* Ability to define custom mode configurations */ + char *zTag; /* Name of this saved mode */ + Mode mode; /* The saved mode */ + } *aSavedModes; /* Array of saved .mode settings. system malloc() */ + int nSavedModes; /* Number of saved .mode settings */ struct AuxDb { /* Storage space for auxiliary database connections */ sqlite3 *db; /* Connection pointer */ const char *zDbFilename; /* Filename used to open the connection */ @@ -1560,14 +1170,19 @@ struct ShellState { #endif } aAuxDb[5], /* Array of all database connections */ *pAuxDb; /* Currently active database connection */ - int *aiIndent; /* Array of indents used in MODE_Explain */ - int nIndent; /* Size of array aiIndent[] */ - int iIndent; /* Index of current op in aiIndent[] */ char *zNonce; /* Nonce for temporary safe-mode escapes */ - EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ #endif + struct DotCmdLine { /* Info about arguments to a dot-command */ + const char *zOrig; /* Original text of the dot-command */ + char *zCopy; /* Copy of zOrig, from malloc() */ + int nAlloc; /* Size of allocates for arrays below */ + int nArg; /* Number of argument slots actually used */ + char **azArg; /* Pointer to each argument, dequoted */ + int *aiOfst; /* Offset into zOrig[] for start of each arg */ + char *abQuot; /* True if the argment was originally quoted */ + } dot; #ifdef SQLITE_SHELL_FIDDLE struct { const char * zInput; /* Input string from wasm/JS proxy */ @@ -1582,7 +1197,7 @@ static ShellState shellState; #endif -/* Allowed values for ShellState.autoEQP +/* Allowed values for ShellState.mode.autoEQP */ #define AUTOEQP_off 0 /* Automatic EXPLAIN QUERY PLAN is off */ #define AUTOEQP_on 1 /* Automatic EQP is on */ @@ -1610,15 +1225,13 @@ static ShellState shellState; ** callback limit is reached, and for each ** top-level SQL statement */ #define SHELL_PROGRESS_ONCE 0x04 /* Cancel the --limit after firing once */ +#define SHELL_PROGRESS_TMOUT 0x08 /* Stop after tmProgress seconds */ -/* Allowed values for ShellState.eEscMode. The default value should -** be 0, so to change the default, reorder the names. +/* Names of values for Mode.spec.eEsc and Mode.spec.eText */ -#define SHELL_ESC_ASCII 0 /* Substitute ^Y for X where Y=X+0x40 */ -#define SHELL_ESC_SYMBOL 1 /* Substitute U+2400 graphics */ -#define SHELL_ESC_OFF 2 /* Send characters verbatim */ - -static const char *shell_EscModeNames[] = { "ascii", "symbol", "off" }; +static const char *qrfEscNames[] = { "auto", "off", "ascii", "symbol" }; +static const char *qrfQuoteNames[] = + { "off","off","sql","hex","csv","tcl","json","relaxed"}; /* ** These are the allowed shellFlgs values @@ -1627,10 +1240,8 @@ static const char *shell_EscModeNames[] = { "ascii", "symbol", "off" }; #define SHFLG_Lookaside 0x00000002 /* Lookaside memory is used */ #define SHFLG_Backslash 0x00000004 /* The --backslash option is used */ #define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */ -#define SHFLG_Newlines 0x00000010 /* .dump --newline flag */ +#define SHFLG_NoErrLineno 0x00000010 /* Omit line numbers from error msgs */ #define SHFLG_CountChanges 0x00000020 /* .changes setting */ -#define SHFLG_Echo 0x00000040 /* .echo on/off, or --echo setting */ -#define SHFLG_HeaderSet 0x00000080 /* showHeader has been specified */ #define SHFLG_DumpDataOnly 0x00000100 /* .dump show data only */ #define SHFLG_DumpNoSys 0x00000200 /* .dump omits system tables */ #define SHFLG_TestingMode 0x00000400 /* allow unsafe testing features */ @@ -1643,54 +1254,107 @@ static const char *shell_EscModeNames[] = { "ascii", "symbol", "off" }; #define ShellClearFlag(P,X) ((P)->shellFlgs&=(~(X))) /* -** These are the allowed modes. -*/ -#define MODE_Line 0 /* One column per line. Blank line between records */ -#define MODE_Column 1 /* One record per line in neat columns */ -#define MODE_List 2 /* One record per line with a separator */ -#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ -#define MODE_Html 4 /* Generate an XHTML table */ -#define MODE_Insert 5 /* Generate SQL "insert" statements */ -#define MODE_Quote 6 /* Quote values as for SQL */ -#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */ -#define MODE_Csv 8 /* Quote strings, numbers are plain */ -#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */ -#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */ -#define MODE_Pretty 11 /* Pretty-print schemas */ -#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */ -#define MODE_Json 13 /* Output JSON */ -#define MODE_Markdown 14 /* Markdown formatting */ -#define MODE_Table 15 /* MySQL-style table formatting */ -#define MODE_Box 16 /* Unicode box-drawing characters */ -#define MODE_Count 17 /* Output only a count of the rows of output */ -#define MODE_Off 18 /* No query output shown */ -#define MODE_ScanExp 19 /* Like MODE_Explain, but for ".scanstats vm" */ -#define MODE_Www 20 /* Full web-page output */ - -static const char *modeDescr[] = { - "line", - "column", - "list", - "semi", - "html", - "insert", - "quote", - "tcl", - "csv", - "explain", - "ascii", - "prettyprint", - "eqp", - "json", - "markdown", - "table", - "box", - "count", - "off", - "scanexp", - "www", +** These are the allowed values for Mode.eMode. There is a lot of overlap +** between these values and the Mode.spec.eStyle values, but they are not +** one-to-one, and thus need to be tracked separately. +*/ +#define MODE_Ascii 0 /* Use ASCII unit and record separators (0x1F/0x1E) */ +#define MODE_Box 1 /* Unicode box-drawing characters */ +#define MODE_C 2 /* Comma-separated list of C-strings */ +#define MODE_Column 3 /* One record per line in neat columns */ +#define MODE_Count 4 /* Output only a count of the rows of output */ +#define MODE_Csv 5 /* Quote strings, numbers are plain */ +#define MODE_Html 6 /* Generate an XHTML table */ +#define MODE_Insert 7 /* Generate SQL "insert" statements */ +#define MODE_JAtom 8 /* Comma-separated list of JSON atoms */ +#define MODE_JObject 9 /* One JSON object per row */ +#define MODE_Json 10 /* Output JSON */ +#define MODE_Line 11 /* One column per line. Blank line between records */ +#define MODE_List 12 /* One record per line with a separator */ +#define MODE_Markdown 13 /* Markdown formatting */ +#define MODE_Off 14 /* No query output shown */ +#define MODE_Psql 15 /* Similar to psql */ +#define MODE_QBox 16 /* BOX with SQL-quoted content */ +#define MODE_Quote 17 /* Quote values as for SQL */ +#define MODE_Split 18 /* Split-column mode */ +#define MODE_Table 19 /* MySQL-style table formatting */ +#define MODE_Tabs 20 /* Tab-separated values */ +#define MODE_Tcl 21 /* Space-separated list of TCL strings */ +#define MODE_Www 22 /* Full web-page output */ + +#define MODE_BUILTIN 22 /* Maximum built-in mode */ +#define MODE_BATCH 50 /* Default mode for batch processing */ +#define MODE_TTY 51 /* Default mode for interactive processing */ +#define MODE_USER 75 /* First user-defined mode */ +#define MODE_N_USER 25 /* Maximum number of user-defined modes */ + +/* +** Information about built-in display modes +*/ +typedef struct ModeInfo ModeInfo; +struct ModeInfo { + char zName[9]; /* Symbolic name of the mode */ + unsigned char eCSep; /* Column separator */ + unsigned char eRSep; /* Row separator */ + unsigned char eNull; /* Null representation */ + unsigned char eText; /* Default text encoding */ + unsigned char eHdr; /* Default header encoding. */ + unsigned char eBlob; /* Default blob encoding. */ + unsigned char bHdr; /* Show headers by default. 0: n/a, 1: no 2: yes */ + unsigned char eStyle; /* Underlying QRF style */ + unsigned char eCx; /* 0: other, 1: line, 2: columnar */ + unsigned char mFlg; /* Flags. 1=border-off 2=split-column */ }; +/* String constants used by built-in modes */ +static const char *aModeStr[] = + /* 0 1 2 3 4 5 6 7 8 */ + { 0, "\n", "|", " ", ",", "\r\n", "\036", "\037", "\t", + "", "NULL", "null", "\"\"", ": ", }; + /* 9 10 11 12 13 */ + +static const ModeInfo aModeInfo[] = { +/* zName eCSep eRSep eNull eText eHdr eBlob bHdr eStyle eCx mFlg */ + { "ascii", 7, 6, 9, 1, 1, 0, 1, 12, 0, 0 }, + { "box", 0, 0, 9, 1, 1, 0, 2, 1, 2, 0 }, + { "c", 4, 1, 10, 5, 5, 4, 1, 12, 0, 0 }, + { "column", 0, 0, 9, 1, 1, 0, 2, 2, 2, 0 }, + { "count", 0, 0, 0, 0, 0, 0, 0, 3, 0, 0 }, + { "csv", 4, 5, 9, 3, 3, 0, 1, 12, 0, 0 }, + { "html", 0, 0, 9, 4, 4, 0, 2, 7, 0, 0 }, + { "insert", 0, 0, 10, 2, 2, 0, 1, 8, 0, 0 }, + { "jatom", 4, 1, 11, 6, 6, 0, 1, 12, 0, 0 }, + { "jobject", 0, 1, 11, 6, 6, 0, 0, 10, 0, 0 }, + { "json", 0, 0, 11, 6, 6, 0, 0, 9, 0, 0 }, + { "line", 13, 1, 9, 1, 1, 0, 0, 11, 1, 0 }, + { "list", 2, 1, 9, 1, 1, 0, 1, 12, 0, 0 }, + { "markdown", 0, 0, 9, 1, 1, 0, 2, 13, 2, 0 }, + { "off", 0, 0, 0, 0, 0, 0, 0, 14, 0, 0 }, + { "psql", 0, 0, 9, 1, 1, 0, 2, 19, 2, 1 }, + { "qbox", 0, 0, 10, 2, 1, 0, 2, 1, 2, 0 }, + { "quote", 4, 1, 10, 2, 2, 0, 1, 12, 0, 0 }, + { "split", 0, 0, 9, 1, 1, 0, 1, 2, 2, 2 }, + { "table", 0, 0, 9, 1, 1, 0, 2, 19, 2, 0 }, + { "tabs", 8, 1, 9, 3, 3, 0, 1, 12, 0, 0 }, + { "tcl", 3, 1, 12, 5, 5, 4, 1, 12, 0, 0 }, + { "www", 0, 0, 9, 4, 4, 0, 2, 7, 0, 0 } +}; /* | / / | / / | | \ + ** | / / | / / | | \_ 2: columnar + ** Index into aModeStr[] | / / | | 1: line + ** | / / | | 0: other + ** | / / | \ + ** text encoding |/ | show | \ + ** v-------------------' | hdrs? | The QRF style + ** 0: n/a blob | v-----' + ** 1: plain v_---------' 0: n/a + ** 2: sql 0: auto 1: no + ** 3: csv 1: as-text 2: yes + ** 4: html 2: sql + ** 5: c 3: hex + ** 6: json 4: c + ** 5: json + ** 6: size + ******************************************************************/ /* ** These are the column/row/line separators used by the various ** import/export modes. @@ -1704,111 +1368,532 @@ static const char *modeDescr[] = { #define SEP_Unit "\x1F" #define SEP_Record "\x1E" +/* +** Default values for the various QRF limits +*/ +#ifndef DFLT_CHAR_LIMIT +# define DFLT_CHAR_LIMIT 300 +#endif +#ifndef DFLT_LINE_LIMIT +# define DFLT_LINE_LIMIT 5 +#endif +#ifndef DFLT_TITLE_LIMIT +# define DFLT_TITLE_LIMIT 20 +#endif + /* ** Limit input nesting via .read or any other input redirect. ** It's not too expensive, so a generous allowance can be made. */ #define MAX_INPUT_NESTING 25 +/************************* BEGIN PERFORMANCE TIMER *****************************/ +#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) +#include +#include +/* VxWorks does not support getrusage() as far as we can determine */ +#if defined(_WRS_KERNEL) || defined(__RTP__) +struct rusage { + struct timeval ru_utime; /* user CPU time used */ + struct timeval ru_stime; /* system CPU time used */ +}; +#define getrusage(A,B) memset(B,0,sizeof(*B)) +#endif + +/* Saved resource information for the beginning of an operation */ +static struct rusage sBegin; /* CPU time at start */ +static sqlite3_int64 iBegin; /* Wall-clock time at start */ + /* -** A callback for the sqlite3_log() interface. +** Begin timing an operation */ -static void shellLog(void *pArg, int iErrCode, const char *zMsg){ - ShellState *p = (ShellState*)pArg; - if( p->pLog==0 ) return; - sqlite3_fprintf(p->pLog, "(%d) %s\n", iErrCode, zMsg); - fflush(p->pLog); +static void beginTimer(ShellState *p){ + if( p->enableTimer || (p->flgProgress & SHELL_PROGRESS_TMOUT)!=0 ){ + getrusage(RUSAGE_SELF, &sBegin); + iBegin = timeOfDay(); + } +} + +/* Return the difference of two time_structs in seconds */ +static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ + return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + + (double)(pEnd->tv_sec - pStart->tv_sec); } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK +/* Return the time since the start of the timer in +** seconds. */ +static double elapseTime(ShellState *NotUsed){ + (void)NotUsed; + if( iBegin==0 ) return 0.0; + return (timeOfDay() - iBegin)*0.000001; +} +#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ + /* -** SQL function: shell_putsnl(X) -** -** Write the text X to the screen (or whatever output is being directed) -** adding a newline at the end, and then return X. +** Print the timing results. */ -static void shellPutsFunc( - sqlite3_context *pCtx, - int nVal, - sqlite3_value **apVal -){ - ShellState *p = (ShellState*)sqlite3_user_data(pCtx); - (void)nVal; - sqlite3_fprintf(p->out, "%s\n", sqlite3_value_text(apVal[0])); - sqlite3_result_value(pCtx, apVal[0]); +static void endTimer(ShellState *p){ + if( p->enableTimer ){ + sqlite3_int64 iEnd = timeOfDay(); + struct rusage sEnd; + getrusage(RUSAGE_SELF, &sEnd); + p->prevTimer = (iEnd - iBegin)*0.000001; + cli_printf(p->out, "Run Time: real %.6f user %.6f sys %.6f\n", + p->prevTimer, + timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); + if( p->enableTimer==1 ) p->enableTimer = 0; + iBegin = 0; + } } +#define BEGIN_TIMER(X) beginTimer(X) +#define END_TIMER(X) endTimer(X) +#define ELAPSE_TIME(X) elapseTime(X) +#define HAS_TIMER 1 + +#elif (defined(_WIN32) || defined(WIN32)) + +/* Saved resource information for the beginning of an operation */ +static HANDLE hProcess; +static FILETIME ftKernelBegin; +static FILETIME ftUserBegin; +static sqlite3_int64 ftWallBegin; +typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, + LPFILETIME, LPFILETIME); +static GETPROCTIMES getProcessTimesAddr = NULL; + /* -** If in safe mode, print an error message described by the arguments -** and exit immediately. +** Check to see if we have timer support. Return 1 if necessary +** support found (or found previously). */ -static void failIfSafeMode( - ShellState *p, - const char *zErrMsg, - ... -){ - if( p->bSafeMode ){ - va_list ap; - char *zMsg; - va_start(ap, zErrMsg); - zMsg = sqlite3_vmprintf(zErrMsg, ap); - va_end(ap); - sqlite3_fprintf(stderr, "line %lld: %s\n", p->lineno, zMsg); - exit(1); +static int hasTimer(void){ + if( getProcessTimesAddr ){ + return 1; + } else { + /* GetProcessTimes() isn't supported in WIN95 and some other Windows + ** versions. See if the version we are running on has it, and if it + ** does, save off a pointer to it and the current process handle. + */ + hProcess = GetCurrentProcess(); + if( hProcess ){ + HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); + if( NULL != hinstLib ){ + getProcessTimesAddr = + (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); + if( NULL != getProcessTimesAddr ){ + return 1; + } + FreeLibrary(hinstLib); + } + } } + return 0; } /* -** SQL function: edit(VALUE) -** edit(VALUE,EDITOR) -** -** These steps: -** -** (1) Write VALUE into a temporary file. -** (2) Run program EDITOR on that temporary file. -** (3) Read the temporary file back and return its content as the result. -** (4) Delete the temporary file -** -** If the EDITOR argument is omitted, use the value in the VISUAL -** environment variable. If still there is no EDITOR, through an error. -** -** Also throw an error if the EDITOR program returns a non-zero exit code. +** Begin timing an operation */ -#ifndef SQLITE_NOHAVE_SYSTEM -static void editFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - const char *zEditor; - char *zTempFile = 0; - sqlite3 *db; - char *zCmd = 0; - int bBin; - int rc; - int hasCRLF = 0; - FILE *f = 0; - sqlite3_int64 sz; - sqlite3_int64 x; - unsigned char *p = 0; - - if( argc==2 ){ - zEditor = (const char*)sqlite3_value_text(argv[1]); - }else{ - zEditor = getenv("VISUAL"); - } - if( zEditor==0 ){ - sqlite3_result_error(context, "no editor for edit()", -1); - return; - } - if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ - sqlite3_result_error(context, "NULL input to edit()", -1); - return; +static void beginTimer(ShellState *p){ + if( (p->enableTimer || (p->flgProgress & SHELL_PROGRESS_TMOUT)!=0) + && getProcessTimesAddr + ){ + FILETIME ftCreation, ftExit; + getProcessTimesAddr(hProcess,&ftCreation,&ftExit, + &ftKernelBegin,&ftUserBegin); + ftWallBegin = timeOfDay(); } - db = sqlite3_context_db_handle(context); - zTempFile = 0; - sqlite3_file_control(db, 0, SQLITE_FCNTL_TEMPFILENAME, &zTempFile); - if( zTempFile==0 ){ - sqlite3_uint64 r = 0; +} + +/* Return the difference of two FILETIME structs in seconds */ +static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ + sqlite_int64 i64Start = *((sqlite_int64 *) pStart); + sqlite_int64 i64End = *((sqlite_int64 *) pEnd); + return (double) ((i64End - i64Start) / 10000000.0); +} + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK +/* Return the time since the start of the timer in +** seconds. */ +static double elapseTime(ShellState *NotUsed){ + (void)NotUsed; + if( ftWallBegin==0 ) return 0.0; + return (timeOfDay() - ftWallBegin)*0.000001; +} +#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ + +/* +** Print the timing results. +*/ +static void endTimer(ShellState *p){ + if( p->enableTimer && getProcessTimesAddr){ + FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; + sqlite3_int64 ftWallEnd = timeOfDay(); + getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); + p->prevTimer = (ftWallEnd - ftWallBegin)*0.000001; +#ifdef _WIN64 + /* microsecond precision on 64-bit windows */ + cli_printf(p->out, "Run Time: real %.6f user %f sys %f\n", + p->prevTimer, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); +#else + /* millisecond precisino on 32-bit windows */ + cli_printf(p->out, "Run Time: real %.3f user %.3f sys %.3f\n", + p->prevTimer, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); +#endif + if( p->enableTimer==1 ) p->enableTimer = 0; + ftWallBegin = 0; + } +} + +#define BEGIN_TIMER(X) beginTimer(X) +#define ELAPSE_TIME(X) elapseTime(X) +#define END_TIMER(X) endTimer(X) +#define HAS_TIMER hasTimer() + +#else +#define BEGIN_TIMER(X) /* no-op */ +#define ELAPSE_TIME(X) 0.0 +#define END_TIMER(X) /*no-op*/ +#define HAS_TIMER 0 +#endif +/************************* END PERFORMANCE TIMER ******************************/ + +/* +** Clear a display mode, freeing any allocated memory that it +** contains. +*/ +static void modeFree(Mode *p){ + u8 autoExplain = p->autoExplain; + free(p->spec.aWidth); + free(p->spec.aAlign); + free(p->spec.zColumnSep); + free(p->spec.zRowSep); + free(p->spec.zTableName); + free(p->spec.zNull); + memset(p, 0, sizeof(*p)); + p->spec.iVersion = 1; + p->autoExplain = autoExplain; +} + +/* +** Duplicate Mode pSrc into pDest. pDest is assumed to be +** uninitialized prior to invoking this routine. +*/ +static void modeDup(Mode *pDest, Mode *pSrc){ + memcpy(pDest, pSrc, sizeof(*pDest)); + if( pDest->spec.aWidth ){ + size_t sz = sizeof(pSrc->spec.aWidth[0]) * pSrc->spec.nWidth; + pDest->spec.aWidth = malloc( sz ); + if( pDest->spec.aWidth ){ + memcpy(pDest->spec.aWidth, pSrc->spec.aWidth, sz); + }else{ + pDest->spec.nWidth = 0; + } + } + if( pDest->spec.aAlign ){ + size_t sz = sizeof(pSrc->spec.aAlign[0]) * pSrc->spec.nAlign; + pDest->spec.aAlign = malloc( sz ); + if( pDest->spec.aAlign ){ + memcpy(pDest->spec.aAlign, pSrc->spec.aAlign, sz); + }else{ + pDest->spec.nAlign = 0; + } + } + if( pDest->spec.zColumnSep ){ + pDest->spec.zColumnSep = strdup(pSrc->spec.zColumnSep); + } + if( pDest->spec.zRowSep ){ + pDest->spec.zRowSep = strdup(pSrc->spec.zRowSep); + } + if( pDest->spec.zTableName ){ + pDest->spec.zTableName = strdup(pSrc->spec.zTableName); + } + if( pDest->spec.zNull ){ + pDest->spec.zNull = strdup(pSrc->spec.zNull); + } +} + +/* +** Set a string value to a copy of the zNew string in memory +** obtained from system malloc(). +*/ +static void modeSetStr(char **az, const char *zNew){ + free(*az); + if( zNew==0 ){ + *az = 0; + }else{ + size_t n = strlen(zNew); + *az = malloc( n+1 ); + if( *az ){ + memcpy(*az, zNew, n+1 ); + } + } +} + +/* +** Change the mode to eMode +*/ +static void modeChange(ShellState *p, unsigned char eMode){ + const ModeInfo *pI; + if( eModemode; + pI = &aModeInfo[eMode]; + pM->eMode = eMode; + if( pI->eCSep ) modeSetStr(&pM->spec.zColumnSep, aModeStr[pI->eCSep]); + if( pI->eRSep ) modeSetStr(&pM->spec.zRowSep, aModeStr[pI->eRSep]); + if( pI->eNull ) modeSetStr(&pM->spec.zNull, aModeStr[pI->eNull]); + pM->spec.eText = pI->eText; + pM->spec.eBlob = pI->eBlob; + if( (pM->mFlags & MFLG_HDR)==0 ){ + pM->spec.bTitles = pI->bHdr; + } + pM->spec.eTitle = pI->eHdr; + if( pI->mFlg & 0x01 ){ + pM->spec.bBorder = QRF_No; + }else{ + pM->spec.bBorder = QRF_Auto; + } + if( pI->mFlg & 0x02 ){ + pM->spec.bSplitColumn = QRF_Yes; + pM->bAutoScreenWidth = 1; + }else{ + pM->spec.bSplitColumn = QRF_No; + } + }else if( eMode>=MODE_USER && eMode-MODE_USERnSavedModes ){ + modeFree(&p->mode); + modeDup(&p->mode, &p->aSavedModes[eMode-MODE_USER].mode); + }else if( eMode==MODE_BATCH ){ + u8 mFlags = p->mode.mFlags; + modeFree(&p->mode); + modeChange(p, MODE_List); + p->mode.mFlags = mFlags; + }else if( eMode==MODE_TTY ){ + u8 mFlags = p->mode.mFlags; + modeFree(&p->mode); + modeChange(p, MODE_QBox); + p->mode.bAutoScreenWidth = 1; + p->mode.spec.eText = QRF_TEXT_Relaxed; + p->mode.spec.nCharLimit = DFLT_CHAR_LIMIT; + p->mode.spec.nLineLimit = DFLT_LINE_LIMIT; + p->mode.spec.bTextJsonb = QRF_Yes; + p->mode.spec.nTitleLimit = DFLT_TITLE_LIMIT; + p->mode.mFlags = mFlags; + } +} + +/* +** Set the mode to the default. It assumed that the mode has +** already been freed and zeroed prior to calling this routine. +*/ +static void modeDefault(ShellState *p){ + p->mode.spec.iVersion = 1; + p->mode.autoExplain = 1; + if( stdin_is_interactive || stdout_is_console ){ + modeChange(p, MODE_TTY); + }else{ + modeChange(p, MODE_BATCH); + } +} + +/* +** Find the number of a display mode given its name. Return -1 if +** the name does not match any mode. +** +** Saved modes are also searched if p!=NULL. The number returned +** for a saved mode is the index into the p->aSavedModes[] array +** plus MODE_USER. +** +** Two special mode names are also available: "batch" and "tty". +** evaluate to the default mode for batch operation and interactive +** operation on a TTY, respectively. +*/ +static int modeFind(ShellState *p, const char *zName){ + int i; + for(i=0; inSavedModes; i++){ + if( cli_strcmp(p->aSavedModes[i].zTag,zName)==0 ) return i+MODE_USER; + } + if( strcmp(zName,"batch")==0 ) return MODE_BATCH; + if( strcmp(zName,"tty")==0 ) return MODE_TTY; + return -1; +} + +/* +** Save or restore the current output mode +*/ +static void modePush(ShellState *p){ + if( p->nPopMode==0 ){ + modeFree(&p->modePrior); + modeDup(&p->modePrior,&p->mode); + } +} +static void modePop(ShellState *p){ + if( p->modePrior.spec.iVersion>0 ){ + modeFree(&p->mode); + p->mode = p->modePrior; + memset(&p->modePrior, 0, sizeof(p->modePrior)); + } +} + + +/* +** A callback for the sqlite3_log() interface. +*/ +static void shellLog(void *pArg, int iErrCode, const char *zMsg){ + ShellState *p = (ShellState*)pArg; + if( p->pLog==0 ) return; + cli_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg); + fflush(p->pLog); +} + +/* +** SQL function: shell_putsnl(X) +** +** Write the text X to the screen (or whatever output is being directed) +** adding a newline at the end, and then return X. +*/ +static void shellPutsFunc( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + ShellState *p = (ShellState*)sqlite3_user_data(pCtx); + (void)nVal; + cli_printf(p->out, "%s\n", sqlite3_value_text(apVal[0])); + sqlite3_result_value(pCtx, apVal[0]); +} + +/* +** Compute the name of the location of an input error in memory +** obtained from sqlite3_malloc(). +*/ +static char *shellErrorLocation(ShellState *p){ + char *zLoc; + if( p->zErrPrefix ){ + zLoc = sqlite3_mprintf("%s", p->zErrPrefix); + }else if( p->zInFile==0 || strcmp(p->zInFile,"")==0){ + zLoc = sqlite3_mprintf("line %lld:", p->lineno); + }else{ + zLoc = sqlite3_mprintf("%s:%lld:", p->zInFile, p->lineno); + } + return zLoc; +} + +/* +** If in safe mode, print an error message described by the arguments +** and exit immediately. +*/ +static void failIfSafeMode( + ShellState *p, + const char *zErrMsg, + ... +){ + if( p->bSafeMode ){ + va_list ap; + char *zMsg; + char *zLoc = shellErrorLocation(p); + va_start(ap, zErrMsg); + zMsg = sqlite3_vmprintf(zErrMsg, ap); + va_end(ap); + cli_printf(stderr, "%s %s\n", zLoc, zMsg); + cli_exit(1); + } +} + +/* +** Issue an error message from a dot-command. +*/ +static void dotCmdError( + ShellState *p, /* Shell state */ + int iArg, /* Index of argument on which error occurred */ + const char *zBrief, /* Brief (<20 character) error description */ + const char *zDetail, /* Error details */ + ... +){ + FILE *out = stderr; + char *zLoc = shellErrorLocation(p); + if( zBrief!=0 && iArg>=0 && iArgdot.nArg ){ + int i = p->dot.aiOfst[iArg]; + int nPrompt = strlen30(zBrief) + 5; + cli_printf(out, "%s %s\n", zLoc, p->dot.zOrig); + if( i > nPrompt ){ + cli_printf(out, "%s %*s%s ---^\n", zLoc, 1+i-nPrompt, "", zBrief); + }else{ + cli_printf(out, "%s %*s^--- %s\n", zLoc, i, "", zBrief); + } + } + if( zDetail ){ + char *zMsg; + va_list ap; + va_start(ap, zDetail); + zMsg = sqlite3_vmprintf(zDetail,ap); + va_end(ap); + cli_printf(out,"%s %s\n", zLoc, zMsg); + sqlite3_free(zMsg); + } + sqlite3_free(zLoc); +} + + +/* +** SQL function: edit(VALUE) +** edit(VALUE,EDITOR) +** +** These steps: +** +** (1) Write VALUE into a temporary file. +** (2) Run program EDITOR on that temporary file. +** (3) Read the temporary file back and return its content as the result. +** (4) Delete the temporary file +** +** If the EDITOR argument is omitted, use the value in the VISUAL +** environment variable. If still there is no EDITOR, through an error. +** +** Also throw an error if the EDITOR program returns a non-zero exit code. +*/ +#ifndef SQLITE_NOHAVE_SYSTEM +static void editFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zEditor; + char *zTempFile = 0; + sqlite3 *db; + char *zCmd = 0; + int bBin; + int rc; + int hasCRLF = 0; + FILE *f = 0; + sqlite3_int64 sz; + sqlite3_int64 x; + unsigned char *p = 0; + + if( argc==2 ){ + zEditor = (const char*)sqlite3_value_text(argv[1]); + }else{ + zEditor = getenv("VISUAL"); + } + if( zEditor==0 ){ + sqlite3_result_error(context, "no editor for edit()", -1); + return; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ + sqlite3_result_error(context, "NULL input to edit()", -1); + return; + } + db = sqlite3_context_db_handle(context); + zTempFile = 0; + sqlite3_file_control(db, 0, SQLITE_FCNTL_TEMPFILENAME, &zTempFile); + if( zTempFile==0 ){ + sqlite3_uint64 r = 0; sqlite3_randomness(sizeof(r), &r); zTempFile = sqlite3_mprintf("temp%llx", r); if( zTempFile==0 ){ @@ -1901,28 +1986,12 @@ edit_func_end: } #endif /* SQLITE_NOHAVE_SYSTEM */ -/* -** Save or restore the current output mode -*/ -static void outputModePush(ShellState *p){ - p->modePrior = p->mode; - p->priorShFlgs = p->shellFlgs; - memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator)); - memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator)); -} -static void outputModePop(ShellState *p){ - p->mode = p->modePrior; - p->shellFlgs = p->priorShFlgs; - memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator)); - memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator)); -} - /* ** Set output mode to text or binary for Windows. */ static void setCrlfMode(ShellState *p){ #ifdef _WIN32 - if( p->crlfMode ){ + if( p->mode.mFlags & MFLG_CRLF ){ sqlite3_fsetmode(p->out, _O_TEXT); }else{ sqlite3_fsetmode(p->out, _O_BINARY); @@ -1932,126 +2001,6 @@ static void setCrlfMode(ShellState *p){ #endif } -/* -** Output the given string as a hex-encoded blob (eg. X'1234' ) -*/ -static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ - int i; - unsigned char *aBlob = (unsigned char*)pBlob; - - char *zStr = sqlite3_malloc64((i64)nBlob*2 + 1); - shell_check_oom(zStr); - - for(i=0; i> 4) ]; - zStr[i*2+1] = aHex[ (aBlob[i] & 0x0F) ]; - } - zStr[i*2] = '\0'; - - sqlite3_fprintf(out, "X'%s'", zStr); - sqlite3_free(zStr); -} - -/* -** Output the given string as a quoted string using SQL quoting conventions: -** -** (1) Single quotes (') within the string are doubled -** (2) The while string is enclosed in '...' -** (3) Control characters other than \n, \t, and \r\n are escaped -** using \u00XX notation and if such substitutions occur, -** the whole string is enclosed in unistr('...') instead of '...'. -** -** Step (3) is omitted if the control-character escape mode is OFF. -** -** See also: output_quoted_escaped_string() which does the same except -** that it does not make exceptions for \n, \t, and \r\n in step (3). -*/ -static void output_quoted_string(ShellState *p, const char *zInX){ - int i; - int needUnistr = 0; - int needDblQuote = 0; - const unsigned char *z = (const unsigned char*)zInX; - unsigned char c; - FILE *out = p->out; - sqlite3_fsetmode(out, _O_BINARY); - if( z==0 ) return; - for(i=0; (c = z[i])!=0; i++){ - if( c=='\'' ){ needDblQuote = 1; } - if( c>0x1f ) continue; - if( c=='\t' || c=='\n' ) continue; - if( c=='\r' && z[i+1]=='\n' ) continue; - needUnistr = 1; - break; - } - if( (needDblQuote==0 && needUnistr==0) - || (needDblQuote==0 && p->eEscMode==SHELL_ESC_OFF) - ){ - sqlite3_fprintf(out, "'%s'",z); - }else if( p->eEscMode==SHELL_ESC_OFF ){ - char *zEncoded = sqlite3_mprintf("%Q", z); - sqlite3_fputs(zEncoded, out); - sqlite3_free(zEncoded); - }else{ - if( needUnistr ){ - sqlite3_fputs("unistr('", out); - }else{ - sqlite3_fputs("'", out); - } - while( *z ){ - for(i=0; (c = z[i])!=0; i++){ - if( c=='\'' ) break; - if( c>0x1f ) continue; - if( c=='\t' || c=='\n' ) continue; - if( c=='\r' && z[i+1]=='\n' ) continue; - break; - } - if( i ){ - sqlite3_fprintf(out, "%.*s", i, z); - z += i; - } - if( c==0 ) break; - if( c=='\'' ){ - sqlite3_fputs("''", out); - }else{ - sqlite3_fprintf(out, "\\u%04x", c); - } - z++; - } - if( needUnistr ){ - sqlite3_fputs("')", out); - }else{ - sqlite3_fputs("'", out); - } - } - setCrlfMode(p); -} - -/* -** Output the given string as a quoted string using SQL quoting conventions. -** Additionallly , escape the "\n" and "\r" characters so that they do not -** get corrupted by end-of-line translation facilities in some operating -** systems. -** -** This is like output_quoted_string() but with the addition of the \r\n -** escape mechanism. -*/ -static void output_quoted_escaped_string(ShellState *p, const char *z){ - char *zEscaped; - sqlite3_fsetmode(p->out, _O_BINARY); - if( p->eEscMode==SHELL_ESC_OFF ){ - zEscaped = sqlite3_mprintf("%Q", z); - }else{ - zEscaped = sqlite3_mprintf("%#Q", z); - } - sqlite3_fputs(zEscaped, p->out); - sqlite3_free(zEscaped); - setCrlfMode(p); -} - /* ** Find earliest of chars within s specified in zAny. ** With ns == ~0, is like strpbrk(s,zAny) and s must be 0-terminated. @@ -2115,13 +2064,14 @@ static void output_c_string(FILE *out, const char *z){ static const char *zDQBSRO = "\"\\\x7f"; /* double-quote, backslash, rubout */ char ace[3] = "\\?"; char cbsSay; - sqlite3_fputs(zq, out); + cli_puts(zq, out); + if( z==0 ) z = ""; while( *z!=0 ){ const char *pcDQBSRO = anyOfInStr(z, zDQBSRO, ~(size_t)0); const char *pcPast = zSkipValidUtf8(z, INT_MAX, ctrlMask); const char *pcEnd = (pcDQBSRO && pcDQBSRO < pcPast)? pcDQBSRO : pcPast; if( pcEnd > z ){ - sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z); + cli_printf(out, "%.*s", (int)(pcEnd-z), z); } if( (c = *pcEnd)==0 ) break; ++pcEnd; @@ -2137,250 +2087,106 @@ static void output_c_string(FILE *out, const char *z){ } if( cbsSay ){ ace[1] = cbsSay; - sqlite3_fputs(ace, out); + cli_puts(ace, out); }else if( !isprint(c&0xff) ){ - sqlite3_fprintf(out, "\\%03o", c&0xff); + cli_printf(out, "\\%03o", c&0xff); }else{ ace[1] = (char)c; - sqlite3_fputs(ace+1, out); + cli_puts(ace+1, out); } z = pcEnd; } - sqlite3_fputs(zq, out); + cli_puts(zq, out); } -/* -** Output the given string as quoted according to JSON quoting rules. +/* Encode input string z[] as a C-language string literal and +** append it to the sqlite3_str. If z is NULL render and empty string. */ -static void output_json_string(FILE *out, const char *z, i64 n){ - unsigned char c; +static void append_c_string(sqlite3_str *out, const char *z){ + char c; static const char *zq = "\""; static long ctrlMask = ~0L; - static const char *zDQBS = "\"\\"; - const char *pcLimit; + static const char *zDQBSRO = "\"\\\x7f"; /* double-quote, backslash, rubout */ char ace[3] = "\\?"; char cbsSay; - if( z==0 ) z = ""; - pcLimit = z + ((n<0)? strlen(z) : (size_t)n); - sqlite3_fputs(zq, out); - while( z < pcLimit ){ - const char *pcDQBS = anyOfInStr(z, zDQBS, pcLimit-z); - const char *pcPast = zSkipValidUtf8(z, (int)(pcLimit-z), ctrlMask); - const char *pcEnd = (pcDQBS && pcDQBS < pcPast)? pcDQBS : pcPast; + sqlite3_str_appendall(out,zq); + while( *z!=0 ){ + const char *pcDQBSRO = anyOfInStr(z, zDQBSRO, ~(size_t)0); + const char *pcPast = zSkipValidUtf8(z, INT_MAX, ctrlMask); + const char *pcEnd = (pcDQBSRO && pcDQBSRO < pcPast)? pcDQBSRO : pcPast; if( pcEnd > z ){ - sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z); - z = pcEnd; + sqlite3_str_appendf(out, "%.*s", (int)(pcEnd-z), z); } - if( z >= pcLimit ) break; - c = (unsigned char)*(z++); + if( (c = *pcEnd)==0 ) break; + ++pcEnd; switch( c ){ - case '"': case '\\': + case '\\': case '"': cbsSay = (char)c; break; - case '\b': cbsSay = 'b'; break; - case '\f': cbsSay = 'f'; break; + case '\t': cbsSay = 't'; break; case '\n': cbsSay = 'n'; break; case '\r': cbsSay = 'r'; break; - case '\t': cbsSay = 't'; break; + case '\f': cbsSay = 'f'; break; default: cbsSay = 0; break; } if( cbsSay ){ ace[1] = cbsSay; - sqlite3_fputs(ace, out); - }else if( c<=0x1f || c>=0x7f ){ - sqlite3_fprintf(out, "\\u%04x", c); + sqlite3_str_appendall(out,ace); + }else if( !isprint(c&0xff) ){ + sqlite3_str_appendf(out, "\\%03o", c&0xff); }else{ ace[1] = (char)c; - sqlite3_fputs(ace+1, out); - } - } - sqlite3_fputs(zq, out); -} - -/* -** Escape the input string if it is needed and in accordance with -** eEscMode. -** -** 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 *ppFree to NULL -** and return the original string. If escaping is needed, write the -** escaped string into memory obtained from sqlite3_malloc64() or the -** equivalent, and return the new string and set *ppFree to the new string -** as well. -** -** The caller is responsible for freeing *ppFree if it is non-NULL in order -** to reclaim memory. -*/ -static const char *escapeOutput( - ShellState *p, - const char *zInX, - char **ppFree -){ - i64 i, j; - i64 nCtrl = 0; - unsigned char *zIn; - unsigned char c; - unsigned char *zOut; - - - /* No escaping if disabled */ - if( p->eEscMode==SHELL_ESC_OFF ){ - *ppFree = 0; - return zInX; - } - - /* Count the number of control characters in the string. */ - zIn = (unsigned char*)zInX; - 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 ){ - *ppFree = 0; - return zInX; - } - if( p->eEscMode==SHELL_ESC_SYMBOL ) nCtrl *= 2; - zOut = sqlite3_malloc64( i + nCtrl + 1 ); - shell_check_oom(zOut); - 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 ){ - memcpy(&zOut[j], zIn, i); - j += i; - } - zIn += i+1; - i = -1; - switch( p->eEscMode ){ - case SHELL_ESC_SYMBOL: - zOut[j++] = 0xe2; - zOut[j++] = 0x90; - zOut[j++] = 0x80+c; - break; - case SHELL_ESC_ASCII: - zOut[j++] = '^'; - zOut[j++] = 0x40+c; - break; + sqlite3_str_appendall(out, ace+1); } + z = pcEnd; } - if( i>0 ){ - memcpy(&zOut[j], zIn, i); - j += i; - } - zOut[j] = 0; - *ppFree = (char*)zOut; - return (char*)zOut; + sqlite3_str_appendall(out, zq); } /* -** Output the given string with characters that are special to -** HTML escaped. +** This routine runs when the user presses Ctrl-C */ -static void output_html_string(FILE *out, const char *z){ - int i; - if( z==0 ) z = ""; - while( *z ){ - for(i=0; z[i] - && z[i]!='<' - && z[i]!='&' - && z[i]!='>' - && z[i]!='\"' - && z[i]!='\''; - i++){} - if( i>0 ){ - sqlite3_fprintf(out, "%.*s",i,z); - } - if( z[i]=='<' ){ - sqlite3_fputs("<", out); - }else if( z[i]=='&' ){ - sqlite3_fputs("&", out); - }else if( z[i]=='>' ){ - sqlite3_fputs(">", out); - }else if( z[i]=='\"' ){ - sqlite3_fputs(""", out); - }else if( z[i]=='\'' ){ - sqlite3_fputs("'", out); - }else{ - break; - } - z += i + 1; - } +static void interrupt_handler(int NotUsed){ + UNUSED_PARAMETER(NotUsed); + if( ++seenInterrupt>1 ) cli_exit(1); + if( globalDb ) sqlite3_interrupt(globalDb); } -/* -** 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 needCsvQuote[] = { - 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, -}; - -/* -** Output a single term of CSV. Actually, p->colSeparator is used for -** the separator, which may or may not be a comma. p->nullValue is -** the null value. Strings are quoted if necessary. The separator -** is only issued if bSep is true. +/* Try to determine the screen width. Use the default if unable. */ -static void output_csv(ShellState *p, const char *z, int bSep){ - if( z==0 ){ - sqlite3_fprintf(p->out, "%s",p->nullValue); +int shellScreenWidth(void){ + if( stdout_tty_width>0 ){ + return stdout_tty_width; }else{ - unsigned i; - for(i=0; z[i]; i++){ - if( needCsvQuote[((unsigned char*)z)[i]] ){ - i = 0; - break; - } +#if defined(TIOCGSIZE) + struct ttysize ts; + if( ioctl(STDIN_FILENO, TIOCGSIZE, &ts)>=0 + || ioctl(STDOUT_FILENO, TIOCGSIZE, &ts)>=0 + || ioctl(STDERR_FILENO, TIOCGSIZE, &ts)>=0 + ){ + return ts.ts_cols; } - if( i==0 || strstr(z, p->colSeparator)!=0 ){ - char *zQuoted = sqlite3_mprintf("\"%w\"", z); - shell_check_oom(zQuoted); - sqlite3_fputs(zQuoted, p->out); - sqlite3_free(zQuoted); - }else{ - sqlite3_fputs(z, p->out); +#elif defined(TIOCGWINSZ) + struct winsize ws; + if( ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)>=0 + || ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)>=0 + || ioctl(STDERR_FILENO, TIOCGWINSZ, &ws)>=0 + ){ + return ws.ws_col; } +#elif defined(_WIN32) + CONSOLE_SCREEN_BUFFER_INFO csbi; + if( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) + || GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), &csbi) + || GetConsoleScreenBufferInfo(GetStdHandle(STD_INPUT_HANDLE), &csbi) + ){ + return csbi.srWindow.Right - csbi.srWindow.Left + 1; + } +#endif +#define DEFAULT_SCREEN_WIDTH 80 + return DEFAULT_SCREEN_WIDTH; } - if( bSep ){ - sqlite3_fputs(p->colSeparator, p->out); - } -} - -/* -** This routine runs when the user presses Ctrl-C -*/ -static void interrupt_handler(int NotUsed){ - UNUSED_PARAMETER(NotUsed); - if( ++seenInterrupt>1 ) exit(1); - if( globalDb ) sqlite3_interrupt(globalDb); } #if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) @@ -2416,6 +2222,7 @@ static int safeModeAuth( "fts3_tokenizer", "load_extension", "readfile", + "realpath", "writefile", "zipfile", "zipfile_cds", @@ -2478,23 +2285,23 @@ static int shellAuth( az[1] = zA2; az[2] = zA3; az[3] = zA4; - sqlite3_fprintf(p->out, "authorizer: %s", azAction[op]); + cli_printf(p->out, "authorizer: %s", azAction[op]); for(i=0; i<4; i++){ - sqlite3_fputs(" ", p->out); + cli_puts(" ", p->out); if( az[i] ){ output_c_string(p->out, az[i]); }else{ - sqlite3_fputs("NULL", p->out); + cli_puts("NULL", p->out); } } - sqlite3_fputs("\n", p->out); + cli_puts("\n", p->out); if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4); return SQLITE_OK; } #endif /* -** Print a schema statement. Part of MODE_Semi and MODE_Pretty output. +** Print a schema statement. This is helper routine to dump_callbac(). ** ** This routine converts some CREATE TABLE statements for shadow tables ** in FTS3/4/5 into CREATE TABLE IF NOT EXISTS statements. @@ -2525,18 +2332,12 @@ static void printSchemaLine(FILE *out, const char *z, const char *zTail){ } } if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){ - sqlite3_fprintf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); + cli_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); }else{ - sqlite3_fprintf(out, "%s%s", z, zTail); + cli_printf(out, "%s%s", z, zTail); } sqlite3_free(zToFree); } -static void printSchemaLineN(FILE *out, char *z, int n, const char *zTail){ - char c = z[n]; - z[n] = 0; - printSchemaLine(out, z, zTail); - z[n] = c; -} /* ** Return true if string z[] has nothing but whitespace and comments to the @@ -2554,95 +2355,130 @@ static int wsToEol(const char *z){ } /* -** Add a new entry to the EXPLAIN QUERY PLAN data -*/ -static void eqp_append(ShellState *p, int iEqpId, int p2, const char *zText){ - EQPGraphRow *pNew; - i64 nText; - if( zText==0 ) return; - nText = strlen(zText); - if( p->autoEQPtest ){ - sqlite3_fprintf(p->out, "%d,%d,%s\n", iEqpId, p2, zText); - } - pNew = sqlite3_malloc64( sizeof(*pNew) + nText ); - shell_check_oom(pNew); - pNew->iEqpId = iEqpId; - pNew->iParentId = p2; - memcpy(pNew->zText, zText, nText+1); - pNew->pNext = 0; - if( p->sGraph.pLast ){ - p->sGraph.pLast->pNext = pNew; - }else{ - p->sGraph.pRow = pNew; - } - p->sGraph.pLast = pNew; -} - -/* -** Free and reset the EXPLAIN QUERY PLAN data that has been collected -** in p->sGraph. +** SQL Function: shell_format_schema(SQL,FLAGS) +** +** This function is internally by the CLI to assist with the +** ".schema", ".fullschema", and ".dump" commands. The first +** argument is the value from sqlite_schema.sql. The value returned +** is a modification of the input that can actually be run as SQL +** to recreate the schema object. +** +** When FLAGS is zero, the only changes is to append ";". If the +** 0x01 bit of FLAGS is set, then transformations are made to implement +** ".schema --indent". */ -static void eqp_reset(ShellState *p){ - EQPGraphRow *pRow, *pNext; - for(pRow = p->sGraph.pRow; pRow; pRow = pNext){ - pNext = pRow->pNext; - sqlite3_free(pRow); +static void shellFormatSchema( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + int flags; /* Value of 2nd parameter */ + const char *zSql; /* Value of 1st parameter */ + int nSql; /* Bytes of text in zSql[] */ + sqlite3_str *pOut; /* Output buffer */ + char *z; /* Writable copy of zSql */ + int i, j; /* Loop counters */ + int nParen = 0; + char cEnd = 0; + char c; + int nLine = 0; + int isIndex; + int isWhere = 0; + + assert( nVal==2 ); + pOut = sqlite3_str_new(sqlite3_context_db_handle(pCtx)); + nSql = sqlite3_value_bytes(apVal[0]); + zSql = (const char*)sqlite3_value_text(apVal[0]); + if( zSql==0 || zSql[0]==0 ) goto shellFormatSchema_finish; + flags = sqlite3_value_int(apVal[1]); + if( (flags & 0x01)==0 ){ + sqlite3_str_append(pOut, zSql, nSql); + sqlite3_str_append(pOut, ";", 1); + goto shellFormatSchema_finish; + } + if( sqlite3_strlike("CREATE VIEW%", zSql, 0)==0 + || sqlite3_strlike("CREATE TRIG%", zSql, 0)==0 + ){ + sqlite3_str_append(pOut, zSql, nSql); + sqlite3_str_append(pOut, ";", 1); + goto shellFormatSchema_finish; } - memset(&p->sGraph, 0, sizeof(p->sGraph)); -} - -/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after -** pOld, or return the first such line if pOld is NULL -*/ -static EQPGraphRow *eqp_next_row(ShellState *p, int iEqpId, EQPGraphRow *pOld){ - EQPGraphRow *pRow = pOld ? pOld->pNext : p->sGraph.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 eqp_render_level(ShellState *p, int iEqpId){ - EQPGraphRow *pRow, *pNext; - i64 n = strlen(p->sGraph.zPrefix); - char *z; - for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){ - pNext = eqp_next_row(p, iEqpId, pRow); - z = pRow->zText; - sqlite3_fprintf(p->out, "%s%s%s\n", p->sGraph.zPrefix, - pNext ? "|--" : "`--", z); - if( n<(i64)sizeof(p->sGraph.zPrefix)-7 ){ - memcpy(&p->sGraph.zPrefix[n], pNext ? "| " : " ", 4); - eqp_render_level(p, pRow->iEqpId); - p->sGraph.zPrefix[n] = 0; - } + isIndex = sqlite3_strlike("CREATE INDEX%", zSql, 0)==0 + || sqlite3_strlike("CREATE UNIQUE INDEX%", zSql, 0)==0; + z = sqlite3_mprintf("%s", zSql); + if( z==0 ){ + sqlite3_str_free(pOut); + sqlite3_result_error_nomem(pCtx); + return; } -} - -/* -** Display and reset the EXPLAIN QUERY PLAN data -*/ -static void eqp_render(ShellState *p, i64 nCycle){ - EQPGraphRow *pRow = p->sGraph.pRow; - if( pRow ){ - if( pRow->zText[0]=='-' ){ - if( pRow->pNext==0 ){ - eqp_reset(p); - return; + j = 0; + for(i=0; IsSpace(z[i]); i++){} + for(; (c = z[i])!=0; i++){ + if( IsSpace(c) ){ + if( z[j-1]=='\r' ) z[j-1] = '\n'; + if( IsSpace(z[j-1]) || z[j-1]=='(' ) continue; + }else if( (c=='(' || c==')') && j>0 && IsSpace(z[j-1]) ){ + j--; + } + z[j++] = c; + } + while( j>0 && IsSpace(z[j-1]) ){ j--; } + z[j] = 0; + if( strlen30(z)>=79 ){ + for(i=j=0; (c = z[i])!=0; i++){ /* Copy from z[i] back to z[j] */ + if( c==cEnd ){ + cEnd = 0; + }else if( cEnd!=0){ + /* No-op */ + }else if( c=='"' || c=='\'' || c=='`' ){ + cEnd = c; + }else if( c=='[' ){ + cEnd = ']'; + }else if( c=='-' && z[i+1]=='-' ){ + cEnd = '\n'; + }else if( c=='(' ){ + nParen++; + }else if( c==')' ){ + nParen--; + if( nLine>0 && nParen==0 && j>0 && !isWhere ){ + sqlite3_str_append(pOut, z, j); + sqlite3_str_append(pOut, "\n", 1); + j = 0; + } + }else if( (c=='w' || c=='W') + && nParen==0 && isIndex + && sqlite3_strnicmp("WHERE",&z[i],5)==0 + && !IsAlnum(z[i+5]) && z[i+5]!='_' ){ + isWhere = 1; + }else if( isWhere && (c=='A' || c=='a') + && nParen==0 + && sqlite3_strnicmp("AND",&z[i],3)==0 + && !IsAlnum(z[i+3]) && z[i+3]!='_' ){ + sqlite3_str_append(pOut, z, j); + sqlite3_str_append(pOut, "\n ", 5); + j = 0; + } + z[j++] = c; + if( nParen==1 && cEnd==0 + && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1))) + && !isWhere + ){ + if( c=='\n' ) j--; + sqlite3_str_append(pOut, z, j); + sqlite3_str_append(pOut, "\n ", 3); + j = 0; + nLine++; + while( IsSpace(z[i+1]) ){ i++; } } - sqlite3_fprintf(p->out, "%s\n", pRow->zText+3); - p->sGraph.pRow = pRow->pNext; - sqlite3_free(pRow); - }else if( nCycle>0 ){ - sqlite3_fprintf(p->out, "QUERY PLAN (cycles=%lld [100%%])\n", nCycle); - }else{ - sqlite3_fputs("QUERY PLAN\n", p->out); } - p->sGraph.zPrefix[0] = 0; - eqp_render_level(p, 0); - eqp_reset(p); + z[j] = 0; } + sqlite3_str_appendall(pOut, z); + sqlite3_str_append(pOut, ";", 1); + sqlite3_free(z); + +shellFormatSchema_finish: + sqlite3_result_text(pCtx, sqlite3_str_finish(pOut), -1, sqlite3_free); } #ifndef SQLITE_OMIT_PROGRESS_CALLBACK @@ -2652,493 +2488,26 @@ static void eqp_render(ShellState *p, i64 nCycle){ static int progress_handler(void *pClientData) { ShellState *p = (ShellState*)pClientData; p->nProgress++; + if( (p->flgProgress & SHELL_PROGRESS_TMOUT)!=0 + && ELAPSE_TIME(p)>=p->tmProgress + ){ + cli_printf(p->out, "Progress timeout after %.6f seconds\n", + ELAPSE_TIME(p)); + return 1; + } if( p->nProgress>=p->mxProgress && p->mxProgress>0 ){ - sqlite3_fprintf(p->out, "Progress limit reached (%u)\n", p->nProgress); + cli_printf(p->out, "Progress limit reached (%u)\n", p->nProgress); if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; if( p->flgProgress & SHELL_PROGRESS_ONCE ) p->mxProgress = 0; return 1; } if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){ - sqlite3_fprintf(p->out, "Progress %u\n", p->nProgress); + cli_printf(p->out, "Progress %u\n", p->nProgress); } return 0; } #endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ -/* -** Print N dashes -*/ -static void print_dashes(FILE *out, int N){ - const char zDash[] = "--------------------------------------------------"; - const int nDash = sizeof(zDash) - 1; - while( N>nDash ){ - sqlite3_fputs(zDash, out); - N -= nDash; - } - sqlite3_fprintf(out, "%.*s", N, zDash); -} - -/* -** Print a markdown or table-style row separator using ascii-art -*/ -static void print_row_separator( - ShellState *p, - int nArg, - const char *zSep -){ - int i; - if( nArg>0 ){ - sqlite3_fputs(zSep, p->out); - print_dashes(p->out, p->actualWidth[0]+2); - for(i=1; iout); - print_dashes(p->out, p->actualWidth[i]+2); - } - sqlite3_fputs(zSep, p->out); - } - sqlite3_fputs("\n", p->out); -} - -/* -** This is the callback routine that the shell -** invokes for each row of a query result. -*/ -static int shell_callback( - void *pArg, - int nArg, /* Number of result columns */ - char **azArg, /* Text of each result column */ - char **azCol, /* Column names */ - int *aiType /* Column types. Might be NULL */ -){ - int i; - ShellState *p = (ShellState*)pArg; - - if( azArg==0 ) return 0; - switch( p->cMode ){ - case MODE_Count: - case MODE_Off: { - break; - } - case MODE_Line: { - int w = 5; - if( azArg==0 ) break; - for(i=0; iw ) w = len; - } - if( p->cnt++>0 ) sqlite3_fputs(p->rowSeparator, p->out); - for(i=0; inullValue, &pFree); - sqlite3_fprintf(p->out, "%*s = %s%s", w, azCol[i], - pDisplay, p->rowSeparator); - if( pFree ) sqlite3_free(pFree); - } - break; - } - case MODE_ScanExp: - case MODE_Explain: { - 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 = ArraySize(aExplainWidth); - int iIndent = 1; - - if( p->cMode==MODE_ScanExp ){ - aWidth = aScanExpWidth; - aMap = aScanExpMap; - nWidth = ArraySize(aScanExpWidth); - iIndent = 3; - } - if( nArg>nWidth ) nArg = nWidth; - - /* If this is the first row seen, print out the headers */ - if( p->cnt++==0 ){ - for(i=0; iout, aWidth[i], azCol[ aMap[i] ]); - sqlite3_fputs(i==nArg-1 ? "\n" : " ", p->out); - } - for(i=0; iout, aWidth[i]); - sqlite3_fputs(i==nArg-1 ? "\n" : " ", p->out); - } - } - - /* If there is no data, exit early. */ - if( azArg==0 ) break; - - for(i=0; iw ){ - w = strlenChar(zVal); - zSep = " "; - } - if( i==iIndent && p->aiIndent && p->pStmt ){ - if( p->iIndentnIndent ){ - sqlite3_fprintf(p->out, "%*.s", p->aiIndent[p->iIndent], ""); - } - p->iIndent++; - } - utf8_width_print(p->out, w, zVal ? zVal : p->nullValue); - sqlite3_fputs(i==nArg-1 ? "\n" : zSep, p->out); - } - break; - } - case MODE_Semi: { /* .schema and .fullschema output */ - printSchemaLine(p->out, azArg[0], ";\n"); - break; - } - case MODE_Pretty: { /* .schema and .fullschema with --indent */ - char *z; - int j; - int nParen = 0; - char cEnd = 0; - char c; - int nLine = 0; - int isIndex; - int isWhere = 0; - assert( nArg==1 ); - if( azArg[0]==0 ) break; - if( sqlite3_strlike("CREATE VIEW%", azArg[0], 0)==0 - || sqlite3_strlike("CREATE TRIG%", azArg[0], 0)==0 - ){ - sqlite3_fprintf(p->out, "%s;\n", azArg[0]); - break; - } - isIndex = sqlite3_strlike("CREATE INDEX%", azArg[0], 0)==0 - || sqlite3_strlike("CREATE UNIQUE INDEX%", azArg[0], 0)==0; - z = sqlite3_mprintf("%s", azArg[0]); - shell_check_oom(z); - j = 0; - for(i=0; IsSpace(z[i]); i++){} - for(; (c = z[i])!=0; i++){ - if( IsSpace(c) ){ - if( z[j-1]=='\r' ) z[j-1] = '\n'; - if( IsSpace(z[j-1]) || z[j-1]=='(' ) continue; - }else if( (c=='(' || c==')') && j>0 && IsSpace(z[j-1]) ){ - j--; - } - z[j++] = c; - } - while( j>0 && IsSpace(z[j-1]) ){ j--; } - z[j] = 0; - if( strlen30(z)>=79 ){ - for(i=j=0; (c = z[i])!=0; i++){ /* Copy from z[i] back to z[j] */ - if( c==cEnd ){ - cEnd = 0; - }else if( c=='"' || c=='\'' || c=='`' ){ - cEnd = c; - }else if( c=='[' ){ - cEnd = ']'; - }else if( c=='-' && z[i+1]=='-' ){ - cEnd = '\n'; - }else if( c=='(' ){ - nParen++; - }else if( c==')' ){ - nParen--; - if( nLine>0 && nParen==0 && j>0 && !isWhere ){ - printSchemaLineN(p->out, z, j, "\n"); - j = 0; - } - }else if( (c=='w' || c=='W') - && nParen==0 && isIndex - && sqlite3_strnicmp("WHERE",&z[i],5)==0 - && !IsAlnum(z[i+5]) && z[i+5]!='_' ){ - isWhere = 1; - }else if( isWhere && (c=='A' || c=='a') - && nParen==0 - && sqlite3_strnicmp("AND",&z[i],3)==0 - && !IsAlnum(z[i+3]) && z[i+3]!='_' ){ - printSchemaLineN(p->out, z, j, "\n "); - j = 0; - } - z[j++] = c; - if( nParen==1 && cEnd==0 - && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1))) - && !isWhere - ){ - if( c=='\n' ) j--; - printSchemaLineN(p->out, z, j, "\n "); - j = 0; - nLine++; - while( IsSpace(z[i+1]) ){ i++; } - } - } - z[j] = 0; - } - printSchemaLine(p->out, z, ";\n"); - sqlite3_free(z); - break; - } - case MODE_List: { - if( p->cnt++==0 && p->showHeader ){ - for(i=0; iout, "%s%s", zOut, - i==nArg-1 ? p->rowSeparator : p->colSeparator); - if( pFree ) sqlite3_free(pFree); - } - } - if( azArg==0 ) break; - for(i=0; inullValue; - zOut = escapeOutput(p, z, &pFree); - sqlite3_fputs(zOut, p->out); - if( pFree ) sqlite3_free(pFree); - sqlite3_fputs((icolSeparator : p->rowSeparator, p->out); - } - break; - } - case MODE_Www: - case MODE_Html: { - if( p->cnt==0 && p->cMode==MODE_Www ){ - sqlite3_fputs( - "\n" - "\n" - ,p->out - ); - } - if( p->cnt==0 && (p->showHeader || p->cMode==MODE_Www) ){ - sqlite3_fputs("", p->out); - for(i=0; i", p->out); - output_html_string(p->out, azCol[i]); - sqlite3_fputs("\n", p->out); - } - sqlite3_fputs("\n", p->out); - } - p->cnt++; - if( azArg==0 ) break; - sqlite3_fputs("", p->out); - for(i=0; i", p->out); - output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue); - sqlite3_fputs("\n", p->out); - } - sqlite3_fputs("\n", p->out); - break; - } - case MODE_Tcl: { - if( p->cnt++==0 && p->showHeader ){ - for(i=0; iout, azCol[i] ? azCol[i] : ""); - if(icolSeparator, p->out); - } - sqlite3_fputs(p->rowSeparator, p->out); - } - if( azArg==0 ) break; - for(i=0; iout, azArg[i] ? azArg[i] : p->nullValue); - if(icolSeparator, p->out); - } - sqlite3_fputs(p->rowSeparator, p->out); - break; - } - case MODE_Csv: { - sqlite3_fsetmode(p->out, _O_BINARY); - if( p->cnt++==0 && p->showHeader ){ - for(i=0; irowSeparator, p->out); - } - if( nArg>0 ){ - for(i=0; irowSeparator, p->out); - } - setCrlfMode(p); - break; - } - case MODE_Insert: { - if( azArg==0 ) break; - sqlite3_fprintf(p->out, "INSERT INTO %s",p->zDestTable); - if( p->showHeader ){ - sqlite3_fputs("(", p->out); - for(i=0; i0 ) sqlite3_fputs(",", p->out); - if( quoteChar(azCol[i]) ){ - char *z = sqlite3_mprintf("\"%w\"", azCol[i]); - shell_check_oom(z); - sqlite3_fputs(z, p->out); - sqlite3_free(z); - }else{ - sqlite3_fprintf(p->out, "%s", azCol[i]); - } - } - sqlite3_fputs(")", p->out); - } - p->cnt++; - for(i=0; i0 ? "," : " VALUES(", p->out); - if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - sqlite3_fputs("NULL", p->out); - }else if( aiType && aiType[i]==SQLITE_TEXT ){ - if( ShellHasFlag(p, SHFLG_Newlines) ){ - output_quoted_string(p, azArg[i]); - }else{ - output_quoted_escaped_string(p, azArg[i]); - } - }else if( aiType && aiType[i]==SQLITE_INTEGER ){ - sqlite3_fputs(azArg[i], p->out); - }else if( aiType && aiType[i]==SQLITE_FLOAT ){ - char z[50]; - double r = sqlite3_column_double(p->pStmt, i); - sqlite3_uint64 ur; - memcpy(&ur,&r,sizeof(r)); - if( ur==0x7ff0000000000000LL ){ - sqlite3_fputs("9.0e+999", p->out); - }else if( ur==0xfff0000000000000LL ){ - sqlite3_fputs("-9.0e+999", p->out); - }else{ - sqlite3_int64 ir = (sqlite3_int64)r; - if( r==(double)ir ){ - sqlite3_snprintf(50,z,"%lld.0", ir); - }else{ - sqlite3_snprintf(50,z,"%!.20g", r); - } - sqlite3_fputs(z, p->out); - } - }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ - const void *pBlob = sqlite3_column_blob(p->pStmt, i); - int nBlob = sqlite3_column_bytes(p->pStmt, i); - output_hex_blob(p->out, pBlob, nBlob); - }else if( isNumber(azArg[i], 0) ){ - sqlite3_fputs(azArg[i], p->out); - }else if( ShellHasFlag(p, SHFLG_Newlines) ){ - output_quoted_string(p, azArg[i]); - }else{ - output_quoted_escaped_string(p, azArg[i]); - } - } - sqlite3_fputs(");\n", p->out); - break; - } - case MODE_Json: { - if( azArg==0 ) break; - if( p->cnt==0 ){ - sqlite3_fputs("[{", p->out); - }else{ - sqlite3_fputs(",\n{", p->out); - } - p->cnt++; - for(i=0; iout, azCol[i], -1); - sqlite3_fputs(":", p->out); - if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - sqlite3_fputs("null", p->out); - }else if( aiType && aiType[i]==SQLITE_FLOAT ){ - char z[50]; - double r = sqlite3_column_double(p->pStmt, i); - sqlite3_uint64 ur; - memcpy(&ur,&r,sizeof(r)); - if( ur==0x7ff0000000000000LL ){ - sqlite3_fputs("9.0e+999", p->out); - }else if( ur==0xfff0000000000000LL ){ - sqlite3_fputs("-9.0e+999", p->out); - }else{ - sqlite3_snprintf(50,z,"%!.20g", r); - sqlite3_fputs(z, p->out); - } - }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ - const void *pBlob = sqlite3_column_blob(p->pStmt, i); - int nBlob = sqlite3_column_bytes(p->pStmt, i); - output_json_string(p->out, pBlob, nBlob); - }else if( aiType && aiType[i]==SQLITE_TEXT ){ - output_json_string(p->out, azArg[i], -1); - }else{ - sqlite3_fputs(azArg[i], p->out); - } - if( iout); - } - } - sqlite3_fputs("}", p->out); - break; - } - case MODE_Quote: { - if( azArg==0 ) break; - if( p->cnt==0 && p->showHeader ){ - for(i=0; i0 ) sqlite3_fputs(p->colSeparator, p->out); - output_quoted_string(p, azCol[i]); - } - sqlite3_fputs(p->rowSeparator, p->out); - } - p->cnt++; - for(i=0; i0 ) sqlite3_fputs(p->colSeparator, p->out); - if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - sqlite3_fputs("NULL", p->out); - }else if( aiType && aiType[i]==SQLITE_TEXT ){ - output_quoted_string(p, azArg[i]); - }else if( aiType && aiType[i]==SQLITE_INTEGER ){ - sqlite3_fputs(azArg[i], p->out); - }else if( aiType && aiType[i]==SQLITE_FLOAT ){ - char z[50]; - double r = sqlite3_column_double(p->pStmt, i); - sqlite3_snprintf(50,z,"%!.20g", r); - sqlite3_fputs(z, p->out); - }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ - const void *pBlob = sqlite3_column_blob(p->pStmt, i); - int nBlob = sqlite3_column_bytes(p->pStmt, i); - output_hex_blob(p->out, pBlob, nBlob); - }else if( isNumber(azArg[i], 0) ){ - sqlite3_fputs(azArg[i], p->out); - }else{ - output_quoted_string(p, azArg[i]); - } - } - sqlite3_fputs(p->rowSeparator, p->out); - break; - } - case MODE_Ascii: { - if( p->cnt++==0 && p->showHeader ){ - for(i=0; i0 ) sqlite3_fputs(p->colSeparator, p->out); - sqlite3_fputs(azCol[i] ? azCol[i] : "", p->out); - } - sqlite3_fputs(p->rowSeparator, p->out); - } - if( azArg==0 ) break; - for(i=0; i0 ) sqlite3_fputs(p->colSeparator, p->out); - sqlite3_fputs(azArg[i] ? azArg[i] : p->nullValue, p->out); - } - sqlite3_fputs(p->rowSeparator, p->out); - break; - } - case MODE_EQP: { - eqp_append(p, atoi(azArg[0]), atoi(azArg[1]), azArg[3]); - break; - } - } - return 0; -} - -/* -** This is the callback routine that the SQLite library -** invokes for each row of a query result. -*/ -static int callback(void *pArg, int nArg, char **azArg, char **azCol){ - /* since we don't have type info, call the shell_callback with a NULL value */ - return shell_callback(pArg, nArg, azArg, azCol, NULL); -} - /* ** This is the callback routine from sqlite3_exec() that appends all ** output onto the end of a ShellText object. @@ -3198,7 +2567,7 @@ static void createSelftestTable(ShellState *p){ "DROP TABLE [_shell$self];" ,0,0,&zErrMsg); if( zErrMsg ){ - sqlite3_fprintf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg); + cli_printf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg); sqlite3_free(zErrMsg); } sqlite3_exec(p->db, "RELEASE selftest_init",0,0,0); @@ -3216,11 +2585,7 @@ static void set_table_name(ShellState *p, const char *zName){ p->zDestTable = 0; } if( zName==0 ) return; - if( quoteChar(zName) ){ - p->zDestTable = sqlite3_mprintf("\"%w\"", zName); - }else{ - p->zDestTable = sqlite3_mprintf("%s", zName); - } + p->zDestTable = sqlite3_mprintf("%s", zName); shell_check_oom(p->zDestTable); } @@ -3290,7 +2655,7 @@ static int run_table_dump_query( rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ char *zContext = shell_error_context(zSelect, p->db); - sqlite3_fprintf(p->out, "/**** ERROR: (%d) %s *****/\n%s", + cli_printf(p->out, "/**** ERROR: (%d) %s *****/\n%s", rc, sqlite3_errmsg(p->db), zContext); sqlite3_free(zContext); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; @@ -3300,22 +2665,22 @@ static int run_table_dump_query( nResult = sqlite3_column_count(pSelect); while( rc==SQLITE_ROW ){ z = (const char*)sqlite3_column_text(pSelect, 0); - sqlite3_fprintf(p->out, "%s", z); + cli_printf(p->out, "%s", z); for(i=1; iout, ",%s", sqlite3_column_text(pSelect, i)); + cli_printf(p->out, ",%s", sqlite3_column_text(pSelect, i)); } if( z==0 ) z = ""; while( z[0] && (z[0]!='-' || z[1]!='-') ) z++; if( z[0] ){ - sqlite3_fputs("\n;\n", p->out); + cli_puts("\n;\n", p->out); }else{ - sqlite3_fputs(";\n", p->out); + cli_puts(";\n", p->out); } rc = sqlite3_step(pSelect); } rc = sqlite3_finalize(pSelect); if( rc!=SQLITE_OK ){ - sqlite3_fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", + cli_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; } @@ -3375,7 +2740,7 @@ static void displayLinuxIoStats(FILE *out){ for(i=0; ipStmt; char z[100]; nCol = sqlite3_column_count(pStmt); - sqlite3_fprintf(out, "%-36s %d\n", "Number of output columns:", nCol); + cli_printf(out, "%-36s %d\n", "Number of output columns:", nCol); for(i=0; istatsOn==3 ){ if( pArg->pStmt ){ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset); - sqlite3_fprintf(out, "VM-steps: %d\n", iCur); + cli_printf(out, "VM-steps: %d\n", iCur); } return 0; } @@ -3481,55 +2846,55 @@ static int display_stats( iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, &iCur, &iHiwtr, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &iCur, &iHiwtr, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Successful lookaside attempts: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Lookaside failures due to size: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Lookaside failures due to OOM: %d\n", iHiwtr); } iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Pager Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1); - sqlite3_fprintf(out, + cli_printf(out, "Page cache hits: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); - sqlite3_fprintf(out, + cli_printf(out, "Page cache misses: %d\n", iCur); iHiwtr64 = iCur64 = -1; sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64, 0); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); - sqlite3_fprintf(out, + cli_printf(out, "Page cache writes: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1); - sqlite3_fprintf(out, + cli_printf(out, "Page cache spills: %d\n", iCur); - sqlite3_fprintf(out, + cli_printf(out, "Temporary data spilled to disk: %lld\n", iCur64); sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64, 1); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Schema Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Statement Heap/Lookaside Usage: %d bytes\n", iCur); } @@ -3537,33 +2902,33 @@ static int display_stats( int iHit, iMiss; iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Fullscan Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Sort Operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); - sqlite3_fprintf(out, + cli_printf(out, "Autoindex Inserts: %d\n", iCur); iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, bReset); iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, bReset); if( iHit || iMiss ){ - sqlite3_fprintf(out, + cli_printf(out, "Bloom filter bypass taken: %d/%d\n", iHit, iHit+iMiss); } iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Virtual Machine Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset); - sqlite3_fprintf(out, + cli_printf(out, "Reprepare operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Number of times run: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset); - sqlite3_fprintf(out, + cli_printf(out, "Memory used by prepared stmt: %d\n", iCur); } @@ -3576,278 +2941,17 @@ static int display_stats( return 0; } - -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS -static int scanStatsHeight(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 - -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS -static void display_explain_scanstats( - sqlite3 *db, /* Database to query */ - ShellState *pArg /* Pointer to ShellState */ -){ - static const int f = SQLITE_SCANSTAT_COMPLEX; - sqlite3_stmt *p = pArg->pStmt; - int ii = 0; - i64 nTotal = 0; - int nWidth = 0; - eqp_reset(pArg); - - for(ii=0; 1; ii++){ - const char *z = 0; - int n = 0; - if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ - break; - } - n = (int)strlen(z) + scanStatsHeight(p, ii)*3; - if( n>nWidth ) nWidth = n; - } - nWidth += 4; - - sqlite3_stmt_scanstatus_v2(p, -1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal); - for(ii=0; 1; ii++){ - i64 nLoop = 0; - i64 nRow = 0; - i64 nCycle = 0; - int iId = 0; - int iPid = 0; - const char *zo = 0; - const char *zName = 0; - char *zText = 0; - double rEst = 0.0; - - if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){ - break; - } - sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_EST,f,(void*)&rEst); - sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop); - sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow); - sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle); - sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId); - sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid); - sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName); - - zText = sqlite3_mprintf("%s", zo); - if( nCycle>=0 || nLoop>=0 || nRow>=0 ){ - char *z = 0; - if( nCycle>=0 && nTotal>0 ){ - z = sqlite3_mprintf("%zcycles=%lld [%d%%]", z, - nCycle, ((nCycle*100)+nTotal/2) / nTotal - ); - } - if( nLoop>=0 ){ - z = sqlite3_mprintf("%z%sloops=%lld", z, z ? " " : "", nLoop); - } - if( nRow>=0 ){ - z = sqlite3_mprintf("%z%srows=%lld", z, z ? " " : "", nRow); - } - - if( zName && pArg->scanstatsOn>1 ){ - double rpl = (double)nRow / (double)nLoop; - z = sqlite3_mprintf("%z rpl=%.1f est=%.1f", z, rpl, rEst); - } - - zText = sqlite3_mprintf( - "% *z (%z)", -1*(nWidth-scanStatsHeight(p, ii)*3), zText, z - ); - } - - eqp_append(pArg, iId, iPid, zText); - sqlite3_free(zText); - } - - eqp_render(pArg, nTotal); -} -#endif - - -/* -** 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 str_in_array(const char *zStr, const char **azArray){ - int i; - for(i=0; azArray[i]; i++){ - if( 0==cli_strcmp(zStr, azArray[i]) ) return 1; - } - return 0; -} - -/* -** If compiled statement pSql appears to be an EXPLAIN statement, allocate -** and populate the ShellState.aiIndent[] array with the number of -** spaces each opcode should be indented before it is output. -** -** 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 explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ - int *abYield = 0; /* True if op is an OP_Yield */ - int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */ - int iOp; /* Index of operation in p->aiIndent[] */ - - 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(pSql)>=4 ); - assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 0), "addr" ) ); - assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 1), "opcode" ) ); - assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 2), "p1" ) ); - assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 3), "p2" ) ); - - for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){ - int i; - int iAddr = sqlite3_column_int(pSql, 0); - const char *zOp = (const char*)sqlite3_column_text(pSql, 1); - int p1 = sqlite3_column_int(pSql, 2); - int p2 = sqlite3_column_int(pSql, 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 p->aiIndent array as required */ - if( iOp>=nAlloc ){ - nAlloc += 100; - p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int)); - shell_check_oom(p->aiIndent); - abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int)); - shell_check_oom(abYield); - } - - abYield[iOp] = str_in_array(zOp, azYield); - p->aiIndent[iOp] = 0; - p->nIndent = iOp+1; - if( str_in_array(zOp, azNext) && p2op>0 ){ - for(i=p2op; iaiIndent[i] += 2; - } - if( str_in_array(zOp, azGoto) && p2opaiIndent[i] += 2; - } - } - - p->iIndent = 0; - sqlite3_free(abYield); - sqlite3_reset(pSql); -} - -/* -** Free the array allocated by explain_data_prepare(). -*/ -static void explain_data_delete(ShellState *p){ - sqlite3_free(p->aiIndent); - p->aiIndent = 0; - p->nIndent = 0; - p->iIndent = 0; -} - -static void exec_prepared_stmt(ShellState*, sqlite3_stmt*); - -/* -** Display scan stats. -*/ -static void display_scanstats( - sqlite3 *db, /* Database to query */ - ShellState *pArg /* Pointer to ShellState */ -){ -#ifndef SQLITE_ENABLE_STMT_SCANSTATUS - UNUSED_PARAMETER(db); - UNUSED_PARAMETER(pArg); -#else - if( pArg->scanstatsOn==3 ){ - 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(?)"; - - int rc = SQLITE_OK; - sqlite3_stmt *pStmt = 0; - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - if( rc==SQLITE_OK ){ - sqlite3_stmt *pSave = pArg->pStmt; - pArg->pStmt = pStmt; - sqlite3_bind_pointer(pStmt, 1, pSave, "stmt-pointer", 0); - - pArg->cnt = 0; - pArg->cMode = MODE_ScanExp; - explain_data_prepare(pArg, pStmt); - exec_prepared_stmt(pArg, pStmt); - explain_data_delete(pArg); - - sqlite3_finalize(pStmt); - pArg->pStmt = pSave; - } - }else{ - display_explain_scanstats(db, pArg); - } -#endif -} - -/* -** Disable and restore .wheretrace and .treetrace/.selecttrace settings. -*/ -static unsigned int savedSelectTrace; -static unsigned int savedWhereTrace; -static void disable_debug_trace_modes(void){ - unsigned int zero = 0; - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 0, &savedSelectTrace); - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &zero); - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 2, &savedWhereTrace); - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &zero); +/* +** Disable and restore .wheretrace and .treetrace/.selecttrace settings. +*/ +static unsigned int savedSelectTrace; +static unsigned int savedWhereTrace; +static void disable_debug_trace_modes(void){ + unsigned int zero = 0; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 0, &savedSelectTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &zero); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 2, &savedWhereTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &zero); } static void restore_debug_trace_modes(void){ sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &savedSelectTrace); @@ -3928,6 +3032,8 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ memcpy(zBuf, &zVar[6], szVar-5); sqlite3_bind_text64(pStmt, i, zBuf, szVar-6, sqlite3_free, SQLITE_UTF8); } + }else if( strcmp(zVar, "$TIMER")==0 ){ + sqlite3_bind_double(pStmt, i, pArg->prevTimer); #ifdef SQLITE_ENABLE_CARRAY }else if( strncmp(zVar, "$carray_", 8)==0 ){ static char *azColorNames[] = { @@ -3966,580 +3072,6 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ sqlite3_finalize(pQ); } -/* -** 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 -|- */ - -/* Draw horizontal line N characters long using unicode box -** characters -*/ -static void print_box_line(FILE *out, int N){ - const char zDash[] = - BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 - BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24; - const int nDash = sizeof(zDash) - 1; - N *= 3; - while( N>nDash ){ - sqlite3_fputs(zDash, out); - N -= nDash; - } - sqlite3_fprintf(out, "%.*s", N, zDash); -} - -/* -** Draw a horizontal separator for a MODE_Box table. -*/ -static void print_box_row_separator( - ShellState *p, - int nArg, - const char *zSep1, - const char *zSep2, - const char *zSep3 -){ - int i; - if( nArg>0 ){ - sqlite3_fputs(zSep1, p->out); - print_box_line(p->out, p->actualWidth[0]+2); - for(i=1; iout); - print_box_line(p->out, p->actualWidth[i]+2); - } - sqlite3_fputs(zSep3, p->out); - } - sqlite3_fputs("\n", p->out); -} - -/* -** z[] is a line of text that is to be displayed the .mode box or table or -** similar tabular formats. z[] might contain control characters such -** as \n, \t, \f, or \r. -** -** Compute characters to display on the first line of z[]. Stop at the -** first \r, \n, or \f. Expand \t into spaces. Return a copy (obtained -** from malloc()) of that first line, which caller should free sometime. -** Write anything to display on the next line into *pzTail. If this is -** the last line, write a NULL into *pzTail. (*pzTail is not allocated.) -*/ -static char *translateForDisplayAndDup( - ShellState *p, /* To access current settings */ - const unsigned char *z, /* Input text to be transformed */ - const unsigned char **pzTail, /* OUT: Tail of the input for next line */ - int mxWidth, /* Max width. 0 means no limit */ - u8 bWordWrap /* If true, avoid breaking mid-word */ -){ - int i; /* Input bytes consumed */ - int j; /* Output bytes generated */ - int k; /* Input bytes to be displayed */ - int n; /* Output column number */ - unsigned char *zOut; /* Output text */ - - if( z==0 ){ - *pzTail = 0; - return 0; - } - if( mxWidth<0 ) mxWidth = -mxWidth; - if( mxWidth==0 ) mxWidth = 1000000; - i = j = n = 0; - while( n=0xc0 ){ - int u; - int len = decodeUtf8(&z[i], &u); - i += len; - j += len; - n += cli_wcwidth(u); - continue; - } - if( c>=' ' ){ - n++; - i++; - j++; - continue; - } - if( c==0 || c=='\n' || (c=='\r' && z[i+1]=='\n') ) break; - if( c=='\t' ){ - do{ - n++; - j++; - }while( (n&7)!=0 && neEscMode==SHELL_ESC_OFF && (k = isVt100(&z[i]))>0 ){ - i += k; - j += k; - }else{ - n++; - j += 3; - i++; - } - } - if( n>=mxWidth && bWordWrap ){ - /* Perhaps try to back up to a better place to break the line */ - for(k=i; k>i/2; k--){ - if( IsSpace(z[k-1]) ) break; - } - if( k<=i/2 ){ - for(k=i; k>i/2; k--){ - if( IsAlnum(z[k-1])!=IsAlnum(z[k]) && (z[k]&0xc0)!=0x80 ) break; - } - } - if( k<=i/2 ){ - k = i; - }else{ - i = k; - while( z[i]==' ' ) i++; - } - }else{ - k = i; - } - if( n>=mxWidth && z[i]>=' ' ){ - *pzTail = &z[i]; - }else if( z[i]=='\r' && z[i+1]=='\n' ){ - *pzTail = z[i+2] ? &z[i+2] : 0; - }else if( z[i]==0 || z[i+1]==0 ){ - *pzTail = 0; - }else{ - *pzTail = &z[i+1]; - } - zOut = malloc( j+1 ); - shell_check_oom(zOut); - i = j = n = 0; - while( i=0xc0 ){ - int u; - int len = decodeUtf8(&z[i], &u); - do{ zOut[j++] = z[i++]; }while( (--len)>0 ); - n += cli_wcwidth(u); - continue; - } - if( c>=' ' ){ - n++; - zOut[j++] = z[i++]; - continue; - } - if( c==0 ) break; - if( z[i]=='\t' ){ - do{ - n++; - zOut[j++] = ' '; - }while( (n&7)!=0 && neEscMode ){ - case SHELL_ESC_SYMBOL: - zOut[j++] = 0xe2; - zOut[j++] = 0x90; - zOut[j++] = 0x80 + c; - break; - case SHELL_ESC_ASCII: - zOut[j++] = '^'; - zOut[j++] = 0x40 + c; - break; - case SHELL_ESC_OFF: { - int nn; - if( c==0x1b && (nn = isVt100(&z[i]))>0 ){ - memcpy(&zOut[j], &z[i], nn); - j += nn; - i += nn - 1; - }else{ - zOut[j++] = c; - } - break; - } - } - i++; - } - zOut[j] = 0; - return (char*)zOut; -} - -/* Return true if the text string z[] contains characters that need -** unistr() escaping. -*/ -static int needUnistr(const unsigned char *z){ - unsigned char c; - if( z==0 ) return 0; - while( (c = *z)>0x1f || c=='\t' || c=='\n' || (c=='\r' && z[1]=='\n') ){ z++; } - return c!=0; -} - -/* Extract the value of the i-th current column for pStmt as an SQL literal -** value. Memory is obtained from sqlite3_malloc64() and must be freed by -** the caller. -*/ -static char *quoted_column(sqlite3_stmt *pStmt, int i){ - switch( sqlite3_column_type(pStmt, i) ){ - case SQLITE_NULL: { - return sqlite3_mprintf("NULL"); - } - case SQLITE_INTEGER: - case SQLITE_FLOAT: { - return sqlite3_mprintf("%s",sqlite3_column_text(pStmt,i)); - } - case SQLITE_TEXT: { - const unsigned char *zText = sqlite3_column_text(pStmt,i); - return sqlite3_mprintf(needUnistr(zText)?"%#Q":"%Q",zText); - } - case SQLITE_BLOB: { - int j; - sqlite3_str *pStr = sqlite3_str_new(0); - const unsigned char *a = sqlite3_column_blob(pStmt,i); - int n = sqlite3_column_bytes(pStmt,i); - sqlite3_str_append(pStr, "x'", 2); - for(j=0; jcmOpts.bWordWrap; - const char *zEmpty = ""; - const char *zShowNull = p->nullValue; - - rc = sqlite3_step(pStmt); - if( rc!=SQLITE_ROW ) return; - nColumn = sqlite3_column_count(pStmt); - if( nColumn==0 ) goto columnar_end; - nAlloc = nColumn*4; - if( nAlloc<=0 ) nAlloc = 1; - azData = sqlite3_malloc64( nAlloc*sizeof(char*) ); - shell_check_oom(azData); - azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) ); - shell_check_oom(azNextLine); - memset((void*)azNextLine, 0, nColumn*sizeof(char*) ); - if( p->cmOpts.bQuote ){ - azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) ); - shell_check_oom(azQuoted); - memset(azQuoted, 0, nColumn*sizeof(char*) ); - } - abRowDiv = sqlite3_malloc64( nAlloc/nColumn ); - shell_check_oom(abRowDiv); - if( nColumn>p->nWidth ){ - p->colWidth = realloc(p->colWidth, (nColumn+1)*2*sizeof(int)); - shell_check_oom(p->colWidth); - for(i=p->nWidth; icolWidth[i] = 0; - p->nWidth = nColumn; - p->actualWidth = &p->colWidth[nColumn]; - } - memset(p->actualWidth, 0, nColumn*sizeof(int)); - for(i=0; icolWidth[i]; - if( w<0 ) w = -w; - p->actualWidth[i] = w; - } - for(i=0; icolWidth[i]; - if( wx==0 ){ - wx = p->cmOpts.iWrap; - } - if( wx<0 ) wx = -wx; - uz = (const unsigned char*)sqlite3_column_name(pStmt,i); - if( uz==0 ) uz = (u8*)""; - azData[i] = translateForDisplayAndDup(p, uz, &zNotUsed, wx, bw); - } - do{ - int useNextLine = bNextLine; - bNextLine = 0; - if( (nRow+2)*nColumn >= nAlloc ){ - nAlloc *= 2; - azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*)); - shell_check_oom(azData); - abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn); - shell_check_oom(abRowDiv); - } - abRowDiv[nRow] = 1; - nRow++; - for(i=0; icolWidth[i]; - if( wx==0 ){ - wx = p->cmOpts.iWrap; - } - if( wx<0 ) wx = -wx; - if( useNextLine ){ - uz = azNextLine[i]; - if( uz==0 ) uz = (u8*)zEmpty; - }else if( p->cmOpts.bQuote ){ - assert( azQuoted!=0 ); - sqlite3_free(azQuoted[i]); - azQuoted[i] = quoted_column(pStmt,i); - uz = (const unsigned char*)azQuoted[i]; - }else{ - uz = (const unsigned char*)sqlite3_column_text(pStmt,i); - if( uz==0 ) uz = (u8*)zShowNull; - } - azData[nRow*nColumn + i] - = translateForDisplayAndDup(p, uz, &azNextLine[i], wx, bw); - if( azNextLine[i] ){ - bNextLine = 1; - abRowDiv[nRow-1] = 0; - bMultiLineRowExists = 1; - } - } - }while( bNextLine || sqlite3_step(pStmt)==SQLITE_ROW ); - nTotal = nColumn*(nRow+1); - for(i=0; ip->actualWidth[j] ) p->actualWidth[j] = n; - } - if( seenInterrupt ) goto columnar_end; - switch( p->cMode ){ - case MODE_Column: { - colSep = " "; - rowSep = "\n"; - if( p->showHeader ){ - for(i=0; iactualWidth[i]; - if( p->colWidth[i]<0 ) w = -w; - utf8_width_print(p->out, w, azData[i]); - sqlite3_fputs(i==nColumn-1?"\n":" ", p->out); - } - for(i=0; iout, p->actualWidth[i]); - sqlite3_fputs(i==nColumn-1?"\n":" ", p->out); - } - } - break; - } - case MODE_Table: { - colSep = " | "; - rowSep = " |\n"; - print_row_separator(p, nColumn, "+"); - sqlite3_fputs("| ", p->out); - for(i=0; iactualWidth[i]; - n = strlenChar(azData[i]); - sqlite3_fprintf(p->out, "%*s%s%*s", (w-n)/2, "", - azData[i], (w-n+1)/2, ""); - sqlite3_fputs(i==nColumn-1?" |\n":" | ", p->out); - } - print_row_separator(p, nColumn, "+"); - break; - } - case MODE_Markdown: { - colSep = " | "; - rowSep = " |\n"; - sqlite3_fputs("| ", p->out); - for(i=0; iactualWidth[i]; - n = strlenChar(azData[i]); - sqlite3_fprintf(p->out, "%*s%s%*s", (w-n)/2, "", - azData[i], (w-n+1)/2, ""); - sqlite3_fputs(i==nColumn-1?" |\n":" | ", p->out); - } - print_row_separator(p, nColumn, "|"); - break; - } - case MODE_Box: { - colSep = " " BOX_13 " "; - rowSep = " " BOX_13 "\n"; - print_box_row_separator(p, nColumn, BOX_23, BOX_234, BOX_34); - sqlite3_fputs(BOX_13 " ", p->out); - for(i=0; iactualWidth[i]; - n = strlenChar(azData[i]); - sqlite3_fprintf(p->out, "%*s%s%*s%s", - (w-n)/2, "", azData[i], (w-n+1)/2, "", - i==nColumn-1?" "BOX_13"\n":" "BOX_13" "); - } - print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); - break; - } - } - for(i=nColumn, j=0; icMode!=MODE_Column ){ - sqlite3_fputs(p->cMode==MODE_Box?BOX_13" ":"| ", p->out); - } - z = azData[i]; - if( z==0 ) z = p->nullValue; - w = p->actualWidth[j]; - if( p->colWidth[j]<0 ) w = -w; - utf8_width_print(p->out, w, z); - if( j==nColumn-1 ){ - sqlite3_fputs(rowSep, p->out); - if( bMultiLineRowExists && abRowDiv[i/nColumn-1] && i+1cMode==MODE_Table ){ - print_row_separator(p, nColumn, "+"); - }else if( p->cMode==MODE_Box ){ - print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); - }else if( p->cMode==MODE_Column ){ - sqlite3_fputs("\n", p->out); - } - } - j = -1; - if( seenInterrupt ) goto columnar_end; - }else{ - sqlite3_fputs(colSep, p->out); - } - } - if( p->cMode==MODE_Table ){ - print_row_separator(p, nColumn, "+"); - }else if( p->cMode==MODE_Box ){ - print_box_row_separator(p, nColumn, BOX_12, BOX_124, BOX_14); - } -columnar_end: - if( seenInterrupt ){ - sqlite3_fputs("Interrupt\n", p->out); - } - nData = (nRow+1)*nColumn; - for(i=0; icMode==MODE_Column - || pArg->cMode==MODE_Table - || pArg->cMode==MODE_Box - || pArg->cMode==MODE_Markdown - ){ - exec_prepared_stmt_columnar(pArg, pStmt); - return; - } - - /* perform the first step. this will tell us if we - ** have a result set or not and how wide it is. - */ - rc = sqlite3_step(pStmt); - /* if we have a result set... */ - if( SQLITE_ROW == rc ){ - /* allocate space for col name ptr, value ptr, and type */ - int nCol = sqlite3_column_count(pStmt); - void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1); - if( !pData ){ - shell_out_of_memory(); - }else{ - char **azCols = (char **)pData; /* Names of result columns */ - char **azVals = &azCols[nCol]; /* Results */ - int *aiTypes = (int *)&azVals[nCol]; /* Result types */ - int i, x; - assert(sizeof(int) <= sizeof(char *)); - /* save off ptrs to column names */ - for(i=0; icMode==MODE_Insert || pArg->cMode==MODE_Quote) - ){ - azVals[i] = ""; - }else{ - azVals[i] = (char*)sqlite3_column_text(pStmt, i); - } - if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){ - rc = SQLITE_NOMEM; - break; /* from for */ - } - } /* end for */ - - /* if data and types extracted successfully... */ - if( SQLITE_ROW == rc ){ - /* call the supplied callback with the result row data */ - if( shell_callback(pArg, nCol, azVals, azCols, aiTypes) ){ - rc = SQLITE_ABORT; - }else{ - rc = sqlite3_step(pStmt); - } - } - } while( SQLITE_ROW == rc ); - sqlite3_free(pData); - if( pArg->cMode==MODE_Json ){ - sqlite3_fputs("]\n", pArg->out); - }else if( pArg->cMode==MODE_Www ){ - sqlite3_fputs("
          \n
          \n", pArg->out);
          -      }else if( pArg->cMode==MODE_Count ){
          -        char zBuf[200];
          -        sqlite3_snprintf(sizeof(zBuf), zBuf, "%llu row%s\n",
          -                         nRow, nRow!=1 ? "s" : "");
          -        printf("%s", zBuf);
          -      }
          -    }
          -  }
          -}
          -
           #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION)
           /*
           ** This function is called to process SQL if the previous shell command
          @@ -4591,8 +3123,8 @@ static int expertFinish(
           
                 if( bVerbose ){
                   const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES);
          -        sqlite3_fputs("-- Candidates -----------------------------\n", out);
          -        sqlite3_fprintf(out, "%s\n", zCand);
          +        cli_puts("-- Candidates -----------------------------\n", out);
          +        cli_printf(out, "%s\n", zCand);
                 }
                 for(i=0; i=2 && 0==cli_strncmp(z, "-sample", n) ){
                 if( i==(nArg-1) ){
          -        sqlite3_fprintf(stderr, "option requires an argument: %s\n", z);
          +        cli_printf(stderr, "option requires an argument: %s\n", z);
                   rc = SQLITE_ERROR;
                 }else{
                   iSample = (int)integerValue(azArg[++i]);
                   if( iSample<0 || iSample>100 ){
          -          sqlite3_fprintf(stderr,"value out of range: %s\n", azArg[i]);
          +          cli_printf(stderr,"value out of range: %s\n", azArg[i]);
                     rc = SQLITE_ERROR;
                   }
                 }
               }
               else{
          -      sqlite3_fprintf(stderr,"unknown option: %s\n", z);
          +      cli_printf(stderr,"unknown option: %s\n", z);
                 rc = SQLITE_ERROR;
               }
             }
          @@ -4659,7 +3191,7 @@ static int expertDotCommand(
             if( rc==SQLITE_OK ){
               pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr);
               if( pState->expert.pExpert==0 ){
          -      sqlite3_fprintf(stderr,
          +      cli_printf(stderr,
                     "sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory");
                 rc = SQLITE_ERROR;
               }else{
          @@ -4674,6 +3206,15 @@ static int expertDotCommand(
           }
           #endif /* !SQLITE_OMIT_VIRTUALTABLE && !SQLITE_OMIT_AUTHORIZATION */
           
          +/*
          +** QRF write callback
          +*/
          +static int shellWriteQR(void *pX, const char *z, sqlite3_int64 n){
          +  ShellState *pArg = (ShellState*)pX;
          +  cli_printf(pArg->out, "%.*s", (int)n, z);
          +  return SQLITE_OK;
          +}
          +
           /*
           ** Execute a statement or set of statements.  Print
           ** any result rows/columns depending on the current mode
          @@ -4693,10 +3234,31 @@ static int shell_exec(
             int rc2;
             const char *zLeftover;          /* Tail of unprocessed SQL */
             sqlite3 *db = pArg->db;
          +  unsigned char eStyle;
          +  sqlite3_qrf_spec spec;
           
             if( pzErrMsg ){
               *pzErrMsg = NULL;
             }
          +  memcpy(&spec, &pArg->mode.spec, sizeof(spec));
          +  spec.xWrite = shellWriteQR;
          +  spec.pWriteArg = (void*)pArg;
          +  if( pArg->mode.eMode==MODE_Insert && ShellHasFlag(pArg, SHFLG_PreserveRowid) ){
          +    spec.bTitles = QRF_SW_On;
          +  }
          +  assert( pArg->mode.eMode>=0 && pArg->mode.eModemode.eMode].eStyle;
          +  if( pArg->mode.bAutoScreenWidth ){
          +    spec.nScreenWidth = shellScreenWidth();
          +  }
          +  if( spec.eBlob==QRF_BLOB_Auto ){
          +    switch( spec.eText ){
          +      case QRF_TEXT_Relaxed: /* fall through */
          +      case QRF_TEXT_Sql:  spec.eBlob = QRF_BLOB_Sql;   break;
          +      case QRF_TEXT_Json: spec.eBlob = QRF_BLOB_Json;  break;
          +      default:            spec.eBlob = QRF_BLOB_Text;  break;
          +    }
          +  }
           
           #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION)
             if( pArg->expert.pExpert ){
          @@ -4705,7 +3267,7 @@ static int shell_exec(
             }
           #endif
           
          -  while( zSql[0] && (SQLITE_OK == rc) ){
          +  while( zSql && zSql[0] && (SQLITE_OK == rc) ){
               static const char *zStmtSql;
               rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
               if( SQLITE_OK != rc ){
          @@ -4713,6 +3275,7 @@ static int shell_exec(
                   *pzErrMsg = save_err_msg(db, "in prepare", rc, zSql);
                 }
               }else{
          +      int isExplain;
                 if( !pStmt ){
                   /* this happens for a comment or white-space */
                   zSql = zLeftover;
          @@ -4723,80 +3286,58 @@ static int shell_exec(
                 if( zStmtSql==0 ) zStmtSql = "";
                 while( IsSpace(zStmtSql[0]) ) zStmtSql++;
           
          -      /* save off the prepared statement handle and reset row count */
          +      /* save off the prepared statement handle */
                 if( pArg ){
                   pArg->pStmt = pStmt;
          -        pArg->cnt = 0;
                 }
          -
          +     
                 /* Show the EXPLAIN QUERY PLAN if .eqp is on */
          -      if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){
          -        sqlite3_stmt *pExplain;
          +      isExplain = sqlite3_stmt_isexplain(pStmt);
          +      if( pArg && pArg->mode.autoEQP && isExplain==0 && pArg->dot.nArg==0 ){
                   int triggerEQP = 0;
          +        u8 savedEnableTimer = pArg->enableTimer;
          +        pArg->enableTimer = 0;
                   disable_debug_trace_modes();
                   sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP);
          -        if( pArg->autoEQP>=AUTOEQP_trigger ){
          +        if( pArg->mode.autoEQP>=AUTOEQP_trigger ){
                     sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0);
                   }
          -        pExplain = pStmt;
          -        sqlite3_reset(pExplain);
          -        rc = sqlite3_stmt_explain(pExplain, 2);
          -        if( rc==SQLITE_OK ){
          -          bind_prepared_stmt(pArg, pExplain);
          -          while( sqlite3_step(pExplain)==SQLITE_ROW ){
          -            const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
          -            int iEqpId = sqlite3_column_int(pExplain, 0);
          -            int iParentId = sqlite3_column_int(pExplain, 1);
          -            if( zEQPLine==0 ) zEQPLine = "";
          -            if( zEQPLine[0]=='-' ) eqp_render(pArg, 0);
          -            eqp_append(pArg, iEqpId, iParentId, zEQPLine);
          -          }
          -          eqp_render(pArg, 0);
          -        }
          -        if( pArg->autoEQP>=AUTOEQP_full ){
          -          /* Also do an EXPLAIN for ".eqp full" mode */
          -          sqlite3_reset(pExplain);
          -          rc = sqlite3_stmt_explain(pExplain, 1);
          -          if( rc==SQLITE_OK ){
          -            pArg->cMode = MODE_Explain;
          -            assert( sqlite3_stmt_isexplain(pExplain)==1 );
          -            bind_prepared_stmt(pArg, pExplain);
          -            explain_data_prepare(pArg, pExplain);
          -            exec_prepared_stmt(pArg, pExplain);
          -            explain_data_delete(pArg);
          -          }
          +        sqlite3_reset(pStmt);
          +        spec.eStyle = QRF_STYLE_Auto;
          +        sqlite3_stmt_explain(pStmt, 2);
          +        sqlite3_format_query_result(pStmt, &spec, 0);
          +        if( pArg->mode.autoEQP>=AUTOEQP_full ){
          +          sqlite3_reset(pStmt);
          +          sqlite3_stmt_explain(pStmt, 1);
          +          sqlite3_format_query_result(pStmt, &spec, 0);
                   }
          -        if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
          +
          +        if( pArg->mode.autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
                     sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
                   }
                   sqlite3_reset(pStmt);
                   sqlite3_stmt_explain(pStmt, 0);
                   restore_debug_trace_modes();
          -      }
          -
          -      if( pArg ){
          -        int bIsExplain = (sqlite3_stmt_isexplain(pStmt)==1);
          -        pArg->cMode = pArg->mode;
          -        if( pArg->autoExplain ){
          -          if( bIsExplain ){
          -            pArg->cMode = MODE_Explain;
          -          }
          -          if( sqlite3_stmt_isexplain(pStmt)==2 ){
          -            pArg->cMode = MODE_EQP;
          -          }
          -        }
          -
          -        /* If the shell is currently in ".explain" mode, gather the extra
          -        ** data required to add indents to the output.*/
          -        if( pArg->cMode==MODE_Explain && bIsExplain ){
          -          explain_data_prepare(pArg, pStmt);
          -        }
          +        pArg->enableTimer = savedEnableTimer;
                 }
           
                 bind_prepared_stmt(pArg, pStmt);
          -      exec_prepared_stmt(pArg, pStmt);
          -      explain_data_delete(pArg);
          -      eqp_render(pArg, 0);
          +      if( isExplain && pArg->mode.autoExplain ){
          +        spec.eStyle = isExplain==1 ? QRF_STYLE_Explain : QRF_STYLE_Eqp;
          +        sqlite3_format_query_result(pStmt, &spec, pzErrMsg);
          +      }else if( pArg->mode.eMode==MODE_Www ){
          +        cli_printf(pArg->out,
          +              "
          \n" + "\n"); + spec.eStyle = QRF_STYLE_Html; + sqlite3_format_query_result(pStmt, &spec, pzErrMsg); + cli_printf(pArg->out, + "
          \n" + "
          ");
          +      }else{
          +        spec.eStyle = eStyle;
          +        sqlite3_format_query_result(pStmt, &spec, pzErrMsg);
          +      }
           
                 /* print usage stats if stats on */
                 if( pArg && pArg->statsOn ){
          @@ -4804,8 +3345,19 @@ static int shell_exec(
                 }
           
                 /* print loop-counters if required */
          -      if( pArg && pArg->scanstatsOn ){
          -        display_scanstats(db, pArg);
          +      if( pArg && pArg->mode.scanstatsOn ){
          +        char *zErr = 0;
          +        switch( pArg->mode.scanstatsOn ){
          +          case 1:   spec.eStyle = QRF_STYLE_Stats;     break;
          +          case 2:   spec.eStyle = QRF_STYLE_StatsEst;  break;
          +          default:  spec.eStyle = QRF_STYLE_StatsVm;   break;
          +        }
          +        sqlite3_reset(pStmt);
          +        rc = sqlite3_format_query_result(pStmt, &spec, &zErr);
          +        if( rc ){
          +          cli_printf(stderr, "Stats query failed: %s\n", zErr);
          +          sqlite3_free(zErr);
          +        }          
                 }
           
                 /* Finalize the statement just executed. If this fails, save a
          @@ -5001,14 +3553,14 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
               */
               if( db_int(p->db, "SELECT count(*) FROM sqlite_sequence")>0 ){
                 if( !p->writableSchema ){
          -        sqlite3_fputs("PRAGMA writable_schema=ON;\n", p->out);
          +        cli_puts("PRAGMA writable_schema=ON;\n", p->out);
                   p->writableSchema = 1;
                 }
          -      sqlite3_fputs("CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq);\n"
          +      cli_puts("CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq);\n"
                               "DELETE FROM sqlite_sequence;\n", p->out);
               }
             }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 && !noSys ){
          -    if( !dataOnly ) sqlite3_fputs("ANALYZE sqlite_schema;\n", p->out);
          +    if( !dataOnly ) cli_puts("ANALYZE sqlite_schema;\n", p->out);
             }else if( cli_strncmp(zTable, "sqlite_", 7)==0 ){
               return 0;
             }else if( dataOnly ){
          @@ -5016,7 +3568,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
             }else if( cli_strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
               char *zIns;
               if( !p->writableSchema ){
          -      sqlite3_fputs("PRAGMA writable_schema=ON;\n", p->out);
          +      cli_puts("PRAGMA writable_schema=ON;\n", p->out);
                 p->writableSchema = 1;
               }
               zIns = sqlite3_mprintf(
          @@ -5024,7 +3576,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
                  "VALUES('table','%q','%q',0,'%q');",
                  zTable, zTable, zSql);
               shell_check_oom(zIns);
          -    sqlite3_fprintf(p->out, "%s\n", zIns);
          +    cli_printf(p->out, "%s\n", zIns);
               sqlite3_free(zIns);
               return 0;
             }else{
          @@ -5036,8 +3588,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
               ShellText sTable;
               char **azCol;
               int i;
          -    char *savedDestTable;
          -    int savedMode;
          +    Mode savedMode;
           
               azCol = tableColumnList(p, zTable);
               if( azCol==0 ){
          @@ -5080,18 +3631,20 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
               appendText(&sSelect, " FROM ", 0);
               appendText(&sSelect, zTable, quoteChar(zTable));
           
          -    savedDestTable = p->zDestTable;
          +
               savedMode = p->mode;
          -    p->zDestTable = sTable.zTxt;
          -    p->mode = p->cMode = MODE_Insert;
          +    p->mode.spec.zTableName = (char*)zTable;
          +    p->mode.eMode = MODE_Insert;
          +    p->mode.spec.eText = QRF_TEXT_Sql;
          +    p->mode.spec.eBlob = QRF_BLOB_Sql;
          +    p->mode.spec.bTitles = QRF_No;
               rc = shell_exec(p, sSelect.zTxt, 0);
               if( (rc&0xff)==SQLITE_CORRUPT ){
          -      sqlite3_fputs("/****** CORRUPTION ERROR *******/\n", p->out);
          +      cli_puts("/****** CORRUPTION ERROR *******/\n", p->out);
                 toggleSelectOrder(p->db);
                 shell_exec(p, sSelect.zTxt, 0);
                 toggleSelectOrder(p->db);
               }
          -    p->zDestTable = savedDestTable;
               p->mode = savedMode;
               freeText(&sTable);
               freeText(&sSelect);
          @@ -5117,9 +3670,9 @@ static int run_schema_dump_query(
             if( rc==SQLITE_CORRUPT ){
               char *zQ2;
               int len = strlen30(zQuery);
          -    sqlite3_fputs("/****** CORRUPTION ERROR *******/\n", p->out);
          +    cli_puts("/****** CORRUPTION ERROR *******/\n", p->out);
               if( zErr ){
          -      sqlite3_fprintf(p->out, "/****** %s ******/\n", zErr);
          +      cli_printf(p->out, "/****** %s ******/\n", zErr);
                 sqlite3_free(zErr);
                 zErr = 0;
               }
          @@ -5128,7 +3681,7 @@ static int run_schema_dump_query(
               sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery);
               rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr);
               if( rc ){
          -      sqlite3_fprintf(p->out, "/****** ERROR: %s ******/\n", zErr);
          +      cli_printf(p->out, "/****** ERROR: %s ******/\n", zErr);
               }else{
                 rc = SQLITE_CORRUPT;
               }
          @@ -5186,8 +3739,8 @@ static const char *(azHelp[]) = {
             ".cd DIRECTORY            Change the working directory to DIRECTORY",
           #endif
             ".changes on|off          Show number of rows changed by SQL",
          +  ".check OPTIONS ...       Verify the results of a .testcase",
           #ifndef SQLITE_SHELL_FIDDLE
          -  ".check GLOB              Fail if output since .testcase does not match",
             ".clone NEWDB             Clone data into NEWDB from the existing database",
           #endif
             ".connection [close] [#]  Open or close an auxiliary database connection",
          @@ -5229,23 +3782,10 @@ static const char *(azHelp[]) = {
             "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
             "   --help                  Show CMD details",
             ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
          -  ".headers on|off          Turn display of headers on or off",
          +  ",headers on|off          Turn display of headers on or off",
             ".help ?-all? ?PATTERN?   Show help text for PATTERN",
           #ifndef SQLITE_SHELL_FIDDLE
             ".import FILE TABLE       Import data from FILE into TABLE",
          -  "   Options:",
          -  "     --ascii               Use \\037 and \\036 as column and row separators",
          -  "     --csv                 Use , and \\n as column and row separators",
          -  "     --skip N              Skip the first N rows of input",
          -  "     --schema S            Target table to be S.TABLE",
          -  "     -v                    \"Verbose\" - increase auxiliary output",
          -  "   Notes:",
          -  "     *  If TABLE does not exist, it is created.  The first row of input",
          -  "        determines the column names.",
          -  "     *  If neither --csv or --ascii are used, the input mode is derived",
          -  "        from the \".mode\" output mode",
          -  "     *  If FILE begins with \"|\" then it is a command that generates the",
          -  "        input text.",
           #endif
           #ifndef SQLITE_OMIT_TEST_CONTROL
             ".imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
          @@ -5270,42 +3810,12 @@ static const char *(azHelp[]) = {
             ".log on|off              Turn logging on or off.",
           #endif
             ".mode ?MODE? ?OPTIONS?   Set output mode",
          -  "   MODE is one of:",
          -  "     ascii       Columns/rows delimited by 0x1F and 0x1E",
          -  "     box         Tables using unicode box-drawing characters",
          -  "     csv         Comma-separated values",
          -  "     column      Output in columns.  (See .width)",
          -  "     html        HTML  code",
          -  "     insert      SQL insert statements for TABLE",
          -  "     json        Results in a JSON array",
          -  "     line        One value per line",
          -  "     list        Values delimited by \"|\"",
          -  "     markdown    Markdown table format",
          -  "     qbox        Shorthand for \"box --wrap 60 --quote\"",
          -  "     quote       Escape answers as for SQL",
          -  "     table       ASCII-art table",
          -  "     tabs        Tab-separated values",
          -  "     tcl         TCL list elements",
          -  "   OPTIONS: (for columnar modes or insert mode):",
          -  "     --escape T     ctrl-char escape; T is one of: symbol, ascii, off",
          -  "     --wrap N       Wrap output lines to no longer than N characters",
          -  "     --wordwrap B   Wrap or not at word boundaries per B (on/off)",
          -  "     --ww           Shorthand for \"--wordwrap 1\"",
          -  "     --quote        Quote output text as SQL literals",
          -  "     --noquote      Do not quote output text",
          -  "     TABLE          The name of SQL table used for \"insert\" mode",
           #ifndef SQLITE_SHELL_FIDDLE
             ".nonce STRING            Suspend safe mode for one command if nonce matches",
           #endif
             ".nullvalue STRING        Use STRING in place of NULL values",
           #ifndef SQLITE_SHELL_FIDDLE
             ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
          -  "     If FILE begins with '|' then open as a pipe",
          -  "       --bom    Put a UTF8 byte-order mark at the beginning",
          -  "       -e       Send output to the system text editor",
          -  "       --plain  Use text/plain output instead of HTML for -w option",
          -  "       -w       Send output as HTML to a web browser (same as \".www\")",
          -  "       -x       Send output as CSV to a spreadsheet (same as \".excel\")",
             /* Note that .open is (partially) available in WASM builds but is
             ** currently only intended to be used by the fiddle tool, not
             ** end users, so is "undocumented." */
          @@ -5331,14 +3841,6 @@ static const char *(azHelp[]) = {
             "        --zip           FILE is a ZIP archive",
           #ifndef SQLITE_SHELL_FIDDLE
             ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
          -  "   If FILE begins with '|' then open it as a pipe.",
          -  "   If FILE is 'off' then output is disabled.",
          -  "   Options:",
          -  "     --bom                 Prefix output with a UTF8 byte-order mark",
          -  "     -e                    Send output to the system text editor",
          -  "     --plain               Use text/plain for -w option",
          -  "     -w                    Send output to a web browser",
          -  "     -x                    Send output as CSV to a spreadsheet",
           #endif
             ".parameter CMD ...       Manage SQL parameter bindings",
             "   clear                   Erase all bindings",
          @@ -5354,6 +3856,7 @@ static const char *(azHelp[]) = {
             "   --once                    Do no more than one progress interrupt",
             "   --quiet|-q                No output except at interrupts",
             "   --reset                   Reset the count for each input and interrupt",
          +  "   --timeout S               Halt after running for S seconds",
           #endif
             ".prompt MAIN CONTINUE    Replace the standard prompts",
           #ifndef SQLITE_SHELL_FIDDLE
          @@ -5381,7 +3884,7 @@ static const char *(azHelp[]) = {
             "    Options:",
             "       --init               Create a new SELFTEST table",
             "       -v                   Verbose output",
          -  ".separator COL ?ROW?     Change the column and row separators",
          +  ",separator COL ?ROW?     Change the column and row separators",
           #if defined(SQLITE_ENABLE_SESSION)
             ".session ?NAME? CMD ...  Create or control sessions",
             "   Subcommands:",
          @@ -5408,7 +3911,7 @@ static const char *(azHelp[]) = {
           #if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
             ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
           #endif
          -  ".show                    Show the current values for various settings",
          +  ",show                    Show the current values for various settings",
             ".stats ?ARG?             Show stats or turn stats on or off",
             "   off                      Turn off automatic stat display",
             "   on                       Turn on automatic stat display",
          @@ -5418,13 +3921,11 @@ static const char *(azHelp[]) = {
             ".system CMD ARGS...      Run CMD ARGS... in a system shell",
           #endif
             ".tables ?TABLE?          List names of tables matching LIKE pattern TABLE",
          -#ifndef SQLITE_SHELL_FIDDLE
          -  ",testcase NAME           Begin redirecting output to 'testcase-out.txt'",
          -#endif
          +  ".testcase NAME           Begin a test case.",
             ",testctrl CMD ...        Run various sqlite3_test_control() operations",
             "                           Run \".testctrl\" with no arguments for details",
             ".timeout MS              Try opening locked tables for MS milliseconds",
          -  ".timer on|off            Turn SQL timer on or off",
          +  ".timer on|off|once       Turn SQL timer on or off.",
           #ifndef SQLITE_OMIT_TRACE
             ".trace ?OPTIONS?         Output each SQL statement as it is run",
             "    FILE                    Send output to FILE",
          @@ -5449,7 +3950,7 @@ static const char *(azHelp[]) = {
             ".vfsinfo ?AUX?           Information about the top-level VFS",
             ".vfslist                 List all available VFSes",
             ".vfsname ?AUX?           Print the name of the VFS stack",
          -  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
          +  ",width NUM1 NUM2 ...     Set minimum column widths for columnar output",
             "     Negative values right-justify",
           #ifndef SQLITE_SHELL_FIDDLE
             ".www                     Display output of the next command in web browser",
          @@ -5457,13 +3958,26 @@ static const char *(azHelp[]) = {
           #endif
           };
           
          +INSERT-USAGE-TEXT-HERE
          +
           /*
          -** Output help text for commands that match zPattern.
          -**
          -**    *   If zPattern is NULL, then show all documented commands, but
          -**        only give a one-line summary of each.
          -**
          -**    *   If zPattern is "-a" or "-all" or "--all" then show all help text
          +** Return a pointer to usage text for zCmd, or NULL if none exists.
          +*/
          +static const char *findUsage(const char *zCmd){
          +  int i;
          +  for(i=0; i return false
          +**    * If the size of the file is not a multiple of 512 -> return false
          +**    * If sqlite3_open() fails -> return false
          +**    * if sqlite3_prepare() or sqlite3_step() fails -> return false
          +**    * Otherwise -> return true
          +*/
          +static int isDatabaseFile(const char *zFile, int openFlags){
          +  sqlite3 *db = 0;
          +  sqlite3_stmt *pStmt = 0;
          +  int rc;
          +  sqlite3_int64 sz = fileSize(zFile);
          +  if( sz<512 || (sz%512)!=0 ) return 0;
          +  if( sqlite3_open_v2(zFile, &db, openFlags, 0)==SQLITE_OK
          +   && sqlite3_prepare_v2(db,"SELECT count(*) FROM sqlite_schema",-1,&pStmt,0)
          +           ==SQLITE_OK
          +   && sqlite3_step(pStmt)==SQLITE_ROW
          +  ){
          +    rc = 1;
          +  }else{
          +    rc = 0;
          +  }
          +  sqlite3_finalize(pStmt);
          +  sqlite3_close(db);
          +  return rc;
          +}
          +
           /*
           ** Try to deduce the type of file for zName based on its content.  Return
           ** one of the SHELL_OPEN_* constants.
          @@ -5670,20 +4240,12 @@ static int session_filter(void *pCtx, const char *zTab){
           int deduceDatabaseType(const char *zName, int dfltZip, int openFlags){
             FILE *f;
             size_t n;
          -  sqlite3 *db = 0;
          -  sqlite3_stmt *pStmt = 0;
             int rc = SHELL_OPEN_UNSPEC;
             char zBuf[100];
             if( access(zName,0)!=0 ) goto database_type_by_name;
          -  if( sqlite3_open_v2(zName, &db, openFlags, 0)==SQLITE_OK
          -   && sqlite3_prepare_v2(db,"SELECT count(*) FROM sqlite_schema",-1,&pStmt,0)
          -           ==SQLITE_OK
          -   && sqlite3_step(pStmt)==SQLITE_ROW
          -  ){
          +  if( isDatabaseFile(zName, openFlags) ){
               rc = SHELL_OPEN_NORMAL;
             }
          -  sqlite3_finalize(pStmt);
          -  sqlite3_close(db);
             if( rc==SHELL_OPEN_NORMAL ) return SHELL_OPEN_NORMAL;
             f = sqlite3_fopen(zName, "rb");
             if( f==0 ) goto database_type_by_name;
          @@ -5718,6 +4280,35 @@ database_type_by_name:
             return rc;
           }
           
          +/*
          +** If the text in z[] is the name of a readable file and that file appears
          +** to contain SQL text and/or dot-commands, then return true.  If z[] is
          +** not a file, or if the file is unreadable, or if the file is a database
          +** or anything else that is not SQL text and dot-commands, then return false.
          +**
          +** If the bLeaveUninit flag is set, then be sure to leave SQLite in an
          +** uninitialized state.  This means invoking sqlite3_shutdown() after any
          +** SQLite API is used.
          +**
          +** Some amount of guesswork is involved in this decision.
          +*/
          +static int isScriptFile(const char *z, int bLeaveUninit){
          +  sqlite3_int64 sz = fileSize(z);
          +  if( sz<=0 ) return 0;
          +  if( (sz%512)==0 ){
          +    int rc;
          +    sqlite3_initialize();
          +    rc = isDatabaseFile(z, SQLITE_OPEN_READONLY);
          +    if( bLeaveUninit ){
          +      sqlite3_shutdown();
          +    }
          +    if( rc ) return 0;  /* Is a database */
          +  }
          +  if( sqlite3_strlike("%.sql",z,0)==0 ) return 1;
          +  if( sqlite3_strlike("%.txt",z,0)==0 ) return 1;
          +  return 0;
          +}
          +
           #ifndef SQLITE_OMIT_DESERIALIZE
           /*
           ** Reconstruct an in-memory database using the output from the "dbtotxt"
          @@ -5739,7 +4330,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
             if( zDbFilename ){
               in = sqlite3_fopen(zDbFilename, "r");
               if( in==0 ){
          -      sqlite3_fprintf(stderr,"cannot open \"%s\" for reading\n", zDbFilename);
          +      cli_printf(stderr,"cannot open \"%s\" for reading\n", zDbFilename);
                 return 0;
               }
               nLine = 0;
          @@ -5755,7 +4346,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
             if( rc!=2 ) goto readHexDb_error;
             if( n<0 ) goto readHexDb_error;
             if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
          -    sqlite3_fputs("invalid pagesize\n", stderr);
          +    cli_puts("invalid pagesize\n", stderr);
               goto readHexDb_error;
             }
             sz = ((i64)n+pgsz-1)&~(pgsz-1); /* Round up to nearest multiple of pgsz */
          @@ -5766,7 +4357,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
               int j = 0;                    /* Page number from "| page" line */
               int k = 0;                    /* Offset from "| page" line */
               if( nLine>=2000000000 ){
          -      sqlite3_fprintf(stderr, "input too big\n");
          +      cli_printf(stderr, "input too big\n");
                 goto readHexDb_error;
               }
               rc = sscanf(zLine, "| page %d offset %d", &j, &k);
          @@ -5807,7 +4398,7 @@ readHexDb_error:
               p->lineno = nLine;
             }
             sqlite3_free(a);
          -  sqlite3_fprintf(stderr,"Error on line %lld of --hexdb input\n", nLine);
          +  cli_printf(stderr,"Error on line %lld of --hexdb input\n", nLine);
             return 0;
           }
           #endif /* SQLITE_OMIT_DESERIALIZE */
          @@ -5912,19 +4503,19 @@ static void open_db(ShellState *p, int openFlags){
                 }
               }
               if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
          -      sqlite3_fprintf(stderr,"Error: unable to open database \"%s\": %s\n",
          +      cli_printf(stderr,"Error: unable to open database \"%s\": %s\n",
                       zDbFilename, sqlite3_errmsg(p->db));
                 if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
          -        exit(1);
          +        cli_exit(1);
                 }
                 sqlite3_close(p->db);
                 sqlite3_open(":memory:", &p->db);
                 if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
          -        sqlite3_fputs("Also: unable to open substitute in-memory database.\n",
          +        cli_puts("Also: unable to open substitute in-memory database.\n",
                                 stderr);
          -        exit(1);
          +        cli_exit(1);
                 }else{
          -        sqlite3_fprintf(stderr,
          +        cli_printf(stderr,
                         "Notice: using substitute in-memory database instead of \"%s\"\n",
                         zDbFilename);
                 }
          @@ -6002,6 +4593,8 @@ static void open_db(ShellState *p, int openFlags){
                                       shellModuleSchema, 0, 0);
               sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
                                       shellPutsFunc, 0, 0);
          +    sqlite3_create_function(p->db, "shell_format_schema", 2, SQLITE_UTF8, p,
          +                            shellFormatSchema, 0, 0);
               sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
                                       shellUSleepFunc, 0, 0);
           #ifndef SQLITE_NOHAVE_SYSTEM
          @@ -6036,7 +4629,7 @@ static void open_db(ShellState *p, int openFlags){
                              SQLITE_DESERIALIZE_RESIZEABLE |
                              SQLITE_DESERIALIZE_FREEONCLOSE);
                 if( rc ){
          -        sqlite3_fprintf(stderr,"Error: sqlite3_deserialize() returns %d\n", rc);
          +        cli_printf(stderr,"Error: sqlite3_deserialize() returns %d\n", rc);
                 }
                 if( p->szMax>0 ){
                   sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax);
          @@ -6051,7 +4644,7 @@ static void open_db(ShellState *p, int openFlags){
               }
           #endif
               sqlite3_db_config(
          -        p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0
          +        p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->mode.scanstatsOn, (int*)0
               );
             }
           }
          @@ -6062,7 +4655,7 @@ static void open_db(ShellState *p, int openFlags){
           void close_db(sqlite3 *db){
             int rc = sqlite3_close(db);
             if( rc ){
          -    sqlite3_fprintf(stderr,
          +    cli_printf(stderr,
                   "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db));
             }
           }
          @@ -6235,7 +4828,7 @@ static int booleanValue(const char *zArg){
             if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
               return 0;
             }
          -  sqlite3_fprintf(stderr,
          +  cli_printf(stderr,
                  "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", zArg);
             return 0;
           }
          @@ -6263,18 +4856,18 @@ static void output_file_close(FILE *f){
           ** recognized and do the right thing.  NULL is returned if the output
           ** filename is "off".
           */
          -static FILE *output_file_open(const char *zFile){
          +static FILE *output_file_open(ShellState *p, const char *zFile){
             FILE *f;
             if( cli_strcmp(zFile,"stdout")==0 ){
               f = stdout;
             }else if( cli_strcmp(zFile, "stderr")==0 ){
               f = stderr;
          -  }else if( cli_strcmp(zFile, "off")==0 ){
          +  }else if( cli_strcmp(zFile, "off")==0 || p->bSafeMode ){
               f = 0;
             }else{
               f = sqlite3_fopen(zFile, "w");
               if( f==0 ){
          -      sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zFile);
          +      cli_printf(stderr,"Error: cannot open \"%s\"\n", zFile);
               }
             }
             return f;
          @@ -6327,12 +4920,12 @@ static int sql_trace_callback(
             switch( mType ){
               case SQLITE_TRACE_ROW:
               case SQLITE_TRACE_STMT: {
          -      sqlite3_fprintf(p->traceOut, "%.*s;\n", (int)nSql, zSql);
          +      cli_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql);
                 break;
               }
               case SQLITE_TRACE_PROFILE: {
                 sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
          -      sqlite3_fprintf(p->traceOut,
          +      cli_printf(p->traceOut,
                                 "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec);
                 break;
               }
          @@ -6361,7 +4954,9 @@ struct ImportCtx {
             const char *zFile;  /* Name of the input file */
             FILE *in;           /* Read the CSV text from this input stream */
             int (SQLITE_CDECL *xCloser)(FILE*);      /* Func to close in */
          +  char *zIn;          /* Input text */
             char *z;            /* Accumulated text for a field */
          +  i64 nUsed;          /* Bytes of zIn[] used so far */
             i64 n;              /* Number of bytes in z */
             i64 nAlloc;         /* Space allocated for z[] */
             int nLine;          /* Current line number */
          @@ -6371,6 +4966,8 @@ struct ImportCtx {
             int cTerm;          /* Character that terminated the most recent field */
             int cColSep;        /* The column separator character.  (Usually ",") */
             int cRowSep;        /* The row separator character.  (Usually "\n") */
          +  int cQEscape;       /* Escape character with "...".  0 for none */
          +  int cUQEscape;      /* Escape character not with "...".  0 for none */
           };
           
           /* Clean up resourced used by an ImportCtx */
          @@ -6381,9 +4978,28 @@ static void import_cleanup(ImportCtx *p){
             }
             sqlite3_free(p->z);
             p->z = 0;
          +  if( p->zIn ){
          +    sqlite3_free(p->zIn);
          +    p->zIn = 0;
          +  }
          +}
          +
          +/* Read a single character of the .import input text.  Return EOF
          +** at end-of-file.
          +*/
          +static int import_getc(ImportCtx *p){
          +  if( p->in ){
          +    return fgetc(p->in);
          +  }else if( p->zIn && p->zIn[p->nUsed]!=0 ){
          +    return p->zIn[p->nUsed++];
          +  }else{
          +    return EOF;
          +  }
           }
           
          -/* Append a single byte to z[] */
          +/* Append a single byte to the field value begin constructed
          +** in the p->z[] buffer
          +*/
           static void import_append_char(ImportCtx *p, int c){
             if( p->n+1>=p->nAlloc ){
               p->nAlloc += p->nAlloc + 100;
          @@ -6399,8 +5015,8 @@ static void import_append_char(ImportCtx *p, int c){
           **   +  Input comes from p->in.
           **   +  Store results in p->z of length p->n.  Space to hold p->z comes
           **      from sqlite3_malloc64().
          -**   +  Use p->cSep as the column separator.  The default is ",".
          -**   +  Use p->rSep as the row separator.  The default is "\n".
          +**   +  Use p->cColSep as the column separator.  The default is ",".
          +**   +  Use p->cRowSep as the row separator.  The default is "\n".
           **   +  Keep track of the line number in p->nLine.
           **   +  Store the character that terminates the field in p->cTerm.  Store
           **      EOF on end-of-file.
          @@ -6411,7 +5027,7 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
             int cSep = (u8)p->cColSep;
             int rSep = (u8)p->cRowSep;
             p->n = 0;
          -  c = fgetc(p->in);
          +  c = import_getc(p);
             if( c==EOF || seenInterrupt ){
               p->cTerm = EOF;
               return 0;
          @@ -6420,10 +5036,17 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
               int pc, ppc;
               int startLine = p->nLine;
               int cQuote = c;
          +    int cEsc = (u8)p->cQEscape;
               pc = ppc = 0;
               while( 1 ){
          -      c = fgetc(p->in);
          +      c = import_getc(p);
                 if( c==rSep ) p->nLine++;
          +      if( c==cEsc && cEsc!=0 ){
          +        c = import_getc(p);
          +        import_append_char(p, c);
          +        ppc = pc = 0;
          +        continue;
          +      }
                 if( c==cQuote ){
                   if( pc==cQuote ){
                     pc = 0;
          @@ -6440,11 +5063,11 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
                   break;
                 }
                 if( pc==cQuote && c!='\r' ){
          -        sqlite3_fprintf(stderr,"%s:%d: unescaped %c character\n", 
          -                        p->zFile, p->nLine, cQuote);
          +        cli_printf(stderr,"%s:%d: unescaped %c character\n", 
          +                   p->zFile, p->nLine, cQuote);
                 }
                 if( c==EOF ){
          -        sqlite3_fprintf(stderr,"%s:%d: unterminated %c-quoted field\n",
          +        cli_printf(stderr,"%s:%d: unterminated %c-quoted field\n",
                         p->zFile, startLine, cQuote);
                   p->cTerm = c;
                   break;
          @@ -6456,12 +5079,13 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
             }else{
               /* If this is the first field being parsed and it begins with the
               ** UTF-8 BOM  (0xEF BB BF) then skip the BOM */
          +    int cEsc = p->cUQEscape;
               if( (c&0xff)==0xef && p->bNotFirst==0 ){
                 import_append_char(p, c);
          -      c = fgetc(p->in);
          +      c = import_getc(p);
                 if( (c&0xff)==0xbb ){
                   import_append_char(p, c);
          -        c = fgetc(p->in);
          +        c = import_getc(p);
                   if( (c&0xff)==0xbf ){
                     p->bNotFirst = 1;
                     p->n = 0;
          @@ -6470,8 +5094,9 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
                 }
               }
               while( c!=EOF && c!=cSep && c!=rSep ){
          +      if( c==cEsc && cEsc!=0 ) c = import_getc(p);
                 import_append_char(p, c);
          -      c = fgetc(p->in);
          +      c = import_getc(p);
               }
               if( c==rSep ){
                 p->nLine++;
          @@ -6489,8 +5114,8 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
           **   +  Input comes from p->in.
           **   +  Store results in p->z of length p->n.  Space to hold p->z comes
           **      from sqlite3_malloc64().
          -**   +  Use p->cSep as the column separator.  The default is "\x1F".
          -**   +  Use p->rSep as the row separator.  The default is "\x1E".
          +**   +  Use p->cColSep as the column separator.  The default is "\x1F".
          +**   +  Use p->cRowSep as the row separator.  The default is "\x1E".
           **   +  Keep track of the row number in p->nLine.
           **   +  Store the character that terminates the field in p->cTerm.  Store
           **      EOF on end-of-file.
          @@ -6501,14 +5126,14 @@ static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){
             int cSep = (u8)p->cColSep;
             int rSep = (u8)p->cRowSep;
             p->n = 0;
          -  c = fgetc(p->in);
          +  c = import_getc(p);
             if( c==EOF || seenInterrupt ){
               p->cTerm = EOF;
               return 0;
             }
             while( c!=EOF && c!=cSep && c!=rSep ){
               import_append_char(p, c);
          -    c = fgetc(p->in);
          +    c = import_getc(p);
             }
             if( c==rSep ){
               p->nLine++;
          @@ -6543,7 +5168,7 @@ static void tryToCloneData(
             shell_check_oom(zQuery);
             rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
             if( rc ){
          -    sqlite3_fprintf(stderr,"Error %d: %s on [%s]\n",
          +    cli_printf(stderr,"Error %d: %s on [%s]\n",
                     sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery);
               goto end_data_xfer;
             }
          @@ -6560,7 +5185,7 @@ static void tryToCloneData(
             memcpy(zInsert+i, ");", 3);
             rc = sqlite3_prepare_v2(newDb, zInsert, -1, &pInsert, 0);
             if( rc ){
          -    sqlite3_fprintf(stderr,"Error %d: %s on [%s]\n",
          +    cli_printf(stderr,"Error %d: %s on [%s]\n",
                     sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb), zInsert);
               goto end_data_xfer;
             }
          @@ -6596,7 +5221,7 @@ static void tryToCloneData(
                 } /* End for */
                 rc = sqlite3_step(pInsert);
                 if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){
          -        sqlite3_fprintf(stderr,"Error %d: %s\n",
          +        cli_printf(stderr,"Error %d: %s\n",
                         sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb));
                 }
                 sqlite3_reset(pInsert);
          @@ -6614,7 +5239,7 @@ static void tryToCloneData(
               shell_check_oom(zQuery);
               rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
               if( rc ){
          -      sqlite3_fprintf(stderr,"Warning: cannot step \"%s\" backwards", zTable);
          +      cli_printf(stderr,"Warning: cannot step \"%s\" backwards", zTable);
                 break;
               }
             } /* End for(k=0...) */
          @@ -6651,7 +5276,7 @@ static void tryToCloneSchema(
             shell_check_oom(zQuery);
             rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
             if( rc ){
          -    sqlite3_fprintf(stderr,
          +    cli_printf(stderr,
                     "Error: (%d) %s on [%s]\n", sqlite3_extended_errcode(p->db),
                     sqlite3_errmsg(p->db), zQuery);
               goto end_schema_xfer;
          @@ -6661,10 +5286,10 @@ static void tryToCloneSchema(
               zSql = sqlite3_column_text(pQuery, 1);
               if( zName==0 || zSql==0 ) continue;
               if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){
          -      sqlite3_fprintf(stdout, "%s... ", zName); fflush(stdout);
          +      cli_printf(stdout, "%s... ", zName); fflush(stdout);
                 sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
                 if( zErrMsg ){
          -        sqlite3_fprintf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
          +        cli_printf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
                   sqlite3_free(zErrMsg);
                   zErrMsg = 0;
                 }
          @@ -6682,7 +5307,7 @@ static void tryToCloneSchema(
               shell_check_oom(zQuery);
               rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
               if( rc ){
          -      sqlite3_fprintf(stderr,"Error: (%d) %s on [%s]\n",
          +      cli_printf(stderr,"Error: (%d) %s on [%s]\n",
                       sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery);
                 goto end_schema_xfer;
               }
          @@ -6691,10 +5316,10 @@ static void tryToCloneSchema(
                 zSql = sqlite3_column_text(pQuery, 1);
                 if( zName==0 || zSql==0 ) continue;
                 if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue;
          -      sqlite3_fprintf(stdout, "%s... ", zName); fflush(stdout);
          +      cli_printf(stdout, "%s... ", zName); fflush(stdout);
                 sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
                 if( zErrMsg ){
          -        sqlite3_fprintf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
          +        cli_printf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
                   sqlite3_free(zErrMsg);
                   zErrMsg = 0;
                 }
          @@ -6718,12 +5343,12 @@ static void tryToClone(ShellState *p, const char *zNewDb){
             int rc;
             sqlite3 *newDb = 0;
             if( access(zNewDb,0)==0 ){
          -    sqlite3_fprintf(stderr,"File \"%s\" already exists.\n", zNewDb);
          +    cli_printf(stderr,"File \"%s\" already exists.\n", zNewDb);
               return;
             }
             rc = sqlite3_open(zNewDb, &newDb);
             if( rc ){
          -    sqlite3_fprintf(stderr,
          +    cli_printf(stderr,
                   "Cannot create output database: %s\n", sqlite3_errmsg(newDb));
             }else{
               sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0);
          @@ -6742,12 +5367,12 @@ static void tryToClone(ShellState *p, const char *zNewDb){
           */
           static void output_redir(ShellState *p, FILE *pfNew){
             if( p->out != stdout ){
          -    sqlite3_fputs("Output already redirected.\n", stderr);
          +    cli_puts("Output already redirected.\n", stderr);
             }else{
               p->out = pfNew;
               setCrlfMode(p);
          -    if( p->mode==MODE_Www ){
          -      sqlite3_fputs(
          +    if( p->mode.eMode==MODE_Www ){
          +      cli_puts(
                   "\n"
                   "
          \n",
                   p->out
          @@ -6769,8 +5394,8 @@ static void output_reset(ShellState *p){
               pclose(p->out);
           #endif
             }else{
          -    if( p->mode==MODE_Www ){
          -      sqlite3_fputs("
          \n", p->out); + if( p->mode.eMode==MODE_Www ){ + cli_puts("\n", p->out); } output_file_close(p->out); #ifndef SQLITE_NOHAVE_SYSTEM @@ -6786,7 +5411,7 @@ static void output_reset(ShellState *p){ char *zCmd; zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile); if( system(zCmd) ){ - sqlite3_fprintf(stderr,"Failed: [%s]\n", zCmd); + cli_printf(stderr,"Failed: [%s]\n", zCmd); }else{ /* Give the start/open/xdg-open command some time to get ** going before we continue, and potential delete the @@ -6794,7 +5419,7 @@ static void output_reset(ShellState *p){ sqlite3_sleep(2000); } sqlite3_free(zCmd); - outputModePop(p); + modePop(p); p->doXdgOpen = 0; } #endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ @@ -6802,6 +5427,10 @@ static void output_reset(ShellState *p){ p->outfile[0] = 0; p->out = stdout; setCrlfMode(p); + if( cli_output_capture ){ + sqlite3_str_free(cli_output_capture); + cli_output_capture = 0; + } } #else # define output_redir(SS,pfO) @@ -6886,7 +5515,7 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1", -1, &pStmt, 0); if( rc ){ - sqlite3_fprintf(stderr,"error: %s\n", sqlite3_errmsg(p->db)); + cli_printf(stderr,"error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); return 1; } @@ -6899,28 +5528,28 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ memcpy(aHdr, pb, 100); sqlite3_finalize(pStmt); }else{ - sqlite3_fputs("unable to read database header\n", stderr); + cli_puts("unable to read database header\n", stderr); sqlite3_finalize(pStmt); return 1; } i = get2byteInt(aHdr+16); if( i==1 ) i = 65536; - sqlite3_fprintf(p->out, "%-20s %d\n", "database page size:", i); - sqlite3_fprintf(p->out, "%-20s %d\n", "write format:", aHdr[18]); - sqlite3_fprintf(p->out, "%-20s %d\n", "read format:", aHdr[19]); - sqlite3_fprintf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); + cli_printf(p->out, "%-20s %d\n", "database page size:", i); + cli_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]); + cli_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]); + cli_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); for(i=0; iout, "%-20s %u", aField[i].zName, val); + cli_printf(p->out, "%-20s %u", aField[i].zName, val); switch( ofst ){ case 56: { - if( val==1 ) sqlite3_fputs(" (utf8)", p->out); - if( val==2 ) sqlite3_fputs(" (utf16le)", p->out); - if( val==3 ) sqlite3_fputs(" (utf16be)", p->out); + if( val==1 ) cli_puts(" (utf8)", p->out); + if( val==2 ) cli_puts(" (utf16le)", p->out); + if( val==3 ) cli_puts(" (utf16be)", p->out); } } - sqlite3_fputs("\n", p->out); + cli_puts("\n", p->out); } if( zDb==0 ){ zSchemaTab = sqlite3_mprintf("main.sqlite_schema"); @@ -6931,11 +5560,11 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ } for(i=0; idb, aQuery[i].zSql, zSchemaTab); - sqlite3_fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val); + cli_printf(p->out, "%-20s %d\n", aQuery[i].zName, val); } sqlite3_free(zSchemaTab); sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); - sqlite3_fprintf(p->out, "%-20s %u\n", "data version", iDataVersion); + cli_printf(p->out, "%-20s %u\n", "data version", iDataVersion); return 0; } #endif /* SQLITE_SHELL_HAVE_RECOVER */ @@ -6995,7 +5624,7 @@ static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){ } zName = strdup(zTail); shell_check_oom(zName); - sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n", + cli_printf(p->out, "| size %lld pagesize %d filename %s\n", nPage*pgSz, pgSz, zName); sqlite3_finalize(pStmt); pStmt = 0; @@ -7011,27 +5640,27 @@ static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){ for(j=0; j<16 && aLine[j]==0; j++){} if( j==16 ) continue; if( !seenPageLabel ){ - sqlite3_fprintf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz); + cli_printf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz); seenPageLabel = 1; } - sqlite3_fprintf(p->out, "| %5d:", i); - for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]); - sqlite3_fprintf(p->out, " "); + cli_printf(p->out, "| %5d:", i); + for(j=0; j<16; j++) cli_printf(p->out, " %02x", aLine[j]); + cli_printf(p->out, " "); for(j=0; j<16; j++){ unsigned char c = (unsigned char)aLine[j]; - sqlite3_fprintf(p->out, "%c", bShow[c]); + cli_printf(p->out, "%c", bShow[c]); } - sqlite3_fprintf(p->out, "\n"); + cli_printf(p->out, "\n"); } } sqlite3_finalize(pStmt); - sqlite3_fprintf(p->out, "| end %s\n", zName); + cli_printf(p->out, "| end %s\n", zName); free(zName); return 0; dbtotxt_error: if( rc ){ - sqlite3_fprintf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db)); + cli_printf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db)); } sqlite3_finalize(pStmt); free(zName); @@ -7042,7 +5671,7 @@ dbtotxt_error: ** Print the given string as an error message. */ static void shellEmitError(const char *zErr){ - sqlite3_fprintf(stderr,"Error: %s\n", zErr); + cli_printf(stderr,"Error: %s\n", zErr); } /* ** Print the current sqlite3_errmsg() value to stderr and return 1. @@ -7225,39 +5854,42 @@ static void clearTempFile(ShellState *p){ p->zTempFile = 0; } +/* Forward reference */ +static char *find_home_dir(int clearFlag); + /* ** Create a new temp file name with the given suffix. +** +** Because the classic temp folders like /tmp are no longer +** accessible to web browsers, for security reasons, create the +** temp file in the user's home directory. */ static void newTempFile(ShellState *p, const char *zSuffix){ - clearTempFile(p); - sqlite3_free(p->zTempFile); - p->zTempFile = 0; - if( p->db ){ - sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile); - } - if( p->zTempFile==0 ){ - /* If p->db is an in-memory database then the TEMPFILENAME file-control - ** will not work and we will need to fallback to guessing */ - char *zTemp; - sqlite3_uint64 r; - sqlite3_randomness(sizeof(r), &r); - zTemp = getenv("TEMP"); - if( zTemp==0 ) zTemp = getenv("TMP"); - if( zTemp==0 ){ + char *zHome; /* Home directory */ + int i; /* Loop counter */ + sqlite3_uint64 r = 0; /* Integer with 64 bits of randomness */ + char zRand[32]; /* Text string with 160 bits of randomness */ #ifdef _WIN32 - zTemp = "\\tmp"; + const char cDirSep = '\\'; #else - zTemp = "/tmp"; + const char cDirSep = '/'; #endif - } - p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix); - }else{ - p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix); + + for(i=0; i<31; i++){ + if( (i%12)==0 ) sqlite3_randomness(sizeof(r),&r); + zRand[i] = "0123456789abcdefghijklmnopqrstuvwxyz"[r%36]; + r /= 36; } + zRand[i] = 0; + clearTempFile(p); + sqlite3_free(p->zTempFile); + p->zTempFile = 0; + zHome = find_home_dir(0); + p->zTempFile = sqlite3_mprintf("%s%ctemp-%s.%s", + zHome,cDirSep,zRand,zSuffix); shell_check_oom(p->zTempFile); } - /* ** The implementation of SQL scalar function fkey_collate_clause(), used ** by the ".lint fkey-indexes" command. This scalar function is always @@ -7402,7 +6034,7 @@ static int lintFkeyIndexes( zIndent = " "; } else{ - sqlite3_fprintf(stderr, + cli_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", azArg[0], azArg[1]); return SQLITE_ERROR; } @@ -7447,22 +6079,22 @@ static int lintFkeyIndexes( if( rc!=SQLITE_OK ) break; if( res<0 ){ - sqlite3_fputs("Error: internal error", stderr); + cli_puts("Error: internal error", stderr); break; }else{ if( bGroupByParent && (bVerbose || res==0) && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) ){ - sqlite3_fprintf(out, "-- Parent table %s\n", zParent); + cli_printf(out, "-- Parent table %s\n", zParent); sqlite3_free(zPrev); zPrev = sqlite3_mprintf("%s", zParent); } if( res==0 ){ - sqlite3_fprintf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); + cli_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); }else if( bVerbose ){ - sqlite3_fprintf(out, + cli_printf(out, "%s/* no extra indexes required for %s -> %s */\n", zIndent, zFrom, zTarget ); @@ -7472,16 +6104,16 @@ static int lintFkeyIndexes( sqlite3_free(zPrev); if( rc!=SQLITE_OK ){ - sqlite3_fprintf(stderr,"%s\n", sqlite3_errmsg(db)); + cli_printf(stderr,"%s\n", sqlite3_errmsg(db)); } rc2 = sqlite3_finalize(pSql); if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ rc = rc2; - sqlite3_fprintf(stderr,"%s\n", sqlite3_errmsg(db)); + cli_printf(stderr,"%s\n", sqlite3_errmsg(db)); } }else{ - sqlite3_fprintf(stderr,"%s\n", sqlite3_errmsg(db)); + cli_printf(stderr,"%s\n", sqlite3_errmsg(db)); } return rc; @@ -7501,9 +6133,9 @@ static int lintDotCommand( return lintFkeyIndexes(pState, azArg, nArg); usage: - sqlite3_fprintf(stderr,"Usage %s sub-command ?switches...?\n", azArg[0]); - sqlite3_fprintf(stderr, "Where sub-commands are:\n"); - sqlite3_fprintf(stderr, " fkey-indexes\n"); + cli_printf(stderr,"Usage %s sub-command ?switches...?\n", azArg[0]); + cli_printf(stderr, "Where sub-commands are:\n"); + cli_printf(stderr, " fkey-indexes\n"); return SQLITE_ERROR; } @@ -7517,7 +6149,7 @@ static void shellPrepare( if( *pRc==SQLITE_OK ){ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); if( rc!=SQLITE_OK ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "sql error: %s (%d)\n", sqlite3_errmsg(db), sqlite3_errcode(db)); *pRc = rc; } @@ -7562,7 +6194,7 @@ static void shellFinalize( int rc = sqlite3_finalize(pStmt); if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ - sqlite3_fprintf(stderr,"SQL error: %s\n", sqlite3_errmsg(db)); + cli_printf(stderr,"SQL error: %s\n", sqlite3_errmsg(db)); } *pRc = rc; } @@ -7584,7 +6216,7 @@ void shellReset( if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ sqlite3 *db = sqlite3_db_handle(pStmt); - sqlite3_fprintf(stderr,"SQL error: %s\n", sqlite3_errmsg(db)); + cli_printf(stderr,"SQL error: %s\n", sqlite3_errmsg(db)); } *pRc = rc; } @@ -7637,9 +6269,9 @@ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ va_end(ap); shellEmitError(z); if( pAr->fromCmdLine ){ - sqlite3_fputs("Use \"-A\" for more help\n", stderr); + cli_puts("Use \"-A\" for more help\n", stderr); }else{ - sqlite3_fputs("Use \".archive --help\" for more help\n", stderr); + cli_puts("Use \".archive --help\" for more help\n", stderr); } sqlite3_free(z); return SQLITE_ERROR; @@ -7739,7 +6371,7 @@ static int arParseCommand( struct ArSwitch *pEnd = &aSwitch[nSwitch]; if( nArg<=1 ){ - sqlite3_fprintf(stderr, "Wrong number of arguments. Usage:\n"); + cli_printf(stderr, "Wrong number of arguments. Usage:\n"); return arUsage(stderr); }else{ char *z = azArg[1]; @@ -7845,7 +6477,7 @@ static int arParseCommand( } } if( pAr->eCmd==0 ){ - sqlite3_fprintf(stderr, "Required argument missing. Usage:\n"); + cli_printf(stderr, "Required argument missing. Usage:\n"); return arUsage(stderr); } return SQLITE_OK; @@ -7888,7 +6520,7 @@ static int arCheckEntries(ArCommand *pAr){ } shellReset(&rc, pTest); if( rc==SQLITE_OK && bOk==0 ){ - sqlite3_fprintf(stderr,"not found in archive: %s\n", z); + cli_printf(stderr,"not found in archive: %s\n", z); rc = SQLITE_ERROR; } } @@ -7971,15 +6603,15 @@ static int arListCommand(ArCommand *pAr){ shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], pAr->zSrcTable, zWhere); if( pAr->bDryRun ){ - sqlite3_fprintf(pAr->out, "%s\n", sqlite3_sql(pSql)); + cli_printf(pAr->out, "%s\n", sqlite3_sql(pSql)); }else{ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ if( pAr->bVerbose ){ - sqlite3_fprintf(pAr->out, "%s % 10d %s %s\n", + cli_printf(pAr->out, "%s % 10d %s %s\n", sqlite3_column_text(pSql, 0), sqlite3_column_int(pSql, 1), sqlite3_column_text(pSql, 2),sqlite3_column_text(pSql, 3)); }else{ - sqlite3_fprintf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); + cli_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); } } } @@ -8006,7 +6638,7 @@ static int arRemoveCommand(ArCommand *pAr){ zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;", pAr->zSrcTable, zWhere); if( pAr->bDryRun ){ - sqlite3_fprintf(pAr->out, "%s\n", zSql); + cli_printf(pAr->out, "%s\n", zSql); }else{ char *zErr = 0; rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0); @@ -8019,7 +6651,7 @@ static int arRemoveCommand(ArCommand *pAr){ } } if( zErr ){ - sqlite3_fprintf(stdout, "ERROR: %s\n", zErr); /* stdout? */ + cli_printf(stdout, "ERROR: %s\n", zErr); /* stdout? */ sqlite3_free(zErr); } } @@ -8034,11 +6666,15 @@ static int arRemoveCommand(ArCommand *pAr){ */ static int arExtractCommand(ArCommand *pAr){ const char *zSql1 = - "SELECT " - " ($dir || name)," - " writefile(($dir || name), %s, mode, mtime) " - "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" - " AND name NOT GLOB '*..[/\\]*'"; + "WITH dest(dpath,dlen) AS (SELECT realpath($dir),length(realpath($dir)))\n" + "SELECT ($dir || name),\n" + " CASE WHEN $dryrun THEN 0\n" + " ELSE writefile($dir||name, %s, mode, mtime) END\n" + " FROM dest CROSS JOIN %s\n" + " WHERE (%s)\n" + " AND (data IS NULL OR $pass==0)\n" /* Dirs both passes */ + " AND dpath=substr(realpath($dir||name),1,dlen)\n" /* No escapes */ + " AND name NOT GLOB '*..[/\\]*'\n"; /* No /../ in paths */ const char *azExtraArg[] = { "sqlar_uncompress(data, sz)", @@ -8073,24 +6709,28 @@ static int arExtractCommand(ArCommand *pAr){ if( rc==SQLITE_OK ){ j = sqlite3_bind_parameter_index(pSql, "$dir"); sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); - - /* Run the SELECT statement twice. The first time, writefile() is called - ** for all archive members that should be extracted. The second time, - ** only for the directories. This is because the timestamps for - ** extracted directories must be reset after they are populated (as - ** populating them changes the timestamp). */ + j = sqlite3_bind_parameter_index(pSql, "$dryrun"); + sqlite3_bind_int(pSql, j, pAr->bDryRun); + + /* Run the SELECT statement twice + ** (0) writefile() all files and directories + ** (1) writefile() for directory again + ** The second pass is so that the timestamps for extracted directories + ** will be reset to the value in the archive, since populating them + ** in the first pass will have changed the timestamp. */ for(i=0; i<2; i++){ - j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); + j = sqlite3_bind_parameter_index(pSql, "$pass"); sqlite3_bind_int(pSql, j, i); if( pAr->bDryRun ){ - sqlite3_fprintf(pAr->out, "%s\n", sqlite3_sql(pSql)); - }else{ - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ - if( i==0 && pAr->bVerbose ){ - sqlite3_fprintf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); - } + cli_printf(pAr->out, "%s\n", sqlite3_sql(pSql)); + if( pAr->bVerbose==0 ) break; + } + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( i==0 && pAr->bVerbose ){ + cli_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); } } + if( pAr->bDryRun ) break; shellReset(&rc, pSql); } shellFinalize(&rc, pSql); @@ -8107,13 +6747,13 @@ static int arExtractCommand(ArCommand *pAr){ static int arExecSql(ArCommand *pAr, const char *zSql){ int rc; if( pAr->bDryRun ){ - sqlite3_fprintf(pAr->out, "%s\n", zSql); + cli_printf(pAr->out, "%s\n", zSql); rc = SQLITE_OK; }else{ char *zErr = 0; rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); if( zErr ){ - sqlite3_fprintf(stdout, "ERROR: %s\n", zErr); + cli_printf(stdout, "ERROR: %s\n", zErr); sqlite3_free(zErr); } } @@ -8289,13 +6929,13 @@ static int arDotCommand( } cmd.db = 0; if( cmd.bDryRun ){ - sqlite3_fprintf(cmd.out, "-- open database '%s'%s\n", cmd.zFile, + cli_printf(cmd.out, "-- open database '%s'%s\n", cmd.zFile, eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); } rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); if( rc!=SQLITE_OK ){ - sqlite3_fprintf(stderr, "cannot open file: %s (%s)\n", + cli_printf(stderr, "cannot open file: %s (%s)\n", cmd.zFile, sqlite3_errmsg(cmd.db)); goto end_ar_command; } @@ -8309,7 +6949,7 @@ static int arDotCommand( if( cmd.eCmd!=AR_CMD_CREATE && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) ){ - sqlite3_fprintf(stderr, "database does not contain an 'sqlar' table\n"); + cli_printf(stderr, "database does not contain an 'sqlar' table\n"); rc = SQLITE_ERROR; goto end_ar_command; } @@ -8367,7 +7007,7 @@ end_ar_command: */ static int recoverSqlCb(void *pCtx, const char *zSql){ ShellState *pState = (ShellState*)pCtx; - sqlite3_fprintf(pState->out, "%s;\n", zSql); + cli_printf(pState->out, "%s;\n", zSql); return SQLITE_OK; } @@ -8410,7 +7050,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ bRowids = 0; } else{ - sqlite3_fprintf(stderr,"unexpected option: %s\n", azArg[i]); + cli_printf(stderr,"unexpected option: %s\n", azArg[i]); showHelp(pState->out, azArg[0]); return 1; } @@ -8420,17 +7060,19 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ pState->db, "main", recoverSqlCb, (void*)pState ); - sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */ + if( !pState->bSafeMode ){ + sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */ + } sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF); sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids); sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist); - sqlite3_fprintf(pState->out, ".dbconfig defensive off\n"); + cli_printf(pState->out, ".dbconfig defensive off\n"); sqlite3_recover_run(p); if( sqlite3_recover_errcode(p)!=SQLITE_OK ){ const char *zErr = sqlite3_recover_errmsg(p); int errCode = sqlite3_recover_errcode(p); - sqlite3_fprintf(stderr,"sql error: %s (%d)\n", zErr, errCode); + cli_printf(stderr,"sql error: %s (%d)\n", zErr, errCode); } rc = sqlite3_recover_finish(p); return rc; @@ -8452,7 +7094,7 @@ static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){ while( SQLITE_OK==sqlite3_intck_step(p) ){ const char *zMsg = sqlite3_intck_message(p); if( zMsg ){ - sqlite3_fprintf(pState->out, "%s\n", zMsg); + cli_printf(pState->out, "%s\n", zMsg); nError++; } nStep++; @@ -8462,11 +7104,11 @@ static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){ } rc = sqlite3_intck_error(p, &zErr); if( zErr ){ - sqlite3_fprintf(stderr,"%s\n", zErr); + cli_printf(stderr,"%s\n", zErr); } sqlite3_intck_close(p); - sqlite3_fprintf(pState->out, "%lld steps, %lld errors\n", nStep, nError); + cli_printf(pState->out, "%lld steps, %lld errors\n", nStep, nError); } return rc; @@ -8489,7 +7131,7 @@ static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){ #define rc_err_oom_die(rc) \ if( rc==SQLITE_NOMEM ) shell_check_oom(0); \ else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \ - sqlite3_fprintf(stderr,"E:%d\n",rc), assert(0) + cli_printf(stderr,"E:%d\n",rc), assert(0) #else static void rc_err_oom_die(int rc){ if( rc==SQLITE_NOMEM ) shell_check_oom(0); @@ -8604,7 +7246,7 @@ SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \ SELECT\ '('||x'0a'\ || group_concat(\ - cname||' TEXT',\ + cname||' ANY',\ ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\ ||')' AS ColsSpec \ FROM (\ @@ -8670,90 +7312,1648 @@ FROM (\ if( rc==SQLITE_ROW ){ zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); }else{ - zColsSpec = 0; + zColsSpec = 0; + } + if( pzRenamed!=0 ){ + if( !hasDupes ) *pzRenamed = 0; + else{ + sqlite3_finalize(pStmt); + if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0) + && SQLITE_ROW==sqlite3_step(pStmt) ){ + *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + }else + *pzRenamed = 0; + } + } + sqlite3_finalize(pStmt); + sqlite3_close(*pDb); + *pDb = 0; + return zColsSpec; + } +} + +/* +** Check if the sqlite_schema table contains one or more virtual tables. If +** parameter zLike is not NULL, then it is an SQL expression that the +** sqlite_schema row must also match. If one or more such rows are found, +** print the following warning to the output: +** +** WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled +*/ +static int outputDumpWarning(ShellState *p, const char *zLike){ + int rc = SQLITE_OK; + sqlite3_stmt *pStmt = 0; + shellPreparePrintf(p->db, &rc, &pStmt, + "SELECT 1 FROM sqlite_schema o WHERE " + "sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true" + ); + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + cli_puts("/* WARNING: " + "Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n", + p->out + ); + } + shellFinalize(&rc, pStmt); + return rc; +} + +/* +** Fault-Simulator state and logic. +*/ +static struct { + int iId; /* ID that triggers a simulated fault. -1 means "any" */ + int iErr; /* The error code to return on a fault */ + int iCnt; /* Trigger the fault only if iCnt is already zero */ + int iInterval; /* Reset iCnt to this value after each fault */ + int eVerbose; /* When to print output */ + int nHit; /* Number of hits seen so far */ + int nRepeat; /* Turn off after this many hits. 0 for never */ + int nSkip; /* Skip this many before first fault */ +} faultsim_state = {-1, 0, 0, 0, 0, 0, 0, 0}; + +/* +** This is the fault-sim callback +*/ +static int faultsim_callback(int iArg){ + if( faultsim_state.iId>0 && faultsim_state.iId!=iArg ){ + return SQLITE_OK; + } + if( faultsim_state.iCnt ){ + if( faultsim_state.iCnt>0 ) faultsim_state.iCnt--; + if( faultsim_state.eVerbose>=2 ){ + cli_printf(stdout, + "FAULT-SIM id=%d no-fault (cnt=%d)\n", iArg, faultsim_state.iCnt); + } + return SQLITE_OK; + } + if( faultsim_state.eVerbose>=1 ){ + cli_printf(stdout, + "FAULT-SIM id=%d returns %d\n", iArg, faultsim_state.iErr); + } + faultsim_state.iCnt = faultsim_state.iInterval; + faultsim_state.nHit++; + if( faultsim_state.nRepeat>0 && faultsim_state.nRepeat<=faultsim_state.nHit ){ + faultsim_state.iCnt = -1; + } + return faultsim_state.iErr; +} + +/* +** pickStr(zArg, &zErr, zS1, zS2, ..., ""); +** +** Try to match zArg against zS1, zS2, and so forth until the first +** emptry string. Return the index of the match or -1 if none is found. +** If no match is found, and &zErr is not NULL, then write into +** zErr a message describing the valid choices. +*/ +static int pickStr(const char *zArg, char **pzErr, ...){ + int i, n; + const char *z; + sqlite3_str *pMsg; + va_list ap; + va_start(ap, pzErr); + i = 0; + while( (z = va_arg(ap,const char*))!=0 && z[0]!=0 ){ + if( cli_strcmp(zArg, z)==0 ) return i; + i++; + } + va_end(ap); + if( pzErr==0 ) return -1; + n = i; + pMsg = sqlite3_str_new(0); + va_start(ap, pzErr); + sqlite3_str_appendall(pMsg, "should be"); + i = 0; + while( (z = va_arg(ap, const char*))!=0 && z[0]!=0 ){ + if( i==n-1 ){ + sqlite3_str_append(pMsg,", or",4); + }else if( i>0 ){ + sqlite3_str_append(pMsg, ",", 1); + } + sqlite3_str_appendf(pMsg, " %s", z); + i++; + } + va_end(ap); + *pzErr = sqlite3_str_finish(pMsg); + return -1; +} + +/* +** DOT-COMMAND: .import +** +** USAGE: .import [OPTIONS] FILE TABLE +** +** Import CSV or similar text from FILE into TABLE. If TABLE does +** not exist, it is created using the first row of FILE as the column +** names. If FILE begins with "|" then it is a command that is run +** and the output from the command is used as the input data. If +** FILE begins with "<<" followed by a label, then content is read from +** the script until the first line that matches the label. +** +** The content of FILE is interpreted using RFC-4180 ("CSV") quoting +** rules unless the current mode is "ascii" or "tabs" or unless one +** the --ascii option is used. +** +** The column and row separators must be single ASCII characters. If +** multiple characters or a Unicode character are specified for the +** separators, then only the first byte of the separator is used. Except, +** if the row separator is \n and the mode is not --ascii, then \r\n is +** understood as a row separator too. +** +** Options: +** --ascii Do not use RFC-4180 quoting. Use \037 and \036 +** as column and row separators on input, unless other +** delimiters are specified using --colsep and/or --rowsep +** --colsep CHAR Use CHAR as the column separator. +** --csv Input is standard RFC-4180 CSV. +** --esc CHAR Use CHAR as an escape character in unquoted CSV inputs. +** --qesc CHAR Use CHAR as an escape character in quoted CSV inputs. +** --rowsep CHAR Use CHAR as the row separator. +** --schema S When creating TABLE, put it in schema S +** --skip N Ignore the first N rows of input +** -v Verbose mode +*/ +static int dotCmdImport(ShellState *p){ + int nArg = p->dot.nArg; /* Number of arguments */ + char **azArg = p->dot.azArg;/* Argument list */ + char *zTable = 0; /* Insert data into this table */ + char *zSchema = 0; /* Schema of zTable */ + char *zFile = 0; /* Name of file to extra content from */ + sqlite3_stmt *pStmt = NULL; /* A statement */ + int nCol; /* Number of columns in the table */ + i64 nByte; /* Number of bytes in an SQL string */ + int i, j; /* Loop counters */ + int needCommit; /* True to COMMIT or ROLLBACK at end */ + char *zSql = 0; /* An SQL statement */ + ImportCtx sCtx; /* Reader context */ + char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ + int eVerbose = 0; /* Larger for more console output */ + i64 nSkip = 0; /* Initial lines to skip */ + i64 iLineOffset = 0; /* Offset to the first line of input */ + char *zCreate = 0; /* CREATE TABLE statement text */ + int rc; /* Result code */ + + failIfSafeMode(p, "cannot run .import in safe mode"); + memset(&sCtx, 0, sizeof(sCtx)); + if( p->mode.eMode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + for(i=1; imode.spec.zColumnSep && p->mode.spec.zColumnSep[0]!=0 ){ + sCtx.cColSep = p->mode.spec.zColumnSep[0]; + }else{ + sCtx.cColSep = ','; + } + } + if( (sCtx.cColSep & 0x80)!=0 ){ + eputz("Error: .import column separator must be ASCII\n"); + return 1; + } + if( sCtx.cRowSep==0 ){ + if( p->mode.spec.zRowSep && p->mode.spec.zRowSep[0]!=0 ){ + sCtx.cRowSep = p->mode.spec.zRowSep[0]; + }else{ + sCtx.cRowSep = '\n'; + } + } + if( sCtx.cRowSep=='\r' && xRead!=ascii_read_one_field ){ + sCtx.cRowSep = '\n'; + } + if( (sCtx.cRowSep & 0x80)!=0 ){ + eputz("Error: .import row separator must be ASCII\n"); + return 1; + } + sCtx.zFile = zFile; + sCtx.nLine = 1; + if( sCtx.zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + eputz("Error: pipes are not supported in this OS\n"); + return 1; +#else + sCtx.in = sqlite3_popen(sCtx.zFile+1, "r"); + sCtx.zFile = ""; + sCtx.xCloser = pclose; +#endif + }else if( sCtx.zFile[0]=='<' && sCtx.zFile[1]=='<' && sCtx.zFile[2]!=0 ){ + /* Input text comes from subsequent lines of script until the zFile + ** delimiter */ + int nEndMark = strlen30(zFile)-2; + char *zEndMark = &zFile[2]; + sqlite3_str *pContent = sqlite3_str_new(p->db); + int ckEnd = 1; + i64 iStart = p->lineno; + char zLine[2000]; + sCtx.zFile = p->zInFile; + sCtx.nLine = p->lineno+1; + iLineOffset = p->lineno; + while( sqlite3_fgets(zLine,sizeof(zLine),p->in) ){ + if( ckEnd && cli_strncmp(zLine,zEndMark,nEndMark)==0 ){ + ckEnd = 2; + if( strchr(zLine,'\n') ) p->lineno++; + break; + } + if( strchr(zLine,'\n') ){ + p->lineno++; + ckEnd = 1; + }else{ + ckEnd = 0; + } + sqlite3_str_appendall(pContent, zLine); + } + sCtx.zIn = sqlite3_str_finish(pContent); + if( sCtx.zIn==0 ){ + sCtx.zIn = sqlite3_mprintf(""); + } + if( ckEnd<2 ){ + i64 savedLn = p->lineno; + p->lineno = iStart; + dotCmdError(p, 0, 0,"Content terminator \"%s\" not found.",zEndMark); + p->lineno = savedLn; + import_cleanup(&sCtx); + return 1; + } + }else{ + sCtx.in = sqlite3_fopen(sCtx.zFile, "rb"); + sCtx.xCloser = fclose; + } + if( sCtx.in==0 && sCtx.zIn==0 ){ + dotCmdError(p, 0, 0, "cannot open \"%s\"", zFile); + import_cleanup(&sCtx); + return 1; + } + if( eVerbose>=1 ){ + char zSep[2]; + zSep[1] = 0; + zSep[0] = sCtx.cColSep; + cli_puts("Column separator ", p->out); + output_c_string(p->out, zSep); + cli_puts(", row separator ", p->out); + zSep[0] = sCtx.cRowSep; + output_c_string(p->out, zSep); + cli_puts("\n", p->out); + } + sCtx.z = sqlite3_malloc64(120); + if( sCtx.z==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + /* Below, resources must be freed before exit. */ + while( nSkip>0 ){ + nSkip--; + while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} + } + import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ + if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) + && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema" + " WHERE name=%Q AND type='view'", + zSchema ? zSchema : "main", zTable) + ){ + /* Table does not exist. Create it. */ + sqlite3 *dbCols = 0; + char *zRenames = 0; + char *zColDefs; + zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", + zSchema ? zSchema : "main", zTable); + while( xRead(&sCtx) ){ + zAutoColumn(sCtx.z, &dbCols, 0); + if( sCtx.cTerm!=sCtx.cColSep ) break; + } + zColDefs = zAutoColumn(0, &dbCols, &zRenames); + if( zRenames!=0 ){ + cli_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr, + "Columns renamed during .import %s due to duplicates:\n" + "%s\n", sCtx.zFile, zRenames); + sqlite3_free(zRenames); + } + assert(dbCols==0); + if( zColDefs==0 ){ + cli_printf(stderr,"%s: empty file\n", sCtx.zFile); + import_cleanup(&sCtx); + sqlite3_free(zCreate); + return 1; + } + zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); + if( zCreate==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + if( eVerbose>=1 ){ + cli_printf(p->out, "%s\n", zCreate); + } + rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); + if( rc ){ + cli_printf(stderr, + "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); + } + sqlite3_free(zCreate); + zCreate = 0; + if( rc ){ + import_cleanup(&sCtx); + return 1; + } + } + zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);", + zTable, zSchema); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; + if( rc ){ + if (pStmt) sqlite3_finalize(pStmt); + shellDatabaseError(p->db); + import_cleanup(&sCtx); + return 1; + } + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + nCol = sqlite3_column_int(pStmt, 0); + }else{ + nCol = 0; + } + sqlite3_finalize(pStmt); + pStmt = 0; + if( nCol==0 ) return 0; /* no columns, no error */ + + nByte = 64 /* space for "INSERT INTO", "VALUES(", ")\0" */ + + (zSchema ? strlen(zSchema)*2 + 2: 0) /* Quoted schema name */ + + strlen(zTable)*2 + 2 /* Quoted table name */ + + nCol*2; /* Space for ",?" for each column */ + zSql = sqlite3_malloc64( nByte ); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + if( zSchema ){ + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", + zSchema, zTable); + }else{ + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); + } + j = strlen30(zSql); + for(i=1; i=2 ){ + cli_printf(p->out, "Insert using: %s\n", zSql); + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; + if( rc ){ + shellDatabaseError(p->db); + if (pStmt) sqlite3_finalize(pStmt); + import_cleanup(&sCtx); + return 1; + } + needCommit = sqlite3_get_autocommit(p->db); + if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); + do{ + int startLine = sCtx.nLine; + for(i=0; imode.eMode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; + /* + ** For CSV mode, per RFC 4180, accept EOF in lieu of final + ** record terminator but only for last field of multi-field row. + ** (If there are too few fields, it's not valid CSV anyway.) + */ + if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){ + z = ""; + } + sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); + if( i=nCol ){ + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ){ + cli_printf(stderr,"%s:%d: INSERT failed: %s\n", + sCtx.zFile, startLine, sqlite3_errmsg(p->db)); + sCtx.nErr++; + if( bail_on_error ) break; + }else{ + sCtx.nRow++; + } + } + }while( sCtx.cTerm!=EOF ); + + import_cleanup(&sCtx); + sqlite3_finalize(pStmt); + if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); + if( eVerbose>0 ){ + cli_printf(p->out, + "Added %d rows with %d errors using %d lines of input\n", + sCtx.nRow, sCtx.nErr, sCtx.nLine-1-iLineOffset); + } + return sCtx.nErr ? 1 : 0; +} + + +/* +** This function computes what to show the user about the configured +** titles (or column-names). Output is an integer between 0 and 3: +** +** 0: The titles do not matter. Never show anything. +** 1: Show "--titles off" +** 2: Show "--titles on" +** 3: Show "--title VALUE" where VALUE is an encoding method +** to use, one of: plain sql csv html tcl json +** +** Inputs are: +** +** spec.bTitles (bT) Whether or not to show the titles +** spec.eTitle (eT) The actual encoding to be used for titles +** ModeInfo.bHdr (bH) Default value for spec.bTitles +** ModeInfo.eHdr (eH) Default value for spec.eTitle +** bAll Whether the -v option is used +*/ +static int modeTitleDsply(ShellState *p, int bAll){ + int eMode = p->mode.eMode; + const ModeInfo *pI = &aModeInfo[eMode]; + int bT = p->mode.spec.bTitles; + int eT = p->mode.spec.eTitle; + int bH = pI->bHdr; + int eH = pI->eHdr; + + /* Variable "v" is the truth table that will determine the answer + ** + ** Actual encoding is different from default + ** vvvvvvvv */ + sqlite3_uint64 v = 0x0133013311220102; + /* ^^^^ ^^^^ + ** Upper 2-byte groups for when ON/OFF disagrees with + ** the default. */ + + if( bH==0 ) return 0; /* Header not appliable. Ex: off, count */ + + if( eT==0 ) eT = eH; /* Fill in missing spec.eTitle */ + if( bT==0 ) bT = bH; /* Fill in missing spec.bTitles */ + + if( eT!=eH ) v >>= 32; /* Encoding disagree in upper 4-bytes */ + if( bT!=bH ) v >>= 16; /* ON/OFF disagree in upper 2-byte pairs */ + if( bT<2 ) v >>= 8; /* ON in even bytes, OFF in odd bytes (1st byte 0) */ + if( !bAll ) v >>= 4; /* bAll values are in the lower half-byte */ + + return v & 3; /* Return the selected truth-table entry */ +} + +/* +** DOT-COMMAND: .mode +** +** USAGE: .mode [MODE] [OPTIONS] +** +** Change the output mode to MODE and/or apply OPTIONS to the output mode. +** Arguments are processed from left to right. If no arguments, show the +** current output mode and relevant options. +** +** Options: +** --align STRING Set the alignment of text in columnar modes +** String consists of characters 'L', 'C', 'R' +** meaning "left", "centered", and "right", with +** one letter per column starting from the left. +** Unspecified alignment defaults to 'L'. +** --blob-quote ARG ARG can be "auto", "text", "sql", "hex", "tcl", +** "json", or "size". Default is "auto". +** --border on|off Show outer border on "box" and "table" modes. +** --charlimit N Set the maximum number of output characters to +** show for any single SQL value to N. Longer values +** truncated. Zero means "no limit". +** --colsep STRING Use STRING as the column separator +** --escape ESC Enable/disable escaping of control characters +** found in the output. ESC can be "off", "ascii", +** or "symbol". +** --linelimit N Set the maximum number of output lines to show for +** any single SQL value to N. Longer values are +** truncated. Zero means "no limit". Only works +** in "line" mode and in columnar modes. +** --limits L,C,T Shorthand for "--linelimit L --charlimit C +** --titlelimit T". The ",T" can be omitted in which +** case the --titlelimit is unchanged. The argument +** can also be "off" to mean "0,0,0" or "on" to +** mean "5,300,20". +** --list List available modes +** --null STRING Render SQL NULL values as the given string +** --once Setting changes to the right are reverted after +** the next SQL command. +** --quote ARG Enable/disable quoting of text. ARG can be +** "off", "on", "sql", "relaxed", "csv", "html", +** "tcl", or "json". "off" means show the text as-is. +** "on" is an alias for "sql". +** --reset Changes all mode settings back to their default. +** --rowsep STRING Use STRING as the row separator +** --sw|--screenwidth N Declare the screen width of the output device +** to be N characters. An attempt may be made to +** wrap output text to fit within this limit. Zero +** means "no limit". Or N can be "auto" to set the +** width automatically. +** --tablename NAME Set the name of the table for "insert" mode. +** --tag NAME Save mode to the left as NAME. +** --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON. +** --title ARG Whether or not to show column headers, and if so +** how to encode them. ARG can be "off", "on", +** "sql", "csv", "html", "tcl", or "json". +** --titlelimit N Limit the length of column titles to N characters. +** -v|--verbose Verbose output +** --widths LIST Set the columns widths for columnar modes. The +** argument is a list of integers, one for each +** column. A "0" width means use a dynamic width +** based on the actual width of data. If there are +** fewer entries in LIST than columns, "0" is used +** for the unspecified widths. +** --wordwrap BOOLEAN Enable/disable word wrapping +** --wrap N Wrap columns wider than N characters +** --ww Shorthand for "--wordwrap on" +*/ +static int dotCmdMode(ShellState *p){ + int nArg = p->dot.nArg; /* Number of arguments */ + char **azArg = p->dot.azArg;/* Argument list */ + int eMode = -1; /* New mode value, or -1 for none */ + int iMode = -1; /* Index of the argument that is the mode name */ + int i; /* Loop counter */ + int k; /* Misc index variable */ + int chng = 0; /* True if anything has changed */ + int bAll = 0; /* Show all details of the mode */ + + for(i=1; i=0 + && eMode!=MODE_Www + ){ + iMode = i; + modeChange(p, eMode); + /* (Legacy) If the mode is MODE_Insert and the next argument + ** is not an option, then the next argument must be the table + ** name. + */ + if( i+1mode.spec.zTableName, azArg[i]); + } + chng = 1; + }else if( optionMatch(z,"align") ){ + char *zAlign; + int nAlign; + int nErr = 0; + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + i++; + zAlign = azArg[i]; + nAlign = 0x3fff & strlen(zAlign); + free(p->mode.spec.aAlign); + p->mode.spec.aAlign = malloc(nAlign); + shell_check_oom(p->mode.spec.aAlign); + for(k=0; kmode.spec.aAlign[k] = c; + } + p->mode.spec.nAlign = nAlign; + chng = 1; + if( nErr ){ + dotCmdError(p, i, "bad alignment string", + "Should contain only characters L, C, and R."); + return 1; + } + }else if( pickStr(z,0,"-blob","-blob-quote","")>=0 ){ + if( (++i)>=nArg ){ + dotCmdError(p, i-1, "missing argument", 0); + return 1; + } + k = pickStr(azArg[i], 0, + "auto", "text", "sql", "hex", "tcl", "json", "size", ""); + /* 0 1 2 3 4 5 6 + ** Must match QRF_BLOB_xxxx values. See also tag-20251124a */ + if( k>=0 ){ + p->mode.spec.eBlob = k & 0xff; + } + chng = 1; + }else if( optionMatch(z,"border") ){ + if( (++i)>=nArg ){ + dotCmdError(p, i-1, "missing argument", 0); + return 1; + } + k = pickStr(azArg[i], 0, "auto", "off", "on", ""); + if( k>=0 ){ + p->mode.spec.bBorder = k & 0x3; + } + chng = 1; + }else if( 0<=(k=pickStr(z,0,"-charlimit","-linelimit","-titlelimit","")) ){ + int w; /* 0 1 */ + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + w = integerValue(azArg[++i]); + switch( k ){ + case 0: p->mode.spec.nCharLimit = w; break; + case 1: p->mode.spec.nLineLimit = w; break; + default: p->mode.spec.nTitleLimit = w; break; + } + chng = 1; + }else if( 0<=(k=pickStr(z,0,"-tablename","-rowsep","-colsep","-null","")) ){ + /* 0 1 2 3 */ + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + i++; + switch( k ){ + case 0: modeSetStr(&p->mode.spec.zTableName, azArg[i]); break; + case 1: modeSetStr(&p->mode.spec.zRowSep, azArg[i]); break; + case 2: modeSetStr(&p->mode.spec.zColumnSep, azArg[i]); break; + case 3: modeSetStr(&p->mode.spec.zNull, azArg[i]); break; + } + chng = 1; + }else if( optionMatch(z,"escape") ){ + /* See similar code at tag-20250224-1 */ + char *zErr = 0; + if( (++i)>=nArg ){ + dotCmdError(p, i-1, "missing argument", 0); + return 1; + } /* 0 1 2 <-- One less than QRF_ESC_ */ + k = pickStr(azArg[i],&zErr,"off","ascii","symbol",""); + if( k<0 ){ + dotCmdError(p, i, "unknown escape type", "%s", zErr); + sqlite3_free(zErr); + return 1; + } + p->mode.spec.eEsc = k+1; + chng = 1; + }else if( optionMatch(z,"limits") ){ + if( (++i)>=nArg ){ + dotCmdError(p, i-1, "missing argument", 0); + return 1; + } + k = pickStr(azArg[i],0,"on","off",""); + if( k==0 ){ + p->mode.spec.nLineLimit = DFLT_LINE_LIMIT; + p->mode.spec.nCharLimit = DFLT_CHAR_LIMIT; + p->mode.spec.nTitleLimit = DFLT_TITLE_LIMIT; + }else if( k==1 ){ + p->mode.spec.nLineLimit = 0; + p->mode.spec.nCharLimit = 0; + p->mode.spec.nTitleLimit = 0; + }else{ + int L, C, T = 0; + int nNum = sscanf(azArg[i], "%d,%d,%d", &L, &C, &T); + if( nNum<2 || L<0 || C<0 || T<0){ + dotCmdError(p, i, "bad argument", "Should be \"L,C,T\" where L, C" + " and T are unsigned integers"); + return 1; + } + p->mode.spec.nLineLimit = L; + p->mode.spec.nCharLimit = C; + if( nNum==3 ) p->mode.spec.nTitleLimit = T; + } + chng = 1; + }else if( optionMatch(z,"list") ){ + int ii; + cli_puts("available modes:", p->out); + for(ii=0; iiout, " %s", aModeInfo[ii].zName); + } + for(ii=0; iinSavedModes; ii++){ + cli_printf(p->out, " %s", p->aSavedModes[ii].zTag); + } + cli_puts(" batch tty\n", p->out); + chng = 1; /* Not really a change, but we still want to suppress the + ** "current mode" output */ + }else if( optionMatch(z,"once") ){ + p->nPopMode = 0; + modePush(p); + p->nPopMode = 1; + }else if( optionMatch(z,"noquote") ){ + /* (undocumented legacy) --noquote always turns quoting off */ + p->mode.spec.eText = QRF_TEXT_Plain; + p->mode.spec.eBlob = QRF_BLOB_Auto; + chng = 1; + }else if( optionMatch(z,"quote") ){ + if( i+10 || strcmp(azArg[i+1],"off")==0 || modeFind(p, azArg[i+1])<0) + ){ + /* --quote is followed by an argument other that is not an option + ** or a mode name. See it must be a boolean or a keyword to describe + ** how to set quoting. */ + i++; + if( (k = pickStr(azArg[i],0,"no","yes","0","1",""))>=0 ){ + k &= 1; /* 0 for "off". 1 for "on". */ + }else{ + char *zErr = 0; + k = pickStr(azArg[i],&zErr, + "off","on","sql","csv","html","tcl","json","relaxed",""); + /* 0 1 2 3 4 5 6 7 */ + if( k<0 ){ + dotCmdError(p, i, "unknown", "%z", zErr); + return 1; + } + } + }else{ + /* (Legacy) no following boolean argument. Turn quoting on */ + k = 1; + } + switch( k ){ + case 1: /* on */ + modeSetStr(&p->mode.spec.zNull, "NULL"); + /* Fall through */ + case 2: /* sql */ + p->mode.spec.eText = QRF_TEXT_Sql; + break; + case 3: /* csv */ + p->mode.spec.eText = QRF_TEXT_Csv; + break; + case 4: /* html */ + p->mode.spec.eText = QRF_TEXT_Html; + break; + case 5: /* tcl */ + p->mode.spec.eText = QRF_TEXT_Tcl; + break; + case 6: /* json */ + p->mode.spec.eText = QRF_TEXT_Json; + break; + case 7: /* relaxed */ + p->mode.spec.eText = QRF_TEXT_Relaxed; + break; + default: /* off */ + p->mode.spec.eText = QRF_TEXT_Plain; + break; + } + chng = 1; + }else if( optionMatch(z,"reset") ){ + int saved_eMode = p->mode.eMode; + modeFree(&p->mode); + modeChange(p, saved_eMode); + }else if( optionMatch(z,"screenwidth") || optionMatch(z,"sw") ){ + if( (++i)>=nArg ){ + dotCmdError(p, i-1, "missing argument", 0); + return 1; + } + k = pickStr(azArg[i],0,"off","auto",""); + if( k==0 ){ + p->mode.bAutoScreenWidth = 0; + p->mode.spec.nScreenWidth = 0; + }else if( k==1 ){ + p->mode.bAutoScreenWidth = 1; + }else{ + i64 w = integerValue(azArg[i]); + p->mode.bAutoScreenWidth = 0; + if( w<0 ) w = 0; + if( w>QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH; + p->mode.spec.nScreenWidth = w; + } + chng = 1; + }else if( optionMatch(z,"tag") ){ + size_t nByte; + int n; + const char *zTag; + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + zTag = azArg[++i]; + if( modeFind(p, zTag)>=0 ){ + dotCmdError(p, i, "mode already exists", 0); + return 1; + } + if( p->nSavedModes > MODE_N_USER ){ + dotCmdError(p, i-1, "cannot add more modes", 0); + return 1; + } + n = p->nSavedModes++; + nByte = sizeof(p->aSavedModes[0]); + nByte *= n+1; + p->aSavedModes = realloc( p->aSavedModes, nByte ); + shell_check_oom(p->aSavedModes); + p->aSavedModes[n].zTag = strdup(zTag); + shell_check_oom(p->aSavedModes[n].zTag); + modeDup(&p->aSavedModes[n].mode, &p->mode); + chng = 1; + }else if( optionMatch(z,"textjsonb") ){ + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + p->mode.spec.bTextJsonb = booleanValue(azArg[++i]) ? QRF_Yes : QRF_No; + chng = 1; + }else if( optionMatch(z,"titles") || optionMatch(z,"title") ){ + char *zErr = 0; + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + k = pickStr(azArg[++i],&zErr, + "off","on","plain","sql","csv","html","tcl","json",""); + /* 0 1 2 3 4 5 6 7 */ + if( k<0 ){ + dotCmdError(p, i, "bad --titles value","%z", zErr); + return 1; + } + p->mode.spec.bTitles = k>=1 ? QRF_Yes : QRF_No; + p->mode.mFlags &= ~MFLG_HDR; + p->mode.spec.eTitle = k>1 ? k-1 : aModeInfo[p->mode.eMode].eHdr; + chng = 1; + }else if( optionMatch(z,"widths") || optionMatch(z,"width") ){ + int nWidth = 0; + short int *aWidth; + const char *zW; + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + zW = azArg[++i]; + /* Every width value takes at least 2 bytes in the input string to + ** specify, so strlen(zW) bytes should be plenty of space to hold the + ** result. */ + aWidth = malloc( strlen(zW) ); + while( IsSpace(zW[0]) ) zW++; + while( zW[0] ){ + int w = 0; + int nDigit = 0; + k = zW[0]=='-' && IsDigit(zW[1]); + while( IsDigit(zW[k]) ){ + w = w*10 + zW[k] - '0'; + if( w>QRF_MAX_WIDTH ){ + dotCmdError(p,i+1,"width too big", + "Maximum column width is %d", QRF_MAX_WIDTH); + free(aWidth); + return 1; + } + nDigit++; + k++; + } + if( nDigit==0 ){ + dotCmdError(p,i+1,"syntax error", + "should be a comma-separated list if integers"); + free(aWidth); + return 1; + } + if( zW[0]=='-' ) w = -w; + aWidth[nWidth++] = w; + zW += k; + if( zW[0]==',' ) zW++; + while( IsSpace(zW[0]) ) zW++; + } + free(p->mode.spec.aWidth); + p->mode.spec.aWidth = aWidth; + p->mode.spec.nWidth = nWidth; + chng = 1; + }else if( optionMatch(z,"wrap") ){ + int w; + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + w = integerValue(azArg[++i]); + if( w<(-QRF_MAX_WIDTH) ) w = -QRF_MAX_WIDTH; + if( w>QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH; + p->mode.spec.nWrap = w; + chng = 1; + }else if( optionMatch(z,"ww") ){ + p->mode.spec.bWordWrap = QRF_Yes; + chng = 1; + }else if( optionMatch(z,"wordwrap") ){ + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + p->mode.spec.bWordWrap = (u8)booleanValue(azArg[++i]) ? QRF_Yes : QRF_No; + chng = 1; + }else if( optionMatch(z,"v") || optionMatch(z,"verbose") ){ + bAll = 1; + }else if( z[0]=='-' ){ + dotCmdError(p, i, "bad option", "Use \".help .mode\" for more info"); + return 1; + }else{ + dotCmdError(p, i, iMode>0?"bad argument":"unknown mode", + "Use \".help .mode\" for more info"); + return 1; + } + } + if( !chng || bAll ){ + const ModeInfo *pI = aModeInfo + p->mode.eMode; + sqlite3_str *pDesc = sqlite3_str_new(p->db); + char *zDesc; + const char *zSetting; + + if( p->nPopMode ) sqlite3_str_appendall(pDesc, "--once "); + sqlite3_str_appendall(pDesc,pI->zName); + if( bAll || (p->mode.spec.nAlign && pI->eCx==2) ){ + int ii; + sqlite3_str_appendall(pDesc, " --align \""); + for(ii=0; iimode.spec.nAlign; ii++){ + unsigned char a = p->mode.spec.aAlign[ii]; + sqlite3_str_appendchar(pDesc, 1, "LLCR"[a&3]); + } + sqlite3_str_append(pDesc, "\"", 1); + } + if( bAll + || (p->mode.spec.bBorder==QRF_No) != ((pI->mFlg&1)!=0) + ){ + sqlite3_str_appendf(pDesc," --border %s", + p->mode.spec.bBorder==QRF_No ? "off" : "on"); + } + if( bAll || p->mode.spec.eBlob!=QRF_BLOB_Auto ){ + const char *azBQuote[] = + { "auto", "text", "sql", "hex", "tcl", "json", "size" }; + /* 0 1 2 3 4 5 6 + ** Must match QRF_BLOB_xxxx values. See all instances of tag-20251124a */ + u8 e = p->mode.spec.eBlob; + sqlite3_str_appendf(pDesc, " --blob-quote %s", azBQuote[e]); + } + zSetting = aModeStr[pI->eCSep]; + if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zColumnSep)!=0) ){ + sqlite3_str_appendf(pDesc, " --colsep "); + append_c_string(pDesc, p->mode.spec.zColumnSep); + } + if( bAll || p->mode.spec.eEsc!=QRF_Auto ){ + sqlite3_str_appendf(pDesc, " --escape %s",qrfEscNames[p->mode.spec.eEsc]); + } + if( bAll + || (p->mode.spec.nLineLimit>0 && pI->eCx>0) + || p->mode.spec.nCharLimit>0 + || (p->mode.spec.nTitleLimit>0 && pI->eCx>0) + ){ + if( p->mode.spec.nLineLimit==0 + && p->mode.spec.nCharLimit==0 + && p->mode.spec.nTitleLimit==0 + ){ + sqlite3_str_appendf(pDesc, " --limits off"); + }else if( p->mode.spec.nLineLimit==DFLT_LINE_LIMIT + && p->mode.spec.nCharLimit==DFLT_CHAR_LIMIT + && p->mode.spec.nTitleLimit==DFLT_TITLE_LIMIT + ){ + sqlite3_str_appendf(pDesc, " --limits on"); + }else{ + sqlite3_str_appendf(pDesc, " --limits %d,%d,%d", + p->mode.spec.nLineLimit, p->mode.spec.nCharLimit, + p->mode.spec.nTitleLimit); + } + } + zSetting = aModeStr[pI->eNull]; + if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zNull)!=0) ){ + sqlite3_str_appendf(pDesc, " --null "); + append_c_string(pDesc, p->mode.spec.zNull); + } + if( bAll + || (pI->eText!=p->mode.spec.eText && (pI->eText>1 || p->mode.spec.eText>1)) + ){ + sqlite3_str_appendf(pDesc," --quote %s",qrfQuoteNames[p->mode.spec.eText]); + } + zSetting = aModeStr[pI->eRSep]; + if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zRowSep)!=0) ){ + sqlite3_str_appendf(pDesc, " --rowsep "); + append_c_string(pDesc, p->mode.spec.zRowSep); + } + if( bAll + || (pI->eCx && (p->mode.spec.nScreenWidth>0 || p->mode.bAutoScreenWidth)) + ){ + if( p->mode.bAutoScreenWidth ){ + sqlite3_str_appendall(pDesc, " --sw auto"); + }else{ + sqlite3_str_appendf(pDesc," --sw %d", + p->mode.spec.nScreenWidth); + } + } + if( bAll || p->mode.eMode==MODE_Insert ){ + sqlite3_str_appendf(pDesc," --tablename "); + append_c_string(pDesc, p->mode.spec.zTableName); + } + if( bAll || p->mode.spec.bTextJsonb ){ + sqlite3_str_appendf(pDesc," --textjsonb %s", + p->mode.spec.bTextJsonb==QRF_Yes ? "on" : "off"); + } + k = modeTitleDsply(p, bAll); + if( k==1 ){ + sqlite3_str_appendall(pDesc, " --titles off"); + }else if( k==2 ){ + sqlite3_str_appendall(pDesc, " --titles on"); + }else if( k==3 ){ + static const char *azTitle[] = + { "plain", "sql", "csv", "html", "tcl", "json"}; + sqlite3_str_appendf(pDesc, " --titles %s", + azTitle[p->mode.spec.eTitle-1]); + } + if( p->mode.spec.nWidth>0 && (bAll || pI->eCx==2) ){ + int ii; + const char *zSep = " --widths "; + for(ii=0; iimode.spec.nWidth; ii++){ + sqlite3_str_appendf(pDesc, "%s%d", zSep, (int)p->mode.spec.aWidth[ii]); + zSep = ","; + } + }else if( bAll ){ + sqlite3_str_appendall(pDesc, " --widths \"\""); + } + if( bAll || (pI->eCx>0 && p->mode.spec.bWordWrap) ){ + if( bAll ){ + sqlite3_str_appendf(pDesc, " --wordwrap %s", + p->mode.spec.bWordWrap==QRF_Yes ? "on" : "off"); + } + if( p->mode.spec.nWrap ){ + sqlite3_str_appendf(pDesc, " --wrap %d", p->mode.spec.nWrap); + } + if( !bAll ) sqlite3_str_append(pDesc, " --ww", 5); + } + zDesc = sqlite3_str_finish(pDesc); + cli_printf(p->out, ".mode %s\n", zDesc); + fflush(p->out); + sqlite3_free(zDesc); + } + return 0; +} + +/* +** DOT-COMMAND: .output +** USAGE: .output [OPTIONS] [FILE] +** +** Begin redirecting output to FILE. Or if FILE is omitted, revert +** to sending output to the console. If FILE begins with "|" then +** the remainder of file is taken as a pipe and output is directed +** into that pipe. If FILE is "memory" then output is captured in an +** internal memory buffer. If FILE is "off" then output is redirected +** into /dev/null or the equivalent. +** +** Options: +** --bom Prepend a byte-order mark to the output +** -e Accumulate output in a temporary text file then +** launch a text editor when the redirection ends. +** --error-prefix X Use X as the left-margin prefix for error messages. +** Set to an empty string to restore the default. +** --keep Keep redirecting output to its current destination. +** Use this option in combination with --show or +** with --error-prefix when you do not want to stop +** a current redirection. +** --plain Use plain text rather than HTML tables with -w +** --show Show output text captured by .testcase or by +** redirecting to "memory". +** -w Show the output in a web browser. Output is +** written into a temporary HTML file until the +** redirect ends, then the web browser is launched. +** Query results are shown as HTML tables, unless +** the --plain is used too. +** -x Show the output in a spreadsheet. Output is +** written to a temp file as CSV then the spreadsheet +** is launched when +** +** DOT-COMMAND: .once +** USAGE: .once [OPTIONS] FILE ... +** +** Write the output for the next line of SQL or the next dot-command into +** FILE. If FILE begins with "|" then it is a program into which output +** is written. The FILE argument should be omitted if one of the -e, -w, +** or -x options is used. +** +** Options: +** -e Capture output into a temporary file then bring up +** a text editor on that temporary file. +** --plain Use plain text rather than HTML tables with -w +** -w Capture output into an HTML file then bring up that +** file in a web browser +** -x Show the output in a spreadsheet. Output is +** written to a temp file as CSV then the spreadsheet +** is launched when +** +** DOT-COMMAND: .excel +** Shorthand for ".once -x" +** +** DOT-COMMAND: .www [--plain] +** Shorthand for ".once -w" or ".once --plain -w" +*/ +static int dotCmdOutput(ShellState *p){ + int nArg = p->dot.nArg; /* Number of arguments */ + char **azArg = p->dot.azArg; /* Text of the arguments */ + char *zFile = 0; /* The FILE argument */ + int i; /* Loop counter */ + int eMode = 0; /* 0: .outout/.once, 'x'=.excel, 'w'=.www */ + int bOnce = 0; /* 0: .output, 1: .once, 2: .excel/.www */ + int bPlain = 0; /* --plain option */ + int bKeep = 0; /* Keep redirecting */ + static const char *zBomUtf8 = "\357\273\277"; + const char *zBom = 0; + char c = azArg[0][0]; + int n = strlen30(azArg[0]); + + failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); + if( c=='e' ){ + eMode = 'x'; + bOnce = 2; + }else if( c=='w' ){ + eMode = 'w'; + bOnce = 2; + }else if( n>=2 && cli_strncmp(azArg[0],"once",n)==0 ){ + bOnce = 1; + } + for(i=1; i=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + free(p->zErrPrefix); + i++; + p->zErrPrefix = azArg[i][0]==0 ? 0 : strdup(azArg[i]); + }else{ + dotCmdError(p, i, "unknown option", 0); + sqlite3_free(zFile); + return 1; + } + }else if( zFile==0 && eMode==0 ){ + if( bKeep ){ + dotCmdError(p, i, "incompatible with prior options",0); + goto dotCmdOutput_error; + } + if( cli_strcmp(z, "memory")==0 && bOnce ){ + dotCmdError(p, 0, "cannot redirect to \"memory\"", 0); + goto dotCmdOutput_error; + } + if( cli_strcmp(z, "off")==0 ){ +#ifdef _WIN32 + zFile = sqlite3_mprintf("nul"); +#else + zFile = sqlite3_mprintf("/dev/null"); +#endif + }else{ + zFile = sqlite3_mprintf("%s", z); + } + if( zFile && zFile[0]=='|' ){ + while( i+1nPopOutput = 2; + }else{ + p->nPopOutput = 0; + } + if( !bKeep ) output_reset(p); +#ifndef SQLITE_NOHAVE_SYSTEM + if( eMode=='e' || eMode=='x' || eMode=='w' ){ + p->doXdgOpen = 1; + modePush(p); + if( eMode=='x' ){ + /* spreadsheet mode. Output as CSV. */ + newTempFile(p, "csv"); + p->mode.mFlags &= ~MFLG_ECHO; + p->mode.eMode = MODE_Csv; + modeSetStr(&p->mode.spec.zColumnSep, SEP_Comma); + modeSetStr(&p->mode.spec.zRowSep, SEP_CrLf); +#ifdef _WIN32 + zBom = zBomUtf8; /* Always include the BOM on Windows, as Excel does + ** not work without it. */ +#endif + }else if( eMode=='w' ){ + /* web-browser mode. */ + newTempFile(p, "html"); + if( !bPlain ) p->mode.eMode = MODE_Www; + }else{ + /* text editor mode */ + newTempFile(p, "txt"); + } + sqlite3_free(zFile); + zFile = sqlite3_mprintf("%s", p->zTempFile); + } +#endif /* SQLITE_NOHAVE_SYSTEM */ + if( !bKeep ) shell_check_oom(zFile); + if( bKeep ){ + /* no-op */ + }else if( cli_strcmp(zFile,"memory")==0 ){ + if( cli_output_capture ){ + sqlite3_str_free(cli_output_capture); + } + cli_output_capture = sqlite3_str_new(0); + }else if( zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + eputz("Error: pipes are not supported in this OS\n"); + output_redir(p, stdout); + goto dotCmdOutput_error; +#else + FILE *pfPipe = sqlite3_popen(zFile + 1, "w"); + if( pfPipe==0 ){ + assert( stderr!=NULL ); + cli_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); + goto dotCmdOutput_error; + }else{ + output_redir(p, pfPipe); + if( zBom ) cli_puts(zBom, pfPipe); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); + } +#endif + }else{ + FILE *pfFile = output_file_open(p, zFile); + if( pfFile==0 ){ + if( cli_strcmp(zFile,"off")!=0 ){ + assert( stderr!=NULL ); + cli_printf(stderr,"Error: cannot write to \"%s\"\n", zFile); + } + goto dotCmdOutput_error; + } else { + output_redir(p, pfFile); + if( zBom ) cli_puts(zBom, pfFile); + if( bPlain && eMode=='w' ){ + cli_puts( + "\n\n\n", + pfFile + ); + } + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); + } + } + sqlite3_free(zFile); + return 0; + +dotCmdOutput_error: + sqlite3_free(zFile); + return 1; +} + +/* +** DOT-COMMAND: .check +** USAGE: .check [OPTIONS] PATTERN +** +** Verify results of commands since the most recent .testcase command. +** Restore output to the console, unless --keep is used. +** +** If PATTERN starts with "<<ENDMARK" then the actual pattern is taken from +** subsequent lines of text up to the first line that begins with ENDMARK. +** All pattern lines and the ENDMARK are discarded. +** +** Options: +** --exact Do an exact comparison including leading and +** trailing whitespace. +** --glob Treat PATTERN as a GLOB +** --keep Do not reset the testcase. More .check commands +** will follow. +** --notglob Output should not match PATTERN +** --show Write testcase output to the screen, for debugging. +*/ +static int dotCmdCheck(ShellState *p){ + int nArg = p->dot.nArg; /* Number of arguments */ + char **azArg = p->dot.azArg; /* Text of the arguments */ + int i; /* Loop counter */ + int k; /* Result of pickStr() */ + char *zTest; /* Textcase result */ + int bKeep = 0; /* --keep option */ + char *zCheck = 0; /* PATTERN argument */ + char *zPattern = 0; /* Actual test pattern */ + int eCheck = 0; /* 1: --glob, 2: --notglob, 3: --exact */ + int isOk; /* True if results are OK */ + sqlite3_int64 iStart = p->lineno; /* Line number of .check statement */ + + if( p->zTestcase[0]==0 ){ + dotCmdError(p, 0, "no .testcase is active", 0); + return 1; + } + for(i=1; i<nArg; i++){ + char *z = azArg[i]; + if( z[0]=='-' && z[1]=='-' && z[2]!=0 ) z++; + if( cli_strcmp(z,"-keep")==0 ){ + bKeep = 1; + }else if( cli_strcmp(z,"-show")==0 ){ + if( cli_output_capture ){ + sqlite3_fprintf(stdout, "%s", sqlite3_str_value(cli_output_capture)); + } + bKeep = 1; + }else if( z[0]=='-' + && (k = pickStr(&z[1],0,"glob","notglob","exact",""))>=0 + ){ + if( eCheck && eCheck!=k+1 ){ + dotCmdError(p, i, "incompatible with prior options",0); + return 1; + } + eCheck = k+1; + }else if( zCheck ){ + dotCmdError(p, i, "unknown option", 0); + return 1; + }else{ + zCheck = azArg[i]; } - if( pzRenamed!=0 ){ - if( !hasDupes ) *pzRenamed = 0; - else{ - sqlite3_finalize(pStmt); - if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0) - && SQLITE_ROW==sqlite3_step(pStmt) ){ - *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); - }else - *pzRenamed = 0; - } + } + if( zCheck==0 ){ + dotCmdError(p, 0, "no PATTERN specified", 0); + return 1; + } + if( cli_output_capture && sqlite3_str_length(cli_output_capture) ){ + zTest = sqlite3_str_value(cli_output_capture); + shell_check_oom(zTest); + }else{ + zTest = ""; + } + p->nTestRun++; + if( zCheck[0]=='<' && zCheck[1]=='<' && zCheck[2]!=0 ){ + int nCheck = strlen30(zCheck); + sqlite3_str *pPattern = sqlite3_str_new(p->db); + char zLine[2000]; + while( sqlite3_fgets(zLine,sizeof(zLine),p->in) ){ + if( strchr(zLine,'\n') ) p->lineno++; + if( cli_strncmp(&zCheck[2],zLine,nCheck-2)==0 ) break; + sqlite3_str_appendall(pPattern, zLine); + } + zPattern = sqlite3_str_finish(pPattern); + if( zPattern==0 ){ + zPattern = sqlite3_mprintf(""); + } + }else{ + zPattern = zCheck; + } + shell_check_oom(zPattern); + switch( eCheck ){ + case 1: { + char *zGlob = sqlite3_mprintf("*%s*", zPattern); + isOk = testcase_glob(zGlob, zTest)!=0; + sqlite3_free(zGlob); + break; + } + case 2: { + char *zGlob = sqlite3_mprintf("*%s*", zPattern); + isOk = testcase_glob(zGlob, zTest)==0; + sqlite3_free(zGlob); + break; + } + case 3: { + isOk = cli_strcmp(zTest,zPattern)==0; + break; + } + default: { + /* Skip leading and trailing \n and \r on both pattern and test output */ + const char *z1 = zPattern; + const char *z2 = zTest; + size_t n1, n2; + while( z1[0]=='\n' || z1[0]=='\r' ) z1++; + n1 = strlen(z1); + while( n1>0 && (z1[n1-1]=='\n' || z1[n1-1]=='\r') ) n1--; + while( z2[0]=='\n' || z2[0]=='\r' ) z2++; + n2 = strlen(z2); + while( n2>0 && (z2[n2-1]=='\n' || z2[n2-1]=='\r') ) n2--; + isOk = n1==n2 && memcmp(z1,z2,n1)==0; + break; } - sqlite3_finalize(pStmt); - sqlite3_close(*pDb); - *pDb = 0; - return zColsSpec; } + if( !isOk ){ + sqlite3_fprintf(stderr, + "%s:%lld: .check failed for testcase %s\n", + p->zInFile, iStart, p->zTestcase); + p->nTestErr++; + sqlite3_fprintf(stderr, "Expected: [%s]\n", zPattern); + sqlite3_fprintf(stderr, "Got: [%s]\n", zTest); + } + if( zPattern!=zCheck ){ + sqlite3_free(zPattern); + } + if( !bKeep ){ + output_reset(p); + p->zTestcase[0] = 0; + } + return 0; } /* -** Check if the sqlite_schema table contains one or more virtual tables. If -** parameter zLike is not NULL, then it is an SQL expression that the -** sqlite_schema row must also match. If one or more such rows are found, -** print the following warning to the output: +** DOT-COMMAND: .testcase +** USAGE: .testcase [OPTIONS] NAME ** -** WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled +** Start a new test case identified by NAME. All output +** through the next ".check" command is captured for comparison. See the +** ".check" commandn for additional informatioon. +** +** Options: +** --error-prefix TEXT Change error message prefix text to TEXT */ -static int outputDumpWarning(ShellState *p, const char *zLike){ - int rc = SQLITE_OK; - sqlite3_stmt *pStmt = 0; - shellPreparePrintf(p->db, &rc, &pStmt, - "SELECT 1 FROM sqlite_schema o WHERE " - "sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true" - ); - if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - sqlite3_fputs("/* WARNING: " - "Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n", - p->out - ); +static int dotCmdTestcase(ShellState *p){ + int nArg = p->dot.nArg; /* Number of arguments */ + char **azArg = p->dot.azArg; /* Text of the arguments */ + int i; /* Loop counter */ + const char *zName = 0; /* Testcase name */ + + for(i=1; i<nArg; i++){ + char *z = azArg[i]; + if( z[0]=='-' && z[1]=='-' && z[2]!=0 ) z++; + if( optionMatch(z,"error-prefix") ){ + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + free(p->zErrPrefix); + i++; + p->zErrPrefix = azArg[i][0]==0 ? 0 : strdup(azArg[i]); + }else if( zName ){ + dotCmdError(p, i, "unknown option", 0); + return 1; + }else{ + zName = azArg[i]; + } } - shellFinalize(&rc, pStmt); - return rc; + output_reset(p); + if( cli_output_capture ){ + sqlite3_str_free(cli_output_capture); + } + cli_output_capture = sqlite3_str_new(0); + if( zName ){ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", zName); + }else{ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s:%lld", + p->zInFile, p->lineno); + } + return 0; } /* -** Fault-Simulator state and logic. +** Enlarge the space allocated in p->dot so that it can hold more +** than nArg parsed command-line arguments. */ -static struct { - int iId; /* ID that triggers a simulated fault. -1 means "any" */ - int iErr; /* The error code to return on a fault */ - int iCnt; /* Trigger the fault only if iCnt is already zero */ - int iInterval; /* Reset iCnt to this value after each fault */ - int eVerbose; /* When to print output */ - int nHit; /* Number of hits seen so far */ - int nRepeat; /* Turn off after this many hits. 0 for never */ - int nSkip; /* Skip this many before first fault */ -} faultsim_state = {-1, 0, 0, 0, 0, 0, 0, 0}; +static void parseDotRealloc(ShellState *p, int nArg){ + p->dot.nAlloc = nArg+22; + p->dot.azArg = realloc(p->dot.azArg,p->dot.nAlloc*sizeof(char*)); + shell_check_oom(p->dot.azArg); + p->dot.aiOfst = realloc(p->dot.aiOfst,p->dot.nAlloc*sizeof(int)); + shell_check_oom(p->dot.aiOfst); + p->dot.abQuot = realloc(p->dot.abQuot,p->dot.nAlloc); + shell_check_oom(p->dot.abQuot); +} + /* -** This is the fault-sim callback +** Parse input line zLine up into individual arguments. Retain the +** parse in the p->dot substructure. */ -static int faultsim_callback(int iArg){ - if( faultsim_state.iId>0 && faultsim_state.iId!=iArg ){ - return SQLITE_OK; - } - if( faultsim_state.iCnt ){ - if( faultsim_state.iCnt>0 ) faultsim_state.iCnt--; - if( faultsim_state.eVerbose>=2 ){ - sqlite3_fprintf(stdout, - "FAULT-SIM id=%d no-fault (cnt=%d)\n", iArg, faultsim_state.iCnt); +static void parseDotCmdArgs(const char *zLine, ShellState *p){ + char *z; + int h = 1; + int nArg = 0; + size_t szLine; + + p->dot.zOrig = zLine; + free(p->dot.zCopy); + z = p->dot.zCopy = strdup(zLine); + shell_check_oom(z); + szLine = strlen(z); + while( szLine>0 && IsSpace(z[szLine-1]) ) szLine--; + if( szLine>0 && z[szLine-1]==';' ){ + szLine--; + while( szLine>0 && IsSpace(z[szLine-1]) ) szLine--; + } + z[szLine] = 0; + parseDotRealloc(p, 2); + while( z[h] ){ + while( IsSpace(z[h]) ){ h++; } + if( z[h]==0 ) break; + if( nArg+2>p->dot.nAlloc ){ + parseDotRealloc(p, nArg); + } + if( z[h]=='\'' || z[h]=='"' ){ + int delim = z[h++]; + p->dot.abQuot[nArg] = 1; + p->dot.azArg[nArg] = &z[h]; + p->dot.aiOfst[nArg] = h; + while( z[h] && z[h]!=delim ){ + if( z[h]=='\\' && delim=='"' && z[h+1]!=0 ) h++; + h++; + } + if( z[h]==delim ){ + z[h++] = 0; + } + if( delim=='"' ) resolve_backslashes(p->dot.azArg[nArg]); + }else{ + p->dot.abQuot[nArg] = 0; + p->dot.azArg[nArg] = &z[h]; + p->dot.aiOfst[nArg] = h; + while( z[h] && !IsSpace(z[h]) ){ h++; } + if( z[h] ) z[h++] = 0; } - return SQLITE_OK; - } - if( faultsim_state.eVerbose>=1 ){ - sqlite3_fprintf(stdout, - "FAULT-SIM id=%d returns %d\n", iArg, faultsim_state.iErr); - } - faultsim_state.iCnt = faultsim_state.iInterval; - faultsim_state.nHit++; - if( faultsim_state.nRepeat>0 && faultsim_state.nRepeat<=faultsim_state.nHit ){ - faultsim_state.iCnt = -1; + nArg++; } - return faultsim_state.iErr; + p->dot.nArg = nArg; + p->dot.azArg[nArg] = 0; } /* @@ -8762,12 +8962,11 @@ static int faultsim_callback(int iArg){ ** ** Return 1 on error, 2 to exit, and 0 otherwise. */ -static int do_meta_command(char *zLine, ShellState *p){ - int h = 1; - int nArg = 0; +static int do_meta_command(const char *zLine, ShellState *p){ + int nArg; int n, c; int rc = 0; - char *azArg[52]; + char **azArg; #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) if( p->expert.pExpert ){ @@ -8775,29 +8974,11 @@ static int do_meta_command(char *zLine, ShellState *p){ } #endif - /* Parse the input line into tokens. + /* Parse the input line into tokens stored in p->dot. */ - while( zLine[h] && nArg<ArraySize(azArg)-1 ){ - while( IsSpace(zLine[h]) ){ h++; } - if( zLine[h]==0 ) break; - if( zLine[h]=='\'' || zLine[h]=='"' ){ - int delim = zLine[h++]; - azArg[nArg++] = &zLine[h]; - while( zLine[h] && zLine[h]!=delim ){ - if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++; - h++; - } - if( zLine[h]==delim ){ - zLine[h++] = 0; - } - if( delim=='"' ) resolve_backslashes(azArg[nArg-1]); - }else{ - azArg[nArg++] = &zLine[h]; - while( zLine[h] && !IsSpace(zLine[h]) ){ h++; } - if( zLine[h] ) zLine[h++] = 0; - } - } - azArg[nArg] = 0; + parseDotCmdArgs(zLine, p); + nArg = p->dot.nArg; + azArg = p->dot.azArg; /* Process the input line. */ @@ -8809,7 +8990,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_OMIT_AUTHORIZATION if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){ if( nArg!=2 ){ - sqlite3_fprintf(stderr, "Usage: .auth ON|OFF\n"); + cli_printf(stderr, "Usage: .auth ON|OFF\n"); rc = 1; goto meta_command_exit; } @@ -8856,7 +9037,7 @@ static int do_meta_command(char *zLine, ShellState *p){ bAsync = 1; }else { - sqlite3_fprintf(stderr,"unknown option: %s\n", azArg[j]); + dotCmdError(p, j, "unknown option", "should be -append or -async"); return 1; } }else if( zDestFile==0 ){ @@ -8865,19 +9046,19 @@ static int do_meta_command(char *zLine, ShellState *p){ zDb = zDestFile; zDestFile = azArg[j]; }else{ - sqlite3_fprintf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); + cli_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); return 1; } } if( zDestFile==0 ){ - sqlite3_fprintf(stderr, "missing FILENAME argument on .backup\n"); + cli_printf(stderr, "missing FILENAME argument on .backup\n"); return 1; } if( zDb==0 ) zDb = "main"; rc = sqlite3_open_v2(zDestFile, &pDest, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); if( rc!=SQLITE_OK ){ - sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zDestFile); + cli_printf(stderr,"Error: cannot open \"%s\"\n", zDestFile); close_db(pDest); return 1; } @@ -8938,7 +9119,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = chdir(azArg[1]); #endif if( rc ){ - sqlite3_fprintf(stderr,"Cannot change to directory \"%s\"\n", azArg[1]); + cli_printf(stderr,"Cannot change to directory \"%s\"\n", azArg[1]); rc = 1; } }else{ @@ -8957,31 +9138,13 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else -#ifndef SQLITE_SHELL_FIDDLE /* Cancel output redirection, if it is currently set (by .testcase) ** Then read the content of the testcase-out.txt file and compare against ** azArg[1]. If there are differences, report an error and exit. */ if( c=='c' && n>=3 && cli_strncmp(azArg[0], "check", n)==0 ){ - char *zRes = 0; - output_reset(p); - if( nArg!=2 ){ - eputz("Usage: .check GLOB-PATTERN\n"); - rc = 2; - }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ - rc = 2; - }else if( testcase_glob(azArg[1],zRes)==0 ){ - sqlite3_fprintf(stderr, - "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", - p->zTestcase, azArg[1], zRes); - rc = 1; - }else{ - sqlite3_fprintf(p->out, "testcase-%s ok\n", p->zTestcase); - p->nCheck++; - } - sqlite3_free(zRes); + rc = dotCmdCheck(p); }else -#endif /* !defined(SQLITE_SHELL_FIDDLE) */ #ifndef SQLITE_SHELL_FIDDLE if( c=='c' && cli_strncmp(azArg[0], "clone", n)==0 ){ @@ -9009,9 +9172,9 @@ static int do_meta_command(char *zLine, ShellState *p){ zFile = "(temporary-file)"; } if( p->pAuxDb == &p->aAuxDb[i] ){ - sqlite3_fprintf(stdout, "ACTIVE %d: %s\n", i, zFile); + cli_printf(stdout, "ACTIVE %d: %s\n", i, zFile); }else if( p->aAuxDb[i].db!=0 ){ - sqlite3_fprintf(stdout, " %d: %s\n", i, zFile); + cli_printf(stdout, " %d: %s\n", i, zFile); } } }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){ @@ -9047,12 +9210,17 @@ static int do_meta_command(char *zLine, ShellState *p){ ){ if( nArg==2 ){ #ifdef _WIN32 - p->crlfMode = booleanValue(azArg[1]); + if( booleanValue(azArg[1]) ){ + p->mode.mFlags |= MFLG_CRLF; + }else{ + p->mode.mFlags &= ~MFLG_CRLF; + } #else - p->crlfMode = 0; + p->mode.mFlags &= ~MFLG_CRLF; #endif } - sqlite3_fprintf(stderr, "crlf is %s\n", p->crlfMode ? "ON" : "OFF"); + cli_printf(stderr, "crlf is %s\n", + (p->mode.mFlags & MFLG_CRLF)!=0 ? "ON" : "OFF"); }else if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){ @@ -9082,7 +9250,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int eTxn = sqlite3_txn_state(p->db, azName[i*2]); int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); const char *z = azName[i*2+1]; - sqlite3_fprintf(p->out, "%s: %s %s%s\n", + cli_printf(p->out, "%s: %s %s%s\n", azName[i*2], z && z[0] ? z : "\"\"", bRdonly ? "r/o" : "r/w", eTxn==SQLITE_TXN_NONE ? "" : eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); @@ -9108,6 +9276,7 @@ static int do_meta_command(char *zLine, ShellState *p){ { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, + { "fp_digits", SQLITE_DBCONFIG_FP_DIGITS }, { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, @@ -9124,16 +9293,24 @@ static int do_meta_command(char *zLine, ShellState *p){ for(ii=0; ii<ArraySize(aDbConfig); ii++){ if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; if( nArg>=3 ){ - sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); + if( aDbConfig[ii].op==SQLITE_DBCONFIG_FP_DIGITS ){ + sqlite3_db_config(p->db, aDbConfig[ii].op, atoi(azArg[2]), 0); + }else{ + sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); + } } sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); - sqlite3_fprintf(p->out, "%19s %s\n", - aDbConfig[ii].zName, v ? "on" : "off"); + if( aDbConfig[ii].op==SQLITE_DBCONFIG_FP_DIGITS ){ + cli_printf(p->out, "%19s %d\n", aDbConfig[ii].zName, v); + }else{ + cli_printf(p->out, "%19s %s\n", + aDbConfig[ii].zName, v ? "on" : "off"); + } if( nArg>1 ) break; } if( nArg>1 && ii==ArraySize(aDbConfig) ){ - sqlite3_fprintf(stderr,"Error: unknown dbconfig \"%s\"\n", azArg[1]); - eputz("Enter \".dbconfig\" with no arguments for a list\n"); + dotCmdError(p, 1, "unknown dbconfig", + "Enter \".dbconfig\" with no arguments for a list"); } }else @@ -9152,19 +9329,19 @@ static int do_meta_command(char *zLine, ShellState *p){ char *zLike = 0; char *zSql; int i; - int savedShowHeader = p->showHeader; int savedShellFlags = p->shellFlgs; + Mode saved_mode; ShellClearFlag(p, - SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo - |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); + SHFLG_PreserveRowid|SHFLG_DumpDataOnly|SHFLG_DumpNoSys); for(i=1; i<nArg; i++){ if( azArg[i][0]=='-' ){ const char *z = azArg[i]+1; if( z[0]=='-' ) z++; if( cli_strcmp(z,"preserve-rowids")==0 ){ #ifdef SQLITE_OMIT_VIRTUALTABLE - eputz("The --preserve-rowids option is not compatible" - " with SQLITE_OMIT_VIRTUALTABLE\n"); + dotCmdError(p, i, "unable", + "The --preserve-rowids option is not compatible" + " with SQLITE_OMIT_VIRTUALTABLE"); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -9173,7 +9350,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif }else if( cli_strcmp(z,"newlines")==0 ){ - ShellSetFlag(p, SHFLG_Newlines); + /*ShellSetFlag(p, SHFLG_Newlines);*/ }else if( cli_strcmp(z,"data-only")==0 ){ ShellSetFlag(p, SHFLG_DumpDataOnly); @@ -9182,8 +9359,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ShellSetFlag(p, SHFLG_DumpNoSys); }else { - sqlite3_fprintf(stderr, - "Unknown option \"%s\" on \".dump\"\n", azArg[i]); + dotCmdError(p, i, "unknown option", 0); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -9213,16 +9389,17 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); + modeDup(&saved_mode, &p->mode); outputDumpWarning(p, zLike); if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. ** So disable foreign-key constraint enforcement to prevent problems. */ - sqlite3_fputs("PRAGMA foreign_keys=OFF;\n", p->out); - sqlite3_fputs("BEGIN TRANSACTION;\n", p->out); + cli_puts("PRAGMA foreign_keys=OFF;\n", p->out); + cli_puts("BEGIN TRANSACTION;\n", p->out); } p->writableSchema = 0; - p->showHeader = 0; + p->mode.spec.bTitles = QRF_No; /* Set writable_schema=ON since doing so forces SQLite to initialize ** as much of the schema as it can even if the sqlite_schema table is ** corrupt. */ @@ -9251,22 +9428,27 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_free(zLike); if( p->writableSchema ){ - sqlite3_fputs("PRAGMA writable_schema=OFF;\n", p->out); + cli_puts("PRAGMA writable_schema=OFF;\n", p->out); p->writableSchema = 0; } sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - sqlite3_fputs(p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n", p->out); + cli_puts(p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n", p->out); } - p->showHeader = savedShowHeader; p->shellFlgs = savedShellFlags; + modeFree(&p->mode); + p->mode = saved_mode; rc = p->nErr>0; }else if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){ if( nArg==2 ){ - setOrClearFlag(p, SHFLG_Echo, azArg[1]); + if( booleanValue(azArg[1]) ){ + p->mode.mFlags |= MFLG_ECHO; + }else{ + p->mode.mFlags &= ~MFLG_ECHO; + } }else{ eputz("Usage: .echo on|off\n"); rc = 1; @@ -9280,28 +9462,24 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ if( nArg==2 ){ - p->autoEQPtest = 0; - if( p->autoEQPtrace ){ + if( p->mode.autoEQPtrace ){ if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0); - p->autoEQPtrace = 0; + p->mode.autoEQPtrace = 0; } if( cli_strcmp(azArg[1],"full")==0 ){ - p->autoEQP = AUTOEQP_full; + p->mode.autoEQP = AUTOEQP_full; }else if( cli_strcmp(azArg[1],"trigger")==0 ){ - p->autoEQP = AUTOEQP_trigger; + p->mode.autoEQP = AUTOEQP_trigger; #ifdef SQLITE_DEBUG - }else if( cli_strcmp(azArg[1],"test")==0 ){ - p->autoEQP = AUTOEQP_on; - p->autoEQPtest = 1; }else if( cli_strcmp(azArg[1],"trace")==0 ){ - p->autoEQP = AUTOEQP_full; - p->autoEQPtrace = 1; + p->mode.autoEQP = AUTOEQP_full; + p->mode.autoEQPtrace = 1; open_db(p, 0); sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0); sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0); #endif }else{ - p->autoEQP = (u8)booleanValue(azArg[1]); + p->mode.autoEQP = (u8)booleanValue(azArg[1]); } }else{ eputz("Usage: .eqp off|on|trace|trigger|full\n"); @@ -9311,7 +9489,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_SHELL_FIDDLE if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){ - if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); + if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) cli_exit(rc); rc = 2; }else #endif @@ -9319,31 +9497,19 @@ static int do_meta_command(char *zLine, ShellState *p){ /* The ".explain" command is automatic now. It is largely pointless. It ** retained purely for backwards compatibility */ if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){ - int val = 1; if( nArg>=2 ){ if( cli_strcmp(azArg[1],"auto")==0 ){ - val = 99; + p->mode.autoExplain = 1; }else{ - val = booleanValue(azArg[1]); + p->mode.autoExplain = booleanValue(azArg[1]); } } - if( val==1 && p->mode!=MODE_Explain ){ - p->normalMode = p->mode; - p->mode = MODE_Explain; - p->autoExplain = 0; - }else if( val==0 ){ - if( p->mode==MODE_Explain ) p->mode = p->normalMode; - p->autoExplain = 0; - }else if( val==99 ){ - if( p->mode==MODE_Explain ) p->mode = p->normalMode; - p->autoExplain = 1; - } }else #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ if( p->bSafeMode ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "Cannot run experimental commands such as \"%s\" in safe mode\n", azArg[0]); rc = 1; @@ -9401,9 +9567,9 @@ static int do_meta_command(char *zLine, ShellState *p){ /* --help lists all file-controls */ if( cli_strcmp(zCmd,"help")==0 ){ - sqlite3_fputs("Available file-controls:\n", p->out); + cli_puts("Available file-controls:\n", p->out); for(i=0; i<ArraySize(aCtrl); i++){ - sqlite3_fprintf(p->out, + cli_printf(p->out, " .filectrl %s %s\n", aCtrl[i].zCtrlName, aCtrl[i].zUsage); } rc = 1; @@ -9419,7 +9585,7 @@ static int do_meta_command(char *zLine, ShellState *p){ filectrl = aCtrl[i].ctrlCode; iCtrl = i; }else{ - sqlite3_fprintf(stderr,"Error: ambiguous file-control: \"%s\"\n" + cli_printf(stderr,"Error: ambiguous file-control: \"%s\"\n" "Use \".filectrl --help\" for help\n", zCmd); rc = 1; goto meta_command_exit; @@ -9427,7 +9593,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( filectrl<0 ){ - sqlite3_fprintf(stderr,"Error: unknown file-control: %s\n" + cli_printf(stderr,"Error: unknown file-control: %s\n" "Use \".filectrl --help\" for help\n", zCmd); }else{ switch(filectrl){ @@ -9471,7 +9637,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg!=2 ) break; sqlite3_file_control(p->db, zSchema, filectrl, &z); if( z ){ - sqlite3_fprintf(p->out, "%s\n", z); + cli_printf(p->out, "%s\n", z); sqlite3_free(z); } isOk = 2; @@ -9485,31 +9651,30 @@ static int do_meta_command(char *zLine, ShellState *p){ } x = -1; sqlite3_file_control(p->db, zSchema, filectrl, &x); - sqlite3_fprintf(p->out, "%d\n", x); + cli_printf(p->out, "%d\n", x); isOk = 2; break; } } } if( isOk==0 && iCtrl>=0 ){ - sqlite3_fprintf(p->out, "Usage: .filectrl %s %s\n", + cli_printf(p->out, "Usage: .filectrl %s %s\n", zCmd, aCtrl[iCtrl].zUsage); rc = 1; }else if( isOk==1 ){ char zBuf[100]; sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); - sqlite3_fprintf(p->out, "%s\n", zBuf); + cli_printf(p->out, "%s\n", zBuf); } }else if( c=='f' && cli_strncmp(azArg[0], "fullschema", n)==0 ){ ShellState data; int doStats = 0; - memcpy(&data, p, sizeof(data)); - data.showHeader = 0; - data.cMode = data.mode = MODE_Semi; + int hasStat[5]; + int flgs = 0; + char *zSql; if( nArg==2 && optionMatch(azArg[1], "indent") ){ - data.cMode = data.mode = MODE_Pretty; nArg = 1; } if( nArg!=1 ){ @@ -9518,385 +9683,82 @@ static int do_meta_command(char *zLine, ShellState *p){ goto meta_command_exit; } open_db(p, 0); - rc = sqlite3_exec(p->db, - "SELECT sql FROM" + zSql = sqlite3_mprintf( + "SELECT shell_format_schema(sql,%d) FROM" " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" " FROM sqlite_schema UNION ALL" " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) " "WHERE type!='meta' AND sql NOTNULL" - " AND name NOT LIKE 'sqlite__%' ESCAPE '_' " - "ORDER BY x", - callback, &data, 0 - ); + " AND name NOT LIKE 'sqlite__%%' ESCAPE '_' " + "ORDER BY x", flgs); + memcpy(&data, p, sizeof(data)); + data.mode.spec.bTitles = QRF_No; + data.mode.eMode = MODE_List; + data.mode.spec.eText = QRF_TEXT_Plain; + data.mode.spec.nCharLimit = 0; + data.mode.spec.zRowSep = "\n"; + rc = shell_exec(&data,zSql,0); + sqlite3_free(zSql); if( rc==SQLITE_OK ){ sqlite3_stmt *pStmt; + memset(hasStat, 0, sizeof(hasStat)); rc = sqlite3_prepare_v2(p->db, - "SELECT rowid FROM sqlite_schema" + "SELECT substr(name,12,1) FROM sqlite_schema" " WHERE name GLOB 'sqlite_stat[134]'", -1, &pStmt, 0); if( rc==SQLITE_OK ){ - doStats = sqlite3_step(pStmt)==SQLITE_ROW; - sqlite3_finalize(pStmt); - } - } - if( doStats==0 ){ - sqlite3_fputs("/* No STAT tables available */\n", p->out); - }else{ - sqlite3_fputs("ANALYZE sqlite_schema;\n", p->out); - data.cMode = data.mode = MODE_Insert; - data.zDestTable = "sqlite_stat1"; - shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); - data.zDestTable = "sqlite_stat4"; - shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); - sqlite3_fputs("ANALYZE sqlite_schema;\n", p->out); - } - }else - - if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){ - if( nArg==2 ){ - p->showHeader = booleanValue(azArg[1]); - p->shellFlgs |= SHFLG_HeaderSet; - }else{ - eputz("Usage: .headers on|off\n"); - rc = 1; - } - }else - - if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){ - if( nArg>=2 ){ - n = showHelp(p->out, azArg[1]); - if( n==0 ){ - sqlite3_fprintf(p->out, "Nothing matches '%s'\n", azArg[1]); - } - }else{ - showHelp(p->out, 0); - } - }else - -#ifndef SQLITE_SHELL_FIDDLE - if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ - char *zTable = 0; /* Insert data into this table */ - char *zSchema = 0; /* Schema of zTable */ - char *zFile = 0; /* Name of file to extra content from */ - sqlite3_stmt *pStmt = NULL; /* A statement */ - int nCol; /* Number of columns in the table */ - i64 nByte; /* Number of bytes in an SQL string */ - int i, j; /* Loop counters */ - int needCommit; /* True to COMMIT or ROLLBACK at end */ - int nSep; /* Number of bytes in p->colSeparator[] */ - char *zSql = 0; /* An SQL statement */ - ImportCtx sCtx; /* Reader context */ - char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ - int eVerbose = 0; /* Larger for more console output */ - i64 nSkip = 0; /* Initial lines to skip */ - int useOutputMode = 1; /* Use output mode to determine separators */ - char *zCreate = 0; /* CREATE TABLE statement text */ - - failIfSafeMode(p, "cannot run .import in safe mode"); - memset(&sCtx, 0, sizeof(sCtx)); - if( p->mode==MODE_Ascii ){ - xRead = ascii_read_one_field; - }else{ - xRead = csv_read_one_field; - } - rc = 1; - for(i=1; i<nArg; i++){ - char *z = azArg[i]; - if( z[0]=='-' && z[1]=='-' ) z++; - if( z[0]!='-' ){ - if( zFile==0 ){ - zFile = z; - }else if( zTable==0 ){ - zTable = z; - }else{ - sqlite3_fprintf(p->out, "ERROR: extra argument: \"%s\". Usage:\n",z); - showHelp(p->out, "import"); - goto meta_command_exit; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + int k = sqlite3_column_int(pStmt,0); + assert( k==1 || k==3 || k==4 ); + hasStat[k] = 1; + doStats = 1; } - }else if( cli_strcmp(z,"-v")==0 ){ - eVerbose++; - }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){ - zSchema = azArg[++i]; - }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){ - nSkip = integerValue(azArg[++i]); - }else if( cli_strcmp(z,"-ascii")==0 ){ - sCtx.cColSep = SEP_Unit[0]; - sCtx.cRowSep = SEP_Record[0]; - xRead = ascii_read_one_field; - useOutputMode = 0; - }else if( cli_strcmp(z,"-csv")==0 ){ - sCtx.cColSep = ','; - sCtx.cRowSep = '\n'; - xRead = csv_read_one_field; - useOutputMode = 0; - }else{ - sqlite3_fprintf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z); - showHelp(p->out, "import"); - goto meta_command_exit; - } - } - if( zTable==0 ){ - sqlite3_fprintf(p->out, "ERROR: missing %s argument. Usage:\n", - zFile==0 ? "FILE" : "TABLE"); - showHelp(p->out, "import"); - goto meta_command_exit; - } - seenInterrupt = 0; - open_db(p, 0); - if( useOutputMode ){ - /* If neither the --csv or --ascii options are specified, then set - ** the column and row separator characters from the output mode. */ - nSep = strlen30(p->colSeparator); - if( nSep==0 ){ - eputz("Error: non-null column separator required for import\n"); - goto meta_command_exit; - } - if( nSep>1 ){ - eputz("Error: multi-character column separators not allowed" - " for import\n"); - goto meta_command_exit; - } - nSep = strlen30(p->rowSeparator); - if( nSep==0 ){ - eputz("Error: non-null row separator required for import\n"); - goto meta_command_exit; - } - if( nSep==2 && p->mode==MODE_Csv - && cli_strcmp(p->rowSeparator,SEP_CrLf)==0 - ){ - /* When importing CSV (only), if the row separator is set to the - ** default output row separator, change it to the default input - ** row separator. This avoids having to maintain different input - ** and output row separators. */ - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - nSep = strlen30(p->rowSeparator); - } - if( nSep>1 ){ - eputz("Error: multi-character row separators not allowed" - " for import\n"); - goto meta_command_exit; - } - sCtx.cColSep = (u8)p->colSeparator[0]; - sCtx.cRowSep = (u8)p->rowSeparator[0]; - } - sCtx.zFile = zFile; - sCtx.nLine = 1; - if( sCtx.zFile[0]=='|' ){ -#ifdef SQLITE_OMIT_POPEN - eputz("Error: pipes are not supported in this OS\n"); - goto meta_command_exit; -#else - sCtx.in = sqlite3_popen(sCtx.zFile+1, "r"); - sCtx.zFile = "<pipe>"; - sCtx.xCloser = pclose; -#endif - }else{ - sCtx.in = sqlite3_fopen(sCtx.zFile, "rb"); - sCtx.xCloser = fclose; - } - if( sCtx.in==0 ){ - sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zFile); - goto meta_command_exit; - } - if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ - char zSep[2]; - zSep[1] = 0; - zSep[0] = sCtx.cColSep; - sqlite3_fputs("Column separator ", p->out); - output_c_string(p->out, zSep); - sqlite3_fputs(", row separator ", p->out); - zSep[0] = sCtx.cRowSep; - output_c_string(p->out, zSep); - sqlite3_fputs("\n", p->out); - } - sCtx.z = sqlite3_malloc64(120); - if( sCtx.z==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - /* Below, resources must be freed before exit. */ - while( nSkip>0 ){ - nSkip--; - while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} - } - import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ - if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) - && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema" - " WHERE name=%Q AND type='view'", - zSchema ? zSchema : "main", zTable) - ){ - /* Table does not exist. Create it. */ - sqlite3 *dbCols = 0; - char *zRenames = 0; - char *zColDefs; - zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", - zSchema ? zSchema : "main", zTable); - while( xRead(&sCtx) ){ - zAutoColumn(sCtx.z, &dbCols, 0); - if( sCtx.cTerm!=sCtx.cColSep ) break; - } - zColDefs = zAutoColumn(0, &dbCols, &zRenames); - if( zRenames!=0 ){ - sqlite3_fprintf((stdin_is_interactive && p->in==stdin)? p->out : stderr, - "Columns renamed during .import %s due to duplicates:\n" - "%s\n", sCtx.zFile, zRenames); - sqlite3_free(zRenames); - } - assert(dbCols==0); - if( zColDefs==0 ){ - sqlite3_fprintf(stderr,"%s: empty file\n", sCtx.zFile); - import_cleanup(&sCtx); - rc = 1; - sqlite3_free(zCreate); - goto meta_command_exit; - } - zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); - if( zCreate==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - if( eVerbose>=1 ){ - sqlite3_fprintf(p->out, "%s\n", zCreate); - } - rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); - if( rc ){ - sqlite3_fprintf(stderr, - "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); - } - sqlite3_free(zCreate); - zCreate = 0; - if( rc ){ - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; } + sqlite3_finalize(pStmt); } - zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);", - zTable, zSchema); - if( zSql==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - zSql = 0; - if( rc ){ - if (pStmt) sqlite3_finalize(pStmt); - shellDatabaseError(p->db); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - nCol = sqlite3_column_int(pStmt, 0); - }else{ - nCol = 0; - } - sqlite3_finalize(pStmt); - pStmt = 0; - if( nCol==0 ) return 0; /* no columns, no error */ - - nByte = 64 /* space for "INSERT INTO", "VALUES(", ")\0" */ - + (zSchema ? strlen(zSchema)*2 + 2: 0) /* Quoted schema name */ - + strlen(zTable)*2 + 2 /* Quoted table name */ - + nCol*2; /* Space for ",?" for each column */ - zSql = sqlite3_malloc64( nByte ); - if( zSql==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - if( zSchema ){ - sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", - zSchema, zTable); - }else{ - sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); - } - j = strlen30(zSql); - for(i=1; i<nCol; i++){ - zSql[j++] = ','; - zSql[j++] = '?'; - } - zSql[j++] = ')'; - zSql[j] = 0; - assert( j<nByte ); - if( eVerbose>=2 ){ - sqlite3_fprintf(p->out, "Insert using: %s\n", zSql); - } - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - zSql = 0; - if( rc ){ - shellDatabaseError(p->db); - if (pStmt) sqlite3_finalize(pStmt); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - needCommit = sqlite3_get_autocommit(p->db); - if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); - do{ - int startLine = sCtx.nLine; - for(i=0; i<nCol; i++){ - char *z = xRead(&sCtx); - /* - ** Did we reach end-of-file before finding any columns? - ** If so, stop instead of NULL filling the remaining columns. - */ - if( z==0 && i==0 ) break; - /* - ** Did we reach end-of-file OR end-of-line before finding any - ** columns in ASCII mode? If so, stop instead of NULL filling - ** the remaining columns. - */ - if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; - /* - ** For CSV mode, per RFC 4180, accept EOF in lieu of final - ** record terminator but only for last field of multi-field row. - ** (If there are too few fields, it's not valid CSV anyway.) - */ - if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){ - z = ""; - } - sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); - if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){ - sqlite3_fprintf(stderr,"%s:%d: expected %d columns but found %d" - " - filling the rest with NULL\n", - sCtx.zFile, startLine, nCol, i+1); - i += 2; - while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; } - } + if( doStats==0 ){ + cli_puts("/* No STAT tables available */\n", p->out); + }else{ + cli_puts("ANALYZE sqlite_schema;\n", p->out); + data.mode.eMode = MODE_Insert; + if( hasStat[1] ){ + data.mode.spec.zTableName = "sqlite_stat1"; + shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); } - if( sCtx.cTerm==sCtx.cColSep ){ - do{ - xRead(&sCtx); - i++; - }while( sCtx.cTerm==sCtx.cColSep ); - sqlite3_fprintf(stderr, - "%s:%d: expected %d columns but found %d - extras ignored\n", - sCtx.zFile, startLine, nCol, i); - } - if( i>=nCol ){ - sqlite3_step(pStmt); - rc = sqlite3_reset(pStmt); - if( rc!=SQLITE_OK ){ - sqlite3_fprintf(stderr,"%s:%d: INSERT failed: %s\n", - sCtx.zFile, startLine, sqlite3_errmsg(p->db)); - sCtx.nErr++; - }else{ - sCtx.nRow++; - } + if( hasStat[4] ){ + data.mode.spec.zTableName = "sqlite_stat4"; + shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); } - }while( sCtx.cTerm!=EOF ); + cli_puts("ANALYZE sqlite_schema;\n", p->out); + } + }else - import_cleanup(&sCtx); - sqlite3_finalize(pStmt); - if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); - if( eVerbose>0 ){ - sqlite3_fprintf(p->out, - "Added %d rows with %d errors using %d lines of input\n", - sCtx.nRow, sCtx.nErr, sCtx.nLine-1); + if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){ + if( nArg==2 ){ + p->mode.spec.bTitles = booleanValue(azArg[1]) ? QRF_Yes : QRF_No; + p->mode.mFlags |= MFLG_HDR; + p->mode.spec.eTitle = aModeInfo[p->mode.eMode].eHdr; + }else{ + eputz("Usage: .headers on|off\n"); + rc = 1; + } + }else + + if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){ + if( nArg>=2 ){ + n = showHelp(p->out, azArg[1]); + if( n==0 ){ + cli_printf(p->out, "Nothing matches '%s'\n", azArg[1]); + } + }else{ + showHelp(p->out, 0); } }else + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ + rc = dotCmdImport(p); + }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ #ifndef SQLITE_UNTESTABLE @@ -9928,10 +9790,10 @@ static int do_meta_command(char *zLine, ShellState *p){ } zSql = sqlite3_mprintf( "SELECT rootpage, 0 FROM sqlite_schema" - " WHERE name='%q' AND type='index'" + " WHERE type='index' AND lower(name)=lower('%q')" "UNION ALL " "SELECT rootpage, 1 FROM sqlite_schema" - " WHERE name='%q' AND type='table'" + " WHERE type='table' AND lower(name)=lower('%q')" " AND sql LIKE '%%without%%rowid%%'", azArg[1], azArg[1] ); @@ -9969,7 +9831,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_finalize(pStmt); if( i==0 || tnum==0 ){ - sqlite3_fprintf(stderr,"no such index: \"%s\"\n", azArg[1]); + cli_printf(stderr,"no such index: \"%s\"\n", azArg[1]); rc = 1; sqlite3_free(zCollist); goto meta_command_exit; @@ -9984,13 +9846,13 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3_exec(p->db, zSql, 0, 0, 0); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); if( rc ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); }else{ - sqlite3_fprintf(stdout, "%s;\n", zSql); + cli_printf(stdout, "%s;\n", zSql); } }else{ - sqlite3_fprintf(stderr,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); + cli_printf(stderr,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); rc = 1; } sqlite3_free(zSql); @@ -10004,7 +9866,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( iArg==0 ) iArg = -1; } if( (nArg!=1 && nArg!=2) || iArg<0 ){ - sqlite3_fprintf(stderr,"%s","Usage: .intck STEPS_PER_UNLOCK\n"); + cli_printf(stderr,"%s","Usage: .intck STEPS_PER_UNLOCK\n"); rc = 1; goto meta_command_exit; } @@ -10025,7 +9887,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ iotrace = sqlite3_fopen(azArg[1], "w"); if( iotrace==0 ){ - sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); + cli_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); sqlite3IoTrace = 0; rc = 1; }else{ @@ -10044,6 +9906,7 @@ static int do_meta_command(char *zLine, ShellState *p){ { "sql_length", SQLITE_LIMIT_SQL_LENGTH }, { "column", SQLITE_LIMIT_COLUMN }, { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH }, + { "parser_depth", SQLITE_LIMIT_PARSER_DEPTH }, { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT }, { "vdbe_op", SQLITE_LIMIT_VDBE_OP }, { "function_arg", SQLITE_LIMIT_FUNCTION_ARG }, @@ -10057,7 +9920,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); if( nArg==1 ){ for(i=0; i<ArraySize(aLimit); i++){ - sqlite3_fprintf(stdout, "%20s %d\n", aLimit[i].zLimitName, + cli_printf(stdout, "%20s %d\n", aLimit[i].zLimitName, sqlite3_limit(p->db, aLimit[i].limitCode, -1)); } }else if( nArg>3 ){ @@ -10072,14 +9935,14 @@ static int do_meta_command(char *zLine, ShellState *p){ if( iLimit<0 ){ iLimit = i; }else{ - sqlite3_fprintf(stderr,"ambiguous limit: \"%s\"\n", azArg[1]); + cli_printf(stderr,"ambiguous limit: \"%s\"\n", azArg[1]); rc = 1; goto meta_command_exit; } } } if( iLimit<0 ){ - sqlite3_fprintf(stderr,"unknown limit: \"%s\"\n" + cli_printf(stderr,"unknown limit: \"%s\"\n" "enter \".limits\" with no arguments for a list.\n", azArg[1]); rc = 1; @@ -10088,9 +9951,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==3 ){ sqlite3_limit(p->db, aLimit[iLimit].limitCode, (int)integerValue(azArg[2])); + }else{ + cli_printf(stdout, "%20s %d\n", aLimit[iLimit].zLimitName, + sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); } - sqlite3_fprintf(stdout, "%20s %d\n", aLimit[iLimit].zLimitName, - sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); } }else @@ -10138,174 +10002,12 @@ static int do_meta_command(char *zLine, ShellState *p){ } output_file_close(p->pLog); if( cli_strcmp(zFile,"on")==0 ) zFile = "stdout"; - p->pLog = output_file_open(zFile); + p->pLog = output_file_open(p, zFile); } }else if( c=='m' && cli_strncmp(azArg[0], "mode", n)==0 ){ - const char *zMode = 0; - const char *zTabname = 0; - int i, n2; - int chng = 0; /* 0x01: change to cmopts. 0x02: Any other change */ - ColModeOpts cmOpts = ColModeOpts_default; - for(i=1; i<nArg; i++){ - const char *z = azArg[i]; - if( optionMatch(z,"wrap") && i+1<nArg ){ - cmOpts.iWrap = integerValue(azArg[++i]); - chng |= 1; - }else if( optionMatch(z,"ww") ){ - cmOpts.bWordWrap = 1; - chng |= 1; - }else if( optionMatch(z,"wordwrap") && i+1<nArg ){ - cmOpts.bWordWrap = (u8)booleanValue(azArg[++i]); - chng |= 1; - }else if( optionMatch(z,"quote") ){ - cmOpts.bQuote = 1; - chng |= 1; - }else if( optionMatch(z,"noquote") ){ - cmOpts.bQuote = 0; - chng |= 1; - }else if( optionMatch(z,"escape") && i+1<nArg ){ - /* See similar code at tag-20250224-1 */ - const char *zEsc = azArg[++i]; - int k; - for(k=0; k<ArraySize(shell_EscModeNames); k++){ - if( sqlite3_stricmp(zEsc,shell_EscModeNames[k])==0 ){ - p->eEscMode = k; - chng |= 2; - break; - } - } - if( k>=ArraySize(shell_EscModeNames) ){ - sqlite3_fprintf(stderr, "unknown control character escape mode \"%s\"" - " - choices:", zEsc); - for(k=0; k<ArraySize(shell_EscModeNames); k++){ - sqlite3_fprintf(stderr, " %s", shell_EscModeNames[k]); - } - sqlite3_fprintf(stderr, "\n"); - rc = 1; - goto meta_command_exit; - } - }else if( zMode==0 ){ - zMode = z; - /* Apply defaults for qbox pseudo-mode. If that - * overwrites already-set values, user was informed of this. - */ - chng |= 1; - if( cli_strcmp(z, "qbox")==0 ){ - ColModeOpts cmo = ColModeOpts_default_qbox; - zMode = "box"; - cmOpts = cmo; - } - }else if( zTabname==0 ){ - zTabname = z; - }else if( z[0]=='-' ){ - sqlite3_fprintf(stderr,"unknown option: %s\n", z); - eputz("options:\n" - " --escape MODE\n" - " --noquote\n" - " --quote\n" - " --wordwrap on/off\n" - " --wrap N\n" - " --ww\n"); - rc = 1; - goto meta_command_exit; - }else{ - sqlite3_fprintf(stderr,"extra argument: \"%s\"\n", z); - rc = 1; - goto meta_command_exit; - } - } - if( !chng ){ - if( p->mode==MODE_Column - || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) - ){ - sqlite3_fprintf(p->out, - "current output mode: %s --wrap %d --wordwrap %s " - "--%squote --escape %s\n", - modeDescr[p->mode], p->cmOpts.iWrap, - p->cmOpts.bWordWrap ? "on" : "off", - p->cmOpts.bQuote ? "" : "no", - shell_EscModeNames[p->eEscMode] - ); - }else{ - sqlite3_fprintf(p->out, - "current output mode: %s --escape %s\n", - modeDescr[p->mode], - shell_EscModeNames[p->eEscMode] - ); - } - } - if( zMode==0 ){ - zMode = modeDescr[p->mode]; - if( (chng&1)==0 ) cmOpts = p->cmOpts; - } - n2 = strlen30(zMode); - if( cli_strncmp(zMode,"lines",n2)==0 ){ - p->mode = MODE_Line; - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( cli_strncmp(zMode,"columns",n2)==0 ){ - p->mode = MODE_Column; - if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){ - p->showHeader = 1; - } - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - p->cmOpts = cmOpts; - }else if( cli_strncmp(zMode,"list",n2)==0 ){ - p->mode = MODE_List; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( cli_strncmp(zMode,"html",n2)==0 ){ - p->mode = MODE_Html; - }else if( cli_strncmp(zMode,"tcl",n2)==0 ){ - p->mode = MODE_Tcl; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( cli_strncmp(zMode,"csv",n2)==0 ){ - p->mode = MODE_Csv; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); - }else if( cli_strncmp(zMode,"tabs",n2)==0 ){ - p->mode = MODE_List; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab); - }else if( cli_strncmp(zMode,"insert",n2)==0 ){ - p->mode = MODE_Insert; - set_table_name(p, zTabname ? zTabname : "table"); - if( p->eEscMode==SHELL_ESC_OFF ){ - ShellSetFlag(p, SHFLG_Newlines); - }else{ - ShellClearFlag(p, SHFLG_Newlines); - } - }else if( cli_strncmp(zMode,"quote",n2)==0 ){ - p->mode = MODE_Quote; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( cli_strncmp(zMode,"ascii",n2)==0 ){ - p->mode = MODE_Ascii; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); - }else if( cli_strncmp(zMode,"markdown",n2)==0 ){ - p->mode = MODE_Markdown; - p->cmOpts = cmOpts; - }else if( cli_strncmp(zMode,"table",n2)==0 ){ - p->mode = MODE_Table; - p->cmOpts = cmOpts; - }else if( cli_strncmp(zMode,"box",n2)==0 ){ - p->mode = MODE_Box; - p->cmOpts = cmOpts; - }else if( cli_strncmp(zMode,"count",n2)==0 ){ - p->mode = MODE_Count; - }else if( cli_strncmp(zMode,"off",n2)==0 ){ - p->mode = MODE_Off; - }else if( cli_strncmp(zMode,"json",n2)==0 ){ - p->mode = MODE_Json; - }else{ - eputz("Error: mode should be one of: " - "ascii box column csv html insert json line list markdown " - "qbox quote table tabs tcl\n"); - rc = 1; - } - p->cMode = p->mode; + rc = dotCmdMode(p); }else #ifndef SQLITE_SHELL_FIDDLE @@ -10314,9 +10016,9 @@ static int do_meta_command(char *zLine, ShellState *p){ eputz("Usage: .nonce NONCE\n"); rc = 1; }else if( p->zNonce==0 || cli_strcmp(azArg[1],p->zNonce)!=0 ){ - sqlite3_fprintf(stderr,"line %lld: incorrect nonce: \"%s\"\n", + cli_printf(stderr,"line %lld: incorrect nonce: \"%s\"\n", p->lineno, azArg[1]); - exit(1); + cli_exit(1); }else{ p->bSafeMode = 0; return 0; /* Return immediately to bypass the safe mode reset @@ -10327,8 +10029,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='n' && cli_strncmp(azArg[0], "nullvalue", n)==0 ){ if( nArg==2 ){ - sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, - "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); + modeSetStr(&p->mode.spec.zNull, azArg[1]); }else{ eputz("Usage: .nullvalue STRING\n"); rc = 1; @@ -10343,6 +10044,8 @@ static int do_meta_command(char *zLine, ShellState *p){ int openMode = SHELL_OPEN_UNSPEC; int openFlags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; + if( p->bSafeMode ) openFlags = SQLITE_OPEN_READONLY; + /* Check for command-line arguments */ for(iName=1; iName<nArg; iName++){ const char *z = azArg[iName]; @@ -10350,10 +10053,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( optionMatch(z,"new") ){ newFlag = 1; #ifdef SQLITE_HAVE_ZLIB - }else if( optionMatch(z, "zip") ){ + }else if( optionMatch(z, "zip") && !p->bSafeMode ){ openMode = SHELL_OPEN_ZIPFILE; #endif - }else if( optionMatch(z, "append") ){ + }else if( optionMatch(z, "append") && !p->bSafeMode ){ openMode = SHELL_OPEN_APPENDVFS; }else if( optionMatch(z, "readonly") ){ openFlags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); @@ -10377,11 +10080,11 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif /* !SQLITE_SHELL_FIDDLE */ if( z[0]=='-' ){ - sqlite3_fprintf(stderr,"unknown option: %s\n", z); + cli_printf(stderr,"unknown option: %s\n", z); rc = 1; goto meta_command_exit; }else if( zFN ){ - sqlite3_fprintf(stderr,"extra argument: \"%s\"\n", z); + cli_printf(stderr,"extra argument: \"%s\"\n", z); rc = 1; goto meta_command_exit; }else{ @@ -10432,7 +10135,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->pAuxDb->zDbFilename = zNewFilename; open_db(p, OPEN_DB_KEEPALIVE); if( p->db==0 ){ - sqlite3_fprintf(stderr,"Error: cannot open '%s'\n", zNewFilename); + cli_printf(stderr,"Error: cannot open '%s'\n", zNewFilename); sqlite3_free(zNewFilename); }else{ p->pAuxDb->zFreeOnClose = zNewFilename; @@ -10452,145 +10155,7 @@ static int do_meta_command(char *zLine, ShellState *p){ || (c=='e' && n==5 && cli_strcmp(azArg[0],"excel")==0) || (c=='w' && n==3 && cli_strcmp(azArg[0],"www")==0) ){ - char *zFile = 0; - int i; - int eMode = 0; /* 0: .outout/.once, 'x'=.excel, 'w'=.www */ - int bOnce = 0; /* 0: .output, 1: .once, 2: .excel/.www */ - int bPlain = 0; /* --plain option */ - static const char *zBomUtf8 = "\357\273\277"; - const char *zBom = 0; - - failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); - if( c=='e' ){ - eMode = 'x'; - bOnce = 2; - }else if( c=='w' ){ - eMode = 'w'; - bOnce = 2; - }else if( cli_strncmp(azArg[0],"once",n)==0 ){ - bOnce = 1; - } - for(i=1; i<nArg; i++){ - char *z = azArg[i]; - if( z[0]=='-' ){ - if( z[1]=='-' ) z++; - if( cli_strcmp(z,"-bom")==0 ){ - zBom = zBomUtf8; - }else if( cli_strcmp(z,"-plain")==0 ){ - bPlain = 1; - }else if( c=='o' && cli_strcmp(z,"-x")==0 ){ - eMode = 'x'; /* spreadsheet */ - }else if( c=='o' && cli_strcmp(z,"-e")==0 ){ - eMode = 'e'; /* text editor */ - }else if( c=='o' && cli_strcmp(z,"-w")==0 ){ - eMode = 'w'; /* Web browser */ - }else{ - sqlite3_fprintf(p->out, - "ERROR: unknown option: \"%s\". Usage:\n", azArg[i]); - showHelp(p->out, azArg[0]); - rc = 1; - sqlite3_free(zFile); - goto meta_command_exit; - } - }else if( zFile==0 && eMode==0 ){ - if( cli_strcmp(z, "off")==0 ){ -#ifdef _WIN32 - zFile = sqlite3_mprintf("nul"); -#else - zFile = sqlite3_mprintf("/dev/null"); -#endif - }else{ - zFile = sqlite3_mprintf("%s", z); - } - if( zFile && zFile[0]=='|' ){ - while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]); - break; - } - }else{ - sqlite3_fprintf(p->out, - "ERROR: extra parameter: \"%s\". Usage:\n", azArg[i]); - showHelp(p->out, azArg[0]); - rc = 1; - sqlite3_free(zFile); - goto meta_command_exit; - } - } - if( zFile==0 ){ - zFile = sqlite3_mprintf("stdout"); - } - shell_check_oom(zFile); - if( bOnce ){ - p->outCount = 2; - }else{ - p->outCount = 0; - } - output_reset(p); -#ifndef SQLITE_NOHAVE_SYSTEM - if( eMode=='e' || eMode=='x' || eMode=='w' ){ - p->doXdgOpen = 1; - outputModePush(p); - if( eMode=='x' ){ - /* spreadsheet mode. Output as CSV. */ - newTempFile(p, "csv"); - ShellClearFlag(p, SHFLG_Echo); - p->mode = MODE_Csv; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); -#ifdef _WIN32 - zBom = zBomUtf8; /* Always include the BOM on Windows, as Excel does - ** not work without it. */ -#endif - }else if( eMode=='w' ){ - /* web-browser mode. */ - newTempFile(p, "html"); - if( !bPlain ) p->mode = MODE_Www; - }else{ - /* text editor mode */ - newTempFile(p, "txt"); - } - sqlite3_free(zFile); - zFile = sqlite3_mprintf("%s", p->zTempFile); - } -#endif /* SQLITE_NOHAVE_SYSTEM */ - shell_check_oom(zFile); - if( zFile[0]=='|' ){ -#ifdef SQLITE_OMIT_POPEN - eputz("Error: pipes are not supported in this OS\n"); - rc = 1; - output_redir(p, stdout); -#else - FILE *pfPipe = sqlite3_popen(zFile + 1, "w"); - if( pfPipe==0 ){ - assert( stderr!=NULL ); - sqlite3_fprintf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); - rc = 1; - }else{ - output_redir(p, pfPipe); - if( zBom ) sqlite3_fputs(zBom, pfPipe); - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); - } -#endif - }else{ - FILE *pfFile = output_file_open(zFile); - if( pfFile==0 ){ - if( cli_strcmp(zFile,"off")!=0 ){ - assert( stderr!=NULL ); - sqlite3_fprintf(stderr,"Error: cannot write to \"%s\"\n", zFile); - } - rc = 1; - } else { - output_redir(p, pfFile); - if( zBom ) sqlite3_fputs(zBom, pfFile); - if( bPlain && eMode=='w' ){ - sqlite3_fputs( - "<!DOCTYPE html>\n<BODY>\n<PLAINTEXT>\n", - pfFile - ); - } - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); - } - } - sqlite3_free(zFile); + rc = dotCmdOutput(p); }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ @@ -10627,7 +10192,7 @@ static int do_meta_command(char *zLine, ShellState *p){ "SELECT key, quote(value) " "FROM temp.sqlite_parameters;", -1, &pStmt, 0); while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - sqlite3_fprintf(p->out, + cli_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), sqlite3_column_text(pStmt,1)); } @@ -10673,7 +10238,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rx!=SQLITE_OK ){ - sqlite3_fprintf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); + cli_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); pStmt = 0; rc = 1; @@ -10703,10 +10268,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='p' && n>=3 && cli_strncmp(azArg[0], "print", n)==0 ){ int i; for(i=1; i<nArg; i++){ - if( i>1 ) sqlite3_fputs(" ", p->out); - sqlite3_fputs(azArg[i], p->out); + if( i>1 ) cli_puts(" ", p->out); + cli_puts(azArg[i], p->out); } - sqlite3_fputs("\n", p->out); + cli_puts("\n", p->out); }else #ifndef SQLITE_OMIT_PROGRESS_CALLBACK @@ -10733,6 +10298,19 @@ static int do_meta_command(char *zLine, ShellState *p){ p->flgProgress |= SHELL_PROGRESS_ONCE; continue; } + if( cli_strcmp(z,"timeout")==0 ){ + if( i==nArg-1 ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + i++; + p->tmProgress = atof(azArg[i]); + if( p->tmProgress>0.0 ){ + p->flgProgress = SHELL_PROGRESS_QUIET|SHELL_PROGRESS_TMOUT; + if( nn==0 ) nn = 100; + } + continue; + } if( cli_strcmp(z,"limit")==0 ){ if( i+1>=nArg ){ eputz("Error: missing argument on --limit\n"); @@ -10743,7 +10321,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } continue; } - sqlite3_fprintf(stderr,"Error: unknown option: \"%s\"\n", azArg[i]); + cli_printf(stderr,"Error: unknown option: \"%s\"\n", azArg[i]); rc = 1; goto meta_command_exit; }else{ @@ -10787,7 +10365,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #else p->in = sqlite3_popen(azArg[1]+1, "r"); if( p->in==0 ){ - sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); + cli_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ rc = process_input(p, "<pipe>"); @@ -10795,10 +10373,12 @@ static int do_meta_command(char *zLine, ShellState *p){ } #endif }else if( (p->in = openChrSource(azArg[1]))==0 ){ - sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); + cli_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ - rc = process_input(p, azArg[1]); + char *zFilename = strdup(azArg[1]); + rc = process_input(p, zFilename); + free(zFilename); fclose(p->in); } p->in = inSaved; @@ -10828,7 +10408,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } rc = sqlite3_open(zSrcFile, &pSrc); if( rc!=SQLITE_OK ){ - sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zSrcFile); + cli_printf(stderr,"Error: cannot open \"%s\"\n", zSrcFile); close_db(pSrc); return 1; } @@ -10866,21 +10446,21 @@ static int do_meta_command(char *zLine, ShellState *p){ ){ if( nArg==2 ){ if( cli_strcmp(azArg[1], "vm")==0 ){ - p->scanstatsOn = 3; + p->mode.scanstatsOn = 3; }else if( cli_strcmp(azArg[1], "est")==0 ){ - p->scanstatsOn = 2; + p->mode.scanstatsOn = 2; }else{ - p->scanstatsOn = (u8)booleanValue(azArg[1]); + p->mode.scanstatsOn = (u8)booleanValue(azArg[1]); } open_db(p, 0); sqlite3_db_config( - p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 + p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->mode.scanstatsOn, (int*)0 ); #if !defined(SQLITE_ENABLE_STMT_SCANSTATUS) eputz("Warning: .scanstats not available in this build.\n"); #elif !defined(SQLITE_ENABLE_BYTECODE_VTAB) - if( p->scanstatsOn==3 ){ + if( p->mode.scanstatsOn==3 ){ eputz("Warning: \".scanstats vm\" not available in this build.\n"); } #endif @@ -10891,7 +10471,6 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( c=='s' && cli_strncmp(azArg[0], "schema", n)==0 ){ - ShellText sSelect; ShellState data; char *zErrMsg = 0; const char *zDiv = "("; @@ -10899,22 +10478,27 @@ static int do_meta_command(char *zLine, ShellState *p){ int iSchema = 0; int bDebug = 0; int bNoSystemTabs = 0; + int bIndent = 0; int ii; - + sqlite3_str *pSql; + sqlite3_stmt *pStmt = 0; + open_db(p, 0); memcpy(&data, p, sizeof(data)); - data.showHeader = 0; - data.cMode = data.mode = MODE_Semi; - initText(&sSelect); + data.mode.spec.bTitles = QRF_No; + data.mode.eMode = MODE_List; + data.mode.spec.eText = QRF_TEXT_Plain; + data.mode.spec.nCharLimit = 0; + data.mode.spec.zRowSep = "\n"; for(ii=1; ii<nArg; ii++){ if( optionMatch(azArg[ii],"indent") ){ - data.cMode = data.mode = MODE_Pretty; + bIndent = 1; }else if( optionMatch(azArg[ii],"debug") ){ bDebug = 1; }else if( optionMatch(azArg[ii],"nosys") ){ bNoSystemTabs = 1; }else if( azArg[ii][0]=='-' ){ - sqlite3_fprintf(stderr,"Unknown option: \"%s\"\n", azArg[ii]); + cli_printf(stderr,"Unknown option: \"%s\"\n", azArg[ii]); rc = 1; goto meta_command_exit; }else if( zName==0 ){ @@ -10931,96 +10515,85 @@ static int do_meta_command(char *zLine, ShellState *p){ || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0 || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0; if( isSchema ){ - char *new_argv[2], *new_colv[2]; - new_argv[0] = sqlite3_mprintf( - "CREATE TABLE %s (\n" + cli_printf(p->out, + "CREATE TABLE %ssqlite_schema (\n" " type text,\n" " name text,\n" " tbl_name text,\n" " rootpage integer,\n" " sql text\n" - ")", zName); - shell_check_oom(new_argv[0]); - new_argv[1] = 0; - new_colv[0] = "sql"; - new_colv[1] = 0; - callback(&data, 1, new_argv, new_colv); - sqlite3_free(new_argv[0]); + ");\n", + sqlite3_strlike("sqlite_t%",zName,0)==0 ? "temp." : "" + ); } } - if( zDiv ){ - sqlite3_stmt *pStmt = 0; - rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list", - -1, &pStmt, 0); - if( rc ){ - shellDatabaseError(p->db); - sqlite3_finalize(pStmt); - rc = 1; - goto meta_command_exit; - } - appendText(&sSelect, "SELECT sql FROM", 0); - iSchema = 0; - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zDb = (const char*)sqlite3_column_text(pStmt, 0); - char zScNum[30]; - sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema); - appendText(&sSelect, zDiv, 0); - zDiv = " UNION ALL "; - appendText(&sSelect, "SELECT shell_add_schema(sql,", 0); - if( sqlite3_stricmp(zDb, "main")!=0 ){ - appendText(&sSelect, zDb, '\''); - }else{ - appendText(&sSelect, "NULL", 0); - } - appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0); - appendText(&sSelect, zScNum, 0); - appendText(&sSelect, " AS snum, ", 0); - appendText(&sSelect, zDb, '\''); - appendText(&sSelect, " AS sname FROM ", 0); - appendText(&sSelect, zDb, quoteChar(zDb)); - appendText(&sSelect, ".sqlite_schema", 0); - } + rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); + if( rc ){ + shellDatabaseError(p->db); sqlite3_finalize(pStmt); -#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS - if( zName ){ - appendText(&sSelect, - " UNION ALL SELECT shell_module_schema(name)," - " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list", - 0); - } -#endif - appendText(&sSelect, ") WHERE ", 0); - if( zName ){ - char *zQarg = sqlite3_mprintf("%Q", zName); - int bGlob; - shell_check_oom(zQarg); - bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 || - strchr(zName, '[') != 0; - if( strchr(zName, '.') ){ - appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0); - }else{ - appendText(&sSelect, "lower(tbl_name)", 0); - } - appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0); - appendText(&sSelect, zQarg, 0); - if( !bGlob ){ - appendText(&sSelect, " ESCAPE '\\' ", 0); - } - appendText(&sSelect, " AND ", 0); - sqlite3_free(zQarg); + + rc = 1; + goto meta_command_exit; + } + pSql = sqlite3_str_new(p->db); + sqlite3_str_appendf(pSql, "SELECT sql FROM", 0); + iSchema = 0; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pStmt, 1); + char zScNum[30]; + sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema); + sqlite3_str_appendall(pSql, zDiv); + zDiv = " UNION ALL "; + if( sqlite3_stricmp(zDb, "main")==0 ){ + sqlite3_str_appendf(pSql, + "SELECT shell_format_schema(shell_add_schema(sql,NULL,name),%d)", + bIndent); + }else{ + sqlite3_str_appendf(pSql, + "SELECT shell_format_schema(shell_add_schema(sql,%Q,name),%d)", + zDb, bIndent); } - if( bNoSystemTabs ){ - appendText(&sSelect, "name NOT LIKE 'sqlite__%%' ESCAPE '_' AND ", 0); + sqlite3_str_appendf(pSql, + " AS sql, type, tbl_name, name, rowid, %d AS snum, %Q as sname", + ++iSchema, zDb); + sqlite3_str_appendf(pSql," FROM \"%w\".sqlite_schema", zDb); + } + sqlite3_finalize(pStmt); +#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) \ + && !defined(SQLITE_OMIT_VIRTUALTABLE) + if( zName ){ + sqlite3_str_appendall(pSql, + " UNION ALL SELECT shell_module_schema(name)," + " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list"); + } +#endif + sqlite3_str_appendf(pSql, ") WHERE ", 0); + if( zName ){ + int bGlob; + bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 || + strchr(zName, '[') != 0; + if( strchr(zName, '.') ){ + sqlite3_str_appendall(pSql, "lower(format('%%s.%%s',sname,tbl_name))"); + }else{ + sqlite3_str_appendall(pSql, "lower(tbl_name)"); } - appendText(&sSelect, "sql IS NOT NULL" - " ORDER BY snum, rowid", 0); - if( bDebug ){ - sqlite3_fprintf(p->out, "SQL: %s;\n", sSelect.zTxt); + if( bGlob ){ + sqlite3_str_appendf(pSql, " GLOB %Q AND ", zName); }else{ - rc = sqlite3_exec(p->db, sSelect.zTxt, callback, &data, &zErrMsg); + sqlite3_str_appendf(pSql, " LIKE %Q ESCAPE '\\' AND ", zName); } - freeText(&sSelect); } + if( bNoSystemTabs ){ + sqlite3_str_appendf(pSql, " name NOT LIKE 'sqlite__%%' ESCAPE '_' AND "); + } + sqlite3_str_appendf(pSql, "sql IS NOT NULL ORDER BY snum, rowid"); + if( bDebug ){ + cli_printf(p->out, "SQL: %s;\n", sqlite3_str_value(pSql)); + }else{ + rc = shell_exec(&data, sqlite3_str_value(pSql), &zErrMsg); + } + sqlite3_str_free(pSql); + if( zErrMsg ){ shellEmitError(zErrMsg); sqlite3_free(zErrMsg); @@ -11076,7 +10649,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ rc = sqlite3session_attach(pSession->p, azCmd[1]); if( rc ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n",rc); rc = 0; } @@ -11096,7 +10669,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( pSession->p==0 ) goto session_not_open; out = sqlite3_fopen(azCmd[1], "wb"); if( out==0 ){ - sqlite3_fprintf(stderr,"ERROR: cannot open \"%s\" for writing\n", + cli_printf(stderr,"ERROR: cannot open \"%s\" for writing\n", azCmd[1]); }else{ int szChng; @@ -11107,12 +10680,12 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); } if( rc ){ - sqlite3_fprintf(stdout, "Error: error code %d\n", rc); + cli_printf(stdout, "Error: error code %d\n", rc); rc = 0; } if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "ERROR: Failed to write entire %d-byte output\n", szChng); } sqlite3_free(pChng); @@ -11140,7 +10713,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); if( pAuxDb->nSession ){ ii = sqlite3session_enable(pSession->p, ii); - sqlite3_fprintf(p->out, + cli_printf(p->out, "session %s enable flag = %d\n", pSession->zName, ii); } }else @@ -11177,7 +10750,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); if( pAuxDb->nSession ){ ii = sqlite3session_indirect(pSession->p, ii); - sqlite3_fprintf(p->out, + cli_printf(p->out, "session %s indirect flag = %d\n", pSession->zName, ii); } }else @@ -11190,7 +10763,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nCmd!=1 ) goto session_syntax_error; if( pAuxDb->nSession ){ ii = sqlite3session_isempty(pSession->p); - sqlite3_fprintf(p->out, + cli_printf(p->out, "session %s isempty flag = %d\n", pSession->zName, ii); } }else @@ -11200,7 +10773,7 @@ static int do_meta_command(char *zLine, ShellState *p){ */ if( cli_strcmp(azCmd[0],"list")==0 ){ for(i=0; i<pAuxDb->nSession; i++){ - sqlite3_fprintf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); + cli_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); } }else @@ -11215,19 +10788,19 @@ static int do_meta_command(char *zLine, ShellState *p){ if( zName[0]==0 ) goto session_syntax_error; for(i=0; i<pAuxDb->nSession; i++){ if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ - sqlite3_fprintf(stderr,"Session \"%s\" already exists\n", zName); + cli_printf(stderr,"Session \"%s\" already exists\n", zName); goto meta_command_exit; } } if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); goto meta_command_exit; } pSession = &pAuxDb->aSession[pAuxDb->nSession]; rc = sqlite3session_create(p->db, azCmd[1], &pSession->p); if( rc ){ - sqlite3_fprintf(stderr,"Cannot open session: error code=%d\n", rc); + cli_printf(stderr,"Cannot open session: error code=%d\n", rc); rc = 0; goto meta_command_exit; } @@ -11251,7 +10824,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int i, v; for(i=1; i<nArg; i++){ v = booleanValue(azArg[i]); - sqlite3_fprintf(p->out, "%s: %d 0x%x\n", azArg[i], v, v); + cli_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v); } } if( cli_strncmp(azArg[0]+9, "integer", n-9)==0 ){ @@ -11260,7 +10833,7 @@ static int do_meta_command(char *zLine, ShellState *p){ char zBuf[200]; v = integerValue(azArg[i]); sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v); - sqlite3_fputs(zBuf, p->out); + cli_puts(zBuf, p->out); } } }else @@ -11287,9 +10860,9 @@ static int do_meta_command(char *zLine, ShellState *p){ bVerbose++; }else { - sqlite3_fprintf(stderr, + cli_printf(stderr, "Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]); - sqlite3_fputs("Should be one of: --init -v\n", stderr); + cli_puts("Should be one of: --init -v\n", stderr); rc = 1; goto meta_command_exit; } @@ -11334,10 +10907,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( zAns==0 ) continue; k = 0; if( bVerbose>0 ){ - sqlite3_fprintf(stdout, "%d: %s %s\n", tno, zOp, zSql); + cli_printf(stdout, "%d: %s %s\n", tno, zOp, zSql); } if( cli_strcmp(zOp,"memo")==0 ){ - sqlite3_fprintf(p->out, "%s\n", zSql); + cli_printf(p->out, "%s\n", zSql); }else if( cli_strcmp(zOp,"run")==0 ){ char *zErrMsg = 0; @@ -11346,22 +10919,22 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg); nTest++; if( bVerbose ){ - sqlite3_fprintf(p->out, "Result: %s\n", str.zTxt); + cli_printf(p->out, "Result: %s\n", str.zTxt); } if( rc || zErrMsg ){ nErr++; rc = 1; - sqlite3_fprintf(p->out, "%d: error-code-%d: %s\n", tno, rc,zErrMsg); + cli_printf(p->out, "%d: error-code-%d: %s\n", tno, rc,zErrMsg); sqlite3_free(zErrMsg); }else if( cli_strcmp(zAns,str.zTxt)!=0 ){ nErr++; rc = 1; - sqlite3_fprintf(p->out, "%d: Expected: [%s]\n", tno, zAns); - sqlite3_fprintf(p->out, "%d: Got: [%s]\n", tno, str.zTxt); + cli_printf(p->out, "%d: Expected: [%s]\n", tno, zAns); + cli_printf(p->out, "%d: Got: [%s]\n", tno, str.zTxt); } } else{ - sqlite3_fprintf(stderr, + cli_printf(stderr, "Unknown operation \"%s\" on selftest line %d\n", zOp, tno); rc = 1; break; @@ -11370,7 +10943,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_finalize(pStmt); } /* End loop over k */ freeText(&str); - sqlite3_fprintf(p->out, "%d errors out of %d tests\n", nErr, nTest); + cli_printf(p->out, "%d errors out of %d tests\n", nErr, nTest); }else if( c=='s' && cli_strncmp(azArg[0], "separator", n)==0 ){ @@ -11379,12 +10952,10 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; } if( nArg>=2 ){ - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, - "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]); + modeSetStr(&p->mode.spec.zColumnSep, azArg[1]); } if( nArg>=3 ){ - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, - "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]); + modeSetStr(&p->mode.spec.zRowSep,azArg[2]); } }else @@ -11418,7 +10989,7 @@ static int do_meta_command(char *zLine, ShellState *p){ bDebug = 1; }else { - sqlite3_fprintf(stderr, + cli_printf(stderr, "Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]); showHelp(p->out, azArg[0]); rc = 1; @@ -11497,7 +11068,7 @@ static int do_meta_command(char *zLine, ShellState *p){ freeText(&sQuery); freeText(&sSql); if( bDebug ){ - sqlite3_fprintf(p->out, "%s\n", zSql); + cli_printf(p->out, "%s\n", zSql); }else{ shell_exec(p, zSql, 0); } @@ -11527,7 +11098,7 @@ static int do_meta_command(char *zLine, ShellState *p){ "' OR ') as query, tname from tabcols group by tname)" , zRevText); shell_check_oom(zRevText); - if( bDebug ) sqlite3_fprintf(p->out, "%s\n", zRevText); + if( bDebug ) cli_printf(p->out, "%s\n", zRevText); lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0); if( lrc!=SQLITE_OK ){ /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the @@ -11540,7 +11111,7 @@ static int do_meta_command(char *zLine, ShellState *p){ const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0); sqlite3_stmt *pCheckStmt; lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0); - if( bDebug ) sqlite3_fprintf(p->out, "%s\n", zGenQuery); + if( bDebug ) cli_printf(p->out, "%s\n", zGenQuery); if( lrc!=SQLITE_OK ){ rc = 1; }else{ @@ -11548,7 +11119,7 @@ static int do_meta_command(char *zLine, ShellState *p){ double countIrreversible = sqlite3_column_double(pCheckStmt, 0); if( countIrreversible>0 ){ int sz = (int)(countIrreversible + 0.5); - sqlite3_fprintf(stderr, + cli_printf(stderr, "Digest includes %d invalidly encoded text field%s.\n", sz, (sz>1)? "s": ""); } @@ -11587,7 +11158,7 @@ static int do_meta_command(char *zLine, ShellState *p){ x = zCmd!=0 ? system(zCmd) : 1; /*consoleRenewSetup();*/ sqlite3_free(zCmd); - if( x ) sqlite3_fprintf(stderr,"System command returns %d\n", x); + if( x ) cli_printf(stderr,"System command returns %d\n", x); }else #endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */ @@ -11600,48 +11171,51 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; goto meta_command_exit; } - sqlite3_fprintf(p->out, "%12.12s: %s\n","echo", - azBool[ShellHasFlag(p, SHFLG_Echo)]); - sqlite3_fprintf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); - sqlite3_fprintf(p->out, "%12.12s: %s\n","explain", - p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); - sqlite3_fprintf(p->out, "%12.12s: %s\n","headers", - azBool[p->showHeader!=0]); - if( p->mode==MODE_Column - || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) + cli_printf(p->out, "%12.12s: %s\n","echo", + azBool[(p->mode.mFlags & MFLG_ECHO)!=0]); + cli_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->mode.autoEQP&3]); + cli_printf(p->out, "%12.12s: %s\n","explain", + p->mode.autoExplain ? "auto" : "off"); + cli_printf(p->out, "%12.12s: %s\n","headers", + azBool[p->mode.spec.bTitles==QRF_Yes]); + if( p->mode.spec.eStyle==QRF_STYLE_Column + || p->mode.spec.eStyle==QRF_STYLE_Box + || p->mode.spec.eStyle==QRF_STYLE_Table + || p->mode.spec.eStyle==QRF_STYLE_Markdown ){ - sqlite3_fprintf(p->out, + cli_printf(p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode", - modeDescr[p->mode], p->cmOpts.iWrap, - p->cmOpts.bWordWrap ? "on" : "off", - p->cmOpts.bQuote ? "" : "no"); + aModeInfo[p->mode.eMode].zName, p->mode.spec.nWrap, + p->mode.spec.bWordWrap==QRF_Yes ? "on" : "off", + p->mode.spec.eText==QRF_TEXT_Sql ? "" : "no"); }else{ - sqlite3_fprintf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]); + cli_printf(p->out, "%12.12s: %s\n","mode", + aModeInfo[p->mode.eMode].zName); } - sqlite3_fprintf(p->out, "%12.12s: ", "nullvalue"); - output_c_string(p->out, p->nullValue); - sqlite3_fputs("\n", p->out); - sqlite3_fprintf(p->out, "%12.12s: %s\n","output", + cli_printf(p->out, "%12.12s: ", "nullvalue"); + output_c_string(p->out, p->mode.spec.zNull); + cli_puts("\n", p->out); + cli_printf(p->out, "%12.12s: %s\n","output", strlen30(p->outfile) ? p->outfile : "stdout"); - sqlite3_fprintf(p->out, "%12.12s: ", "colseparator"); - output_c_string(p->out, p->colSeparator); - sqlite3_fputs("\n", p->out); - sqlite3_fprintf(p->out, "%12.12s: ", "rowseparator"); - output_c_string(p->out, p->rowSeparator); - sqlite3_fputs("\n", p->out); + cli_printf(p->out, "%12.12s: ", "colseparator"); + output_c_string(p->out, p->mode.spec.zColumnSep); + cli_puts("\n", p->out); + cli_printf(p->out, "%12.12s: ", "rowseparator"); + output_c_string(p->out, p->mode.spec.zRowSep); + cli_puts("\n", p->out); switch( p->statsOn ){ case 0: zOut = "off"; break; default: zOut = "on"; break; case 2: zOut = "stmt"; break; case 3: zOut = "vmstep"; break; } - sqlite3_fprintf(p->out, "%12.12s: %s\n","stats", zOut); - sqlite3_fprintf(p->out, "%12.12s: ", "width"); - for (i=0;i<p->nWidth;i++) { - sqlite3_fprintf(p->out, "%d ", p->colWidth[i]); + cli_printf(p->out, "%12.12s: %s\n","stats", zOut); + cli_printf(p->out, "%12.12s: ", "width"); + for(i=0; i<p->mode.spec.nWidth; i++){ + cli_printf(p->out, "%d ", (int)p->mode.spec.aWidth[i]); } - sqlite3_fputs("\n", p->out); - sqlite3_fprintf(p->out, "%12.12s: %s\n", "filename", + cli_puts("\n", p->out); + cli_printf(p->out, "%12.12s: %s\n", "filename", p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); }else @@ -11667,11 +11241,9 @@ static int do_meta_command(char *zLine, ShellState *p){ || cli_strncmp(azArg[0], "indexes", n)==0) ) ){ sqlite3_stmt *pStmt; - char **azResult; - int nRow, nAlloc; - int ii; - ShellText s; - initText(&s); + sqlite3_str *pSql; + const char *zPattern = nArg>1 ? azArg[1] : 0; + open_db(p, 0); rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); if( rc ){ @@ -11688,103 +11260,53 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_finalize(pStmt); goto meta_command_exit; } - for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){ + pSql = sqlite3_str_new(p->db); + while( sqlite3_step(pStmt)==SQLITE_ROW ){ const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); if( zDbName==0 ) continue; - if( s.zTxt && s.zTxt[0] ) appendText(&s, " UNION ALL ", 0); + if( sqlite3_str_length(pSql) ){ + sqlite3_str_appendall(pSql, " UNION ALL "); + } if( sqlite3_stricmp(zDbName, "main")==0 ){ - appendText(&s, "SELECT name FROM ", 0); + sqlite3_str_appendall(pSql, "SELECT name FROM "); }else{ - appendText(&s, "SELECT ", 0); - appendText(&s, zDbName, '\''); - appendText(&s, "||'.'||name FROM ", 0); + sqlite3_str_appendf(pSql, "SELECT %Q||'.'||name FROM ", zDbName); } - appendText(&s, zDbName, '"'); - appendText(&s, ".sqlite_schema ", 0); + sqlite3_str_appendf(pSql, "\"%w\".sqlite_schema", zDbName); if( c=='t' ){ - appendText(&s," WHERE type IN ('table','view')" - " AND name NOT LIKE 'sqlite__%' ESCAPE '_'" - " AND name LIKE ?1", 0); + sqlite3_str_appendf(pSql, + " WHERE type IN ('table','view')" + " AND name NOT LIKE 'sqlite__%%' ESCAPE '_'" + ); + if( zPattern ){ + sqlite3_str_appendf(pSql," AND name LIKE %Q", zPattern); + } }else{ - appendText(&s," WHERE type='index'" - " AND tbl_name LIKE ?1", 0); + sqlite3_str_appendf(pSql, " WHERE type='index'"); + if( zPattern ){ + sqlite3_str_appendf(pSql," AND tbl_name LIKE %Q", zPattern); + } } } rc = sqlite3_finalize(pStmt); if( rc==SQLITE_OK ){ - appendText(&s, " ORDER BY 1", 0); - rc = sqlite3_prepare_v2(p->db, s.zTxt, -1, &pStmt, 0); - } - freeText(&s); - if( rc ) return shellDatabaseError(p->db); - - /* Run the SQL statement prepared by the above block. Store the results - ** as an array of nul-terminated strings in azResult[]. */ - nRow = nAlloc = 0; - azResult = 0; - if( nArg>1 ){ - sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT); - }else{ - sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); - } - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - if( nRow>=nAlloc ){ - char **azNew; - sqlite3_int64 n2 = 2*(sqlite3_int64)nAlloc + 10; - azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); - shell_check_oom(azNew); - nAlloc = (int)n2; - azResult = azNew; - } - azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); - shell_check_oom(azResult[nRow]); - nRow++; - } - if( sqlite3_finalize(pStmt)!=SQLITE_OK ){ - rc = shellDatabaseError(p->db); - } - - /* Pretty-print the contents of array azResult[] to the output */ - if( rc==0 && nRow>0 ){ - int len, maxlen = 0; - int i, j; - int nPrintCol, nPrintRow; - for(i=0; i<nRow; i++){ - len = strlen30(azResult[i]); - if( len>maxlen ) maxlen = len; - } - nPrintCol = 80/(maxlen+2); - if( nPrintCol<1 ) nPrintCol = 1; - nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; - for(i=0; i<nPrintRow; i++){ - for(j=i; j<nRow; j+=nPrintRow){ - char *zSp = j<nPrintRow ? "" : " "; - sqlite3_fprintf(p->out, - "%s%-*s", zSp, maxlen, azResult[j] ? azResult[j]:""); - } - sqlite3_fputs("\n", p->out); - } + sqlite3_str_appendall(pSql, " ORDER BY 1"); } - for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]); - sqlite3_free(azResult); + /* Run the SQL statement in "split" mode. */ + modePush(p); + modeChange(p, MODE_Split); + shell_exec(p, sqlite3_str_value(pSql), 0); + sqlite3_str_free(pSql); + modePop(p); + if( rc ) return shellDatabaseError(p->db); }else -#ifndef SQLITE_SHELL_FIDDLE - /* Begin redirecting output to the file "testcase-out.txt" */ + /* Set the p->zTestcase name and begin redirecting output into + ** the cli_output_capture sqlite3_str */ if( c=='t' && cli_strcmp(azArg[0],"testcase")==0 ){ - output_reset(p); - p->out = output_file_open("testcase-out.txt"); - if( p->out==0 ){ - eputz("Error: cannot open 'testcase-out.txt'\n"); - } - if( nArg>=2 ){ - sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); - }else{ - sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); - } + rc = dotCmdTestcase(p); }else -#endif /* !defined(SQLITE_SHELL_FIDDLE) */ #ifndef SQLITE_UNTESTABLE if( c=='t' && n>=8 && cli_strncmp(azArg[0], "testctrl", n)==0 ){ @@ -11837,10 +11359,10 @@ static int do_meta_command(char *zLine, ShellState *p){ /* --help lists all test-controls */ if( cli_strcmp(zCmd,"help")==0 ){ - sqlite3_fputs("Available test-controls:\n", p->out); + cli_puts("Available test-controls:\n", p->out); for(i=0; i<ArraySize(aCtrl); i++){ if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue; - sqlite3_fprintf(p->out, " .testctrl %s %s\n", + cli_printf(p->out, " .testctrl %s %s\n", aCtrl[i].zCtrlName, aCtrl[i].zUsage); } rc = 1; @@ -11857,7 +11379,7 @@ static int do_meta_command(char *zLine, ShellState *p){ testctrl = aCtrl[i].ctrlCode; iCtrl = i; }else{ - sqlite3_fprintf(stderr,"Error: ambiguous test-control: \"%s\"\n" + cli_printf(stderr,"Error: ambiguous test-control: \"%s\"\n" "Use \".testctrl --help\" for help\n", zCmd); rc = 1; goto meta_command_exit; @@ -11865,7 +11387,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( testctrl<0 ){ - sqlite3_fprintf(stderr,"Error: unknown test-control: %s\n" + cli_printf(stderr,"Error: unknown test-control: %s\n" "Use \".testctrl --help\" for help\n", zCmd); }else{ switch(testctrl){ @@ -11945,13 +11467,13 @@ static int do_meta_command(char *zLine, ShellState *p){ if( sqlite3_stricmp(zLabel, aLabel[jj].zLabel)==0 ) break; } if( jj>=ArraySize(aLabel) ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "Error: no such optimization: \"%s\"\n", zLabel); - sqlite3_fputs("Should be one of:", stderr); + cli_puts("Should be one of:", stderr); for(jj=0; jj<ArraySize(aLabel); jj++){ - sqlite3_fprintf(stderr," %s", aLabel[jj].zLabel); + cli_printf(stderr," %s", aLabel[jj].zLabel); } - sqlite3_fputs("\n", stderr); + cli_puts("\n", stderr); rc = 1; goto meta_command_exit; } @@ -11969,23 +11491,23 @@ static int do_meta_command(char *zLine, ShellState *p){ if( m & newOpt ) nOff++; } if( nOff<12 ){ - sqlite3_fputs("+All", p->out); + cli_puts("+All", p->out); for(ii=0; ii<ArraySize(aLabel); ii++){ if( !aLabel[ii].bDsply ) continue; if( (newOpt & aLabel[ii].mask)!=0 ){ - sqlite3_fprintf(p->out, " -%s", aLabel[ii].zLabel); + cli_printf(p->out, " -%s", aLabel[ii].zLabel); } } }else{ - sqlite3_fputs("-All", p->out); + cli_puts("-All", p->out); for(ii=0; ii<ArraySize(aLabel); ii++){ if( !aLabel[ii].bDsply ) continue; if( (newOpt & aLabel[ii].mask)==0 ){ - sqlite3_fprintf(p->out, " +%s", aLabel[ii].zLabel); + cli_printf(p->out, " +%s", aLabel[ii].zLabel); } } } - sqlite3_fputs("\n", p->out); + cli_puts("\n", p->out); rc2 = isOk = 3; break; } @@ -12025,7 +11547,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3 *db; if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){ sqlite3_randomness(sizeof(ii),&ii); - sqlite3_fprintf(stdout, "-- random seed: %d\n", ii); + cli_printf(stdout, "-- random seed: %d\n", ii); } if( nArg==3 ){ db = 0; @@ -12078,7 +11600,7 @@ static int do_meta_command(char *zLine, ShellState *p){ case SQLITE_TESTCTRL_SEEK_COUNT: { u64 x = 0; rc2 = sqlite3_test_control(testctrl, p->db, &x); - sqlite3_fprintf(p->out, "%llu\n", x); + cli_printf(p->out, "%llu\n", x); isOk = 3; break; } @@ -12109,11 +11631,11 @@ static int do_meta_command(char *zLine, ShellState *p){ int val = 0; rc2 = sqlite3_test_control(testctrl, -id, &val); if( rc2!=SQLITE_OK ) break; - if( id>1 ) sqlite3_fputs(" ", p->out); - sqlite3_fprintf(p->out, "%d: %d", id, val); + if( id>1 ) cli_puts(" ", p->out); + cli_printf(p->out, "%d: %d", id, val); id++; } - if( id>1 ) sqlite3_fputs("\n", p->out); + if( id>1 ) cli_puts("\n", p->out); isOk = 3; } break; @@ -12152,7 +11674,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int ii, jj, x; int *aOp; if( nArg!=4 ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "ERROR - should be: \".testctrl bitvec_test SIZE INT-ARRAY\"\n" ); rc = 1; @@ -12175,7 +11697,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } aOp[jj] = x; x = sqlite3_test_control(testctrl, iSize, aOp); - sqlite3_fprintf(p->out, "result: %d\n", x); + cli_printf(p->out, "result: %d\n", x); free(aOp); break; } @@ -12198,21 +11720,21 @@ static int do_meta_command(char *zLine, ShellState *p){ faultsim_state.nHit = 0; sqlite3_test_control(testctrl, faultsim_callback); }else if( cli_strcmp(z,"status")==0 ){ - sqlite3_fprintf(p->out, "faultsim.iId: %d\n", + cli_printf(p->out, "faultsim.iId: %d\n", faultsim_state.iId); - sqlite3_fprintf(p->out, "faultsim.iErr: %d\n", + cli_printf(p->out, "faultsim.iErr: %d\n", faultsim_state.iErr); - sqlite3_fprintf(p->out, "faultsim.iCnt: %d\n", + cli_printf(p->out, "faultsim.iCnt: %d\n", faultsim_state.iCnt); - sqlite3_fprintf(p->out, "faultsim.nHit: %d\n", + cli_printf(p->out, "faultsim.nHit: %d\n", faultsim_state.nHit); - sqlite3_fprintf(p->out, "faultsim.iInterval: %d\n", + cli_printf(p->out, "faultsim.iInterval: %d\n", faultsim_state.iInterval); - sqlite3_fprintf(p->out, "faultsim.eVerbose: %d\n", + cli_printf(p->out, "faultsim.eVerbose: %d\n", faultsim_state.eVerbose); - sqlite3_fprintf(p->out, "faultsim.nRepeat: %d\n", + cli_printf(p->out, "faultsim.nRepeat: %d\n", faultsim_state.nRepeat); - sqlite3_fprintf(p->out, "faultsim.nSkip: %d\n", + cli_printf(p->out, "faultsim.nSkip: %d\n", faultsim_state.nSkip); }else if( cli_strcmp(z,"-v")==0 ){ if( faultsim_state.eVerbose<2 ) faultsim_state.eVerbose++; @@ -12231,7 +11753,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( cli_strcmp(z,"-?")==0 || sqlite3_strglob("*help*",z)==0){ bShowHelp = 1; }else{ - sqlite3_fprintf(stderr, + cli_printf(stderr, "Unrecognized fault_install argument: \"%s\"\n", azArg[kk]); rc = 1; @@ -12240,7 +11762,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( bShowHelp ){ - sqlite3_fputs( + cli_puts( "Usage: .testctrl fault_install ARGS\n" "Possible arguments:\n" " off Disable faultsim\n" @@ -12262,13 +11784,13 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( isOk==0 && iCtrl>=0 ){ - sqlite3_fprintf(p->out, + cli_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); rc = 1; }else if( isOk==1 ){ - sqlite3_fprintf(p->out, "%d\n", rc2); + cli_printf(p->out, "%d\n", rc2); }else if( isOk==2 ){ - sqlite3_fprintf(p->out, "0x%08x\n", rc2); + cli_printf(p->out, "0x%08x\n", rc2); } }else #endif /* !defined(SQLITE_UNTESTABLE) */ @@ -12280,13 +11802,17 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='t' && n>=5 && cli_strncmp(azArg[0], "timer", n)==0 ){ if( nArg==2 ){ - enableTimer = booleanValue(azArg[1]); - if( enableTimer && !HAS_TIMER ){ + if( cli_strcmp(azArg[1],"once")==0 ){ + p->enableTimer = 1; + }else{ + p->enableTimer = 2*booleanValue(azArg[1]); + } + if( p->enableTimer && !HAS_TIMER ){ eputz("Error: timer not available on this system.\n"); - enableTimer = 0; + p->enableTimer = 0; } }else{ - eputz("Usage: .timer on|off\n"); + eputz("Usage: .timer on|off|once\n"); rc = 1; } }else @@ -12323,13 +11849,13 @@ static int do_meta_command(char *zLine, ShellState *p){ mType |= SQLITE_TRACE_CLOSE; } else { - sqlite3_fprintf(stderr,"Unknown option \"%s\" on \".trace\"\n", z); + cli_printf(stderr,"Unknown option \"%s\" on \".trace\"\n", z); rc = 1; goto meta_command_exit; } }else{ output_file_close(p->traceOut); - p->traceOut = output_file_open(z); + p->traceOut = output_file_open(p, z); } } if( p->traceOut==0 ){ @@ -12368,21 +11894,21 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; - sqlite3_fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/, + cli_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); #if SQLITE_HAVE_ZLIB - sqlite3_fprintf(p->out, "zlib version %s\n", zlibVersion()); + cli_printf(p->out, "zlib version %s\n", zlibVersion()); #endif #define CTIMEOPT_VAL_(opt) #opt #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) #if defined(__clang__) && defined(__clang_major__) - sqlite3_fprintf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." + cli_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." CTIMEOPT_VAL(__clang_minor__) "." CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); #elif defined(_MSC_VER) - sqlite3_fprintf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); + cli_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); #elif defined(__GNUC__) && defined(__VERSION__) - sqlite3_fprintf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); + cli_printf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); #endif }else @@ -12392,10 +11918,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( p->db ){ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs); if( pVfs ){ - sqlite3_fprintf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName); - sqlite3_fprintf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - sqlite3_fprintf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - sqlite3_fprintf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + cli_printf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName); + cli_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); + cli_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); + cli_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); } } }else @@ -12407,13 +11933,13 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); } for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){ - sqlite3_fprintf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName, + cli_printf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName, pVfs==pCurrent ? " <--- CURRENT" : ""); - sqlite3_fprintf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - sqlite3_fprintf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - sqlite3_fprintf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + cli_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); + cli_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); + cli_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); if( pVfs->pNext ){ - sqlite3_fputs("-----------------------------------\n", p->out); + cli_puts("-----------------------------------\n", p->out); } } }else @@ -12424,7 +11950,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( p->db ){ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName); if( zVfsName ){ - sqlite3_fprintf(p->out, "%s\n", zVfsName); + cli_printf(p->out, "%s\n", zVfsName); sqlite3_free(zVfsName); } } @@ -12437,31 +11963,31 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='w' && cli_strncmp(azArg[0], "width", n)==0 ){ int j; - assert( nArg<=ArraySize(azArg) ); - p->nWidth = nArg-1; - p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2); - if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory(); - if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth]; + p->mode.spec.nWidth = nArg-1; + p->mode.spec.aWidth = realloc(p->mode.spec.aWidth, + (p->mode.spec.nWidth+1)*sizeof(short int)); + shell_check_oom(p->mode.spec.aWidth); for(j=1; j<nArg; j++){ i64 w = integerValue(azArg[j]); - if( w < -30000 ) w = -30000; - if( w > +30000 ) w = +30000; - p->colWidth[j-1] = (int)w; + if( w < -QRF_MAX_WIDTH ) w = -QRF_MAX_WIDTH; + if( w > QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH; + p->mode.spec.aWidth[j-1] = (short int)w; } }else { - sqlite3_fprintf(stderr,"Error: unknown command or invalid arguments: " + cli_printf(stderr,"Error: unknown command or invalid arguments: " " \"%s\". Enter \".help\" for help\n", azArg[0]); rc = 1; } meta_command_exit: - if( p->outCount ){ - p->outCount--; - if( p->outCount==0 ) output_reset(p); + if( p->nPopOutput ){ + p->nPopOutput--; + if( p->nPopOutput==0 ) output_reset(p); } p->bSafeMode = p->bSafeModePersist; + p->dot.nArg = 0; return rc; } @@ -12698,9 +12224,9 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ open_db(p, 0); if( ShellHasFlag(p,SHFLG_Backslash) ) resolve_backslashes(zSql); if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; - BEGIN_TIMER; + BEGIN_TIMER(p); rc = shell_exec(p, zSql, &zErrMsg); - END_TIMER(p->out); + END_TIMER(p); if( rc || zErrMsg ){ char zPrefix[100]; const char *zErrorTail; @@ -12724,7 +12250,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ }else{ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); } - sqlite3_fprintf(stderr,"%s %s\n", zPrefix, zErrorTail); + cli_printf(stderr,"%s %s\n", zPrefix, zErrorTail); sqlite3_free(zErrMsg); zErrMsg = 0; return 1; @@ -12733,7 +12259,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ sqlite3_snprintf(sizeof(zLineBuf), zLineBuf, "changes: %lld total_changes: %lld", sqlite3_changes64(p->db), sqlite3_total_changes64(p->db)); - sqlite3_fprintf(p->out, "%s\n", zLineBuf); + cli_printf(p->out, "%s\n", zLineBuf); } if( doAutoDetectRestore(p, zSql) ) return 1; @@ -12741,8 +12267,8 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ } static void echo_group_input(ShellState *p, const char *zDo){ - if( ShellHasFlag(p, SHFLG_Echo) ){ - sqlite3_fprintf(p->out, "%s\n", zDo); + if( p->mode.mFlags & MFLG_ECHO ){ + cli_printf(p->out, "%s\n", zDo); fflush(p->out); } } @@ -12799,14 +12325,19 @@ static int process_input(ShellState *p, const char *zSrc){ int errCnt = 0; /* Number of errors seen */ i64 startline = 0; /* Line number for start of current input */ QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ + const char *saved_zInFile; /* Prior value of p->zInFile */ + i64 saved_lineno; /* Prior value of p->lineno */ if( p->inputNesting==MAX_INPUT_NESTING ){ /* This will be more informative in a later version. */ - sqlite3_fprintf(stderr,"%s: Input nesting limit (%d) reached at line %lld." + cli_printf(stderr,"%s: Input nesting limit (%d) reached at line %lld." " Check recursion.\n", zSrc, MAX_INPUT_NESTING, p->lineno); return 1; } ++p->inputNesting; + saved_zInFile = p->zInFile; + p->zInFile = zSrc; + saved_lineno = p->lineno; p->lineno = 0; CONTINUE_PROMPT_RESET; while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ @@ -12814,7 +12345,7 @@ static int process_input(ShellState *p, const char *zSrc){ zLine = one_input_line(p->in, zLine, nSql>0); if( zLine==0 ){ /* End of input */ - if( p->in==0 && stdin_is_interactive ) sqlite3_fputs("\n", p->out); + if( p->in==0 && stdin_is_interactive ) cli_puts("\n", p->out); break; } if( seenInterrupt ){ @@ -12871,7 +12402,7 @@ static int process_input(ShellState *p, const char *zSrc){ if( nSql>0x7fff0000 ){ char zSize[100]; sqlite3_snprintf(sizeof(zSize),zSize,"%,lld",nSql); - sqlite3_fprintf(stderr, "%s:%lld: Input SQL is too big: %s bytes\n", + cli_printf(stderr, "%s:%lld: Input SQL is too big: %s bytes\n", zSrc, startline, zSize); nSql = 0; errCnt++; @@ -12881,12 +12412,16 @@ static int process_input(ShellState *p, const char *zSrc){ errCnt += runOneSqlLine(p, zSql, p->in, startline); CONTINUE_PROMPT_RESET; nSql = 0; - if( p->outCount ){ + if( p->nPopOutput ){ output_reset(p); - p->outCount = 0; + p->nPopOutput = 0; }else{ clearTempFile(p); } + if( p->nPopMode ){ + modePop(p); + p->nPopMode = 0; + } p->bSafeMode = p->bSafeModePersist; qss = QSS_Start; }else if( nSql && QSS_PLAINWHITE(qss) ){ @@ -12904,6 +12439,8 @@ static int process_input(ShellState *p, const char *zSrc){ free(zSql); free(zLine); --p->inputNesting; + p->zInFile = saved_zInFile; + p->lineno = saved_lineno; return errCnt>0; } @@ -13064,13 +12601,13 @@ static void process_sqliterc( p->in = sqliterc ? sqlite3_fopen(sqliterc,"rb") : 0; if( p->in ){ if( stdin_is_interactive ){ - sqlite3_fprintf(stderr,"-- Loading resources from %s\n", sqliterc); + cli_printf(stderr,"-- Loading resources from %s\n", sqliterc); } - if( process_input(p, sqliterc) && bail_on_error ) exit(1); + if( process_input(p, sqliterc) && bail_on_error ) cli_exit(1); fclose(p->in); }else if( sqliterc_override!=0 ){ - sqlite3_fprintf(stderr,"cannot open: \"%s\"\n", sqliterc); - if( bail_on_error ) exit(1); + cli_printf(stderr,"cannot open: \"%s\"\n", sqliterc); + if( bail_on_error ) cli_exit(1); } p->in = inSaved; p->lineno = savedLineno; @@ -13092,8 +12629,8 @@ static const char zOptions[] = " -bail stop after hitting an error\n" " -batch force batch I/O\n" " -box set output mode to 'box'\n" - " -column set output mode to 'column'\n" " -cmd COMMAND run \"COMMAND\" before reading stdin\n" + " -column set output mode to 'column'\n" " -csv set output mode to 'csv'\n" #if !defined(SQLITE_OMIT_DESERIALIZE) " -deserialize open the database using sqlite3_deserialize()\n" @@ -13124,6 +12661,7 @@ static const char zOptions[] = #endif " -newline SEP set output row separator. Default: '\\n'\n" " -nofollow refuse to open symbolic links to database files\n" + " -noinit Do not read the ~/.sqliterc file at startup\n" " -nonce STRING set the safe-mode escape nonce\n" " -no-rowid-in-view Disable rowid-in-view using sqlite3_config()\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" @@ -13132,6 +12670,7 @@ static const char zOptions[] = " -quote set output mode to 'quote'\n" " -readonly open the database read-only\n" " -safe enable safe-mode\n" + " -screenwidth N use N as the default screenwidth \n" " -separator SEP set output column separator. Default: '|'\n" #ifdef SQLITE_ENABLE_SORTER_REFERENCES " -sorterref SIZE sorter references threshold size\n" @@ -13148,11 +12687,11 @@ static const char zOptions[] = #endif ; static void usage(int showDetail){ - sqlite3_fprintf(stderr,"Usage: %s [OPTIONS] [FILENAME [SQL...]]\n" + cli_printf(stderr,"Usage: %s [OPTIONS] [FILENAME [SQL...]]\n" "FILENAME is the name of an SQLite database. A new database is created\n" "if the file does not previously exist. Defaults to :memory:.\n", Argv0); if( showDetail ){ - sqlite3_fprintf(stderr,"OPTIONS include:\n%s", zOptions); + cli_printf(stderr,"OPTIONS include:\n%s", zOptions); }else{ eputz("Use the -help option for additional information\n"); } @@ -13173,19 +12712,11 @@ static void verify_uninitialized(void){ /* ** Initialize the state information in data */ -static void main_init(ShellState *data) { - memset(data, 0, sizeof(*data)); - data->normalMode = data->cMode = data->mode = MODE_List; - data->autoExplain = 1; -#ifdef _WIN32 - data->crlfMode = 1; -#endif - data->pAuxDb = &data->aAuxDb[0]; - memcpy(data->colSeparator,SEP_Column, 2); - memcpy(data->rowSeparator,SEP_Row, 2); - data->showHeader = 0; - data->shellFlgs = SHFLG_Lookaside; - sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); +static void main_init(ShellState *p) { + memset(p, 0, sizeof(*p)); + p->pAuxDb = &p->aAuxDb[0]; + p->shellFlgs = SHFLG_Lookaside; + sqlite3_config(SQLITE_CONFIG_LOG, shellLog, p); #if !defined(SQLITE_SHELL_FIDDLE) verify_uninitialized(); #endif @@ -13200,22 +12731,18 @@ static void main_init(ShellState *data) { */ #if defined(_WIN32) || defined(WIN32) static void printBold(const char *zText){ -#if !SQLITE_OS_WINRT HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO defaultScreenInfo; GetConsoleScreenBufferInfo(out, &defaultScreenInfo); SetConsoleTextAttribute(out, FOREGROUND_RED|FOREGROUND_INTENSITY ); -#endif sputz(stdout, zText); -#if !SQLITE_OS_WINRT SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); -#endif } #else static void printBold(const char *zText){ - sqlite3_fprintf(stdout, "\033[1m%s\033[0m", zText); + cli_printf(stdout, "\033[1m%s\033[0m", zText); } #endif @@ -13225,9 +12752,9 @@ static void printBold(const char *zText){ */ static char *cmdline_option_value(int argc, char **argv, int i){ if( i==argc ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "%s: Error: missing argument to %s\n", argv[0], argv[argc-1]); - exit(1); + cli_exit(1); } return argv[i]; } @@ -13240,30 +12767,66 @@ static void sayAbnormalExit(void){ */ static int vfstraceOut(const char *z, void *pArg){ ShellState *p = (ShellState*)pArg; - sqlite3_fputs(z, p->out); + cli_puts(z, p->out); fflush(p->out); return 1; } -#ifndef SQLITE_SHELL_IS_UTF8 -# if (defined(_WIN32) || defined(WIN32)) \ - && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__))) -# define SQLITE_SHELL_IS_UTF8 (0) -# else -# define SQLITE_SHELL_IS_UTF8 (1) -# endif -#endif - +/* Alternative name to the entry point for Fiddle */ #ifdef SQLITE_SHELL_FIDDLE # define main fiddle_main #endif -#if SQLITE_SHELL_IS_UTF8 -int SQLITE_CDECL main(int argc, char **argv){ -#else +/* Use the wmain() entry point on Windows. Translate arguments to +** UTF8, then invoke the traditional main() entry point which is +** renamed using a #define to utf8_main() . +*/ +#if defined(_WIN32) && !defined(main) +# define main utf8_main /* Rename entry point to utf_main() */ +int SQLITE_CDECL utf8_main(int,char**); /* Forward declaration */ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ - char **argv; -#endif + int rc, i; + char **argv = malloc( sizeof(char*) * (argc+1) ); + char **orig = argv; + if( argv==0 ){ + fprintf(stderr, "malloc failed\n"); + exit(1); + } + for(i=0; i<argc; i++){ + int nByte = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, 0, 0, 0, 0); + if( nByte==0 ){ + argv[i] = 0; + }else{ + argv[i] = malloc( nByte ); + if( argv[i]==0 ){ + fprintf(stderr, "malloc failed\n"); + exit(1); + } + nByte = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i],nByte,0,0); + if( nByte==0 ){ + free(argv[i]); + argv[i] = 0; + } + } + } + argv[argc] = 0; + rc = utf8_main(argc, argv); + for(i=0; i<argc; i++) free(orig[i]); + free(argv); + return rc; +} +#endif /* WIN32 */ + +/* +** This is the main entry point for the process. Everything starts here. +** +** The "main" identifier may have been #defined to something else: +** +** utf8_main On Windows +** fiddle_main In Fiddle +** sqlite3_shell Other projects that use shell.c as a subroutine +*/ +int SQLITE_CDECL main(int argc, char **argv){ #ifdef SQLITE_DEBUG sqlite3_int64 mem_main_enter = 0; #endif @@ -13278,15 +12841,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ int rc = 0; int warnInmemoryDb = 0; int readStdin = 1; + int noInit = 0; /* Do not read ~/.sqliterc if true */ int nCmd = 0; int nOptsEnd = argc; int bEnableVfstrace = 0; char **azCmd = 0; + int *aiCmd = 0; const char *zVfs = 0; /* Value of -vfs command-line option */ -#if !SQLITE_SHELL_IS_UTF8 - char **argvToFree = 0; - int argcToFree = 0; -#endif setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ #ifdef SQLITE_SHELL_FIDDLE @@ -13305,7 +12866,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( getenv("SQLITE_DEBUG_BREAK") ){ if( isatty(0) && isatty(2) ){ char zLine[100]; - sqlite3_fprintf(stderr, + cli_printf(stderr, "attach debugger to process %d and press ENTER to continue...", GETPID()); if( sqlite3_fgets(zLine, sizeof(zLine), stdin)!=0 @@ -13315,11 +12876,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } }else{ #if defined(_WIN32) || defined(WIN32) -#if SQLITE_OS_WINRT - __debugbreak(); -#else DebugBreak(); -#endif #elif defined(SIGTRAP) raise(SIGTRAP); #endif @@ -13337,7 +12894,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #if USE_SYSTEM_SQLITE+0!=1 if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){ - sqlite3_fprintf(stderr, + cli_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", sqlite3_sourceid(), SQLITE_SOURCE_ID); exit(1); @@ -13345,32 +12902,6 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif main_init(&data); - /* On Windows, we must translate command-line arguments into UTF-8. - ** The SQLite memory allocator subsystem has to be enabled in order to - ** do this. But we want to run an sqlite3_shutdown() afterwards so that - ** subsequent sqlite3_config() calls will work. So copy all results into - ** memory that does not come from the SQLite memory allocator. - */ -#if !SQLITE_SHELL_IS_UTF8 - sqlite3_initialize(); - argvToFree = malloc(sizeof(argv[0])*argc*2); - shell_check_oom(argvToFree); - argcToFree = argc; - argv = argvToFree + argc; - for(i=0; i<argc; i++){ - char *z = sqlite3_win32_unicode_to_utf8(wargv[i]); - i64 n; - shell_check_oom(z); - n = strlen(z); - argv[i] = malloc( n+1 ); - shell_check_oom(argv[i]); - memcpy(argv[i], z, n+1); - argvToFree[i] = argv[i]; - sqlite3_free(z); - } - sqlite3_shutdown(); -#endif - assert( argc>=1 && argv && argv[0] ); Argv0 = argv[0]; @@ -13386,7 +12917,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } #endif - /* Do an initial pass through the command-line argument to locate + /* Do an initial pass through the command-line arguments to locate ** the name of the database file, the name of the initialization file, ** the size of the alternative malloc heap, options affecting commands ** or SQL run from the command line, and the first command to execute. @@ -13398,16 +12929,20 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ char *z; z = argv[i]; if( z[0]!='-' || i>nOptsEnd ){ - if( data.aAuxDb->zDbFilename==0 ){ + if( data.aAuxDb->zDbFilename==0 && !isScriptFile(z,1) ){ data.aAuxDb->zDbFilename = z; }else{ /* Excess arguments are interpreted as SQL (or dot-commands) and ** mean that nothing is read from stdin */ readStdin = 0; + stdin_is_interactive = 0; nCmd++; azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd); shell_check_oom(azCmd); + aiCmd = realloc(aiCmd, sizeof(aiCmd[0])*nCmd); + shell_check_oom(azCmd); azCmd[nCmd-1] = z; + aiCmd[nCmd-1] = i; } continue; } @@ -13430,6 +12965,15 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ ** we do the actual processing of arguments later in a second pass. */ stdin_is_interactive = 0; + stdout_is_console = 0; + modeChange(&data, MODE_BATCH); + }else if( cli_strcmp(z,"-screenwidth")==0 ){ + int n = atoi(cmdline_option_value(argc, argv, ++i)); + if( n<2 ){ + sqlite3_fprintf(stderr,"minimum --screenwidth is 2\n"); + exit(1); + } + stdout_tty_width = n; }else if( cli_strcmp(z,"-utf8")==0 ){ }else if( cli_strcmp(z,"-no-utf8")==0 ){ }else if( cli_strcmp(z,"-no-rowid-in-view")==0 ){ @@ -13463,7 +13007,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ int szHdr = 0; sqlite3_config(SQLITE_CONFIG_PCACHE_HDRSZ, &szHdr); sz += szHdr; - sqlite3_fprintf(stdout, "Page cache size increased to %d to accommodate" + cli_printf(stdout, "Page cache size increased to %d to accommodate" " the %d-byte headers\n", (int)sz, szHdr); } verify_uninitialized(); @@ -13524,6 +13068,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ data.openFlags |= SQLITE_OPEN_READONLY; }else if( cli_strcmp(z,"-nofollow")==0 ){ data.openFlags |= SQLITE_OPEN_NOFOLLOW; + }else if( cli_strcmp(z,"-noinit")==0 ){ + noInit = 1; }else if( cli_strcmp(z,"-exclusive")==0 ){ /* UNDOCUMENTED */ data.openFlags |= SQLITE_OPEN_EXCLUSIVE; }else if( cli_strcmp(z,"-ifexists")==0 ){ @@ -13551,6 +13097,16 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-escape")==0 && i+1<argc ){ /* skip over the argument */ i++; + }else if( cli_strcmp(z,"-test-argv")==0 ){ + /* Undocumented test option. Print the values in argv[] and exit. + ** Use this to verify that any translation of the argv[], for example + ** on Windows that receives wargv[] from the OS and must convert + ** to UTF8 prior to calling this routine. */ + int kk; + for(kk=0; kk<argc; kk++){ + sqlite3_fprintf(stdout,"argv[%d] = \"%s\"\n", kk, argv[kk]); + } + return 0; } } #ifndef SQLITE_SHELL_FIDDLE @@ -13577,8 +13133,27 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs); if( pVfs ){ sqlite3_vfs_register(pVfs, 1); - }else{ - sqlite3_fprintf(stderr,"no such VFS: \"%s\"\n", zVfs); + } +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) + else if( access(zVfs,0)==0 ){ + /* If the VFS name is not the name of an existing VFS, but it is + ** the name of a file, then try to load that file as an extension. + ** Presumably the extension implements the desired VFS. */ + sqlite3 *db = 0; + char *zErr = 0; + sqlite3_open(":memory:", &db); + sqlite3_enable_load_extension(db, 1); + rc = sqlite3_load_extension(db, zVfs, 0, &zErr); + sqlite3_close(db); + if( (rc&0xff)!=SQLITE_OK ){ + cli_printf(stderr, "could not load extension VFS \"%s\": %s\n", + zVfs, zErr); + exit(1); + } + } +#endif + else{ + cli_printf(stderr,"no such VFS: \"%s\"\n", zVfs); exit(1); } } @@ -13588,9 +13163,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ data.pAuxDb->zDbFilename = ":memory:"; warnInmemoryDb = argc==1; #else - sqlite3_fprintf(stderr, + cli_printf(stderr, "%s: Error: no database filename specified\n", Argv0); - return 1; + rc = 1; + goto shell_main_exit; #endif } data.out = stdout; @@ -13600,6 +13176,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #ifndef SQLITE_SHELL_FIDDLE sqlite3_appendvfs_init(0,0,0); #endif + modeDefault(&data); /* Go ahead and open the database file if it already exists. If the ** file does not exist, delay opening it. This prevents empty database @@ -13614,9 +13191,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ ** is given on the command line, look for a file named ~/.sqliterc and ** try to process it. */ - process_sqliterc(&data,zInitFile); + if( !noInit ) process_sqliterc(&data,zInitFile); - /* Make a second pass through the command-line argument and set + /* Make a second pass through the command-line arguments and set ** options. This second pass is delayed until after the initialization ** file is processed so that the command-line arguments will override ** settings in the initialization file. @@ -13628,45 +13205,44 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( cli_strcmp(z,"-init")==0 ){ i++; }else if( cli_strcmp(z,"-html")==0 ){ - data.mode = MODE_Html; + modeChange(&data, MODE_Html); }else if( cli_strcmp(z,"-list")==0 ){ - data.mode = MODE_List; + modeChange(&data, MODE_List); }else if( cli_strcmp(z,"-quote")==0 ){ - data.mode = MODE_Quote; - sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row); + modeChange(&data, MODE_Quote); }else if( cli_strcmp(z,"-line")==0 ){ - data.mode = MODE_Line; + modeChange(&data, MODE_Line); }else if( cli_strcmp(z,"-column")==0 ){ - data.mode = MODE_Column; + modeChange(&data, MODE_Column); }else if( cli_strcmp(z,"-json")==0 ){ - data.mode = MODE_Json; + modeChange(&data, MODE_Json); }else if( cli_strcmp(z,"-markdown")==0 ){ - data.mode = MODE_Markdown; + modeChange(&data, MODE_Markdown); }else if( cli_strcmp(z,"-table")==0 ){ - data.mode = MODE_Table; + modeChange(&data, MODE_Table); + }else if( cli_strcmp(z,"-psql")==0 ){ + modeChange(&data, MODE_Psql); }else if( cli_strcmp(z,"-box")==0 ){ - data.mode = MODE_Box; + modeChange(&data, MODE_Box); }else if( cli_strcmp(z,"-csv")==0 ){ - data.mode = MODE_Csv; - memcpy(data.colSeparator,",",2); + modeChange(&data, MODE_Csv); }else if( cli_strcmp(z,"-escape")==0 && i+1<argc ){ /* See similar code at tag-20250224-1 */ const char *zEsc = argv[++i]; int k; - for(k=0; k<ArraySize(shell_EscModeNames); k++){ - if( sqlite3_stricmp(zEsc,shell_EscModeNames[k])==0 ){ - data.eEscMode = k; + for(k=0; k<ArraySize(qrfEscNames); k++){ + if( sqlite3_stricmp(zEsc,qrfEscNames[k])==0 ){ + data.mode.spec.eEsc = k; break; } } - if( k>=ArraySize(shell_EscModeNames) ){ - sqlite3_fprintf(stderr, "unknown control character escape mode \"%s\"" + if( k>=ArraySize(qrfEscNames) ){ + cli_printf(stderr, "unknown control character escape mode \"%s\"" " - choices:", zEsc); - for(k=0; k<ArraySize(shell_EscModeNames); k++){ - sqlite3_fprintf(stderr, " %s", shell_EscModeNames[k]); + for(k=0; k<ArraySize(qrfEscNames); k++){ + cli_printf(stderr, " %s", qrfEscNames[k]); } - sqlite3_fprintf(stderr, "\n"); + cli_printf(stderr, "\n"); exit(1); } #ifdef SQLITE_HAVE_ZLIB @@ -13686,44 +13262,40 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ data.openFlags |= SQLITE_OPEN_READONLY; }else if( cli_strcmp(z,"-nofollow")==0 ){ data.openFlags |= SQLITE_OPEN_NOFOLLOW; + }else if( cli_strcmp(z,"-noinit")==0 ){ + /* No-op */ }else if( cli_strcmp(z,"-exclusive")==0 ){ /* UNDOCUMENTED */ data.openFlags |= SQLITE_OPEN_EXCLUSIVE; }else if( cli_strcmp(z,"-ifexists")==0 ){ data.openFlags &= ~(SQLITE_OPEN_CREATE); if( data.openFlags==0 ) data.openFlags = SQLITE_OPEN_READWRITE; }else if( cli_strcmp(z,"-ascii")==0 ){ - data.mode = MODE_Ascii; - sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit); - sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record); + modeChange(&data, MODE_Ascii); }else if( cli_strcmp(z,"-tabs")==0 ){ - data.mode = MODE_List; - sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab); - sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row); + modeChange(&data, MODE_Tabs); }else if( cli_strcmp(z,"-separator")==0 ){ - sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, - "%s",cmdline_option_value(argc,argv,++i)); + modeSetStr(&data.mode.spec.zColumnSep, + cmdline_option_value(argc,argv,++i)); }else if( cli_strcmp(z,"-newline")==0 ){ - sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, - "%s",cmdline_option_value(argc,argv,++i)); + modeSetStr(&data.mode.spec.zRowSep, + cmdline_option_value(argc,argv,++i)); }else if( cli_strcmp(z,"-nullvalue")==0 ){ - sqlite3_snprintf(sizeof(data.nullValue), data.nullValue, - "%s",cmdline_option_value(argc,argv,++i)); + modeSetStr(&data.mode.spec.zNull, + cmdline_option_value(argc,argv,++i)); }else if( cli_strcmp(z,"-header")==0 ){ - data.showHeader = 1; - ShellSetFlag(&data, SHFLG_HeaderSet); + data.mode.spec.bTitles = QRF_Yes; }else if( cli_strcmp(z,"-noheader")==0 ){ - data.showHeader = 0; - ShellSetFlag(&data, SHFLG_HeaderSet); + data.mode.spec.bTitles = QRF_No; }else if( cli_strcmp(z,"-echo")==0 ){ - ShellSetFlag(&data, SHFLG_Echo); + data.mode.mFlags |= MFLG_ECHO; }else if( cli_strcmp(z,"-eqp")==0 ){ - data.autoEQP = AUTOEQP_on; + data.mode.autoEQP = AUTOEQP_on; }else if( cli_strcmp(z,"-eqpfull")==0 ){ - data.autoEQP = AUTOEQP_full; + data.mode.autoEQP = AUTOEQP_full; }else if( cli_strcmp(z,"-stats")==0 ){ data.statsOn = 1; }else if( cli_strcmp(z,"-scanstats")==0 ){ - data.scanstatsOn = 1; + data.mode.scanstatsOn = 1; }else if( cli_strcmp(z,"-backslash")==0 ){ /* Undocumented command-line option: -backslash ** Causes C-style backslash escapes to be evaluated in SQL statements @@ -13734,9 +13306,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ }else if( cli_strcmp(z,"-version")==0 ){ - sqlite3_fprintf(stdout, "%s %s (%d-bit)\n", + cli_printf(stdout, "%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), 8*(int)sizeof(char*)); - return 0; + rc = 0; + goto shell_main_exit; }else if( cli_strcmp(z,"-interactive")==0 ){ /* Need to check for interactive override here to so that it can ** affect console setup (for Windows only) and testing thereof. @@ -13744,6 +13317,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ stdin_is_interactive = 1; }else if( cli_strcmp(z,"-batch")==0 ){ /* already handled */ + }else if( cli_strcmp(z,"-screenwidth")==0 ){ + i++; }else if( cli_strcmp(z,"-utf8")==0 ){ /* already handled */ }else if( cli_strcmp(z,"-no-utf8")==0 ){ @@ -13789,23 +13364,26 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ z = cmdline_option_value(argc,argv,++i); if( z[0]=='.' ){ rc = do_meta_command(z, &data); - if( rc && bail_on_error ) return rc==2 ? 0 : rc; + if( rc && (bail_on_error || rc==2) ){ + if( rc==2 ) rc = 0; + goto shell_main_exit; + } }else{ open_db(&data, 0); rc = shell_exec(&data, z, &zErrMsg); if( zErrMsg!=0 ){ shellEmitError(zErrMsg); sqlite3_free(zErrMsg); - if( bail_on_error ) return rc!=0 ? rc : 1; + if( !rc ) rc = 1; }else if( rc!=0 ){ - sqlite3_fprintf(stderr,"Error: unable to process SQL \"%s\"\n", z); - if( bail_on_error ) return rc; + cli_printf(stderr,"Error: unable to process SQL \"%s\"\n", z); } + if( bail_on_error ) goto shell_main_exit; } #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) }else if( cli_strncmp(z, "-A", 2)==0 ){ if( nCmd>0 ){ - sqlite3_fprintf(stderr,"Error: cannot mix regular SQL or dot-commands" + cli_printf(stderr,"Error: cannot mix regular SQL or dot-commands" " with \"%s\"\n", z); rc = 1; goto shell_main_exit; @@ -13818,6 +13396,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ arDotCommand(&data, 1, argv+i, argc-i); } readStdin = 0; + stdin_is_interactive = 0; break; #endif }else if( cli_strcmp(z,"-safe")==0 ){ @@ -13825,11 +13404,11 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ /* Acted upon in first pass. */ }else{ - sqlite3_fprintf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); + cli_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); eputz("Use -help for a list of options.\n"); - return 1; + rc = 1; + goto shell_main_exit; } - data.cMode = data.mode; } if( !readStdin ){ @@ -13839,8 +13418,26 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ */ for(i=0; i<nCmd; i++){ echo_group_input(&data, azCmd[i]); - if( azCmd[i][0]=='.' ){ + if( isScriptFile(azCmd[i],0) ){ + FILE *inSaved = data.in; + i64 savedLineno = data.lineno; + int res = 1; + if( (data.in = openChrSource(azCmd[i]))!=0 ){ + res = process_input(&data, azCmd[i]); + fclose(data.in); + } + data.in = inSaved; + data.lineno = savedLineno; + if( res ) i = nCmd; + }else if( azCmd[i][0]=='.' ){ + char *zErrCtx = malloc( 64 ); + shell_check_oom(zErrCtx); + sqlite3_snprintf(64,zErrCtx,"argv[%i]:",aiCmd[i]); + data.zInFile = "<cmdline>"; + data.zErrPrefix = zErrCtx; rc = do_meta_command(azCmd[i], &data); + free(data.zErrPrefix); + data.zErrPrefix = 0; if( rc ){ if( rc==2 ) rc = 0; goto shell_main_exit; @@ -13852,13 +13449,23 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( zErrMsg!=0 ){ shellEmitError(zErrMsg); }else{ - sqlite3_fprintf(stderr, + cli_printf(stderr, "Error: unable to process SQL: %s\n", azCmd[i]); } sqlite3_free(zErrMsg); if( rc==0 ) rc = 1; goto shell_main_exit; } + if( data.nPopMode ){ + modePop(&data); + data.nPopMode = 0; + } + } + if( data.nPopOutput ){ + output_reset(&data); + data.nPopOutput = 0; + }else{ + clearTempFile(&data); } } }else{ @@ -13867,7 +13474,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( stdin_is_interactive ){ char *zHome; char *zHistory; - sqlite3_fprintf(stdout, + cli_printf(stdout, "SQLite version %s %.19s\n" /*extra-version-info*/ "Enter \".help\" for usage hints.\n", sqlite3_libversion(), sqlite3_sourceid()); @@ -13920,6 +13527,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif shell_main_exit: free(azCmd); + free(aiCmd); set_table_name(&data, 0); if( data.db ){ session_close_all(&data, -1); @@ -13936,12 +13544,28 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ output_reset(&data); data.doXdgOpen = 0; clearTempFile(&data); -#if !SQLITE_SHELL_IS_UTF8 - for(i=0; i<argcToFree; i++) free(argvToFree[i]); - free(argvToFree); -#endif - free(data.colWidth); + modeFree(&data.mode); + if( data.nSavedModes ){ + int ii; + for(ii=0; ii<data.nSavedModes; ii++){ + modeFree(&data.aSavedModes[ii].mode); + free(data.aSavedModes[ii].zTag); + } + free(data.aSavedModes); + } + free(data.zErrPrefix); free(data.zNonce); + free(data.dot.zCopy); + free(data.dot.azArg); + free(data.dot.aiOfst); + free(data.dot.abQuot); + if( data.nTestRun ){ + sqlite3_fprintf(stdout, "%d test%s run with %d error%s\n", + data.nTestRun, data.nTestRun==1 ? "" : "s", + data.nTestErr, data.nTestErr==1 ? "" : "s"); + fflush(stdout); + rc = data.nTestErr>0; + } /* Clear the global data structure so that valgrind will detect memory ** leaks */ memset(&data, 0, sizeof(data)); @@ -13950,7 +13574,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } #ifdef SQLITE_DEBUG if( sqlite3_memory_used()>mem_main_enter ){ - sqlite3_fprintf(stderr,"Memory leaked: %u bytes\n", + cli_printf(stderr,"Memory leaked: %u bytes\n", (unsigned int)(sqlite3_memory_used()-mem_main_enter)); } #endif @@ -13990,7 +13614,7 @@ sqlite3_vfs * fiddle_db_vfs(const char *zDbName){ /* Only for emcc experimentation purposes. */ sqlite3 * fiddle_db_arg(sqlite3 *arg){ - sqlite3_fprintf(stdout, "fiddle_db_arg(%p)\n", (const void*)arg); + cli_printf(stdout, "fiddle_db_arg(%p)\n", (const void*)arg); return arg; } @@ -14027,7 +13651,7 @@ void fiddle_reset_db(void){ ** Resolve problem reported in ** https://sqlite.org/forum/forumpost/0b41a25d65 */ - sqlite3_fputs("Rolling back in-progress transaction.\n", stdout); + cli_puts("Rolling back in-progress transaction.\n", stdout); sqlite3_exec(globalDb,"ROLLBACK", 0, 0, 0); } rc = sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index f6ed48c20..b6773e1d9 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -1490,7 +1490,7 @@ typedef const char *sqlite3_filename; ** greater and the function pointer is not NULL) and will fall back ** to xCurrentTime() if xCurrentTimeInt64() is unavailable. ** -** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces +** ^The xSetSystemCall(), xGetSystemCall(), and xNextSystemCall() interfaces ** are not used by the SQLite core. These optional interfaces are provided ** by some VFSes to facilitate testing of the VFS code. By overriding ** system calls with functions under its control, a test program can @@ -2567,12 +2567,15 @@ struct sqlite3_mem_methods { ** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] ** <dt>SQLITE_DBCONFIG_STMT_SCANSTATUS</dt> ** <dd>The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in -** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears -** a flag that enables collection of the sqlite3_stmt_scanstatus_v2() -** statistics. For statistics to be collected, the flag must be set on -** the database handle both when the SQL statement is prepared and when it -** is stepped. The flag is set (collection of statistics is enabled) -** by default. <p>This option takes two arguments: an integer and a pointer to +** [SQLITE_ENABLE_STMT_SCANSTATUS] builds. In this case, it sets or clears +** a flag that enables collection of run-time performance statistics +** used by [sqlite3_stmt_scanstatus_v2()] and the [nexec and ncycle] +** columns of the [bytecode virtual table]. +** For statistics to be collected, the flag must be set on +** the database handle both when the SQL statement is +** [sqlite3_prepare|prepared] and when it is [sqlite3_step|stepped]. +** The flag is set (collection of statistics is enabled) by default. +** <p>This option takes two arguments: an integer and a pointer to ** an integer. The first argument is 1, 0, or -1 to enable, disable, or ** leave unchanged the statement scanstatus option. If the second argument ** is not NULL, then the value of the statement scanstatus setting after @@ -2645,6 +2648,22 @@ struct sqlite3_mem_methods { ** comments are allowed in SQL text after processing the first argument. ** </dd> ** +** [[SQLITE_DBCONFIG_FP_DIGITS]] +** <dt>SQLITE_DBCONFIG_FP_DIGITS</dt> +** <dd>The SQLITE_DBCONFIG_FP_DIGITS setting is a small integer that determines +** the number of significant digits that SQLite will attempt to preserve when +** converting floating point numbers (IEEE 754 "doubles") into text. The +** default value 17, as of SQLite version 3.52.0. The value was 15 in all +** prior versions.<p> +** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is a small integer, between 3 and 23, or +** zero. The FP_DIGITS setting is changed to that small integer, or left +** altered if the first argument is zero or out of range. The second argument +** is a pointer to an integer. If the pointer is not NULL, then the value of +** the FP_DIGITS setting, after possibly being modified by the first +** arguments, is written into the integer to which the second argument points. +** </dd> +** ** </dl> ** ** [[DBCONFIG arguments]] <h3>Arguments To SQLITE_DBCONFIG Options</h3> @@ -2662,9 +2681,10 @@ struct sqlite3_mem_methods { ** the first argument. ** ** <p>While most SQLITE_DBCONFIG options use the argument format -** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME] -** and [SQLITE_DBCONFIG_LOOKASIDE] options are different. See the -** documentation of those exceptional options for details. +** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME], +** [SQLITE_DBCONFIG_LOOKASIDE], and [SQLITE_DBCONFIG_FP_DIGITS] options +** are different. See the documentation of those exceptional options for +** details. */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ @@ -2689,7 +2709,8 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */ -#define SQLITE_DBCONFIG_MAX 1022 /* Largest DBCONFIG */ +#define SQLITE_DBCONFIG_FP_DIGITS 1023 /* int int* */ +#define SQLITE_DBCONFIG_MAX 1023 /* Largest DBCONFIG */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes @@ -4171,6 +4192,7 @@ void sqlite3_free_filename(sqlite3_filename); ** <li> sqlite3_errmsg() ** <li> sqlite3_errmsg16() ** <li> sqlite3_error_offset() +** <li> sqlite3_db_handle() ** </ul> ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language @@ -4217,7 +4239,7 @@ const char *sqlite3_errstr(int); int sqlite3_error_offset(sqlite3 *db); /* -** CAPI3REF: Set Error Codes And Message +** CAPI3REF: Set Error Code And Message ** METHOD: sqlite3 ** ** Set the error code of the database handle passed as the first argument @@ -4336,6 +4358,10 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(<dt>SQLITE_LIMIT_EXPR_DEPTH</dt> ** <dd>The maximum depth of the parse tree on any expression.</dd>)^ ** +** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(<dt>SQLITE_LIMIT_PARSER_DEPTH</dt> +** <dd>The maximum depth of the LALR(1) parser stack used to analyze +** input SQL statements.</dd>)^ +** ** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(<dt>SQLITE_LIMIT_COMPOUND_SELECT</dt> ** <dd>The maximum number of terms in a compound SELECT statement.</dd>)^ ** @@ -4380,6 +4406,7 @@ int sqlite3_limit(sqlite3*, int id, int newVal); #define SQLITE_LIMIT_VARIABLE_NUMBER 9 #define SQLITE_LIMIT_TRIGGER_DEPTH 10 #define SQLITE_LIMIT_WORKER_THREADS 11 +#define SQLITE_LIMIT_PARSER_DEPTH 12 /* ** CAPI3REF: Prepare Flags @@ -4424,12 +4451,29 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** fails, the sqlite3_prepare_v3() call returns the same error indications ** with or without this flag; it just omits the call to [sqlite3_log()] that ** logs the error. +** +** [[SQLITE_PREPARE_FROM_DDL]] <dt>SQLITE_PREPARE_FROM_DDL</dt> +** <dd>The SQLITE_PREPARE_FROM_DDL flag causes the SQL compiler to enforce +** security constraints that would otherwise only be enforced when parsing +** the database schema. In other words, the SQLITE_PREPARE_FROM_DDL flag +** causes the SQL compiler to treat the SQL statement being prepared as if +** it had come from an attacker. When SQLITE_PREPARE_FROM_DDL is used and +** [SQLITE_DBCONFIG_TRUSTED_SCHEMA] is off, SQL functions may only be called +** if they are tagged with [SQLITE_INNOCUOUS] and virtual tables may only +** be used if they are tagged with [SQLITE_VTAB_INNOCUOUS]. Best practice +** is to use the SQLITE_PREPARE_FROM_DDL option when preparing any SQL that +** is derived from parts of the database schema. In particular, virtual +** table implementations that run SQL statements that are derived from +** arguments to their CREATE VIRTUAL TABLE statement should always use +** [sqlite3_prepare_v3()] and set the SQLITE_PREPARE_FROM_DDL flag to +** prevent bypass of the [SQLITE_DBCONFIG_TRUSTED_SCHEMA] security checks. ** </dl> */ #define SQLITE_PREPARE_PERSISTENT 0x01 #define SQLITE_PREPARE_NORMALIZE 0x02 #define SQLITE_PREPARE_NO_VTAB 0x04 #define SQLITE_PREPARE_DONT_LOG 0x10 +#define SQLITE_PREPARE_FROM_DDL 0x20 /* ** CAPI3REF: Compiling An SQL Statement @@ -4443,8 +4487,9 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** ** The preferred routine to use is [sqlite3_prepare_v2()]. The ** [sqlite3_prepare()] interface is legacy and should be avoided. -** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used -** for special purposes. +** [sqlite3_prepare_v3()] has an extra +** [SQLITE_PREPARE_FROM_DDL|"prepFlags" option] that is some times +** needed for special purpose or to pass along security restrictions. ** ** The use of the UTF-8 interfaces is preferred, as SQLite currently ** does all parsing using UTF-8. The UTF-16 interfaces are provided @@ -4849,8 +4894,8 @@ typedef struct sqlite3_context sqlite3_context; ** it should be a pointer to well-formed UTF16 text. ** ^If the third parameter to sqlite3_bind_text64() is not NULL, then ** it should be a pointer to a well-formed unicode string that is -** either UTF8 if the sixth parameter is SQLITE_UTF8, or UTF16 -** otherwise. +** either UTF8 if the sixth parameter is SQLITE_UTF8 or SQLITE_UTF8_ZT, +** or UTF16 otherwise. ** ** [[byte-order determination rules]] ^The byte-order of ** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) @@ -4896,10 +4941,15 @@ typedef struct sqlite3_context sqlite3_context; ** object and pointer to it must remain valid until then. ^SQLite will then ** manage the lifetime of its private copy. ** -** ^The sixth argument to sqlite3_bind_text64() must be one of -** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] -** to specify the encoding of the text in the third parameter. If -** the sixth argument to sqlite3_bind_text64() is not one of the +** ^The sixth argument (the E argument) +** to sqlite3_bind_text64(S,K,Z,N,D,E) must be one of +** [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE] to specify the encoding of the text in the +** third parameter, Z. The special value [SQLITE_UTF8_ZT] means that the +** string argument is both UTF-8 encoded and is zero-terminated. In other +** words, SQLITE_UTF8_ZT means that the Z array is allocated to hold at +** least N+1 bytes and that the Z&#91;N&#93; byte is zero. If +** the E argument to sqlite3_bind_text64(S,K,Z,N,D,E) is not one of the ** allowed values shown above, or if the text encoding is different ** from the encoding specified by the sixth parameter, then the behavior ** is undefined. @@ -5766,6 +5816,51 @@ int sqlite3_create_window_function( ** ** These constants define integer codes that represent the various ** text encodings supported by SQLite. +** +** <dl> +** [[SQLITE_UTF8]] <dt>SQLITE_UTF8</dt><dd>Text is encoding as UTF-8</dd> +** +** [[SQLITE_UTF16LE]] <dt>SQLITE_UTF16LE</dt><dd>Text is encoding as UTF-16 +** with each code point being expressed "little endian" - the least significant +** byte first. This is the usual encoding, for example on Windows.</dd> +** +** [[SQLITE_UTF16BE]] <dt>SQLITE_UTF16BE</dt><dd>Text is encoding as UTF-16 +** with each code point being expressed "big endian" - the most significant +** byte first. This encoding is less common, but is still sometimes seen, +** specially on older systems. +** +** [[SQLITE_UTF16]] <dt>SQLITE_UTF16</dt><dd>Text is encoding as UTF-16 +** with each code point being expressed either little endian or as big +** endian, according to the native endianness of the host computer. +** +** [[SQLITE_ANY]] <dt>SQLITE_ANY</dt><dd>This encoding value may only be used +** to declare the preferred text for [application-defined SQL functions] +** created using [sqlite3_create_function()] and similar. If the preferred +** encoding (the 4th parameter to sqlite3_create_function() - the eTextRep +** parameter) is SQLITE_ANY, that indicates that the function does not have +** a preference regarding the text encoding of its parameters and can take +** any text encoding that the SQLite core find convenient to supply. This +** option is deprecated. Please do not use it in new applications. +** +** [[SQLITE_UTF16_ALIGNED]] <dt>SQLITE_UTF16_ALIGNED</dt><dd>This encoding +** value may be used as the 3rd parameter (the eTextRep parameter) to +** [sqlite3_create_collation()] and similar. This encoding value means +** that the application-defined collating sequence created expects its +** input strings to be in UTF16 in native byte order, and that the start +** of the strings must be aligned to a 2-byte boundary. +** +** [[SQLITE_UTF8_ZT]] <dt>SQLITE_UTF8_ZT</dt><dd>This option can only be +** used to specify the text encoding to strings input to [sqlite3_result_text64()] +** and [sqlite3_bind_text64()]. It means that the input string (call it "z") +** is UTF-8 encoded and that it is zero-terminated. If the length parameter +** (call it "n") is non-negative, this encoding option means that the caller +** guarantees that z array contains at least n+1 bytes and that the z&#91;n&#93; +** byte has a value of zero. +** This option gives the same output as SQLITE_UTF8, but can be more efficient +** by avoiding the need to make a copy of the input string, in some cases. +** However, if z is allocated to hold fewer than n+1 bytes or if the +** z&#91;n&#93; byte is not zero, undefined behavior may result. +** </dl> */ #define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ #define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ @@ -5773,6 +5868,7 @@ int sqlite3_create_window_function( #define SQLITE_UTF16 4 /* Use native byte order */ #define SQLITE_ANY 5 /* Deprecated */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ +#define SQLITE_UTF8_ZT 16 /* Zero-terminated UTF8 */ /* ** CAPI3REF: Function Flags @@ -6278,10 +6374,14 @@ void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); ** ** There is no limit (other than available memory) on the number of different ** client data pointers (with different names) that can be attached to a -** single database connection. However, the implementation is optimized -** for the case of having only one or two different client data names. -** Applications and wrapper libraries are discouraged from using more than -** one client data name each. +** single database connection. However, the current implementation stores +** the content on a linked list. Insert and retrieval performance will +** be proportional to the number of entries. The design use case, and +** the use case for which the implementation is optimized, is +** that an application will store only small number of client data names, +** typically just one or two. This interface is not intended to be a +** generalized key/value store for thousands or millions of keys. It +** will work for that, but performance might be disappointing. ** ** There is no way to enumerate the client data pointers ** associated with a database connection. The N parameter can be thought @@ -6389,10 +6489,14 @@ typedef void (*sqlite3_destructor_type)(void*); ** set the return value of the application-defined function to be ** a text string which is represented as UTF-8, UTF-16 native byte order, ** UTF-16 little endian, or UTF-16 big endian, respectively. -** ^The sqlite3_result_text64() interface sets the return value of an +** ^The sqlite3_result_text64(C,Z,N,D,E) interface sets the return value of an ** application-defined function to be a text string in an encoding -** specified by the fifth (and last) parameter, which must be one -** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. +** specified the E parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE]. ^The special value [SQLITE_UTF8_ZT] means that +** the result text is both UTF-8 and zero-terminated. In other words, +** SQLITE_UTF8_ZT means that the Z array holds at least N+1 byes and that +** the Z&#91;N&#93; is zero. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. ** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces @@ -6479,7 +6583,7 @@ void sqlite3_result_int(sqlite3_context*, int); void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); void sqlite3_result_null(sqlite3_context*); void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); -void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64, +void sqlite3_result_text64(sqlite3_context*, const char *z, sqlite3_uint64 n, void(*)(void*), unsigned char encoding); void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); @@ -7418,7 +7522,7 @@ int sqlite3_table_column_metadata( ** ^The sqlite3_load_extension() interface attempts to load an ** [SQLite extension] library contained in the file zFile. If ** the file cannot be loaded directly, attempts are made to load -** with various operating-system specific extensions added. +** with various operating-system specific filename extensions added. ** So for example, if "samplelib" cannot be loaded, then names like ** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might ** be tried also. @@ -7426,10 +7530,10 @@ int sqlite3_table_column_metadata( ** ^The entry point is zProc. ** ^(zProc may be 0, in which case SQLite will try to come up with an ** entry point name on its own. It first tries "sqlite3_extension_init". -** If that does not work, it constructs a name "sqlite3_X_init" where -** X consists of the lower-case equivalent of all ASCII alphabetic -** characters in the filename from the last "/" to the first following -** "." and omitting any initial "lib".)^ +** If that does not work, it tries names of the form "sqlite3_X_init" +** where X consists of the lower-case equivalent of all ASCII alphabetic +** characters or all ASCII alphanumeric characters in the filename from +** the last "/" to the first following "." and omitting any initial "lib".)^ ** ^The sqlite3_load_extension() interface returns ** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. ** ^If an error occurs and pzErrMsg is not 0, then the @@ -8722,17 +8826,22 @@ sqlite3_str *sqlite3_str_new(sqlite3*); ** pass the returned value to [sqlite3_free()] to avoid a memory leak. ** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any ** errors were encountered during construction of the string. ^The -** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the +** [sqlite3_str_finish(X)] interface might also return a NULL pointer if the ** string in [sqlite3_str] object X is zero bytes long. +** +** ^The [sqlite3_str_free(X)] interface destroys both the sqlite3_str object +** X and the string content it contains. Calling sqlite3_str_free(X) is +** the equivalent of calling [sqlite3_free](sqlite3_str_finish(X)). */ char *sqlite3_str_finish(sqlite3_str*); +void sqlite3_str_free(sqlite3_str*); /* ** CAPI3REF: Add Content To A Dynamic String ** METHOD: sqlite3_str ** -** These interfaces add content to an sqlite3_str object previously obtained -** from [sqlite3_str_new()]. +** These interfaces add or remove content to an sqlite3_str object +** previously obtained from [sqlite3_str_new()]. ** ** ^The [sqlite3_str_appendf(X,F,...)] and ** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] @@ -8755,6 +8864,10 @@ char *sqlite3_str_finish(sqlite3_str*); ** ^The [sqlite3_str_reset(X)] method resets the string under construction ** inside [sqlite3_str] object X back to zero bytes in length. ** +** ^The [sqlite3_str_truncate(X,N)] method changes the length of the string +** under construction to be N bytes are less. This routine is a no-op if +** N is negative or if the string is already N bytes or smaller in size. +** ** These methods do not return a result code. ^If an error occurs, that fact ** is recorded in the [sqlite3_str] object and can be recovered by a ** subsequent call to [sqlite3_str_errcode(X)]. @@ -8765,6 +8878,7 @@ void sqlite3_str_append(sqlite3_str*, const char *zIn, int N); void sqlite3_str_appendall(sqlite3_str*, const char *zIn); void sqlite3_str_appendchar(sqlite3_str*, int N, char C); void sqlite3_str_reset(sqlite3_str*); +void sqlite3_str_truncate(sqlite3_str*,int N); /* ** CAPI3REF: Status Of A Dynamic String @@ -10598,9 +10712,9 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); ** a variable pointed to by the "pOut" parameter. ** ** The "flags" parameter must be passed a mask of flags. At present only -** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX +** one flag is defined - [SQLITE_SCANSTAT_COMPLEX]. If SQLITE_SCANSTAT_COMPLEX ** is specified, then status information is available for all elements -** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If +** of a query plan that are reported by "[EXPLAIN QUERY PLAN]" output. If ** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements ** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of ** the EXPLAIN QUERY PLAN output) are available. Invoking API @@ -10614,7 +10728,8 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); ** elements used to implement the statement - a non-zero value is returned and ** the variable that pOut points to is unchanged. ** -** See also: [sqlite3_stmt_scanstatus_reset()] +** See also: [sqlite3_stmt_scanstatus_reset()] and the +** [nexec and ncycle] columnes of the [bytecode virtual table]. */ int sqlite3_stmt_scanstatus( sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ @@ -11156,19 +11271,41 @@ int sqlite3_deserialize( /* ** CAPI3REF: Bind array values to the CARRAY table-valued function ** -** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to -** one of the first argument of the [carray() table-valued function]. The -** S parameter is a pointer to the [prepared statement] that uses the carray() -** functions. I is the parameter index to be bound. P is a pointer to the -** array to be bound, and N is the number of eements in the array. The -** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], -** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to -** indicate the datatype of the array being bound. The X argument is not a -** NULL pointer, then SQLite will invoke the function X on the P parameter -** after it has finished using P, even if the call to -** sqlite3_carray_bind() fails. The special-case finalizer -** SQLITE_TRANSIENT has no effect here. -*/ +** The sqlite3_carray_bind_v2(S,I,P,N,F,X,D) interface binds an array value to +** parameter that is the first argument of the [carray() table-valued function]. +** The S parameter is a pointer to the [prepared statement] that uses the carray() +** functions. I is the parameter index to be bound. I must be the index of the +** parameter that is the first argument to the carray() table-valued function. +** P is a pointer to the array to be bound, and N is the number of elements in +** the array. The F argument is one of constants [SQLITE_CARRAY_INT32], +** [SQLITE_CARRAY_INT64], [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], +** or [SQLITE_CARRAY_BLOB] to indicate the datatype of the array P. +** +** If the X argument is not a NULL pointer or one of the special +** values [SQLITE_STATIC] or [SQLITE_TRANSIENT], then SQLite will invoke +** the function X with argument D when it is finished using the data in P. +** The call to X(D) is a destructor for the array P. The destructor X(D) +** is invoked even if the call to sqlite3_carray_bind() fails. If the X +** parameter is the special-case value [SQLITE_STATIC], then SQLite assumes +** that the data static and the destructor is never invoked. If the X +** parameter is the special-case value [SQLITE_TRANSIENT], then +** sqlite3_carray_bind_v2() makes its own private copy of the data prior +** to returning and never invokes the destructor X. +** +** The sqlite3_carray_bind() function works the same as sqlite_carray_bind_v2() +** with a D parameter set to P. In other words, +** sqlite3_carray_bind(S,I,P,N,F,X) is same as +** sqlite3_carray_bind(S,I,P,N,F,X,P). +*/ +int sqlite3_carray_bind_v2( + sqlite3_stmt *pStmt, /* Statement to be bound */ + int i, /* Parameter index */ + void *aData, /* Pointer to array data */ + int nData, /* Number of data elements */ + int mFlags, /* CARRAY flags */ + void (*xDel)(void*), /* Destructor for aData */ + void *pDel /* Optional argument to xDel() */ +); int sqlite3_carray_bind( sqlite3_stmt *pStmt, /* Statement to be bound */ int i, /* Parameter index */ diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 5258faaed..cad1a2a00 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -371,7 +371,11 @@ struct sqlite3_api_routines { /* Version 3.51.0 and later */ int (*set_errmsg)(sqlite3*,int,const char*); int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); - + /* Version 3.52.0 and later */ + void (*str_truncate)(sqlite3_str*,int); + void (*str_free)(sqlite3_str*); + int (*carray_bind)(sqlite3_stmt*,int,void*,int,int,void(*)(void*)); + int (*carray_bind_v2)(sqlite3_stmt*,int,void*,int,int,void(*)(void*),void*); }; /* @@ -710,6 +714,11 @@ typedef int (*sqlite3_loadext_entry)( /* Version 3.51.0 and later */ #define sqlite3_set_errmsg sqlite3_api->set_errmsg #define sqlite3_db_status64 sqlite3_api->db_status64 +/* Version 3.52.0 and later */ +#define sqlite3_str_truncate sqlite3_api->str_truncate +#define sqlite3_str_free sqlite3_api->str_free +#define sqlite3_carray_bind sqlite3_api->carray_bind +#define sqlite3_carray_bind_v2 sqlite3_api->carray_bind_v2 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 523bcfb3b..c4a876861 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -661,6 +661,7 @@ # define float sqlite_int64 # define fabs(X) ((X)<0?-(X):(X)) # define sqlite3IsOverflow(X) 0 +# define INFINITY (9223372036854775807LL) # ifndef SQLITE_BIG_DBL # define SQLITE_BIG_DBL (((sqlite3_int64)1)<<50) # endif @@ -1070,6 +1071,7 @@ typedef INT16_TYPE LogEst; #else # define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) #endif +#define TWO_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&1)==0) /* ** Disable MMAP on platforms where it is known to not work @@ -1537,7 +1539,7 @@ struct Schema { ** The number of different kinds of things that can be limited ** using the sqlite3_limit() interface. */ -#define SQLITE_N_LIMIT (SQLITE_LIMIT_WORKER_THREADS+1) +#define SQLITE_N_LIMIT (SQLITE_LIMIT_PARSER_DEPTH+1) /* ** Lookaside malloc is a set of fixed-size buffers that can be used @@ -1691,6 +1693,7 @@ struct sqlite3 { u8 noSharedCache; /* True if no shared-cache backends */ u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ u8 eOpenState; /* Current condition of the connection */ + u8 nFpDigit; /* Significant digits to keep on double->text */ int nextPagesize; /* Pagesize after VACUUM if >0 */ i64 nChange; /* Value returned by sqlite3_changes() */ i64 nTotalChange; /* Value returned by sqlite3_total_changes() */ @@ -3585,19 +3588,6 @@ struct Upsert { /* ** An instance of the following structure contains all information ** needed to generate code for a single SELECT statement. -** -** See the header comment on the computeLimitRegisters() routine for a -** detailed description of the meaning of the iLimit and iOffset fields. -** -** addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes. -** These addresses must be stored so that we can go back and fill in -** the P4_KEYINFO and P2 parameters later. Neither the KeyInfo nor -** the number of columns in P2 can be computed at the same time -** as the OP_OpenEphm instruction is coded because not -** enough information about the compound query is known at that point. -** The KeyInfo for addrOpenTran[0] and [1] contains collating sequences -** for the result set. The KeyInfo for addrOpenEphm[2] contains collating -** sequences for the ORDER BY clause. */ struct Select { u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */ @@ -3605,7 +3595,6 @@ struct Select { u32 selFlags; /* Various SF_* values */ int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */ u32 selId; /* Unique identifier number for this SELECT */ - int addrOpenEphm[2]; /* OP_OpenEphem opcodes related to this select */ ExprList *pEList; /* The fields of the result */ SrcList *pSrc; /* The FROM clause */ Expr *pWhere; /* The WHERE clause */ @@ -3637,7 +3626,7 @@ struct Select { #define SF_Resolved 0x0000004 /* Identifiers have been resolved */ #define SF_Aggregate 0x0000008 /* Contains agg functions or a GROUP BY */ #define SF_HasAgg 0x0000010 /* Contains aggregate functions */ -#define SF_UsesEphemeral 0x0000020 /* Uses the OpenEphemeral opcode */ +#define SF_ClonedRhsIn 0x0000020 /* Cloned RHS of an IN operator */ #define SF_Expanded 0x0000040 /* sqlite3SelectExpand() called on this */ #define SF_HasTypeInfo 0x0000080 /* FROM subqueries have Table metadata */ #define SF_Compound 0x0000100 /* Part of a compound query */ @@ -3647,14 +3636,14 @@ struct Select { #define SF_MinMaxAgg 0x0001000 /* Aggregate containing min() or max() */ #define SF_Recursive 0x0002000 /* The recursive part of a recursive CTE */ #define SF_FixedLimit 0x0004000 /* nSelectRow set by a constant LIMIT */ -#define SF_MaybeConvert 0x0008000 /* Need convertCompoundSelectToSubquery() */ +/* 0x0008000 // available for reuse */ #define SF_Converted 0x0010000 /* By convertCompoundSelectToSubquery() */ #define SF_IncludeHidden 0x0020000 /* Include hidden columns in output */ #define SF_ComplexResult 0x0040000 /* Result contains subquery or function */ #define SF_WhereBegin 0x0080000 /* Really a WhereBegin() call. Debug Only */ #define SF_WinRewrite 0x0100000 /* Window function rewrite accomplished */ #define SF_View 0x0200000 /* SELECT statement is a view */ -#define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ +/* 0x0400000 // available for reuse */ #define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ #define SF_PushDown 0x1000000 /* Modified by WHERE-clause push-down opt */ #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ @@ -3674,11 +3663,6 @@ struct Select { ** by one of the following macros. The "SRT" prefix means "SELECT Result ** Type". ** -** SRT_Union Store results as a key in a temporary index -** identified by pDest->iSDParm. -** -** SRT_Except Remove results from the temporary index pDest->iSDParm. -** ** SRT_Exists Store a 1 in memory cell pDest->iSDParm if the result ** set is not empty. ** @@ -3742,30 +3726,28 @@ struct Select { ** table. (pDest->iSDParm) is the number of key columns in ** each index record in this case. */ -#define SRT_Union 1 /* Store result as keys in an index */ -#define SRT_Except 2 /* Remove result from a UNION index */ -#define SRT_Exists 3 /* Store 1 if the result is not empty */ -#define SRT_Discard 4 /* Do not save the results anywhere */ -#define SRT_DistFifo 5 /* Like SRT_Fifo, but unique results only */ -#define SRT_DistQueue 6 /* Like SRT_Queue, but unique results only */ +#define SRT_Exists 1 /* Store 1 if the result is not empty */ +#define SRT_Discard 2 /* Do not save the results anywhere */ +#define SRT_DistFifo 3 /* Like SRT_Fifo, but unique results only */ +#define SRT_DistQueue 4 /* Like SRT_Queue, but unique results only */ /* The DISTINCT clause is ignored for all of the above. Not that ** IgnorableDistinct() implies IgnorableOrderby() */ #define IgnorableDistinct(X) ((X->eDest)<=SRT_DistQueue) -#define SRT_Queue 7 /* Store result in an queue */ -#define SRT_Fifo 8 /* Store result as data with an automatic rowid */ +#define SRT_Queue 5 /* Store result in an queue */ +#define SRT_Fifo 6 /* Store result as data with an automatic rowid */ /* The ORDER BY clause is ignored for all of the above */ #define IgnorableOrderby(X) ((X->eDest)<=SRT_Fifo) -#define SRT_Output 9 /* Output each row of result */ -#define SRT_Mem 10 /* Store result in a memory cell */ -#define SRT_Set 11 /* Store results as keys in an index */ -#define SRT_EphemTab 12 /* Create transient tab and store like SRT_Table */ -#define SRT_Coroutine 13 /* Generate a single row of result */ -#define SRT_Table 14 /* Store result as data with an automatic rowid */ -#define SRT_Upfrom 15 /* Store result as data with rowid */ +#define SRT_Output 7 /* Output each row of result */ +#define SRT_Mem 8 /* Store result in a memory cell */ +#define SRT_Set 9 /* Store results as keys in an index */ +#define SRT_EphemTab 10 /* Create transient tab and store like SRT_Table */ +#define SRT_Coroutine 11 /* Generate a single row of result */ +#define SRT_Table 12 /* Store result as data with an automatic rowid */ +#define SRT_Upfrom 13 /* Store result as data with rowid */ /* ** An instance of this object describes where to put of the results of @@ -3901,17 +3883,12 @@ struct Parse { u8 nested; /* Number of nested calls to the parser/code generator */ u8 nTempReg; /* Number of temporary registers in aTempReg[] */ u8 isMultiWrite; /* True if statement may modify/insert multiple rows */ - u8 mayAbort; /* True if statement may throw an ABORT exception */ - u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */ u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ - u8 bHasExists; /* Has a correlated "EXISTS (SELECT ....)" expression */ u8 mSubrtnSig; /* mini Bloom filter on available SubrtnSig.selId */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ - u8 bReturning; /* Coding a RETURNING trigger */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ - u8 disableTriggers; /* True to disable triggers */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ #endif @@ -3920,10 +3897,15 @@ struct Parse { u8 isCreate; /* CREATE TABLE, INDEX, or VIEW (but not TRIGGER) ** and ALTER TABLE ADD COLUMN. */ #endif - bft colNamesSet :1; /* TRUE after OP_ColumnName has been issued to pVdbe */ - bft bHasWith :1; /* True if statement contains WITH */ - bft okConstFactor :1; /* OK to factor out constants */ - bft checkSchema :1; /* Causes schema cookie check after an error */ + bft disableTriggers:1; /* True to disable triggers */ + bft mayAbort :1; /* True if statement may throw an ABORT exception */ + bft hasCompound :1; /* Need to invoke convertCompoundSelectToSubquery() */ + bft bReturning :1; /* Coding a RETURNING trigger */ + bft bHasExists :1; /* Has a correlated "EXISTS (SELECT ....)" expression */ + bft colNamesSet :1; /* TRUE after OP_ColumnName has been issued to pVdbe */ + bft bHasWith :1; /* True if statement contains WITH */ + bft okConstFactor:1; /* OK to factor out constants */ + bft checkSchema :1; /* Causes schema cookie check after an error */ int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ int nErr; /* Number of errors seen */ @@ -4152,19 +4134,19 @@ struct Trigger { ** orconf -> stores the ON CONFLICT algorithm ** pSelect -> The content to be inserted - either a SELECT statement or ** a VALUES clause. -** zTarget -> Dequoted name of the table to insert into. +** pSrc -> Table to insert into. ** pIdList -> If this is an INSERT INTO ... (<column-names>) VALUES ... ** statement, then this stores the column-names to be ** inserted into. ** pUpsert -> The ON CONFLICT clauses for an Upsert ** ** (op == TK_DELETE) -** zTarget -> Dequoted name of the table to delete from. +** pSrc -> Table to delete from ** pWhere -> The WHERE clause of the DELETE statement if one is specified. ** Otherwise NULL. ** ** (op == TK_UPDATE) -** zTarget -> Dequoted name of the table to update. +** pSrc -> Table to update, followed by any FROM clause tables. ** pWhere -> The WHERE clause of the UPDATE statement if one is specified. ** Otherwise NULL. ** pExprList -> A list of the columns to update and the expressions to update @@ -4184,8 +4166,7 @@ struct TriggerStep { u8 orconf; /* OE_Rollback etc. */ Trigger *pTrig; /* The trigger that this step is a part of */ Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */ - char *zTarget; /* Target table for DELETE, UPDATE, INSERT */ - SrcList *pFrom; /* FROM clause for UPDATE statement (if any) */ + SrcList *pSrc; /* Table to insert/update/delete */ Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ ExprList *pExprList; /* SET clause for UPDATE, or RETURNING clause */ IdList *pIdList; /* Column names for INSERT */ @@ -4268,10 +4249,11 @@ typedef struct { /* ** Allowed values for mInitFlags */ -#define INITFLAG_AlterMask 0x0003 /* Types of ALTER */ +#define INITFLAG_AlterMask 0x0007 /* Types of ALTER */ #define INITFLAG_AlterRename 0x0001 /* Reparse after a RENAME */ #define INITFLAG_AlterDrop 0x0002 /* Reparse after a DROP COLUMN */ #define INITFLAG_AlterAdd 0x0003 /* Reparse after an ADD COLUMN */ +#define INITFLAG_AlterDropCons 0x0004 /* Reparse after an ADD COLUMN */ /* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled ** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning @@ -4401,6 +4383,7 @@ struct Walker { NameContext *pNC; /* Naming context */ int n; /* A counter */ int iCur; /* A cursor number */ + int sz; /* String literal length */ SrcList *pSrcList; /* FROM clause */ struct CCurHint *pCCurHint; /* Used by codeCursorHint() */ struct RefSrcList *pRefSrcList; /* sqlite3ReferencesSrcList() */ @@ -4805,7 +4788,20 @@ int sqlite3LookasideUsed(sqlite3*,int*); sqlite3_mutex *sqlite3Pcache1Mutex(void); sqlite3_mutex *sqlite3MallocMutex(void); -#if defined(SQLITE_ENABLE_MULTITHREADED_CHECKS) && !defined(SQLITE_MUTEX_OMIT) + +/* The SQLITE_THREAD_MISUSE_WARNINGS compile-time option used to be called +** SQLITE_ENABLE_MULTITHREADED_CHECKS. Keep that older macro for backwards +** compatibility, at least for a while... */ +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS +# define SQLITE_THREAD_MISUSE_WARNINGS 1 +#endif + +/* SQLITE_THREAD_MISUSE_ABORT implies SQLITE_THREAD_MISUSE_WARNINGS */ +#ifdef SQLITE_THREAD_MISUSE_ABORT +# define SQLITE_THREAD_MISUSE_WARNINGS 1 +#endif + +#if defined(SQLITE_THREAD_MISUSE_WARNINGS) && !defined(SQLITE_MUTEX_OMIT) void sqlite3MutexWarnOnContention(sqlite3_mutex*); #else # define sqlite3MutexWarnOnContention(x) @@ -4839,12 +4835,12 @@ struct PrintfArguments { ** value into an approximate decimal representation. */ struct FpDecode { - char sign; /* '+' or '-' */ - char isSpecial; /* 1: Infinity 2: NaN */ int n; /* Significant digits in the decode */ int iDP; /* Location of the decimal point */ char *z; /* Start of significant digits */ - char zBuf[24]; /* Storage for significant digits */ + char zBuf[20]; /* Storage for significant digits */ + char sign; /* '+' or '-' */ + char isSpecial; /* 1: Infinity 2: NaN */ }; void sqlite3FpDecode(FpDecode*,double,int,int); @@ -4933,6 +4929,7 @@ int sqlite3NoTempsInRange(Parse*,int,int); #endif Expr *sqlite3ExprAlloc(sqlite3*,int,const Token*,int); Expr *sqlite3Expr(sqlite3*,int,const char*); +Expr *sqlite3ExprInt32(sqlite3*,int); void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*); Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*); void sqlite3PExprAddSelect(Parse*, Expr*, Select*); @@ -5086,6 +5083,7 @@ Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*, Expr*,ExprList*,u32,Expr*); void sqlite3SelectDelete(sqlite3*, Select*); void sqlite3SelectDeleteGeneric(sqlite3*,void*); +void sqlite3SelectCheckOnClauses(Parse *pParse, Select *pSelect); Table *sqlite3SrcListLookup(Parse*, SrcList*); int sqlite3IsReadOnly(Parse*, Table*, Trigger*); void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int); @@ -5183,6 +5181,7 @@ int sqlite3ExprContainsSubquery(Expr*); int sqlite3ExprIsInteger(const Expr*, int*, Parse*); int sqlite3ExprCanBeNull(const Expr*); int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); +int sqlite3ExprIsLikeOperator(const Expr*); int sqlite3IsRowid(const char*); const char *sqlite3RowidAlias(Table *pTab); void sqlite3GenerateRowDelete( @@ -5251,17 +5250,16 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*, const char*,const char*); - TriggerStep *sqlite3TriggerInsertStep(Parse*,Token*, IdList*, + TriggerStep *sqlite3TriggerInsertStep(Parse*,SrcList*, IdList*, Select*,u8,Upsert*, const char*,const char*); - TriggerStep *sqlite3TriggerUpdateStep(Parse*,Token*,SrcList*,ExprList*, + TriggerStep *sqlite3TriggerUpdateStep(Parse*,SrcList*,SrcList*,ExprList*, Expr*, u8, const char*,const char*); - TriggerStep *sqlite3TriggerDeleteStep(Parse*,Token*, Expr*, + TriggerStep *sqlite3TriggerDeleteStep(Parse*,SrcList*, Expr*, const char*,const char*); void sqlite3DeleteTrigger(sqlite3*, Trigger*); void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); - SrcList *sqlite3TriggerStepSrc(Parse*, TriggerStep*); # define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) # define sqlite3IsToplevel(p) ((p)->pToplevel==0) #else @@ -5275,7 +5273,6 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); # define sqlite3ParseToplevel(p) p # define sqlite3IsToplevel(p) 1 # define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0 -# define sqlite3TriggerStepSrc(A,B) 0 #endif int sqlite3JoinType(Parse*, Token*, Token*, Token*); @@ -5308,7 +5305,7 @@ int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); int sqlite3RealSameAsInt(double,sqlite3_int64); i64 sqlite3RealToI64(double); int sqlite3Int64ToText(i64,char*); -int sqlite3AtoF(const char *z, double*, int, u8); +int sqlite3AtoF(const char *z, double*); int sqlite3GetInt32(const char *, int*); int sqlite3GetUInt32(const char*, u32*); int sqlite3Atoi(const char*); @@ -5452,10 +5449,13 @@ void sqlite3Reindex(Parse*, Token*, Token*); void sqlite3AlterFunctions(void); void sqlite3AlterRenameTable(Parse*, SrcList*, Token*); void sqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*); +void sqlite3AlterDropConstraint(Parse*,SrcList*,Token*,Token*); +void sqlite3AlterAddConstraint(Parse*,SrcList*,Token*,Token*,const char*,int); +void sqlite3AlterSetNotNull(Parse*, SrcList*, Token*, Token*); i64 sqlite3GetToken(const unsigned char *, int *); void sqlite3NestedParse(Parse*, const char*, ...); void sqlite3ExpirePreparedStatements(sqlite3*, int); -void sqlite3CodeRhsOfIN(Parse*, Expr*, int); +void sqlite3CodeRhsOfIN(Parse*, Expr*, int, int); int sqlite3CodeSubselect(Parse*, Expr*); void sqlite3SelectPrep(Parse*, Select*, NameContext*); int sqlite3ExpandSubquery(Parse*, SrcItem*); diff --git a/src/sqliteLimit.h b/src/sqliteLimit.h index 6b6bb7167..ecbd0858e 100644 --- a/src/sqliteLimit.h +++ b/src/sqliteLimit.h @@ -25,6 +25,27 @@ #endif #define SQLITE_MIN_LENGTH 30 /* Minimum value for the length limit */ +/* +** Maximum size of any single memory allocation. +** +** This is not a limit on the total amount of memory used. This is +** a limit on the size parameter to sqlite3_malloc() and sqlite3_realloc(). +** +** The upper bound is slightly less than 2GiB: 0x7ffffeff == 2,147,483,391 +** This provides a 256-byte safety margin for defense against 32-bit +** signed integer overflow bugs when computing memory allocation sizes. +** Paranoid applications might want to reduce the maximum allocation size +** further for an even larger safety margin. 0x3fffffff or 0x0fffffff +** or even smaller would be reasonable upper bounds on the size of a memory +** allocations for most applications. +*/ +#ifndef SQLITE_MAX_ALLOCATION_SIZE +# define SQLITE_MAX_ALLOCATION_SIZE 2147483391 +#endif +#if SQLITE_MAX_ALLOCATION_SIZE>2147483391 +# error Maximum size for SQLITE_MAX_ALLOCATION_SIZE is 2147483391 +#endif + /* ** This is the maximum number of ** @@ -60,21 +81,42 @@ ** It used to be the case that setting this value to zero would ** turn the limit off. That is no longer true. It is not possible ** to turn this limit off. +** +** The hard limit is the largest possible 32-bit signed integer less +** 1024, or 2147482624. */ #ifndef SQLITE_MAX_SQL_LENGTH # define SQLITE_MAX_SQL_LENGTH 1000000000 #endif /* -** The maximum depth of an expression tree. This is limited to -** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might -** want to place more severe limits on the complexity of an -** expression. A value of 0 means that there is no limit. +** The maximum depth of an expression tree. The expression tree depth +** is also limited indirectly by SQLITE_MAX_SQL_LENGTH and by +** SQLITE_MAX_PARSER_DEPTH. Reducing the maximum complexity of +** expressions can help prevent excess memory usage by hostile SQL. +** +** A value of 0 for this compile-time option causes all expression +** depth limiting code to be omitted. */ #ifndef SQLITE_MAX_EXPR_DEPTH # define SQLITE_MAX_EXPR_DEPTH 1000 #endif +/* +** The maximum depth of the LALR(1) stack used in the parser that +** interprets SQL inputs. The parser stack depth can also be limited +** indirectly by SQLITE_MAX_SQL_LENGTH. Limiting the parser stack +** depth can help prevent excess memory usage and excess CPU stack +** usage when processing hostile SQL. +** +** Prior to version 3.45.0 (2024-01-15), the parser stack was +** hard-coded to 100 entries, and that worked fine for almost all +** applications. So the upper bound on this limit need not be large. +*/ +#ifndef SQLITE_MAX_PARSER_DEPTH +# define SQLITE_MAX_PARSER_DEPTH 2500 +#endif + /* ** The maximum number of terms in a compound SELECT statement. ** The code generator for compound SELECT statements does one diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 02a4d84e4..2c7918926 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -124,6 +124,15 @@ /* Forward declaration */ typedef struct SqliteDb SqliteDb; +/* Add -DSQLITE_ENABLE_QRF_IN_TCL to add the Query Result Formatter (QRF) +** into the build of the TCL extension, when building using separate +** source files. The QRF is included automatically when building from +** the tclsqlite3.c amalgamation. +*/ +#if defined(SQLITE_ENABLE_QRF_IN_TCL) +#include "qrf.h" +#endif + /* ** New SQL functions can be created as TCL scripts. Each such function ** is described by an instance of the following structure. @@ -2035,6 +2044,367 @@ static void DbHookCmd( sqlite3_wal_hook(db, (pDb->pWalHook?DbWalHandler:0), pDb); } +/* +** Implementation of the "db format" command. +** +** Based on provided options, format the results of the SQL statement(s) +** provided into human-readable form using the Query Result Formatter (QRF) +** and return the resuling text. +** +** Syntax: db format OPTIONS SQL +** +** OPTIONS may be: +** +** -style ("auto"|"box"|"column"|...) Output style +** -esc ("auto"|"off"|"ascii"|"symbol") How to deal with ctrl chars +** -text ("auto"|"off"|"sql"|"csv"|...) How to escape TEXT values +** -title ("auto"|"off"|"sql"|...|"off") How to escape column names +** -blob ("auto"|"text"|"sql"|...) How to escape BLOB values +** -wordwrap ("auto"|"off"|"on") Try to wrap at word boundry? +** -textjsonb ("auto"|"off"|"on") Auto-convert JSONB to text? +** -splitcolumn ("auto"|"off"|"on") Enable split-column mode +** -defaultalign ("auto"|"left"|...) Default alignment +** -titalalign ("auto"|"left"|"right"|...) Default column name alignment +** -border ("auto"|"off"|"on") Border for box and table styles +** -wrap NUMBER Max width of any single column +** -screenwidth NUMBER Width of the display TTY +** -linelimit NUMBER Max lines for any cell +** -charlimit NUMBER Content truncated to this size +** -titlelimit NUMBER Max width of column titles +** -align LIST-OF-ALIGNMENT Alignment of columns +** -widths LIST-OF-NUMBERS Widths for individual columns +** -columnsep TEXT Column separator text +** -rowsep TEXT Row separator text +** -tablename TEXT Table name for style "insert" +** -null TEXT Text for NULL values +** +** A mapping from TCL "format" command options to sqlite3_qrf_spec fields +** is below. Use this to reference the QRF documentation: +** +** TCL Option spec field +** ---------- ---------- +** -style eStyle +** -esc eEsc +** -text eText +** -title eTitle, bTitle +** -blob eBlob +** -wordwrap bWordWrap +** -textjsonb bTextJsonb +** -splitcolumn bSplitColumn +** -defaultalign eDfltAlign +** -titlealign eTitleAlign +** -border bBorder +** -wrap nWrap +** -screenwidth nScreenWidth +** -linelimit nLineLimit +** -charlimit nCharLimit +** -titlelimit nTitleLimit +** -align nAlign, aAlign +** -widths nWidth, aWidth +** -columnsep zColumnSep +** -rowsep zRowSep +** -tablename zTableName +** -null zNull +*/ +static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ +#ifndef SQLITE_QRF_H + Tcl_SetResult(pDb->interp, "QRF not available in this build", TCL_VOLATILE); + return TCL_ERROR; +#else + char *zResult = 0; /* Result to be returned */ + const char *zSql = 0; /* SQL to run */ + int i; /* Loop counter */ + int rc; /* Result code */ + sqlite3_qrf_spec qrf; /* Formatting spec */ + static const char *azAlign[] = { + "auto", "bottom", "c", + "center", "e", "left", + "middle", "n", "ne", + "nw", "right", "s", + "se", "sw", "top", + "w", 0 + }; + static const unsigned char aAlignMap[] = { + QRF_ALIGN_Auto, QRF_ALIGN_Bottom, QRF_ALIGN_C, + QRF_ALIGN_Center, QRF_ALIGN_E, QRF_ALIGN_Left, + QRF_ALIGN_Middle, QRF_ALIGN_N, QRF_ALIGN_NE, + QRF_ALIGN_NW, QRF_ALIGN_Right, QRF_ALIGN_S, + QRF_ALIGN_SE, QRF_ALIGN_SW, QRF_ALIGN_Top, + QRF_ALIGN_W + }; + + memset(&qrf, 0, sizeof(qrf)); + qrf.iVersion = 1; + qrf.pzOutput = &zResult; + for(i=2; i<objc; i++){ + const char *zArg = Tcl_GetString(objv[i]); + const char *azBool[] = { "auto", "yes", "no", "on", "off", 0 }; + const unsigned char aBoolMap[] = { 0, 2, 1, 2, 1 }; + if( zArg[0]!='-' ){ + if( zSql ){ + Tcl_AppendResult(pDb->interp, "unknown argument: ", zArg, (char*)0); + rc = TCL_ERROR; + goto format_failed; + } + zSql = zArg; + }else if( i==objc-1 ){ + Tcl_AppendResult(pDb->interp, "option has no argument: ", zArg, (char*)0); + rc = TCL_ERROR; + goto format_failed; + }else if( strcmp(zArg,"-style")==0 ){ + static const char *azStyles[] = { + "auto", "box", "column", + "count", "csv", "eqp", + "explain", "html", "insert", + "jobject", "json", "line", + "list", "markdown", "quote", + "stats", "stats-est", "stats-vm", + "table", 0 + }; + static unsigned char aStyleMap[] = { + QRF_STYLE_Auto, QRF_STYLE_Box, QRF_STYLE_Column, + QRF_STYLE_Count, QRF_STYLE_Csv, QRF_STYLE_Eqp, + QRF_STYLE_Explain, QRF_STYLE_Html, QRF_STYLE_Insert, + QRF_STYLE_JObject, QRF_STYLE_Json, QRF_STYLE_Line, + QRF_STYLE_List, QRF_STYLE_Markdown, QRF_STYLE_Quote, + QRF_STYLE_Stats, QRF_STYLE_StatsEst, QRF_STYLE_StatsVm, + QRF_STYLE_Table + }; + int style; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azStyles, + "format style (-style)", 0, &style); + if( rc ) goto format_failed; + qrf.eStyle = aStyleMap[style]; + i++; + }else if( strcmp(zArg,"-esc")==0 ){ + static const char *azEsc[] = { + "ascii", "auto", "off", "symbol", 0 + }; + static unsigned char aEscMap[] = { + QRF_ESC_Ascii, QRF_ESC_Auto, QRF_ESC_Off, QRF_ESC_Symbol + }; + int esc; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azEsc, + "control character escape (-esc)", 0, &esc); + if( rc ) goto format_failed; + qrf.eEsc = aEscMap[esc]; + i++; + }else if( strcmp(zArg,"-text")==0 || strcmp(zArg, "-title")==0 ){ + /* NB: --title can be "off" or "on but --text may not be. Thus we put + ** the "off" and "on" choices first and start the search on the + ** thrid element of the array when processing --text */ + static const char *azText[] = { "off", "on", + "auto", "csv", "html", + "json", "plain", "relaxed", + "sql", "tcl", 0 + }; + static unsigned char aTextMap[] = { + QRF_TEXT_Auto, QRF_TEXT_Csv, QRF_TEXT_Html, + QRF_TEXT_Json, QRF_TEXT_Plain, QRF_TEXT_Relaxed, + QRF_TEXT_Sql, QRF_TEXT_Tcl + }; + int txt; + int k = zArg[2]=='e'; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], &azText[k*2], zArg, + 0, &txt); + if( rc ) goto format_failed; + if( k ){ + qrf.eText = aTextMap[txt]; + }else if( txt<=1 ){ + qrf.bTitles = txt ? QRF_Yes : QRF_No; + qrf.eTitle = QRF_TEXT_Auto; + }else{ + qrf.bTitles = QRF_Yes; + qrf.eTitle = aTextMap[txt-2]; + } + i++; + }else if( strcmp(zArg,"-blob")==0 ){ + static const char *azBlob[] = { + "auto", "hex", "json", + "tcl", "text", "sql", + "size", 0 + }; + static unsigned char aBlobMap[] = { + QRF_BLOB_Auto, QRF_BLOB_Hex, QRF_BLOB_Json, + QRF_BLOB_Tcl, QRF_BLOB_Text, QRF_BLOB_Sql, + QRF_BLOB_Size + }; + int blob; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBlob, + "BLOB encoding (-blob)", 0, &blob); + if( rc ) goto format_failed; + qrf.eBlob = aBlobMap[blob]; + i++; + }else if( strcmp(zArg,"-wordwrap")==0 ){ + int v = 0; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, + "-wordwrap", 0, &v); + if( rc ) goto format_failed; + qrf.bWordWrap = aBoolMap[v]; + i++; + }else if( strcmp(zArg,"-textjsonb")==0 + || strcmp(zArg,"-splitcolumn")==0 + || strcmp(zArg,"-border")==0 + ){ + int v = 0; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, + zArg, 0, &v); + if( rc ) goto format_failed; + if( zArg[1]=='t' ){ + qrf.bTextJsonb = aBoolMap[v]; + }else if( zArg[1]=='b' ){ + qrf.bBorder = aBoolMap[v]; + }else{ + qrf.bSplitColumn = aBoolMap[v]; + } + i++; + }else if( strcmp(zArg,"-defaultalign")==0 || strcmp(zArg,"-titlealign")==0){ + int ax = 0; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azAlign, + zArg[1]=='d' ? "default alignment (-defaultalign)" : + "title alignment (-titlealign)", + 0, &ax); + if( rc ) goto format_failed; + if( zArg[1]=='d' ){ + qrf.eDfltAlign = aAlignMap[ax]; + }else{ + qrf.eTitleAlign = aAlignMap[ax]; + } + i++; + }else if( strcmp(zArg,"-wrap")==0 + || strcmp(zArg,"-screenwidth")==0 + || strcmp(zArg,"-linelimit")==0 + || strcmp(zArg,"-titlelimit")==0 + ){ + int v = 0; + rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v); + if( rc ) goto format_failed; + if( v<QRF_MIN_WIDTH ){ + v = QRF_MIN_WIDTH; + }else if( v>QRF_MAX_WIDTH ){ + v = QRF_MAX_WIDTH; + } + if( zArg[1]=='w' ){ + qrf.nWrap = v; + }else if( zArg[1]=='s' ){ + qrf.nScreenWidth = v; + }else if( zArg[1]=='t' ){ + qrf.nTitleLimit = v; + }else{ + qrf.nLineLimit = v; + } + i++; + }else if( strcmp(zArg,"-charlimit")==0 ){ + int v = 0; + rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v); + if( rc ) goto format_failed; + if( v<0 ) v = 0; + qrf.nCharLimit = v; + i++; + }else if( strcmp(zArg,"-align")==0 ){ + Tcl_Size n = 0; + int jj; + rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n); + if( rc ) goto format_failed; + sqlite3_free(qrf.aAlign); + qrf.aAlign = sqlite3_malloc64( (n+1)*sizeof(qrf.aAlign[0]) ); + if( qrf.aAlign==0 ){ + Tcl_AppendResult(pDb->interp, "out of memory", (char*)0); + rc = TCL_ERROR; + goto format_failed; + } + memset(qrf.aAlign, 0, (n+1)*sizeof(qrf.aAlign[0])); + qrf.nAlign = n; + for(jj=0; jj<n; jj++){ + int x; + Tcl_Obj *pTerm; + rc = Tcl_ListObjIndex(pDb->interp, objv[i+1], jj, &pTerm); + if( rc ) goto format_failed; + rc = Tcl_GetIndexFromObj(pDb->interp, pTerm, azAlign, + "column alignment (-align)", 0, &x); + if( rc ) goto format_failed; + qrf.aAlign[jj] = aAlignMap[x]; + } + i++; + }else if( strcmp(zArg,"-widths")==0 ){ + Tcl_Size n = 0; + int jj; + rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n); + if( rc ) goto format_failed; + sqlite3_free(qrf.aWidth); + qrf.aWidth = sqlite3_malloc64( (n+1)*sizeof(qrf.aWidth[0]) ); + if( qrf.aWidth==0 ){ + Tcl_AppendResult(pDb->interp, "out of memory", (char*)0); + rc = TCL_ERROR; + goto format_failed; + } + memset(qrf.aWidth, 0, (n+1)*sizeof(qrf.aWidth[0])); + qrf.nWidth = n; + for(jj=0; jj<n; jj++){ + Tcl_Obj *pTerm; + int v; + rc = Tcl_ListObjIndex(pDb->interp, objv[i+1], jj, &pTerm); + if( rc ) goto format_failed; + rc = Tcl_GetIntFromObj(pDb->interp, pTerm, &v); + if( v<(-QRF_MAX_WIDTH) ){ + v = -QRF_MAX_WIDTH; + }else if( v>QRF_MAX_WIDTH ){ + v = QRF_MAX_WIDTH; + } + qrf.aWidth[jj] = (short int)v; + } + i++; + }else if( strcmp(zArg,"-columnsep")==0 ){ + qrf.zColumnSep = Tcl_GetString(objv[i+1]); + i++; + }else if( strcmp(zArg,"-rowsep")==0 ){ + qrf.zRowSep = Tcl_GetString(objv[i+1]); + i++; + }else if( strcmp(zArg,"-tablename")==0 ){ + qrf.zTableName = Tcl_GetString(objv[i+1]); + i++; + }else if( strcmp(zArg,"-null")==0 ){ + qrf.zNull = Tcl_GetString(objv[i+1]); + i++; + }else if( strcmp(zArg,"-version")==0 ){ + /* Undocumented. Testing use only */ + qrf.iVersion = atoi(Tcl_GetString(objv[i+1])); + i++; + }else{ + Tcl_AppendResult(pDb->interp, "unknown option: ", zArg, (char*)0); + rc = TCL_ERROR; + goto format_failed; + } + } + while( zSql && zSql[0] ){ + SqlPreparedStmt *pStmt = 0; /* Next statement to run */ + char *zErr = 0; /* Error message from QRF */ + + rc = dbPrepareAndBind(pDb, zSql, &zSql, &pStmt); + if( rc ) goto format_failed; + if( pStmt==0 ) continue; + rc = sqlite3_format_query_result(pStmt->pStmt, &qrf, &zErr); + dbReleaseStmt(pDb, pStmt, 0); + if( rc ){ + Tcl_SetResult(pDb->interp, zErr, TCL_VOLATILE); + sqlite3_free(zErr); + rc = TCL_ERROR; + goto format_failed; + } + } + Tcl_SetResult(pDb->interp, zResult, TCL_VOLATILE); + rc = TCL_OK; + /* Fall through...*/ + +format_failed: + sqlite3_free(qrf.aWidth); + sqlite3_free(qrf.aAlign); + sqlite3_free(zResult); + return rc; + +#endif +} + /* ** The "sqlite" command below creates a new Tcl command for each ** connection it opens to an SQLite database. This routine is invoked @@ -2064,15 +2434,15 @@ static int SQLITE_TCLAPI DbObjCmd( "commit_hook", "complete", "config", "copy", "deserialize", "enable_load_extension", "errorcode", "erroroffset", "eval", - "exists", "function", "incrblob", - "interrupt", "last_insert_rowid", "nullvalue", - "onecolumn", "preupdate", "profile", - "progress", "rekey", "restore", - "rollback_hook", "serialize", "status", - "timeout", "total_changes", "trace", - "trace_v2", "transaction", "unlock_notify", - "update_hook", "version", "wal_hook", - 0 + "exists", "format", "function", + "incrblob", "interrupt", "last_insert_rowid", + "nullvalue", "onecolumn", "preupdate", + "profile", "progress", "rekey", + "restore", "rollback_hook", "serialize", + "status", "timeout", "total_changes", + "trace", "trace_v2", "transaction", + "unlock_notify", "update_hook", "version", + "wal_hook", 0 }; enum DB_enum { DB_AUTHORIZER, DB_BACKUP, DB_BIND_FALLBACK, @@ -2081,14 +2451,15 @@ static int SQLITE_TCLAPI DbObjCmd( DB_COMMIT_HOOK, DB_COMPLETE, DB_CONFIG, DB_COPY, DB_DESERIALIZE, DB_ENABLE_LOAD_EXTENSION, DB_ERRORCODE, DB_ERROROFFSET, DB_EVAL, - DB_EXISTS, DB_FUNCTION, DB_INCRBLOB, - DB_INTERRUPT, DB_LAST_INSERT_ROWID, DB_NULLVALUE, - DB_ONECOLUMN, DB_PREUPDATE, DB_PROFILE, - DB_PROGRESS, DB_REKEY, DB_RESTORE, - DB_ROLLBACK_HOOK, DB_SERIALIZE, DB_STATUS, - DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE, - DB_TRACE_V2, DB_TRANSACTION, DB_UNLOCK_NOTIFY, - DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK, + DB_EXISTS, DB_FORMAT, DB_FUNCTION, + DB_INCRBLOB, DB_INTERRUPT, DB_LAST_INSERT_ROWID, + DB_NULLVALUE, DB_ONECOLUMN, DB_PREUPDATE, + DB_PROFILE, DB_PROGRESS, DB_REKEY, + DB_RESTORE, DB_ROLLBACK_HOOK, DB_SERIALIZE, + DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES, + DB_TRACE, DB_TRACE_V2, DB_TRANSACTION, + DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, DB_VERSION, + DB_WAL_HOOK }; /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */ @@ -2978,6 +3349,18 @@ static int SQLITE_TCLAPI DbObjCmd( break; } + /* + ** $db format [OPTIONS] SQL + ** + ** Run the SQL statement(s) given as the final argument. Use the + ** Query Result Formatter extension of SQLite to format the output as + ** text and return that text. + */ + case DB_FORMAT: { + rc = dbQrf(pDb, objc, objv); + break; + } + /* ** $db function NAME [OPTIONS] SCRIPT ** diff --git a/src/test1.c b/src/test1.c index f8e83dc42..3ca5c837a 100644 --- a/src/test1.c +++ b/src/test1.c @@ -4411,7 +4411,7 @@ static void delIntptr(void *p){ } /* -** bind_carray_intptr STMT IPARAM INT0 INT1 INT2... +** bind_carray_intptr STMT IPARAM INT-0 INT-1 INT-2... */ static int SQLITE_TCLAPI bind_carray_intptr( void * clientData, @@ -4455,6 +4455,7 @@ static int SQLITE_TCLAPI bind_carray_intptr( ** -malloc ** -transient ** -static +** -v2 ** -int32 ** -int64 ** -double @@ -4477,6 +4478,7 @@ static int SQLITE_TCLAPI test_carray_bind( void *aData = 0; int isTransient = 0; int isStatic = 0; + int isV2 = 0; int isMalloc = 0; /* True to use custom xDel function */ int idx; int i, j; @@ -4509,16 +4511,22 @@ static int SQLITE_TCLAPI test_carray_bind( const char *z = Tcl_GetString(objv[i]); if( strcmp(z, "-transient")==0 ){ isTransient = 1; + isStatic = isMalloc = 0; xDel = SQLITE_TRANSIENT; }else if( strcmp(z, "-static")==0 ){ isStatic = 1; + isMalloc = isTransient = 0; xDel = SQLITE_STATIC; }else if( strcmp(z, "-malloc")==0 ){ isMalloc = 1; + isStatic = isTransient = 0; xDel = testCarrayFree; }else + if( strcmp(z, "-v2")==0 ){ + isV2 = 1; + }else if( strcmp(z, "-int32")==0 ){ eType = 0; /* CARRAY_INT32 */ }else @@ -4687,7 +4695,20 @@ static int SQLITE_TCLAPI test_carray_bind( if( rc==SQLITE_OK ){ if( mFlagsOverride==0 ) mFlagsOverride = eType; - rc = sqlite3_carray_bind(pStmt, idx, aData, nData, mFlagsOverride, xDel); + if( isV2 ){ + void *pDel; + if( xDel==testCarrayFree ){ + u8 *p2 = (u8*)aData; + pDel = (void*)&p2[-16]; + xDel = sqlite3_free; + }else{ + pDel = aData; + } + rc = sqlite3_carray_bind_v2(pStmt, idx, aData, nData, mFlagsOverride, + xDel, pDel); + }else{ + rc = sqlite3_carray_bind(pStmt, idx, aData, nData, mFlagsOverride, xDel); + } } if( isTransient ){ if( eType==3 && aData ){ @@ -7370,6 +7391,7 @@ static int SQLITE_TCLAPI test_limit( { "SQLITE_LIMIT_SQL_LENGTH", SQLITE_LIMIT_SQL_LENGTH }, { "SQLITE_LIMIT_COLUMN", SQLITE_LIMIT_COLUMN }, { "SQLITE_LIMIT_EXPR_DEPTH", SQLITE_LIMIT_EXPR_DEPTH }, + { "SQLITE_LIMIT_PARSER_DEPTH", SQLITE_LIMIT_PARSER_DEPTH }, { "SQLITE_LIMIT_COMPOUND_SELECT", SQLITE_LIMIT_COMPOUND_SELECT }, { "SQLITE_LIMIT_VDBE_OP", SQLITE_LIMIT_VDBE_OP }, { "SQLITE_LIMIT_FUNCTION_ARG", SQLITE_LIMIT_FUNCTION_ARG }, @@ -7381,7 +7403,7 @@ static int SQLITE_TCLAPI test_limit( /* Out of range test cases */ { "SQLITE_LIMIT_TOOSMALL", -1, }, - { "SQLITE_LIMIT_TOOBIG", SQLITE_LIMIT_WORKER_THREADS+1 }, + { "SQLITE_LIMIT_TOOBIG", SQLITE_LIMIT_PARSER_DEPTH+1 }, }; int i, id = 0; int val; @@ -7675,7 +7697,8 @@ static int SQLITE_TCLAPI test_wal_checkpoint_v2( int nCkpt = -555; Tcl_Obj *pRet; - const char * aMode[] = { "passive", "full", "restart", "truncate", 0 }; + const char * aMode[] = {"noop", "passive", "full", "restart", "truncate", 0}; + assert( SQLITE_CHECKPOINT_NOOP==-1 ); assert( SQLITE_CHECKPOINT_PASSIVE==0 ); assert( SQLITE_CHECKPOINT_FULL==1 ); assert( SQLITE_CHECKPOINT_RESTART==2 ); @@ -7689,12 +7712,15 @@ static int SQLITE_TCLAPI test_wal_checkpoint_v2( if( objc==4 ){ zDb = Tcl_GetString(objv[3]); } - if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) || ( - TCL_OK!=Tcl_GetIntFromObj(0, objv[2], &eMode) - && TCL_OK!=Tcl_GetIndexFromObj(interp, objv[2], aMode, "mode", 0, &eMode) - )){ + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ return TCL_ERROR; } + if( TCL_OK!=Tcl_GetIntFromObj(0, objv[2], &eMode) ){ + if( TCL_OK!=Tcl_GetIndexFromObj(interp, objv[2], aMode, "mode", 0,&eMode) ){ + return TCL_ERROR; + } + eMode = eMode - 1; + } rc = sqlite3_wal_checkpoint_v2(db, zDb, eMode, &nLog, &nCkpt); if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ @@ -8618,6 +8644,7 @@ static int SQLITE_TCLAPI test_sqlite3_db_config( { "ATTACH_CREATE", SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE }, { "ATTACH_WRITE", SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE }, { "COMMENTS", SQLITE_DBCONFIG_ENABLE_COMMENTS }, + { "FP_DIGITS", SQLITE_DBCONFIG_FP_DIGITS }, }; int i; int v = 0; diff --git a/src/test_bestindex.c b/src/test_bestindex.c index f6b5db0fb..963abfec0 100644 --- a/src/test_bestindex.c +++ b/src/test_bestindex.c @@ -828,7 +828,6 @@ static int tclUpdate( tcl_vtab *pTab = (tcl_vtab*)tab; Tcl_Interp *interp = pTab->interp; Tcl_Obj *pEval = Tcl_DuplicateObj(pTab->pCmd); - Tcl_Obj *pRes = 0; int rc = TCL_OK; Tcl_IncrRefCount(pEval); diff --git a/src/test_config.c b/src/test_config.c index 3dbef3c9a..bebf8625a 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -82,7 +82,7 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options","configslower","1.0",TCL_GLOBAL_ONLY); #endif -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE Tcl_SetVar2(interp, "sqlite_options", "curdir", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "curdir", "0", TCL_GLOBAL_ONLY); @@ -94,12 +94,6 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "win32malloc", "0", TCL_GLOBAL_ONLY); #endif -#if defined(SQLITE_OS_WINRT) && SQLITE_OS_WINRT - Tcl_SetVar2(interp, "sqlite_options", "winrt", "1", TCL_GLOBAL_ONLY); -#else - Tcl_SetVar2(interp, "sqlite_options", "winrt", "0", TCL_GLOBAL_ONLY); -#endif - #ifdef SQLITE_DEBUG Tcl_SetVar2(interp, "sqlite_options", "debug", "1", TCL_GLOBAL_ONLY); #else @@ -685,6 +679,14 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "sqlite_options", "trace", "1", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_THREAD_MISUSE_WARNINGS + Tcl_SetVar2(interp, "sqlite_options", "thread_misuse_warnings", + "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "thread_misuse_warnings", + "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_OMIT_TRIGGER Tcl_SetVar2(interp, "sqlite_options", "trigger", "0", TCL_GLOBAL_ONLY); #else diff --git a/src/test_quota.c b/src/test_quota.c index d2f9cddd1..3eeacc7e7 100644 --- a/src/test_quota.c +++ b/src/test_quota.c @@ -387,11 +387,7 @@ static char *quota_utf8_to_mbcs(const char *zUtf8){ zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) ); if( zTmpWide==0 ) return 0; MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide); -#ifdef SQLITE_OS_WINRT - codepage = CP_ACP; -#else codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; -#endif nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0); zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0; if( zMbcs ){ diff --git a/src/tokenize.c b/src/tokenize.c index 152ada64f..884d1acb8 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -508,7 +508,7 @@ i64 sqlite3GetToken(const unsigned char *z, int *tokenType){ } case CC_DOLLAR: case CC_VARALPHA: { - int n = 0; + i64 n = 0; testcase( z[0]=='$' ); testcase( z[0]=='@' ); testcase( z[0]==':' ); testcase( z[0]=='#' ); *tokenType = TK_VARIABLE; @@ -604,7 +604,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql){ int tokenType; /* type of the next token */ int lastTokenParsed = -1; /* type of the previous token */ sqlite3 *db = pParse->db; /* The database connection */ - int mxSqlLen; /* Max length of an SQL string */ + i64 mxSqlLen; /* Max length of an SQL string */ Parse *pParentParse = 0; /* Outer parse context, if any */ #ifdef sqlite3Parser_ENGINEALWAYSONSTACK yyParser sEngine; /* Space to hold the Lemon-generated Parser object */ diff --git a/src/treeview.c b/src/treeview.c index 153fec88d..b482e1581 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -1300,7 +1300,13 @@ void sqlite3TreeViewTrigger( void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } -void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); } +void sqlite3ShowSrcList(const SrcList *p){ + TreeView *pView = 0; + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, "SRCLIST"); + sqlite3TreeViewSrcList(pView,p); + sqlite3TreeViewPop(&pView); +} void sqlite3ShowSelect(const Select *p){ sqlite3TreeViewSelect(0,p,0); } void sqlite3ShowWith(const With *p){ sqlite3TreeViewWith(0,p,0); } void sqlite3ShowUpsert(const Upsert *p){ sqlite3TreeViewUpsert(0,p,0); } diff --git a/src/trigger.c b/src/trigger.c index 799fbe57f..4f9068ad8 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -26,7 +26,7 @@ void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){ sqlite3SelectDelete(db, pTmp->pSelect); sqlite3IdListDelete(db, pTmp->pIdList); sqlite3UpsertDelete(db, pTmp->pUpsert); - sqlite3SrcListDelete(db, pTmp->pFrom); + sqlite3SrcListDelete(db, pTmp->pSrc); sqlite3DbFree(db, pTmp->zSpan); sqlite3DbFree(db, pTmp); @@ -215,11 +215,16 @@ void sqlite3BeginTrigger( } } + /* NB: The SQLITE_ALLOW_TRIGGERS_ON_SYSTEM_TABLES compile-time option is + ** experimental and unsupported. Do not use it unless understand the + ** implications and you cannot get by without this capability. */ +#if !defined(SQLITE_ALLOW_TRIGGERS_ON_SYSTEM_TABLES) /* Experimental */ /* Do not create a trigger on a system table */ if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ sqlite3ErrorMsg(pParse, "cannot create trigger on system table"); goto trigger_cleanup; } +#endif /* INSTEAD of triggers are only for views and views only support INSTEAD ** of triggers. @@ -331,6 +336,7 @@ void sqlite3FinishTrigger( if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup; zName = pTrig->zName; iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema); + assert( iDb>=00 && iDb<db->nDb ); pTrig->step_list = pStepList; while( pStepList ){ pStepList->pTrig = pTrig; @@ -365,12 +371,12 @@ void sqlite3FinishTrigger( if( sqlite3ReadOnlyShadowTables(db) ){ TriggerStep *pStep; for(pStep=pTrig->step_list; pStep; pStep=pStep->pNext){ - if( pStep->zTarget!=0 - && sqlite3ShadowTableName(db, pStep->zTarget) + if( pStep->pSrc!=0 + && sqlite3ShadowTableName(db, pStep->pSrc->a[0].zName) ){ sqlite3ErrorMsg(pParse, "trigger \"%s\" may not write to shadow table \"%s\"", - pTrig->zName, pStep->zTarget); + pTrig->zName, pStep->pSrc->a[0].zName); goto triggerfinish_cleanup; } } @@ -461,26 +467,39 @@ TriggerStep *sqlite3TriggerSelectStep( static TriggerStep *triggerStepAllocate( Parse *pParse, /* Parser context */ u8 op, /* Trigger opcode */ - Token *pName, /* The target name */ + SrcList *pTabList, /* Target table */ const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ ){ + Trigger *pNew = pParse->pNewTrigger; sqlite3 *db = pParse->db; - TriggerStep *pTriggerStep; + TriggerStep *pTriggerStep = 0; - if( pParse->nErr ) return 0; - pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1); - if( pTriggerStep ){ - char *z = (char*)&pTriggerStep[1]; - memcpy(z, pName->z, pName->n); - sqlite3Dequote(z); - pTriggerStep->zTarget = z; - pTriggerStep->op = op; - pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); - if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, pTriggerStep->zTarget, pName); + if( pParse->nErr==0 ){ + if( pNew + && pNew->pSchema!=db->aDb[1].pSchema + && pTabList->a[0].u4.zDatabase + ){ + sqlite3ErrorMsg(pParse, + "qualified table names are not allowed on INSERT, UPDATE, and DELETE " + "statements within triggers"); + }else{ + pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep)); + if( pTriggerStep ){ + pTriggerStep->pSrc = sqlite3SrcListDup(db, pTabList, EXPRDUP_REDUCE); + pTriggerStep->op = op; + pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); + if( pTriggerStep->pSrc && IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, + pTriggerStep->pSrc->a[0].zName, + pTabList->a[0].zName + ); + } + } } } + + sqlite3SrcListDelete(db, pTabList); return pTriggerStep; } @@ -493,7 +512,7 @@ static TriggerStep *triggerStepAllocate( */ TriggerStep *sqlite3TriggerInsertStep( Parse *pParse, /* Parser */ - Token *pTableName, /* Name of the table into which we insert */ + SrcList *pTabList, /* Table to INSERT into */ IdList *pColumn, /* List of columns in pTableName to insert into */ Select *pSelect, /* A SELECT statement that supplies values */ u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */ @@ -506,7 +525,7 @@ TriggerStep *sqlite3TriggerInsertStep( assert(pSelect != 0 || db->mallocFailed); - pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTableName,zStart,zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTabList, zStart, zEnd); if( pTriggerStep ){ if( IN_RENAME_OBJECT ){ pTriggerStep->pSelect = pSelect; @@ -538,7 +557,7 @@ TriggerStep *sqlite3TriggerInsertStep( */ TriggerStep *sqlite3TriggerUpdateStep( Parse *pParse, /* Parser */ - Token *pTableName, /* Name of the table to be updated */ + SrcList *pTabList, /* Name of the table to be updated */ SrcList *pFrom, /* FROM clause for an UPDATE-FROM, or NULL */ ExprList *pEList, /* The SET clause: list of column and new values */ Expr *pWhere, /* The WHERE clause */ @@ -549,21 +568,36 @@ TriggerStep *sqlite3TriggerUpdateStep( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTableName,zStart,zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTabList, zStart, zEnd); if( pTriggerStep ){ + SrcList *pFromDup = 0; if( IN_RENAME_OBJECT ){ pTriggerStep->pExprList = pEList; pTriggerStep->pWhere = pWhere; - pTriggerStep->pFrom = pFrom; + pFromDup = pFrom; pEList = 0; pWhere = 0; pFrom = 0; }else{ pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE); pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); - pTriggerStep->pFrom = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE); + pFromDup = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE); } pTriggerStep->orconf = orconf; + + if( pFromDup && !IN_RENAME_OBJECT){ + Select *pSub; + Token as = {0, 0}; + pSub = sqlite3SelectNew(pParse, 0, pFromDup, 0,0,0,0, SF_NestedFrom, 0); + pFromDup = sqlite3SrcListAppendFromTerm(pParse, 0, 0, 0, &as, pSub ,0); + } + if( pFromDup && pTriggerStep->pSrc ){ + pTriggerStep->pSrc = sqlite3SrcListAppendList( + pParse, pTriggerStep->pSrc, pFromDup + ); + }else{ + sqlite3SrcListDelete(db, pFromDup); + } } sqlite3ExprListDelete(db, pEList); sqlite3ExprDelete(db, pWhere); @@ -578,7 +612,7 @@ TriggerStep *sqlite3TriggerUpdateStep( */ TriggerStep *sqlite3TriggerDeleteStep( Parse *pParse, /* Parser */ - Token *pTableName, /* The table from which rows are deleted */ + SrcList *pTabList, /* The table from which rows are deleted */ Expr *pWhere, /* The WHERE clause */ const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ @@ -586,7 +620,7 @@ TriggerStep *sqlite3TriggerDeleteStep( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTableName,zStart,zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTabList, zStart, zEnd); if( pTriggerStep ){ if( IN_RENAME_OBJECT ){ pTriggerStep->pWhere = pWhere; @@ -786,6 +820,7 @@ static SQLITE_NOINLINE Trigger *triggersReallyExist( p = pList; if( (pParse->db->flags & SQLITE_EnableTrigger)==0 && pTab->pTrigger!=0 + && sqlite3SchemaToIndex(pParse->db, pTab->pTrigger->pSchema)!=1 ){ /* The SQLITE_DBCONFIG_ENABLE_TRIGGER setting is off. That means that ** only TEMP triggers are allowed. Truncate the pList so that it @@ -848,52 +883,6 @@ Trigger *sqlite3TriggersExist( return triggersReallyExist(pParse,pTab,op,pChanges,pMask); } -/* -** Convert the pStep->zTarget string into a SrcList and return a pointer -** to that SrcList. -** -** This routine adds a specific database name, if needed, to the target when -** forming the SrcList. This prevents a trigger in one database from -** referring to a target in another database. An exception is when the -** trigger is in TEMP in which case it can refer to any other database it -** wants. -*/ -SrcList *sqlite3TriggerStepSrc( - Parse *pParse, /* The parsing context */ - TriggerStep *pStep /* The trigger containing the target token */ -){ - sqlite3 *db = pParse->db; - SrcList *pSrc; /* SrcList to be returned */ - char *zName = sqlite3DbStrDup(db, pStep->zTarget); - pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); - assert( pSrc==0 || pSrc->nSrc==1 ); - assert( zName || pSrc==0 ); - if( pSrc ){ - Schema *pSchema = pStep->pTrig->pSchema; - pSrc->a[0].zName = zName; - if( pSchema!=db->aDb[1].pSchema ){ - assert( pSrc->a[0].fg.fixedSchema || pSrc->a[0].u4.zDatabase==0 ); - pSrc->a[0].u4.pSchema = pSchema; - pSrc->a[0].fg.fixedSchema = 1; - } - if( pStep->pFrom ){ - SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0); - if( pDup && pDup->nSrc>1 && !IN_RENAME_OBJECT ){ - Select *pSubquery; - Token as; - pSubquery = sqlite3SelectNew(pParse,0,pDup,0,0,0,0,SF_NestedFrom,0); - as.n = 0; - as.z = 0; - pDup = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); - } - pSrc = sqlite3SrcListAppendList(pParse, pSrc, pDup); - } - }else{ - sqlite3DbFree(db, zName); - } - return pSrc; -} - /* ** Return true if the pExpr term from the RETURNING clause argument ** list is of the form "*". Raise an error if the terms if of the @@ -1159,7 +1148,7 @@ static int codeTriggerProgram( switch( pStep->op ){ case TK_UPDATE: { sqlite3Update(pParse, - sqlite3TriggerStepSrc(pParse, pStep), + sqlite3SrcListDup(db, pStep->pSrc, 0), sqlite3ExprListDup(db, pStep->pExprList, 0), sqlite3ExprDup(db, pStep->pWhere, 0), pParse->eOrconf, 0, 0, 0 @@ -1169,7 +1158,7 @@ static int codeTriggerProgram( } case TK_INSERT: { sqlite3Insert(pParse, - sqlite3TriggerStepSrc(pParse, pStep), + sqlite3SrcListDup(db, pStep->pSrc, 0), sqlite3SelectDup(db, pStep->pSelect, 0), sqlite3IdListDup(db, pStep->pIdList), pParse->eOrconf, @@ -1180,7 +1169,7 @@ static int codeTriggerProgram( } case TK_DELETE: { sqlite3DeleteFrom(pParse, - sqlite3TriggerStepSrc(pParse, pStep), + sqlite3SrcListDup(db, pStep->pSrc, 0), sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0 ); sqlite3VdbeAddOp0(v, OP_ResetCount); diff --git a/src/util.c b/src/util.c index 8e4fd516e..071029173 100644 --- a/src/util.c +++ b/src/util.c @@ -458,48 +458,262 @@ u8 sqlite3StrIHash(const char *z){ return h; } -/* Double-Double multiplication. (x[0],x[1]) *= (y,yy) +/* +** Two inputs are multiplied to get a 128-bit result. Return +** the high-order 64 bits of that result. +*/ +static u64 sqlite3Multiply128(u64 a, u64 b){ +#if (defined(__GNUC__) || defined(__clang__)) \ + && (defined(__x86_64__) || defined(__aarch64__) || defined(__riscv)) + return ((__uint128_t)a * b) >> 64; +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(a, b); +#else + u64 a1 = (u32)a; + u64 a2 = a >> 32; + u64 b1 = (u32)b; + u64 b2 = b >> 32; + u64 p0 = a1 * b1; + u64 p1 = a1 * b2; + u64 p2 = a2 * b1; + u64 p3 = a2 * b2; + u64 carry = ((p0 >> 32) + (u32)p1 + (u32)p2) >> 32; + return p3 + (p1 >> 32) + (p2 >> 32) + carry; +#endif +} + +/* +** Return a u64 with the N-th bit set. +*/ +#define U64_BIT(N) (((u64)1)<<(N)) + +/* +** Range of powers of 10 that we need to deal with when converting +** IEEE754 doubles to and from decimal. +*/ +#define POWERSOF10_FIRST (-348) +#define POWERSOF10_LAST (+347) + +/* +** For any p between -348 and +347, return the integer part of +** +** pow(10,p) * pow(2,63-pow10to2(p)) +** +** Or, in other words, for any p in range, return the most significant +** 64 bits of pow(10,p). The pow(10,p) value is shifted left or right, +** as appropriate so the most significant 64 bits fit exactly into a +** 64-bit unsigned integer. ** -** Reference: -** T. J. Dekker, "A Floating-Point Technique for Extending the -** Available Precision". 1971-07-26. +** Algorithm: +** +** (1) For p between 0 and 26, return the value directly from the aBase[] +** lookup table. +** +** (2) For p outside the range 0 to 26, use aScale[] for the initial value +** then refine that result (if necessary) by a single multiplication +** against aBase[]. */ -static void dekkerMul2(volatile double *x, double y, double yy){ - /* - ** The "volatile" keywords on parameter x[] and on local variables - ** below are needed force intermediate results to be truncated to - ** binary64 rather than be carried around in an extended-precision - ** format. The truncation is necessary for the Dekker algorithm to - ** work. Intel x86 floating point might omit the truncation without - ** the use of volatile. - */ - volatile double tx, ty, p, q, c, cc; - double hx, hy; - u64 m; - memcpy(&m, (void*)&x[0], 8); - m &= 0xfffffffffc000000LL; - memcpy(&hx, &m, 8); - tx = x[0] - hx; - memcpy(&m, &y, 8); - m &= 0xfffffffffc000000LL; - memcpy(&hy, &m, 8); - ty = y - hy; - p = hx*hy; - q = hx*ty + tx*hy; - c = p+q; - cc = p - c + q + tx*ty; - cc = x[0]*yy + x[1]*y + cc; - x[0] = c + cc; - x[1] = c - x[0]; - x[1] += cc; +static u64 powerOfTen(int p){ + static const u64 aBase[] = { + 0x8000000000000000LLU, /* 0: 1.0e+0 << 63 */ + 0xa000000000000000LLU, /* 1: 1.0e+1 << 60 */ + 0xc800000000000000LLU, /* 2: 1.0e+2 << 57 */ + 0xfa00000000000000LLU, /* 3: 1.0e+3 << 54 */ + 0x9c40000000000000LLU, /* 4: 1.0e+4 << 50 */ + 0xc350000000000000LLU, /* 5: 1.0e+5 << 47 */ + 0xf424000000000000LLU, /* 6: 1.0e+6 << 44 */ + 0x9896800000000000LLU, /* 7: 1.0e+7 << 40 */ + 0xbebc200000000000LLU, /* 8: 1.0e+8 << 37 */ + 0xee6b280000000000LLU, /* 9: 1.0e+9 << 34 */ + 0x9502f90000000000LLU, /* 10: 1.0e+10 << 30 */ + 0xba43b74000000000LLU, /* 11: 1.0e+11 << 27 */ + 0xe8d4a51000000000LLU, /* 12: 1.0e+12 << 24 */ + 0x9184e72a00000000LLU, /* 13: 1.0e+13 << 20 */ + 0xb5e620f480000000LLU, /* 14: 1.0e+14 << 17 */ + 0xe35fa931a0000000LLU, /* 15: 1.0e+15 << 14 */ + 0x8e1bc9bf04000000LLU, /* 16: 1.0e+16 << 10 */ + 0xb1a2bc2ec5000000LLU, /* 17: 1.0e+17 << 7 */ + 0xde0b6b3a76400000LLU, /* 18: 1.0e+18 << 4 */ + 0x8ac7230489e80000LLU, /* 19: 1.0e+19 >> 0 */ + 0xad78ebc5ac620000LLU, /* 20: 1.0e+20 >> 3 */ + 0xd8d726b7177a8000LLU, /* 21: 1.0e+21 >> 6 */ + 0x878678326eac9000LLU, /* 22: 1.0e+22 >> 10 */ + 0xa968163f0a57b400LLU, /* 23: 1.0e+23 >> 13 */ + 0xd3c21bcecceda100LLU, /* 24: 1.0e+24 >> 16 */ + 0x84595161401484a0LLU, /* 25: 1.0e+25 >> 20 */ + 0xa56fa5b99019a5c8LLU, /* 26: 1.0e+26 >> 23 */ + }; + static const u64 aScale[] = { + 0x8049a4ac0c5811aeLLU, /* 0: 1.0e-351 << 1229 */ + 0xcf42894a5dce35eaLLU, /* 1: 1.0e-324 << 1140 */ + 0xa76c582338ed2622LLU, /* 2: 1.0e-297 << 1050 */ + 0x873e4f75e2224e68LLU, /* 3: 1.0e-270 << 960 */ + 0xda7f5bf590966849LLU, /* 4: 1.0e-243 << 871 */ + 0xb080392cc4349dedLLU, /* 5: 1.0e-216 << 781 */ + 0x8e938662882af53eLLU, /* 6: 1.0e-189 << 691 */ + 0xe65829b3046b0afaLLU, /* 7: 1.0e-162 << 602 */ + 0xba121a4650e4ddecLLU, /* 8: 1.0e-135 << 512 */ + 0x964e858c91ba2655LLU, /* 9: 1.0e-108 << 422 */ + 0xf2d56790ab41c2a3LLU, /* 10: 1.0e-81 << 333 */ + 0xc428d05aa4751e4dLLU, /* 11: 1.0e-54 << 243 */ + 0x9e74d1b791e07e48LLU, /* 12: 1.0e-27 << 153 */ + 0x8000000000000000LLU, /* 13: 1.0e+0 << 63 */ + 0xcecb8f27f4200f3aLLU, /* 14: 1.0e+27 >> 26 */ + 0xa70c3c40a64e6c52LLU, /* 15: 1.0e+54 >> 116 */ + 0x86f0ac99b4e8dafdLLU, /* 16: 1.0e+81 >> 206 */ + 0xda01ee641a708deaLLU, /* 17: 1.0e+108 >> 295 */ + 0xb01ae745b101e9e4LLU, /* 18: 1.0e+135 >> 385 */ + 0x8e41ade9fbebc27dLLU, /* 19: 1.0e+162 >> 475 */ + 0xe5d3ef282a242e82LLU, /* 20: 1.0e+189 >> 564 */ + 0xb9a74a0637ce2ee1LLU, /* 21: 1.0e+216 >> 654 */ + 0x95f83d0a1fb69cd9LLU, /* 22: 1.0e+243 >> 744 */ + 0xf24a01a73cf2dcd0LLU, /* 23: 1.0e+270 >> 833 */ + 0xc3b8358109e84f07LLU, /* 24: 1.0e+297 >> 923 */ + 0x9e19db92b4e31ba9LLU, /* 25: 1.0e+324 >> 1013 */ + }; + int g, n; + u64 x, y; + + assert( p>=POWERSOF10_FIRST && p<=POWERSOF10_LAST ); + if( p<0 ){ + g = p/27; + n = p%27; + if( n ){ + g--; + n += 27; + } + }else if( p<27 ){ + return aBase[p]; + }else{ + g = p/27; + n = p%27; + } + y = aScale[g+13]; + if( n==0 ){ + return y; + } + x = sqlite3Multiply128(aBase[n],y); + if( (U64_BIT(63) & x)==0 ){ + x = (x<<1)|1; + } + return x; +} + +/* +** pow10to2(x) computes floor(log2(pow(10,x))). +** pow2to10(y) computes floor(log10(pow(2,y))). +** +** Conceptually, pow10to2(p) converts a base-10 exponent p into +** a corresponding base-2 exponent, and pow2to10(e) converts a base-2 +** exponent into a base-10 exponent. +** +** The conversions are based on the observation that: +** +** ln(10.0)/ln(2.0) == 108853/32768 (approximately) +** ln(2.0)/ln(10.0) == 78913/262144 (approximately) +** +** These ratios are approximate, but they are accurate to 5 digits, +** which is close enough for the usage here. Right-shift is used +** for division so that rounding of negative numbers happens in the +** right direction. +*/ +static int pwr10to2(int p){ return (p*108853) >> 15; } +static int pwr2to10(int p){ return (p*78913) >> 18; } + +/* +** Count leading zeros for a 64-bit unsigned integer. +*/ +static int countLeadingZeros(u64 m){ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_clzll(m); +#else + int n = 0; + if( m <= 0x00000000ffffffffULL) { n += 32; m <<= 32; } + if( m <= 0x0000ffffffffffffULL) { n += 16; m <<= 16; } + if( m <= 0x00ffffffffffffffULL) { n += 8; m <<= 8; } + if( m <= 0x0fffffffffffffffULL) { n += 4; m <<= 4; } + if( m <= 0x3fffffffffffffffULL) { n += 2; m <<= 2; } + if( m <= 0x7fffffffffffffffULL) { n += 1; } + return n; +#endif +} + +/* +** Given m and e, which represent a quantity r == m*pow(2,e), +** return values *pD and *pP such that r == (*pD)*pow(10,*pP), +** approximately. *pD should contain at least n significant digits. +** +** The input m is required to have its highest bit set. In other words, +** m should be left-shifted, and e decremented, to maximize the value of m. +*/ +static void sqlite3Fp2Convert10(u64 m, int e, int n, u64 *pD, int *pP){ + int p; + u64 h; + assert( n>=1 && n<=18 ); + p = n - 1 - pwr2to10(e+63); + h = sqlite3Multiply128(m, powerOfTen(p)); + assert( -(e + pwr10to2(p) + 2) >= 0 ); + assert( -(e + pwr10to2(p) + 1) <= 63 ); + if( n==18 ){ + h >>= -(e + pwr10to2(p) + 2); + *pD = (h + ((h<<1)&2))>>1; + }else{ + *pD = h >> -(e + pwr10to2(p) + 1); + } + *pP = -p; +} + +/* +** Return an IEEE754 floating point value that approximates d*pow(10,p). +*/ +static double sqlite3Fp10Convert2(u64 d, int p){ + u64 out; + int e1; + int lz; + int lp; + int x; + u64 h; + double r; + assert( (d & U64_BIT(63))==0 ); + assert( d!=0 ); + if( p<POWERSOF10_FIRST ){ + return 0.0; + } + if( p>POWERSOF10_LAST ){ + return INFINITY; + } + lz = countLeadingZeros(d); + lp = pwr10to2(p); + e1 = lz - (lp + 11); + if( e1>1074 ){ + if( e1>=1130 ) return 0.0; + e1 = 1074; + } + h = sqlite3Multiply128(d<<lz, powerOfTen(p)); + x = lz - (e1 + lp + 3); + assert( x >= 0 ); + assert( x <= 63 ); + out = h >> x; + if( out >= U64_BIT(55)-2 ){ + out >>= 1; + e1--; + } + if( e1<=(-972) ){ + return INFINITY; + } + out = (out + 2) >> 2; + if( (out & U64_BIT(52))!=0 ){ + out = (out & ~U64_BIT(52)) | ((u64)(1075-e1)<<52); + } + memcpy(&r, &out, 8); + return r; } /* ** The string z[] is an text representation of a real number. ** Convert this string to a double and write it into *pResult. ** -** The string z[] is length bytes in length (bytes, not characters) and -** uses the encoding enc. The string is not necessarily zero-terminated. +** z[] must be UTF-8 and zero-terminated. ** ** Return TRUE if the result is a valid real number (or integer) and FALSE ** if the string is empty or contains extraneous text. More specifically @@ -526,198 +740,131 @@ static void dekkerMul2(volatile double *x, double y, double yy){ #if defined(_MSC_VER) #pragma warning(disable : 4756) #endif -int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ +int sqlite3AtoF(const char *z, double *pResult){ #ifndef SQLITE_OMIT_FLOATING_POINT - int incr; - const char *zEnd; /* sign * significand * (10 ^ (esign * exponent)) */ - int sign = 1; /* sign of significand */ - u64 s = 0; /* significand */ - int d = 0; /* adjust exponent for shifting decimal point */ - int esign = 1; /* sign of exponent */ - int e = 0; /* exponent */ - int eValid = 1; /* True exponent is either not used or is well-formed */ + int neg = 0; /* True for a negative value */ + u64 s = 0; /* mantissa */ + int d = 0; /* Value is s * pow(10,d) */ int nDigit = 0; /* Number of digits processed */ - int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ - u64 s2; /* round-tripped significand */ - double rr[2]; + int eType = 1; /* 1: pure integer, 2+: fractional */ - assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); *pResult = 0.0; /* Default return value, in case of an error */ - if( length==0 ) return 0; - - if( enc==SQLITE_UTF8 ){ - incr = 1; - zEnd = z + length; - }else{ - int i; - incr = 2; - length &= ~1; - assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); - testcase( enc==SQLITE_UTF16LE ); - testcase( enc==SQLITE_UTF16BE ); - for(i=3-enc; i<length && z[i]==0; i+=2){} - if( i<length ) eType = -100; - zEnd = &z[i^1]; - z += (enc&1); - } /* skip leading spaces */ - while( z<zEnd && sqlite3Isspace(*z) ) z+=incr; - if( z>=zEnd ) return 0; + while( sqlite3Isspace(*z) ) z++; /* get sign of significand */ if( *z=='-' ){ - sign = -1; - z+=incr; + neg = 1; + z++; }else if( *z=='+' ){ - z+=incr; + z++; } /* copy max significant digits to significand */ - while( z<zEnd && sqlite3Isdigit(*z) ){ + while( sqlite3Isdigit(*z) ){ s = s*10 + (*z - '0'); - z+=incr; nDigit++; - if( s>=((LARGEST_UINT64-9)/10) ){ + z++; nDigit++; + if( s>=((LARGEST_INT64-9)/10) ){ /* skip non-significant significand digits ** (increase exponent by d to shift decimal left) */ - while( z<zEnd && sqlite3Isdigit(*z) ){ z+=incr; d++; } + while( sqlite3Isdigit(*z) ){ z++; d++; } } } - if( z>=zEnd ) goto do_atof_calc; /* if decimal point is present */ if( *z=='.' ){ - z+=incr; + z++; eType++; /* copy digits from after decimal to significand ** (decrease exponent by d to shift decimal right) */ - while( z<zEnd && sqlite3Isdigit(*z) ){ - if( s<((LARGEST_UINT64-9)/10) ){ + while( sqlite3Isdigit(*z) ){ + if( s<((LARGEST_INT64-9)/10) ){ s = s*10 + (*z - '0'); d--; nDigit++; } - z+=incr; + z++; } } - if( z>=zEnd ) goto do_atof_calc; /* if exponent is present */ if( *z=='e' || *z=='E' ){ - z+=incr; - eValid = 0; + int esign = 1; /* sign of exponent */ + z++; eType++; - /* This branch is needed to avoid a (harmless) buffer overread. The - ** special comment alerts the mutation tester that the correct answer - ** is obtained even if the branch is omitted */ - if( z>=zEnd ) goto do_atof_calc; /*PREVENTS-HARMLESS-OVERREAD*/ - /* get sign of exponent */ if( *z=='-' ){ esign = -1; - z+=incr; + z++; }else if( *z=='+' ){ - z+=incr; + z++; } /* copy digits to exponent */ - while( z<zEnd && sqlite3Isdigit(*z) ){ - e = e<10000 ? (e*10 + (*z - '0')) : 10000; - z+=incr; - eValid = 1; + if( sqlite3Isdigit(*z) ){ + int exp = *z - '0'; + z++; + while( sqlite3Isdigit(*z) ){ + exp = exp<10000 ? (exp*10 + (*z - '0')) : 10000; + z++; + } + d += esign*exp; + }else{ + eType = -1; } } /* skip trailing spaces */ - while( z<zEnd && sqlite3Isspace(*z) ) z+=incr; + while( sqlite3Isspace(*z) ) z++; -do_atof_calc: /* Zero is a special case */ if( s==0 ){ - *pResult = sign<0 ? -0.0 : +0.0; - goto atof_return; - } - - /* adjust exponent by d, and update sign */ - e = (e*esign) + d; - - /* Try to adjust the exponent to make it smaller */ - while( e>0 && s<((LARGEST_UINT64-0x7ff)/10) ){ - s *= 10; - e--; - } - while( e<0 && (s%10)==0 ){ - s /= 10; - e++; - } - - rr[0] = (double)s; - assert( sizeof(s2)==sizeof(rr[0]) ); -#ifdef SQLITE_DEBUG - rr[1] = 18446744073709549568.0; - memcpy(&s2, &rr[1], sizeof(s2)); - assert( s2==0x43efffffffffffffLL ); -#endif - /* Largest double that can be safely converted to u64 - ** vvvvvvvvvvvvvvvvvvvvvv */ - if( rr[0]<=18446744073709549568.0 ){ - s2 = (u64)rr[0]; - rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); - }else{ - rr[1] = 0.0; - } - assert( rr[1]<=1.0e-10*rr[0] ); /* Equal only when rr[0]==0.0 */ - - if( e>0 ){ - while( e>=100 ){ - e -= 100; - dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); - } - while( e>=10 ){ - e -= 10; - dekkerMul2(rr, 1.0e+10, 0.0); - } - while( e>=1 ){ - e -= 1; - dekkerMul2(rr, 1.0e+01, 0.0); - } + *pResult = neg ? -0.0 : +0.0; }else{ - while( e<=-100 ){ - e += 100; - dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); - } - while( e<=-10 ){ - e += 10; - dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); - } - while( e<=-1 ){ - e += 1; - dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); - } + *pResult = sqlite3Fp10Convert2(s,d); + if( neg ) *pResult = -*pResult; + assert( !sqlite3IsNaN(*pResult) ); } - *pResult = rr[0]+rr[1]; - if( sqlite3IsNaN(*pResult) ) *pResult = 1e300*1e300; - if( sign<0 ) *pResult = -*pResult; - assert( !sqlite3IsNaN(*pResult) ); -atof_return: /* return true if number and no extra non-whitespace characters after */ - if( z==zEnd && nDigit>0 && eValid && eType>0 ){ + if( z[0]==0 && nDigit>0 ){ return eType; - }else if( eType>=2 && (eType==3 || eValid) && nDigit>0 ){ + }else if( eType>=2 && nDigit>0 ){ return -1; }else{ return 0; } #else - return !sqlite3Atoi64(z, pResult, length, enc); + return !sqlite3Atoi64(z, pResult, strlen(z), SQLITE_UTF8); #endif /* SQLITE_OMIT_FLOATING_POINT */ } #if defined(_MSC_VER) #pragma warning(default : 4756) #endif +/* +** Digit pairs used to convert a U64 or I64 into text, two digits +** at a time. +*/ +static const union { + char a[201]; + short int forceAlignment; +} sqlite3DigitPairs = { + "00010203040506070809" + "10111213141516171819" + "20212223242526272829" + "30313233343536373839" + "40414243444546474849" + "50515253545556575859" + "60616263646566676869" + "70717273747576777879" + "80818283848586878889" + "90919293949596979899" +}; + + /* ** Render an signed 64-bit integer as text. Store the result in zOut[] and ** return the length of the string that was stored, in bytes. The value @@ -729,23 +876,35 @@ int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ int sqlite3Int64ToText(i64 v, char *zOut){ int i; u64 x; - char zTemp[22]; - if( v<0 ){ - x = (v==SMALLEST_INT64) ? ((u64)1)<<63 : (u64)-v; - }else{ + union { + char a[23]; + u16 forceAlignment; + } u; + if( v>0 ){ x = v; + }else if( v==0 ){ + zOut[0] = '0'; + zOut[1] = 0; + return 1; + }else{ + x = (v==SMALLEST_INT64) ? ((u64)1)<<63 : (u64)-v; } - i = sizeof(zTemp)-2; - zTemp[sizeof(zTemp)-1] = 0; - while( 1 /*exit-by-break*/ ){ - zTemp[i] = (x%10) + '0'; - x = x/10; - if( x==0 ) break; - i--; - }; - if( v<0 ) zTemp[--i] = '-'; - memcpy(zOut, &zTemp[i], sizeof(zTemp)-i); - return sizeof(zTemp)-1-i; + i = sizeof(u.a)-1; + u.a[i] = 0; + while( x>=10 ){ + int kk = (x%100)*2; + assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[kk]) ); + assert( TWO_BYTE_ALIGNMENT(&u.a[i-2]) ); + *(u16*)(&u.a[i-2]) = *(u16*)&sqlite3DigitPairs.a[kk]; + i -= 2; + x /= 100; + } + if( x ){ + u.a[--i] = x + '0'; + } + if( v<0 ) u.a[--i] = '-'; + memcpy(zOut, &u.a[i], sizeof(u.a)-i); + return sizeof(u.a)-1-i; } /* @@ -1002,7 +1161,7 @@ int sqlite3Atoi(const char *z){ ** representation. ** ** If iRound<=0 then round to -iRound significant digits to the -** the left of the decimal point, or to a maximum of mxRound total +** the right of the decimal point, or to a maximum of mxRound total ** significant digits. ** ** If iRound>0 round to min(iRound,mxRound) significant digits total. @@ -1015,13 +1174,14 @@ int sqlite3Atoi(const char *z){ ** The p->z[] array is *not* zero-terminated. */ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ - int i; - u64 v; - int e, exp = 0; - double rr[2]; + int i; /* Index into zBuf[] where to put next character */ + int n; /* Number of digits */ + u64 v; /* mantissa */ + int e, exp = 0; /* Base-2 and base-10 exponent */ + char *zBuf; /* Local alias for p->zBuf */ + char *z; /* Local alias for p->z */ p->isSpecial = 0; - p->z = p->zBuf; assert( mxRound>0 ); /* Convert negative numbers to positive. Deal with Infinity, 0.0, and @@ -1039,78 +1199,94 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ p->sign = '+'; } memcpy(&v,&r,8); - e = v>>52; - if( (e&0x7ff)==0x7ff ){ + e = (v>>52)&0x7ff; + if( e==0x7ff ){ p->isSpecial = 1 + (v!=0x7ff0000000000000LL); p->n = 0; p->iDP = 0; + p->z = p->zBuf; return; } - - /* Multiply r by powers of ten until it lands somewhere in between - ** 1.0e+19 and 1.0e+17. - ** - ** Use Dekker-style double-double computation to increase the - ** precision. - ** - ** The error terms on constants like 1.0e+100 computed using the - ** decimal extension, for example as follows: - ** - ** SELECT decimal_exp(decimal_sub('1.0e+100',decimal(1.0e+100))); - */ - rr[0] = r; - rr[1] = 0.0; - if( rr[0]>9.223372036854774784e+18 ){ - while( rr[0]>9.223372036854774784e+118 ){ - exp += 100; - dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); - } - while( rr[0]>9.223372036854774784e+28 ){ - exp += 10; - dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); - } - while( rr[0]>9.223372036854774784e+18 ){ - exp += 1; - dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); - } + v &= 0x000fffffffffffffULL; + if( e==0 ){ + int nn = countLeadingZeros(v); + v <<= nn; + e = -1074 - nn; }else{ - while( rr[0]<9.223372036854774784e-83 ){ - exp -= 100; - dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); - } - while( rr[0]<9.223372036854774784e+07 ){ - exp -= 10; - dekkerMul2(rr, 1.0e+10, 0.0); - } - while( rr[0]<9.22337203685477478e+17 ){ - exp -= 1; - dekkerMul2(rr, 1.0e+01, 0.0); - } + v = (v<<11) | U64_BIT(63); + e -= 1086; } - v = rr[1]<0.0 ? (u64)rr[0]-(u64)(-rr[1]) : (u64)rr[0]+(u64)rr[1]; + sqlite3Fp2Convert10(v, e, (iRound<=0||iRound>=18)?18:iRound+1, &v, &exp); - /* Extract significant digits. */ + /* Extract significant digits, start at the right-most slot in p->zBuf + ** and working back to the right. "i" keeps track of the next slot in + ** which to store a digit. */ i = sizeof(p->zBuf)-1; + zBuf = p->zBuf; assert( v>0 ); - while( v ){ p->zBuf[i--] = (v%10) + '0'; v /= 10; } + while( v>=10 ){ + int kk = (v%100)*2; + assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[kk]) ); + assert( TWO_BYTE_ALIGNMENT(&zBuf[i-1]) ); + *(u16*)(&zBuf[i-1]) = *(u16*)&sqlite3DigitPairs.a[kk]; + i -= 2; + v /= 100; + } + if( v ){ + assert( v<10 ); + zBuf[i--] = v + '0'; + } assert( i>=0 && i<sizeof(p->zBuf)-1 ); - p->n = sizeof(p->zBuf) - 1 - i; - assert( p->n>0 ); - assert( p->n<sizeof(p->zBuf) ); - p->iDP = p->n + exp; + n = sizeof(p->zBuf) - 1 - i; /* Total number of digits extracted */ + assert( n>0 ); + assert( n<sizeof(p->zBuf) ); + testcase( n==sizeof(p->zBuf)-1 ); + p->iDP = n + exp; if( iRound<=0 ){ iRound = p->iDP - iRound; - if( iRound==0 && p->zBuf[i+1]>='5' ){ + if( iRound==0 && zBuf[i+1]>='5' ){ iRound = 1; - p->zBuf[i--] = '0'; - p->n++; + zBuf[i--] = '0'; + n++; p->iDP++; } } - if( iRound>0 && (iRound<p->n || p->n>mxRound) ){ - char *z = &p->zBuf[i+1]; + z = &zBuf[i+1]; /* z points to the first digit */ + if( iRound>0 && (iRound<n || n>mxRound) ){ if( iRound>mxRound ) iRound = mxRound; - p->n = iRound; + if( iRound==17 ){ + /* If the precision is exactly 17, which only happens with the "!" + ** flag (ex: "%!.17g") then try to reduce the precision if that + ** yields text that will round-trip to the original floating-point. + ** value. Thus, for exaple, 49.47 will render as 49.47, rather than + ** as 49.469999999999999. */ + if( z[15]=='9' && z[14]=='9' ){ + int jj, kk; + u64 v2; + for(jj=14; jj>0 && z[jj-1]=='9'; jj--){} + if( jj==0 ){ + v2 = 1; + }else{ + v2 = z[0] - '0'; + for(kk=1; kk<jj; kk++) v2 = (v2*10) + z[kk] - '0'; + v2++; + } + if( r==sqlite3Fp10Convert2(v2, exp + n - jj) ){ + iRound = jj+1; + } + }else if( p->iDP>=n || (z[15]=='0' && z[14]=='0' && z[13]=='0') ){ + int jj, kk; + u64 v2; + assert( z[0]!='0' ); + for(jj=14; z[jj-1]=='0'; jj--){} + v2 = z[0] - '0'; + for(kk=1; kk<jj; kk++) v2 = (v2*10) + z[kk] - '0'; + if( r==sqlite3Fp10Convert2(v2, exp + n - jj) ){ + iRound = jj+1; + } + } + } + n = iRound; if( z[iRound]>='5' ){ int j = iRound-1; while( 1 /*exit-by-break*/ ){ @@ -1118,8 +1294,9 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ if( z[j]<='9' ) break; z[j] = '0'; if( j==0 ){ - p->z[i--] = '1'; - p->n++; + z--; + z[0] = '1'; + n++; p->iDP++; break; }else{ @@ -1128,13 +1305,13 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ } } } - p->z = &p->zBuf[i+1]; - assert( i+p->n < sizeof(p->zBuf) ); - assert( p->n>0 ); - while( p->z[p->n-1]=='0' ){ - p->n--; - assert( p->n>0 ); + assert( n>0 ); + while( z[n-1]=='0' ){ + n--; + assert( n>0 ); } + p->n = n; + p->z = z; } /* diff --git a/src/vacuum.c b/src/vacuum.c index 1b4838040..70e62e1ef 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -230,9 +230,11 @@ SQLITE_NOINLINE int sqlite3RunVacuum( pDb = &db->aDb[nDb]; assert( strcmp(pDb->zDbSName,zDbVacuum)==0 ); pTemp = pDb->pBt; + nRes = sqlite3BtreeGetRequestedReserve(pMain); if( pOut ){ sqlite3_file *id = sqlite3PagerFile(sqlite3BtreePager(pTemp)); i64 sz = 0; + const char *zFilename; if( id->pMethods!=0 && (sqlite3OsFileSize(id, &sz)!=SQLITE_OK || sz>0) ){ rc = SQLITE_ERROR; sqlite3SetString(pzErrMsg, db, "output file already exists"); @@ -244,8 +246,16 @@ SQLITE_NOINLINE int sqlite3RunVacuum( ** they are for the database being vacuumed, except that PAGER_CACHESPILL ** is always set. */ pgflags = db->aDb[iDb].safety_level | (db->flags & PAGER_FLAGS_MASK); + + /* If the VACUUM INTO target file is a URI filename and if the + ** "reserve=N" query parameter is present, reset the reserve to the + ** amount specified, if the amount is within range */ + zFilename = sqlite3BtreeGetFilename(pTemp); + if( ALWAYS(zFilename) ){ + int nNew = (int)sqlite3_uri_int64(zFilename, "reserve", nRes); + if( nNew>=0 && nNew<=255 ) nRes = nNew; + } } - nRes = sqlite3BtreeGetRequestedReserve(pMain); sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size); sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0)); diff --git a/src/vdbe.c b/src/vdbe.c index b5a262e63..e2e98eb5f 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -353,10 +353,9 @@ static int alsoAnInt(Mem *pRec, double rValue, i64 *piValue){ */ static void applyNumericAffinity(Mem *pRec, int bTryForInt){ double rValue; - u8 enc = pRec->enc; int rc; assert( (pRec->flags & (MEM_Str|MEM_Int|MEM_Real|MEM_IntReal))==MEM_Str ); - rc = sqlite3AtoF(pRec->z, &rValue, pRec->n, enc); + rValue = sqlite3MemRealValueRC(pRec, &rc); if( rc<=0 ) return; if( rc==1 && alsoAnInt(pRec, rValue, &pRec->u.i) ){ pRec->flags |= MEM_Int; @@ -438,7 +437,10 @@ int sqlite3_value_numeric_type(sqlite3_value *pVal){ int eType = sqlite3_value_type(pVal); if( eType==SQLITE_TEXT ){ Mem *pMem = (Mem*)pVal; + assert( pMem->db!=0 ); + sqlite3_mutex_enter(pMem->db->mutex); applyNumericAffinity(pMem, 0); + sqlite3_mutex_leave(pMem->db->mutex); eType = sqlite3_value_type(pVal); } return eType; @@ -471,7 +473,7 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ pMem->u.i = 0; return MEM_Int; } - rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); + pMem->u.r = sqlite3MemRealValueRC(pMem, &rc); if( rc<=0 ){ if( rc==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1 ){ pMem->u.i = ix; @@ -6620,20 +6622,15 @@ case OP_SorterInsert: { /* in2 */ break; } -/* Opcode: IdxDelete P1 P2 P3 * P5 +/* Opcode: IdxDelete P1 P2 P3 * * ** Synopsis: key=r[P2@P3] ** ** The content of P3 registers starting at register P2 form ** an unpacked index key. This opcode removes that entry from the ** index opened by cursor P1. ** -** If P5 is not zero, then raise an SQLITE_CORRUPT_INDEX error -** if no matching index entry is found. This happens when running -** an UPDATE or DELETE statement and the index entry to be updated -** or deleted is not found. For some uses of IdxDelete -** (example: the EXCEPT operator) it does not matter that no matching -** entry is found. For those cases, P5 is zero. Also, do not raise -** this (self-correcting and non-critical) error if in writable_schema mode. +** Raise an SQLITE_CORRUPT_INDEX error if no matching index entry is found +** and not in writable_schema mode. */ case OP_IdxDelete: { VdbeCursor *pC; @@ -6659,7 +6656,7 @@ case OP_IdxDelete: { if( res==0 ){ rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE); if( rc ) goto abort_due_to_error; - }else if( pOp->p5 && !sqlite3WritableSchema(db) ){ + }else if( !sqlite3WritableSchema(db) ){ rc = sqlite3ReportError(SQLITE_CORRUPT_INDEX, __LINE__, "index corruption"); goto abort_due_to_error; } diff --git a/src/vdbe.h b/src/vdbe.h index 28df764bc..a2905eae4 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -186,7 +186,7 @@ typedef struct VdbeOpList VdbeOpList; ** Additional non-public SQLITE_PREPARE_* flags */ #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ -#define SQLITE_PREPARE_MASK 0x1f /* Mask of public flags */ +#define SQLITE_PREPARE_MASK 0x3f /* Mask of public flags */ /* ** Prototypes for the VDBE interface. See comments on the implementation diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 8b68c339a..320721d06 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -630,6 +630,7 @@ void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int); void sqlite3VdbeMemMove(Mem*, Mem*); int sqlite3VdbeMemNulTerminate(Mem*); int sqlite3VdbeMemSetStr(Mem*, const char*, i64, u8, void(*)(void*)); +int sqlite3VdbeMemSetText(Mem*, const char*, i64, void(*)(void*)); void sqlite3VdbeMemSetInt64(Mem*, i64); #ifdef SQLITE_OMIT_FLOATING_POINT # define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64 @@ -648,13 +649,14 @@ int sqlite3VdbeMemSetZeroBlob(Mem*,int); int sqlite3VdbeMemIsRowSet(const Mem*); #endif int sqlite3VdbeMemSetRowSet(Mem*); -void sqlite3VdbeMemZeroTerminateIfAble(Mem*); +int sqlite3VdbeMemZeroTerminateIfAble(Mem*); int sqlite3VdbeMemMakeWriteable(Mem*); int sqlite3VdbeMemStringify(Mem*, u8, u8); int sqlite3IntFloatCompare(i64,double); i64 sqlite3VdbeIntValue(const Mem*); int sqlite3VdbeMemIntegerify(Mem*); double sqlite3VdbeRealValue(Mem*); +SQLITE_NOINLINE double sqlite3MemRealValueRC(Mem*, int*); int sqlite3VdbeBooleanValue(Mem*, int ifNull); void sqlite3VdbeIntegerAffinity(Mem*); int sqlite3VdbeMemRealify(Mem*); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 1118481d1..9fd4715ce 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -392,7 +392,23 @@ static void setResultStrOrError( void (*xDel)(void*) /* Destructor function */ ){ Mem *pOut = pCtx->pOut; - int rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); + int rc; + if( enc==SQLITE_UTF8 ){ + rc = sqlite3VdbeMemSetText(pOut, z, n, xDel); + }else if( enc==SQLITE_UTF8_ZT ){ + /* It is usually considered improper to assert() on an input. However, + ** the following assert() is checking for inputs that are documented + ** to result in undefined behavior. */ + assert( z==0 + || n<0 + || n>pOut->db->aLimit[SQLITE_LIMIT_LENGTH] + || z[n]==0 + ); + rc = sqlite3VdbeMemSetText(pOut, z, n, xDel); + pOut->flags |= MEM_Term; + }else{ + rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); + } if( rc ){ if( rc==SQLITE_TOOBIG ){ sqlite3_result_error_toobig(pCtx); @@ -585,7 +601,7 @@ void sqlite3_result_text64( #endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); - if( enc!=SQLITE_UTF8 ){ + if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF8_ZT ){ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; n &= ~(u64)1; } @@ -736,6 +752,8 @@ static int doWalCallbacks(sqlite3 *db){ } } } +#else + UNUSED_PARAMETER(db); #endif return rc; } @@ -1043,7 +1061,7 @@ static int valueFromValueList( Mem sMem; /* Raw content of current row */ memset(&sMem, 0, sizeof(sMem)); sz = sqlite3BtreePayloadSize(pRhs->pCsr); - rc = sqlite3VdbeMemFromBtreeZeroOffset(pRhs->pCsr,(int)sz,&sMem); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pRhs->pCsr,sz,&sMem); if( rc==SQLITE_OK ){ u8 *zBuf = (u8*)sMem.z; u32 iSerial; @@ -1692,13 +1710,25 @@ static int bindText( assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ if( zData!=0 ){ pVar = &p->aVar[i-1]; - rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); - if( rc==SQLITE_OK ){ - if( encoding==0 ){ - pVar->enc = ENC(p->db); - }else{ - rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); - } + if( encoding==SQLITE_UTF8 ){ + rc = sqlite3VdbeMemSetText(pVar, zData, nData, xDel); + }else if( encoding==SQLITE_UTF8_ZT ){ + /* It is usually consider improper to assert() on an input. + ** However, the following assert() is checking for inputs + ** that are documented to result in undefined behavior. */ + assert( zData==0 + || nData<0 + || nData>pVar->db->aLimit[SQLITE_LIMIT_LENGTH] + || ((u8*)zData)[nData]==0 + ); + rc = sqlite3VdbeMemSetText(pVar, zData, nData, xDel); + pVar->flags |= MEM_Term; + }else{ + rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); + if( encoding==0 ) pVar->enc = ENC(p->db); + } + if( rc==SQLITE_OK && encoding!=0 ){ + rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); } if( rc ){ sqlite3Error(p->db, rc); @@ -1810,7 +1840,7 @@ int sqlite3_bind_text64( unsigned char enc ){ assert( xDel!=SQLITE_DYNAMIC ); - if( enc!=SQLITE_UTF8 ){ + if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF8_ZT ){ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; nData &= ~(u64)1; } diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 5368c0c42..603e85ddf 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -2901,7 +2901,7 @@ int sqlite3VdbeSetColName( } assert( p->aColName!=0 ); pColName = &(p->aColName[idx+var*p->nResAlloc]); - rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel); + rc = sqlite3VdbeMemSetText(pColName, zName, -1, xDel); assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 ); return rc; } diff --git a/src/vdbemem.c b/src/vdbemem.c index 2c4d1c568..5689cb755 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -107,21 +107,27 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ StrAccum acc; assert( p->flags & (MEM_Int|MEM_Real|MEM_IntReal) ); assert( sz>22 ); - if( p->flags & MEM_Int ){ -#if GCC_VERSION>=7000000 - /* Work-around for GCC bug - ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270 */ + if( p->flags & (MEM_Int|MEM_IntReal) ){ +#if GCC_VERSION>=7000000 && GCC_VERSION<15000000 && defined(__i386__) + /* Work-around for GCC bug or bugs: + ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270 + ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114659 + ** The problem appears to be fixed in GCC 15 */ i64 x; - assert( (p->flags&MEM_Int)*2==sizeof(x) ); - memcpy(&x, (char*)&p->u, (p->flags&MEM_Int)*2); + assert( (MEM_Str&~p->flags)*4==sizeof(x) ); + memcpy(&x, (char*)&p->u, (MEM_Str&~p->flags)*4); p->n = sqlite3Int64ToText(x, zBuf); #else p->n = sqlite3Int64ToText(p->u.i, zBuf); #endif + if( p->flags & MEM_IntReal ){ + memcpy(zBuf+p->n,".0", 3); + p->n += 2; + } }else{ sqlite3StrAccumInit(&acc, 0, zBuf, sz, 0); - sqlite3_str_appendf(&acc, "%!.15g", - (p->flags & MEM_IntReal)!=0 ? (double)p->u.i : p->u.r); + sqlite3_str_appendf(&acc, "%!.*g", + (p->db ? p->db->nFpDigit : 17), p->u.r); assert( acc.zText==zBuf && acc.mxAlloc<=0 ); zBuf[acc.nChar] = 0; /* Fast version of sqlite3StrAccumFinish(&acc) */ p->n = acc.nChar; @@ -170,6 +176,9 @@ int sqlite3VdbeMemValidStrRep(Mem *p){ assert( p->enc==SQLITE_UTF8 || p->z[((p->n+1)&~1)+1]==0 ); } if( (p->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ) return 1; + if( p->db==0 ){ + return 1; /* db->nFpDigit required to validate p->z[] */ + } memcpy(&tmp, p, sizeof(tmp)); vdbeMemRenderNum(sizeof(zBuf), zBuf, &tmp); z = p->z; @@ -320,13 +329,16 @@ int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ ** ** This is an optimization. Correct operation continues even if ** this routine is a no-op. +** +** Return true if the strig is zero-terminated after this routine is +** called and false if it is not. */ -void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ +int sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ if( (pMem->flags & (MEM_Str|MEM_Term|MEM_Ephem|MEM_Static))!=MEM_Str ){ /* pMem must be a string, and it cannot be an ephemeral or static string */ - return; + return 0; } - if( pMem->enc!=SQLITE_UTF8 ) return; + if( pMem->enc!=SQLITE_UTF8 ) return 0; assert( pMem->z!=0 ); if( pMem->flags & MEM_Dyn ){ if( pMem->xDel==sqlite3_free @@ -334,18 +346,19 @@ void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; - return; + return 1; } if( pMem->xDel==sqlite3RCStrUnref ){ /* Blindly assume that all RCStr objects are zero-terminated */ pMem->flags |= MEM_Term; - return; + return 1; } }else if( pMem->szMalloc >= pMem->n+1 ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; - return; + return 1; } + return 0; } /* @@ -643,18 +656,70 @@ i64 sqlite3VdbeIntValue(const Mem *pMem){ } } +/* +** Invoke sqlite3AtoF() on the text value of pMem and return the +** double result. If sqlite3AtoF() returns an error code, write +** that code into *pRC if (*pRC)!=NULL. +** +** The caller must ensure that pMem->db!=0 and that pMem is in +** mode MEM_Str or MEM_Blob. +*/ +SQLITE_NOINLINE double sqlite3MemRealValueRC(Mem *pMem, int *pRC){ + double val = (double)0; + int rc = 0; + assert( pMem->db!=0 ); + assert( pMem->flags & (MEM_Str|MEM_Blob) ); + if( pMem->z==0 ){ + /* no-op */ + }else if( pMem->enc==SQLITE_UTF8 + && ((pMem->flags & MEM_Term)!=0 || sqlite3VdbeMemZeroTerminateIfAble(pMem)) + ){ + rc = sqlite3AtoF(pMem->z, &val); + }else if( pMem->n==0 ){ + /* no-op */ + }else if( pMem->enc==SQLITE_UTF8 ){ + char *zCopy = sqlite3DbStrNDup(pMem->db, pMem->z, pMem->n); + if( zCopy ){ + rc = sqlite3AtoF(zCopy, &val); + sqlite3DbFree(pMem->db, zCopy); + } + }else{ + int n, i, j; + char *zCopy; + const char *z; + + n = pMem->n & ~1; + zCopy = sqlite3DbMallocRaw(pMem->db, n/2 + 2); + if( zCopy ){ + z = pMem->z; + if( pMem->enc==SQLITE_UTF16LE ){ + for(i=j=0; i<n-1; i+=2, j++){ + zCopy[j] = z[i]; + if( z[i+1]!=0 ) break; + } + }else{ + for(i=j=0; i<n-1; i+=2, j++){ + if( z[i]!=0 ) break; + zCopy[j] = z[i+1]; + } + } + assert( j<=n/2 ); + zCopy[j] = 0; + rc = sqlite3AtoF(zCopy, &val); + if( i<n ) rc = -100; + sqlite3DbFree(pMem->db, zCopy); + } + } + if( pRC ) *pRC = rc; + return val; +} + /* ** Return the best representation of pMem that we can get into a ** double. If pMem is already a double or an integer, return its ** value. If it is a string or blob, try to convert it to a double. ** If it is a NULL, return 0.0. */ -static SQLITE_NOINLINE double memRealValue(Mem *pMem){ - /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ - double val = (double)0; - sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc); - return val; -} double sqlite3VdbeRealValue(Mem *pMem){ assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -665,7 +730,7 @@ double sqlite3VdbeRealValue(Mem *pMem){ testcase( pMem->flags & MEM_IntReal ); return (double)pMem->u.i; }else if( pMem->flags & (MEM_Str|MEM_Blob) ){ - return memRealValue(pMem); + return sqlite3MemRealValueRC(pMem, 0); }else{ /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ return (double)0; @@ -789,7 +854,7 @@ int sqlite3VdbeMemNumerify(Mem *pMem){ sqlite3_int64 ix; assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); + pMem->u.r = sqlite3MemRealValueRC(pMem, &rc); if( ((rc==0 || rc==1) && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1) || sqlite3RealSameAsInt(pMem->u.r, (ix = sqlite3RealToI64(pMem->u.r))) ){ @@ -1254,6 +1319,84 @@ int sqlite3VdbeMemSetStr( return SQLITE_OK; } +/* Like sqlite3VdbeMemSetStr() except: +** +** enc is always SQLITE_UTF8 +** pMem->db is always non-NULL +*/ +int sqlite3VdbeMemSetText( + Mem *pMem, /* Memory cell to set to string value */ + const char *z, /* String pointer */ + i64 n, /* Bytes in string, or negative */ + void (*xDel)(void*) /* Destructor function */ +){ + i64 nByte = n; /* New value for pMem->n */ + u16 flags; + + assert( pMem!=0 ); + assert( pMem->db!=0 ); + assert( sqlite3_mutex_held(pMem->db->mutex) ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + + /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ + if( !z ){ + sqlite3VdbeMemSetNull(pMem); + return SQLITE_OK; + } + + if( nByte<0 ){ + nByte = strlen(z); + flags = MEM_Str|MEM_Term; + }else{ + flags = MEM_Str; + } + if( nByte>(i64)pMem->db->aLimit[SQLITE_LIMIT_LENGTH] ){ + if( xDel && xDel!=SQLITE_TRANSIENT ){ + if( xDel==SQLITE_DYNAMIC ){ + sqlite3DbFree(pMem->db, (void*)z); + }else{ + xDel((void*)z); + } + } + sqlite3VdbeMemSetNull(pMem); + return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); + } + + /* The following block sets the new values of Mem.z and Mem.xDel. It + ** also sets a flag in local variable "flags" to indicate the memory + ** management (one of MEM_Dyn or MEM_Static). + */ + if( xDel==SQLITE_TRANSIENT ){ + i64 nAlloc = nByte + 1; + testcase( nAlloc==31 ); + testcase( nAlloc==32 ); + if( sqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){ + return SQLITE_NOMEM_BKPT; + } + assert( pMem->z!=0 ); + memcpy(pMem->z, z, nByte); + pMem->z[nByte] = 0; + }else{ + sqlite3VdbeMemRelease(pMem); + pMem->z = (char *)z; + if( xDel==SQLITE_DYNAMIC ){ + pMem->zMalloc = pMem->z; + pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); + pMem->xDel = 0; + }else if( xDel==SQLITE_STATIC ){ + pMem->xDel = xDel; + flags |= MEM_Static; + }else{ + pMem->xDel = xDel; + flags |= MEM_Dyn; + } + } + pMem->flags = flags; + pMem->n = (int)(nByte & 0x7fffffff); + pMem->enc = SQLITE_UTF8; + return SQLITE_OK; +} + /* ** Move data out of a btree key or data field and into a Mem structure. ** The data is payload from the entry that pCur is currently pointing @@ -1277,7 +1420,12 @@ int sqlite3VdbeMemFromBtree( ){ int rc; pMem->flags = MEM_Null; - if( sqlite3BtreeMaxRecordSize(pCur)<offset+amt ){ + testcase( amt==SQLITE_MAX_ALLOCATION_SIZE-1 ); + testcase( amt==SQLITE_MAX_ALLOCATION_SIZE ); + if( amt>=SQLITE_MAX_ALLOCATION_SIZE ){ + return SQLITE_NOMEM_BKPT; + } + if( (u64)amt + (u64)offset > (u64)sqlite3BtreeMaxRecordSize(pCur) ){ return SQLITE_CORRUPT_BKPT; } if( SQLITE_OK==(rc = sqlite3VdbeMemClearAndResize(pMem, amt+1)) ){ @@ -1677,7 +1825,7 @@ static int valueFromExpr( if( affinity==SQLITE_AFF_BLOB ){ if( op==TK_FLOAT ){ assert( pVal && pVal->z && pVal->flags==(MEM_Str|MEM_Term) ); - sqlite3AtoF(pVal->z, &pVal->u.r, pVal->n, SQLITE_UTF8); + sqlite3AtoF(pVal->z, &pVal->u.r); pVal->flags = MEM_Real; }else if( op==TK_INTEGER ){ /* This case is required by -9223372036854775808 and other strings @@ -1945,6 +2093,11 @@ int sqlite3Stat4ValueFromExpr( ** ** If *ppVal is initially NULL then the caller is responsible for ** ensuring that the value written into *ppVal is eventually freed. +** +** If the buffer does not contain a well-formed record, this routine may +** read several bytes past the end of the buffer. Callers must therefore +** ensure that any buffer which may contain a corrupt record is padded +** with at least 8 bytes of addressable memory. */ int sqlite3Stat4Column( sqlite3 *db, /* Database handle */ diff --git a/src/wal.c b/src/wal.c index 069852158..7f7bee626 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2254,68 +2254,82 @@ static int walCheckpoint( && (rc = walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(0),1))==SQLITE_OK ){ u32 nBackfill = pInfo->nBackfill; - pInfo->nBackfillAttempted = mxSafeFrame; SEH_INJECT_FAULT; - - /* Sync the WAL to disk */ - rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags)); - - /* If the database may grow as a result of this checkpoint, hint - ** about the eventual size of the db file to the VFS layer. - */ - if( rc==SQLITE_OK ){ - i64 nReq = ((i64)mxPage * szPage); - i64 nSize; /* Current size of database file */ - sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_START, 0); - rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); - if( rc==SQLITE_OK && nSize<nReq ){ - if( (nSize+65536+(i64)pWal->hdr.mxFrame*szPage)<nReq ){ - /* If the size of the final database is larger than the current - ** database plus the amount of data in the wal file, plus the - ** maximum size of the pending-byte page (65536 bytes), then - ** must be corruption somewhere. */ - rc = SQLITE_CORRUPT_BKPT; - }else{ - sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT,&nReq); + WalIndexHdr *pLive = (WalIndexHdr*)walIndexHdr(pWal); + + /* Now that read-lock slot 0 is locked, check that the wal has not been + ** wrapped since the header was read for this checkpoint. If it was, then + ** there was no work to do anyway. In this case the + ** (pInfo->nBackfill<pWal->hdr.mxFrame) test above only passed because + ** pInfo->nBackfill had already been set to 0 by the writer that wrapped + ** the wal file. It would also be dangerous to proceed, as there may be + ** fewer than pWal->hdr.mxFrame valid frames in the wal file. */ + int bChg = memcmp(pLive->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)); + if( 0==bChg ){ + pInfo->nBackfillAttempted = mxSafeFrame; SEH_INJECT_FAULT; + + /* Sync the WAL to disk */ + rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags)); + + /* If the database may grow as a result of this checkpoint, hint + ** about the eventual size of the db file to the VFS layer. + */ + if( rc==SQLITE_OK ){ + i64 nReq = ((i64)mxPage * szPage); + i64 nSize; /* Current size of database file */ + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_START, 0); + rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); + if( rc==SQLITE_OK && nSize<nReq ){ + if( (nSize+65536+(i64)pWal->hdr.mxFrame*szPage)<nReq ){ + /* If the size of the final database is larger than the current + ** database plus the amount of data in the wal file, plus the + ** maximum size of the pending-byte page (65536 bytes), then + ** must be corruption somewhere. */ + rc = SQLITE_CORRUPT_BKPT; + }else{ + sqlite3OsFileControlHint( + pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq); + } } + } - - } - - /* Iterate through the contents of the WAL, copying data to the db file */ - while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ - i64 iOffset; - assert( walFramePgno(pWal, iFrame)==iDbpage ); - SEH_INJECT_FAULT; - if( AtomicLoad(&db->u1.isInterrupted) ){ - rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; - break; - } - if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ - continue; - } - iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; - /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ - rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); - if( rc!=SQLITE_OK ) break; - iOffset = (iDbpage-1)*(i64)szPage; - testcase( IS_BIG_INT(iOffset) ); - rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); - if( rc!=SQLITE_OK ) break; - } - sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_DONE, 0); - - /* If work was actually accomplished... */ - if( rc==SQLITE_OK ){ - if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ - i64 szDb = pWal->hdr.nPage*(i64)szPage; - testcase( IS_BIG_INT(szDb) ); - rc = sqlite3OsTruncate(pWal->pDbFd, szDb); - if( rc==SQLITE_OK ){ - rc = sqlite3OsSync(pWal->pDbFd, CKPT_SYNC_FLAGS(sync_flags)); + + /* Iterate through the contents of the WAL, copying data to the + ** db file */ + while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ + i64 iOffset; + assert( walFramePgno(pWal, iFrame)==iDbpage ); + SEH_INJECT_FAULT; + if( AtomicLoad(&db->u1.isInterrupted) ){ + rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; + break; } + if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ + continue; + } + iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ + rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); + if( rc!=SQLITE_OK ) break; + iOffset = (iDbpage-1)*(i64)szPage; + testcase( IS_BIG_INT(iOffset) ); + rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); + if( rc!=SQLITE_OK ) break; } + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_DONE, 0); + + /* If work was actually accomplished... */ if( rc==SQLITE_OK ){ - AtomicStore(&pInfo->nBackfill, mxSafeFrame); SEH_INJECT_FAULT; + if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ + i64 szDb = pWal->hdr.nPage*(i64)szPage; + testcase( IS_BIG_INT(szDb) ); + rc = sqlite3OsTruncate(pWal->pDbFd, szDb); + if( rc==SQLITE_OK ){ + rc = sqlite3OsSync(pWal->pDbFd, CKPT_SYNC_FLAGS(sync_flags)); + } + } + if( rc==SQLITE_OK ){ + AtomicStore(&pInfo->nBackfill, mxSafeFrame); SEH_INJECT_FAULT; + } } } @@ -4362,9 +4376,10 @@ int sqlite3WalCheckpoint( sqlite3OsUnfetch(pWal->pDbFd, 0, 0); } } - + /* Copy data from the log to the database file. */ if( rc==SQLITE_OK ){ + sqlite3FaultSim(660); if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ rc = SQLITE_CORRUPT_BKPT; }else if( eMode2!=SQLITE_CHECKPOINT_NOOP ){ diff --git a/src/where.c b/src/where.c index c4f2c5543..2ef2ce0be 100644 --- a/src/where.c +++ b/src/where.c @@ -1514,11 +1514,14 @@ static sqlite3_index_info *allocateIndexInfo( break; } if( i==n ){ + int bSortByGroup = (pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0; nOrderBy = n; if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) && !pSrc->fg.rowidUsed ){ - eDistinct = 2 + ((pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0); + eDistinct = 2 + bSortByGroup; }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){ - eDistinct = 1; + eDistinct = 1 - bSortByGroup; + }else if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ + eDistinct = 3; } } } @@ -2929,6 +2932,67 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ return rc; } +/* +** Callback for estLikePatternLength(). +** +** If this node is a string literal that is longer pWalker->sz, then set +** pWalker->sz to the byte length of that string literal. +** +** pWalker->eCode indicates how to count characters: +** +** eCode==0 Count as a GLOB pattern +** eCode==1 Count as a LIKE pattern +*/ +static int exprNodePatternLengthEst(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_STRING ){ + int sz = 0; /* Pattern size in bytes */ + u8 *z = (u8*)pExpr->u.zToken; /* The pattern */ + u8 c; /* Next character of the pattern */ + u8 c1, c2, c3; /* Wildcards */ + if( pWalker->eCode ){ + c1 = '%'; + c2 = '_'; + c3 = 0; + }else{ + c1 = '*'; + c2 = '?'; + c3 = '['; + } + while( (c = *(z++))!=0 ){ + if( c==c3 ){ + if( *z ) z++; + while( *z && *z!=']' ) z++; + }else if( c!=c1 && c!=c2 ){ + sz++; + } + } + if( sz>pWalker->u.sz ) pWalker->u.sz = sz; + } + return WRC_Continue; +} + +/* +** Return the length of the longest string literal in the given +** expression. +** +** eCode indicates how to count characters: +** +** eCode==0 Count as a GLOB pattern +** eCode==1 Count as a LIKE pattern +*/ +static int estLikePatternLength(Expr *p, u16 eCode){ + Walker w; + w.u.sz = 0; + w.eCode = eCode; + w.xExprCallback = exprNodePatternLengthEst; + w.xSelectCallback = sqlite3SelectWalkFail; +#ifdef SQLITE_DEBUG + w.xSelectCallback2 = sqlite3SelectWalkAssert2; +#endif + sqlite3WalkExpr(&w, p); + return w.u.sz; +} + /* ** Adjust the WhereLoop.nOut value downward to account for terms of the ** WHERE clause that reference the loop but which are not used by an @@ -2957,6 +3021,13 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ ** "x" column is boolean or else -1 or 0 or 1 is a common default value ** on the "x" column and so in that case only cap the output row estimate ** at 1/2 instead of 1/4. +** +** Heuristic 3: If there is a LIKE or GLOB (or REGEXP or MATCH) operator +** with a large constant pattern, then reduce the size of the search +** space according to the length of the pattern, under the theory that +** longer patterns are less likely to match. This heuristic was added +** to give better output-row count estimates when preparing queries for +** the Join-Order Benchmarks. See forum thread 2026-01-30T09:57:54z */ static void whereLoopOutputAdjust( WhereClause *pWC, /* The WHERE clause */ @@ -3006,13 +3077,14 @@ static void whereLoopOutputAdjust( }else{ /* In the absence of explicit truth probabilities, use heuristics to ** guess a reasonable truth probability. */ + Expr *pOpExpr = pTerm->pExpr; pLoop->nOut--; if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && (pTerm->wtFlags & TERM_HIGHTRUTH)==0 /* tag-20200224-1 */ ){ - Expr *pRight = pTerm->pExpr->pRight; + Expr *pRight = pOpExpr->pRight; int k = 0; - testcase( pTerm->pExpr->op==TK_IS ); + testcase( pOpExpr->op==TK_IS ); if( sqlite3ExprIsInteger(pRight, &k, 0) && k>=(-1) && k<=1 ){ k = 10; }else{ @@ -3022,6 +3094,23 @@ static void whereLoopOutputAdjust( pTerm->wtFlags |= TERM_HEURTRUTH; iReduce = k; } + }else + if( ExprHasProperty(pOpExpr, EP_InfixFunc) + && pOpExpr->op==TK_FUNCTION + ){ + int eOp; + assert( ExprUseXList(pOpExpr) ); + assert( pOpExpr->x.pList->nExpr>=2 ); + eOp = sqlite3ExprIsLikeOperator(pOpExpr); + if( ALWAYS(eOp>0) ){ + int szPattern; + Expr *pRHS = pOpExpr->x.pList->a[0].pExpr; + eOp = eOp==SQLITE_INDEX_CONSTRAINT_LIKE; + szPattern = estLikePatternLength(pRHS, eOp); + if( szPattern>0 ){ + pLoop->nOut -= szPattern*2; + } + } } } } @@ -3093,7 +3182,7 @@ static int whereRangeVectorLen( idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn); if( aff!=idxaff ) break; - pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); + pColl = sqlite3ExprCompareCollSeq(pParse, pTerm->pExpr); if( pColl==0 ) break; if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break; } @@ -3482,6 +3571,7 @@ static int whereLoopAddBtreeIndex( pNew->rRun += nInMul + nIn; pNew->nOut += nInMul + nIn; whereLoopOutputAdjust(pBuilder->pWC, pNew, rSize); + if( pSrc->fg.fromExists ) pNew->nOut = 0; rc = whereLoopInsert(pBuilder, pNew); if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ @@ -4078,6 +4168,8 @@ static int whereLoopAddBtree( if( pSrc->fg.isSubquery ){ if( pSrc->fg.viaCoroutine ) pNew->wsFlags |= WHERE_COROUTINE; pNew->u.btree.pOrderBy = pSrc->u4.pSubq->pSelect->pOrderBy; + }else if( pSrc->fg.fromExists ){ + pNew->nOut = 0; } rc = whereLoopInsert(pBuilder, pNew); pNew->nOut = rSize; @@ -4180,6 +4272,7 @@ static int whereLoopAddBtree( ** positioned to the correct row during the right-join no-match ** loop. */ }else{ + if( pSrc->fg.fromExists ) pNew->nOut = 0; rc = whereLoopInsert(pBuilder, pNew); } pNew->nOut = rSize; @@ -4842,7 +4935,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ sqlite3 *db = pWInfo->pParse->db; int rc = SQLITE_OK; int bFirstPastRJ = 0; - int hasRightJoin = 0; + int hasRightCrossJoin = 0; WhereLoop *pNew; @@ -4869,15 +4962,34 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ ** prevents the right operand of a RIGHT JOIN from being swapped with ** other elements even further to the right. ** - ** The JT_LTORJ case and the hasRightJoin flag work together to - ** prevent FROM-clause terms from moving from the right side of - ** a LEFT JOIN over to the left side of that join if the LEFT JOIN - ** is itself on the left side of a RIGHT JOIN. + ** The hasRightCrossJoin flag prevent FROM-clause terms from moving + ** from the right side of a LEFT JOIN or CROSS JOIN over to the + ** left side of that same join. This is a required restriction in + ** the case of LEFT JOIN - an incorrect answer may results if it is + ** not enforced. This restriction is not required for CROSS JOIN. + ** It is provided merely as a means of controlling join order, under + ** the theory that no real-world queries that care about performance + ** actually use the CROSS JOIN syntax. */ - if( pItem->fg.jointype & JT_LTORJ ) hasRightJoin = 1; + if( pItem->fg.jointype & (JT_LTORJ|JT_CROSS) ){ + testcase( pItem->fg.jointype & JT_LTORJ ); + testcase( pItem->fg.jointype & JT_CROSS ); + hasRightCrossJoin = 1; + } mPrereq |= mPrior; bFirstPastRJ = (pItem->fg.jointype & JT_RIGHT)!=0; - }else if( !hasRightJoin ){ + }else if( pItem->fg.fromExists ){ + /* joins that result from the EXISTS-to-JOIN optimization should not + ** be moved to the left of any of their dependencies */ + WhereClause *pWC = &pWInfo->sWC; + WhereTerm *pTerm; + int i; + for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){ + if( (pNew->maskSelf & pTerm->prereqAll)!=0 ){ + mPrereq |= (pTerm->prereqAll & (pNew->maskSelf-1)); + } + } + }else if( !hasRightCrossJoin ){ mPrereq = 0; } #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -5100,9 +5212,7 @@ static i8 wherePathSatisfiesOrderBy( pLoop = pLast; } if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ - if( pLoop->u.vtab.isOrdered - && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) - ){ + if( pLoop->u.vtab.isOrdered && pWInfo->pOrderBy==pOrderBy ){ obSat = obDone; }else{ /* No further ORDER BY terms may be matched. So this call should @@ -5478,12 +5588,21 @@ static LogEst whereSortingCost( ** 12 otherwise ** ** For the purposes of this heuristic, a star-query is defined as a query -** with a large central table that is joined using an INNER JOIN, -** not CROSS or OUTER JOINs, against four or more smaller tables. -** The central table is called the "fact" table. The smaller tables -** that get joined are "dimension tables". Also, any table that is -** self-joined cannot be a dimension table; we assume that dimension -** tables may only be joined against fact tables. +** with a central "fact" table that is joined against multiple +** "dimension" tables, subject to the following constraints: +** +** (aa) Only a five-way or larger join is considered for this +** optimization. If there are fewer than four terms in the FROM +** clause, this heuristic does not apply. +** +** (bb) The join between the fact table and the dimension tables must +** be an INNER join. CROSS and OUTER JOINs do not qualify. +** +** (cc) A table must have 3 or more dimension tables in order to be +** considered a fact table. (Was 4 prior to 2026-02-10.) +** +** (dd) A table that is a self-join cannot be a dimension table. +** Dimension tables are joined against fact tables. ** ** SIDE EFFECT: (and really the whole point of this subroutine) ** @@ -5536,7 +5655,7 @@ static int computeMxChoice(WhereInfo *pWInfo){ } #endif /* SQLITE_DEBUG */ - if( nLoop>=5 + if( nLoop>=4 /* Constraint (aa) */ && !pWInfo->bStarDone && OptimizationEnabled(pWInfo->pParse->db, SQLITE_StarQuery) ){ @@ -5548,7 +5667,7 @@ static int computeMxChoice(WhereInfo *pWInfo){ pWInfo->bStarDone = 1; /* Only do this computation once */ - /* Look for fact tables with four or more dimensions where the + /* Look for fact tables with three or more dimensions where the ** dimension tables are not separately from the fact tables by an outer ** or cross join. Adjust cost weights if found. */ @@ -5565,18 +5684,17 @@ static int computeMxChoice(WhereInfo *pWInfo){ if( (pFactTab->fg.jointype & (JT_OUTER|JT_CROSS))!=0 ){ /* If the candidate fact-table is the right table of an outer join ** restrict the search for dimension-tables to be tables to the right - ** of the fact-table. */ - if( iFromIdx+4 > nLoop ) break; /* Impossible to reach nDep>=4 */ + ** of the fact-table. Constraint (bb) */ + if( iFromIdx+3 > nLoop ){ + break; /* ^-- Impossible to reach nDep>=2 - Constraint (cc) */ + } while( pStart && pStart->iTab<=iFromIdx ){ pStart = pStart->pNextLoop; } } for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){ if( (aFromTabs[pWLoop->iTab].fg.jointype & (JT_OUTER|JT_CROSS))!=0 ){ - /* Fact-tables and dimension-tables cannot be separated by an - ** outer join (at least for the definition of fact- and dimension- - ** used by this heuristic). */ - break; + break; /* Constraint (bb) */ } if( (pWLoop->prereq & m)!=0 /* pWInfo depends on iFromIdx */ && (pWLoop->maskSelf & mSeen)==0 /* pWInfo not already a dependency */ @@ -5590,7 +5708,9 @@ static int computeMxChoice(WhereInfo *pWInfo){ } } } - if( nDep<=3 ) continue; + if( nDep<=2 ){ + continue; /* Constraint (cc) */ + } /* If we reach this point, it means that pFactTab is a fact table ** with four or more dimensions connected by inner joins. Proceed @@ -5603,6 +5723,23 @@ static int computeMxChoice(WhereInfo *pWInfo){ pWLoop->rStarDelta = 0; } } +#endif +#ifdef WHERETRACE_ENABLED /* 0x80000 */ + if( sqlite3WhereTrace & 0x80000 ){ + Bitmask mShow = mSeen; + sqlite3DebugPrintf("Fact table %s(%d), dimensions:", + pFactTab->zAlias ? pFactTab->zAlias : pFactTab->pSTab->zName, + iFromIdx); + for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){ + if( mShow & pWLoop->maskSelf ){ + SrcItem *pDim = aFromTabs + pWLoop->iTab; + mShow &= ~pWLoop->maskSelf; + sqlite3DebugPrintf(" %s(%d)", + pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, pWLoop->iTab); + } + } + sqlite3DebugPrintf("\n"); + } #endif pWInfo->bStarUsed = 1; @@ -5626,10 +5763,8 @@ static int computeMxChoice(WhereInfo *pWInfo){ if( sqlite3WhereTrace & 0x80000 ){ SrcItem *pDim = aFromTabs + pWLoop->iTab; sqlite3DebugPrintf( - "Increase SCAN cost of dimension %s(%d) of fact %s(%d) to %d\n", - pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, pWLoop->iTab, - pFactTab->zAlias ? pFactTab->zAlias : pFactTab->pSTab->zName, - iFromIdx, mxRun + "Increase SCAN cost of %s to %d\n", + pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, mxRun ); } pWLoop->rStarDelta = mxRun - pWLoop->rRun; @@ -6443,6 +6578,7 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( for(pTerm=pWInfo->sWC.a; pTerm<pEnd; pTerm++){ if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){ pTerm->wtFlags |= TERM_CODED; + pTerm->prereqAll = 0; } } if( i!=pWInfo->nLevel-1 ){ @@ -7430,14 +7566,15 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } #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. */ + if( pTabList->a[pLevel->iFrom].fg.fromExists + && (i==pWInfo->nLevel-1 + || pTabList->a[pWInfo->a[i+1].iFrom].fg.fromExists==0) + ){ + /* This is an EXISTS-to-JOIN optimization which is either the + ** inner-most loop, or the inner-most of a group of nested + ** EXISTS-to-JOIN optimization loops. If this loop sees a successful + ** row, it should break out of itself as well as other EXISTS-to-JOIN + ** loops in which is is directly nested. */ int nOuter = 0; /* Nr of outer EXISTS that this one is nested within */ while( nOuter<i ){ if( !pTabList->a[pLevel[-nOuter-1].iFrom].fg.fromExists ) break; @@ -7445,7 +7582,11 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } testcase( nOuter>0 ); sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel[-nOuter].addrBrk); - VdbeComment((v, "EXISTS break")); + if( nOuter ){ + VdbeComment((v, "EXISTS break %d..%d", i-nOuter, i)); + }else{ + VdbeComment((v, "EXISTS break %d", i)); + } } sqlite3VdbeResolveLabel(v, pLevel->addrCont); if( pLevel->op!=OP_Noop ){ diff --git a/src/wherecode.c b/src/wherecode.c index 1efa34a5d..31d7990ad 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -1573,7 +1573,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( SMASKBIT32(j) & pLoop->u.vtab.mHandleIn ){ int iTab = pParse->nTab++; int iCache = ++pParse->nMem; - sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab); + sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab, 0); sqlite3VdbeAddOp3(v, OP_VInitIn, iTab, iTarget, iCache); }else{ codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); diff --git a/src/whereexpr.c b/src/whereexpr.c index 0d99ca85e..74bf624c8 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -293,13 +293,14 @@ static int isLikeOrGlob( ){ int isNum; double rDummy; - isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8); + assert( zNew[iTo]==0 ); + isNum = sqlite3AtoF(zNew, &rDummy); if( isNum<=0 ){ if( iTo==1 && zNew[0]=='-' ){ isNum = +1; }else{ zNew[iTo-1]++; - isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8); + isNum = sqlite3AtoF(zNew, &rDummy); zNew[iTo-1]--; } } @@ -342,6 +343,34 @@ static int isLikeOrGlob( } #endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ +/* +** If pExpr is one of "like", "glob", "match", or "regexp", then +** return the corresponding SQLITE_INDEX_CONSTRAINT_xxxx value. +** If not, return 0. +** +** pExpr is guaranteed to be a TK_FUNCTION. +*/ +int sqlite3ExprIsLikeOperator(const Expr *pExpr){ + static const struct { + const char *zOp; + unsigned char eOp; + } aOp[] = { + { "match", SQLITE_INDEX_CONSTRAINT_MATCH }, + { "glob", SQLITE_INDEX_CONSTRAINT_GLOB }, + { "like", SQLITE_INDEX_CONSTRAINT_LIKE }, + { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP } + }; + int i; + assert( pExpr->op==TK_FUNCTION ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + for(i=0; i<ArraySize(aOp); i++){ + if( sqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){ + return aOp[i].eOp; + } + } + return 0; +} + #ifndef SQLITE_OMIT_VIRTUALTABLE /* @@ -378,15 +407,6 @@ static int isAuxiliaryVtabOperator( Expr **ppRight /* Expression to left of MATCH/op2 */ ){ if( pExpr->op==TK_FUNCTION ){ - static const struct Op2 { - const char *zOp; - unsigned char eOp2; - } aOp[] = { - { "match", SQLITE_INDEX_CONSTRAINT_MATCH }, - { "glob", SQLITE_INDEX_CONSTRAINT_GLOB }, - { "like", SQLITE_INDEX_CONSTRAINT_LIKE }, - { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP } - }; ExprList *pList; Expr *pCol; /* Column reference */ int i; @@ -406,16 +426,11 @@ static int isAuxiliaryVtabOperator( */ pCol = pList->a[1].pExpr; assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) ); - if( ExprIsVtab(pCol) ){ - for(i=0; i<ArraySize(aOp); i++){ - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - if( sqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){ - *peOp2 = aOp[i].eOp2; - *ppRight = pList->a[0].pExpr; - *ppLeft = pCol; - return 1; - } - } + if( ExprIsVtab(pCol) && (i = sqlite3ExprIsLikeOperator(pExpr))!=0 ){ + *peOp2 = i; + *ppRight = pList->a[0].pExpr; + *ppLeft = pCol; + return 1; } /* We can also match against the first column of overloaded @@ -549,16 +564,22 @@ static void whereCombineDisjuncts( Expr *pNew; /* New virtual expression */ int op; /* Operator for the combined expression */ int idxNew; /* Index in pWC of the next virtual term */ + Expr *pA, *pB; /* Expressions associated with pOne and pTwo */ if( (pOne->wtFlags | pTwo->wtFlags) & TERM_VNULL ) return; if( (pOne->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (pTwo->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (eOp & (WO_EQ|WO_LT|WO_LE))!=eOp && (eOp & (WO_EQ|WO_GT|WO_GE))!=eOp ) return; - assert( pOne->pExpr->pLeft!=0 && pOne->pExpr->pRight!=0 ); - assert( pTwo->pExpr->pLeft!=0 && pTwo->pExpr->pRight!=0 ); - if( sqlite3ExprCompare(0,pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return; - if( sqlite3ExprCompare(0,pOne->pExpr->pRight, pTwo->pExpr->pRight,-1) )return; + pA = pOne->pExpr; + pB = pTwo->pExpr; + assert( pA->pLeft!=0 && pA->pRight!=0 ); + assert( pB->pLeft!=0 && pB->pRight!=0 ); + if( sqlite3ExprCompare(0,pA->pLeft, pB->pLeft, -1) ) return; + if( sqlite3ExprCompare(0,pA->pRight, pB->pRight,-1) ) return; + if( ExprHasProperty(pA,EP_Commuted)!=ExprHasProperty(pB,EP_Commuted) ){ + return; + } /* If we reach this point, it means the two subterms can be combined */ if( (eOp & (eOp-1))!=0 ){ if( eOp & (WO_LT|WO_LE) ){ @@ -569,7 +590,7 @@ static void whereCombineDisjuncts( } } db = pWC->pWInfo->pParse->db; - pNew = sqlite3ExprDup(db, pOne->pExpr, 0); + pNew = sqlite3ExprDup(db, pA, 0); if( pNew==0 ) return; for(op=TK_EQ; eOp!=(WO_EQ<<(op-TK_EQ)); op++){ assert( op<TK_GE ); } pNew->op = op; @@ -1609,13 +1630,11 @@ static void whereAddLimitExpr( int iVal = 0; if( sqlite3ExprIsInteger(pExpr, &iVal, pParse) && iVal>=0 ){ - Expr *pVal = sqlite3Expr(db, TK_INTEGER, 0); + Expr *pVal = sqlite3ExprInt32(db, iVal); if( pVal==0 ) return; - ExprSetProperty(pVal, EP_IntValue); - pVal->u.iValue = iVal; pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); }else{ - Expr *pVal = sqlite3Expr(db, TK_REGISTER, 0); + Expr *pVal = sqlite3ExprAlloc(db, TK_REGISTER, 0, 0); if( pVal==0 ) return; pVal->iTable = iReg; pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); diff --git a/src/window.c b/src/window.c index 1f22ab194..ea2781864 100644 --- a/src/window.c +++ b/src/window.c @@ -717,7 +717,7 @@ void sqlite3WindowUpdate( pWin->eEnd = aUp[i].eEnd; pWin->eExclude = 0; if( pWin->eStart==TK_FOLLOWING ){ - pWin->pStart = sqlite3Expr(db, TK_INTEGER, "1"); + pWin->pStart = sqlite3ExprInt32(db, 1); } break; } @@ -1062,9 +1062,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ ** keep everything legal in this case. */ if( pSublist==0 ){ - pSublist = sqlite3ExprListAppend(pParse, 0, - sqlite3Expr(db, TK_INTEGER, "0") - ); + pSublist = sqlite3ExprListAppend(pParse, 0, sqlite3ExprInt32(db, 0)); } pSub = sqlite3SelectNew( diff --git a/test/alterauth2.test b/test/alterauth2.test index 6f9242d36..260b635c3 100644 --- a/test/alterauth2.test +++ b/test/alterauth2.test @@ -116,4 +116,55 @@ do_auth_test 1.3 { {SQLITE_UPDATE sqlite_temp_master sql temp {}} } +do_auth_test 1.4 { + ALTER TABLE t2 ALTER COLUMN b SET NOT NULL; +} { + {SQLITE_ALTER_TABLE main t2 b {}} + {SQLITE_FUNCTION {} sqlite_add_constraint {} {}} + {SQLITE_FUNCTION {} sqlite_drop_constraint {} {}} + {SQLITE_FUNCTION {} sqlite_fail {} {}} + {SQLITE_READ sqlite_master sql main {}} + {SQLITE_READ sqlite_master tbl_name main {}} + {SQLITE_READ sqlite_master type main {}} + {SQLITE_READ t2 b main {}} + {SQLITE_SELECT {} {} {} {}} + {SQLITE_UPDATE sqlite_master sql main {}} +} +do_auth_test 1.5 { + ALTER TABLE t2 ALTER COLUMN 'b' DROP NOT NULL; +} { + {SQLITE_ALTER_TABLE main t2 b {}} + {SQLITE_FUNCTION {} sqlite_drop_constraint {} {}} + {SQLITE_READ sqlite_master sql main {}} + {SQLITE_READ sqlite_master tbl_name main {}} + {SQLITE_READ sqlite_master type main {}} + {SQLITE_UPDATE sqlite_master sql main {}} +} + +do_auth_test 1.6 { + ALTER TABLE t2 ADD CONSTRAINT abc CHECK (aaa>b) +} { + {SQLITE_ALTER_TABLE main t2 {} {}} + {SQLITE_FUNCTION {} sqlite_add_constraint {} {}} + {SQLITE_FUNCTION {} sqlite_fail {} {}} + {SQLITE_FUNCTION {} sqlite_find_constraint {} {}} + {SQLITE_READ sqlite_master sql main {}} + {SQLITE_READ sqlite_master tbl_name main {}} + {SQLITE_READ sqlite_master type main {}} + {SQLITE_READ t2 aaa main {}} + {SQLITE_READ t2 b main {}} + {SQLITE_SELECT {} {} {} {}} + {SQLITE_UPDATE sqlite_master sql main {}} +} +do_auth_test 1.7 { + ALTER TABLE t2 DROP CONSTRAINT abc; +} { + {SQLITE_ALTER_TABLE main t2 {} {}} + {SQLITE_FUNCTION {} sqlite_drop_constraint {} {}} + {SQLITE_READ sqlite_master sql main {}} + {SQLITE_READ sqlite_master tbl_name main {}} + {SQLITE_READ sqlite_master type main {}} + {SQLITE_UPDATE sqlite_master sql main {}} +} + finish_test diff --git a/test/altercol.test b/test/altercol.test index 5f7de57a4..d94e0529c 100644 --- a/test/altercol.test +++ b/test/altercol.test @@ -946,4 +946,17 @@ do_execsql_test 23.20 { ROLLBACK; } {t4new} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 24.0 { + CREATE TABLE t1(a PRIMARY KEY, b); + CREATE TABLE t2(a, b, c); + INSERT INTO t2 VALUES(1, 1, 1); +} + +do_catchsql_test 24.1 { + PRAGMA foreign_keys = 1; + ALTER TABLE t2 ADD COLUMN d REFERENCES t1 DEFAULT 123; +} {1 {Cannot add a REFERENCES column with non-NULL default value}} + finish_test diff --git a/test/altercons.test b/test/altercons.test new file mode 100644 index 000000000..fecdf858b --- /dev/null +++ b/test/altercons.test @@ -0,0 +1,442 @@ +# 2025 September 18 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#************************************************************************* +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix altercons + +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + +foreach {tn before after} { + 1 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b)) } + { CREATE TABLE t1(a, b) } + + 2 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) NOT NULL) } + { CREATE TABLE t1(a, b NOT NULL) } + + 3 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b)NOT NULL) } + { CREATE TABLE t1(a, b NOT NULL) } + + 3 { CREATE TABLE t1(a, b NOT NULL CONSTRAINT abc CHECK(t1.a != t1.b)); } + { CREATE TABLE t1(a, b NOT NULL) } + + 4 { CREATE TABLE t1(a, b, CONSTRAINT abc CHECK(t1.a != t1.b)) } + { CREATE TABLE t1(a, b) } + + 5 { CREATE TABLE t1(a, b, CONSTRAINT abc CHECK(t1.a != t1.b), PRIMARY KEY(a))} + { CREATE TABLE t1(a, b, PRIMARY KEY(a)) } + + 6 { CREATE TABLE t1(a, b,CONSTRAINT abc CHECK(t1.a != t1.b),PRIMARY KEY(a))} + { CREATE TABLE t1(a, b,PRIMARY KEY(a)) } + + 7 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) CONSTRAINT def UNIQUE) } + { CREATE TABLE t1(a, b CONSTRAINT def UNIQUE) } + + 8 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) CHECK (123)) } + { CREATE TABLE t1(a, b CHECK (123)) } + + 9 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) DEFAULT NULL) } + { CREATE TABLE t1(a, b DEFAULT NULL) } + + 10 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) COLLATE nocase) } + { CREATE TABLE t1(a, b COLLATE nocase) } + + 11 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) REFERENCES t2) } + { CREATE TABLE t1(a, b REFERENCES t2) } + + 12 { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT abc CHECK(a!=b) CONSTRAINT three) } + { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT three) } + + 13 { CREATE TABLE t1(a, b, c, CONSTRAINT abc CONSTRAINT one CHECK(a!=b) CONSTRAINT three) } + { CREATE TABLE t1(a, b, c, CONSTRAINT one CHECK(a!=b) CONSTRAINT three) } + + 14 { CREATE TABLE t1(a, b, c, CONSTRAINT abc) } + { CREATE TABLE t1(a, b, c) } + + 15 { CREATE TABLE t1(a, b, c, + CONSTRAINT abc, CHECK( a!=b )) } + { CREATE TABLE t1(a, b, c, CHECK( a!=b )) } + + 16 { CREATE TABLE t1(a, b, c, CONSTRAINT abc /* hello */ CHECK( a!=b )) } + { CREATE TABLE t1(a, b, c) } + + 17 { CREATE TABLE t1(a, b, c, /* world */ CONSTRAINT abc CHECK( a!=b )) } + { CREATE TABLE t1(a, b, c) } + + 18 { CREATE TABLE t1(a, b, c -- comment + CONSTRAINT abc NOT NULL + ) } + { CREATE TABLE t1(a, b, c) } + + 19 { CREATE TABLE t1(a, b, c, -- comment + CONSTRAINT abc CHECK (a>b) CONSTRAINT two + ) } + { CREATE TABLE t1(a, b, c, CONSTRAINT two + ) } + + 20 { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT abc CHECK (a>b)CONSTRAINT two) } + { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT two) } + + 21 { CREATE TABLE t1(a, b, c CONSTRAINT abc AS (b+1)) } + { CREATE TABLE t1(a, b, c AS (b+1)) } + + 22 { CREATE TABLE t1(a, b, c CONSTRAINT abc GENERATED ALWAYS AS (b+1) STORED) } + { CREATE TABLE t1(a, b, c GENERATED ALWAYS AS (b+1) STORED) } +} { + reset_db + + do_execsql_test 1.$tn.0 $before + + do_execsql_test 1.$tn.1 { + ALTER TABLE t1 DROP CONSTRAINT abc; + } {} + + do_execsql_test 1.$tn.2 { + SELECT sql FROM sqlite_schema WHERE name='t1' + } [list [string trim $after]] +} + +#------------------------------------------------------------------------- + +do_execsql_test 2.0 { + CREATE TABLE t2(x, y CONSTRAINT ccc UNIQUE); +} +do_catchsql_test 2.1 { + ALTER TABLE t2 DROP CONSTRAINT ccc +} {1 {constraint may not be dropped: ccc}} +do_catchsql_test 2.2 { + ALTER TABLE t2 DROP CONSTRAINT ddd +} {1 {no such constraint: ddd}} + +#------------------------------------------------------------------------- +reset_db +foreach {tn col before after} { + 1 a { CREATE TABLE t1(a NOT NULL, b) } + { CREATE TABLE t1(a, b) } + + 2 a { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL, b) } + { CREATE TABLE t1(a, b) } + + 3 a { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL UNIQUE, b) } + { CREATE TABLE t1(a UNIQUE, b) } + + 4 b { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL UNIQUE, b) } + { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL UNIQUE, b) } + + 5 a { CREATE TABLE t1(a CHECK(a<b) NOT NULL, b) } + { CREATE TABLE t1(a CHECK(a<b), b) } + + 6 a { CREATE TABLE t1(a CHECK(a<b) CONSTRAINT nn NOT NULL, b) } + { CREATE TABLE t1(a CHECK(a<b), b) } + + 7 b { CREATE TABLE t1(a, b NOT NULL PRIMARY KEY) } + { CREATE TABLE t1(a, b PRIMARY KEY) } + + 8 b { CREATE TABLE t1(a, b CHECK ((b+a) IS NOT NULL) NOT NULL PRIMARY KEY) } + { CREATE TABLE t1(a, b CHECK ((b+a) IS NOT NULL) PRIMARY KEY) } + + 9 b { CREATE TABLE t1(a, b CONSTRAINT nn CHECK (b IS NOT NULL) NOT NULL) } + { CREATE TABLE t1(a, b CONSTRAINT nn CHECK (b IS NOT NULL)) } + + 10 b { CREATE TABLE t1(a, b NOT NULL AS (a+1)) } + { CREATE TABLE t1(a, b AS (a+1)) } + + 11 b { CREATE TABLE t1(a, b NOT NULL GENERATED ALWAYS AS (a+1)) } + { CREATE TABLE t1(a, b GENERATED ALWAYS AS (a+1)) } +} { + reset_db + + do_execsql_test 3.$tn.0 $before + + do_execsql_test 3.$tn.1 " + ALTER TABLE t1 ALTER COLUMN $col DROP NOT NULL + " + + do_execsql_test 3.$tn.2 { + SELECT sql FROM sqlite_schema WHERE name='t1' + } [list [string trim $after]] +} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 4.0 { + CREATE TABLE t2(x, y CONSTRAINT ccc UNIQUE); +} +do_execsql_test 4.1 { + ALTER TABLE t2 ALTER x DROP NOT NULL; + ALTER TABLE t2 ALTER x DROP NOT NULL; + ALTER TABLE t2 ALTER x DROP NOT NULL; +} {} + +#------------------------------------------------------------------------- +# +reset_db + +do_execsql_test 5.1 { + CREATE TABLE t3(a INTEGER PRIMARY KEY, b); + INSERT INTO t3 VALUES(1000, NULL); +} + +do_catchsql_test 5.2.1 { + ALTER TABLE t3 ALTER b SET NOT NULL +} {1 {constraint failed}} + +do_test 5.2.2 { + sqlite3_errcode db +} {SQLITE_CONSTRAINT} + +foreach {tn before alter after} { + 1 { CREATE TABLE t1(a, b) } + { ALTER TABLE t1 ALTER a SET NOT NULL } + { CREATE TABLE t1(a NOT NULL, b) } + + 2 { CREATE TABLE t1(a, b) } + { ALTER TABLE t1 ALTER a SET NOT NULL ON CONFLICT FAIL } + { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL, b) } + + 3 { CREATE TABLE t1(a, b) } + { ALTER TABLE t1 ALTER a SET NOT NULL ON CONFLICT fail; } + { CREATE TABLE t1(a NOT NULL ON CONFLICT fail, b) } + + 4 { CREATE TABLE t1(a, b) } + { ALTER TABLE t1 ALTER b SET NOT NULL ON CONFLICT IGNORE ; } + { CREATE TABLE t1(a, b NOT NULL ON CONFLICT IGNORE) } + + 5 { CREATE TABLE t1(a, 'a b c' VARCHAR(10), UNIQUE(a)) } + { ALTER TABLE t1 ALTER 'a b c' SET NOT NULL } + { CREATE TABLE t1(a, 'a b c' VARCHAR(10) NOT NULL, UNIQUE(a)) } +} { + reset_db + do_execsql_test 5.3.$tn.1 $before + do_execsql_test 5.3.$tn.2 $alter + do_execsql_test 5.3.$tn.3 { + SELECT sql FROM sqlite_schema WHERE name='t1'; + } [list [string trim $after]] +} + +do_execsql_test 5.4.1 { + CREATE TABLE x1(a, b, c); +} +do_catchsql_test 5.4.2 { + ALTER TABLE x1 ALTER d SET NOT NULL; +} {1 {no such column: d}} +do_catchsql_test 5.4.3 { + ALTER TABLE x2 ALTER c SET NOT NULL; +} {1 {no such table: x2}} +do_catchsql_test 5.4.4 { + ALTER TABLE temp.x1 ALTER c SET NOT NULL; +} {1 {no such table: temp.x1}} + +#------------------------------------------------------------------------- +# +reset_db + +do_execsql_test 6.1 { + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); +} + +do_catchsql_test 6.2.1 { + ALTER TABLE t1 ADD CONSTRAINT nn CHECK (c!=6); +} {1 {constraint failed}} +do_execsql_test 6.2.2 { + DELETE FROM t1 WHERE c=6; + ALTER TABLE t1 ADD CONSTRAINT nn CHECK (c!=6); +} {} +do_catchsql_test 6.2.3 { + INSERT INTO t1 VALUES(4, 5, 6); +} {1 {CHECK constraint failed: nn}} + +foreach {tn before alter after} { + 1 { CREATE TABLE t1(a, b) } + { ALTER TABLE t1 ADD CONSTRAINT nn CHECK (a>=0) } + { CREATE TABLE t1(a, b, CONSTRAINT nn CHECK (a>=0)) } + + 2 { CREATE TABLE t1(a, b ) } + { ALTER TABLE t1 ADD CONSTRAINT nn CHECK (a>=0) } + { CREATE TABLE t1(a, b , CONSTRAINT nn CHECK (a>=0)) } + + 3 { CREATE TABLE t1(a, b ) } + { ALTER TABLE t1 ADD CHECK (a>=0) } + { CREATE TABLE t1(a, b , CHECK (a>=0)) } +} { + reset_db + do_execsql_test 6.3.$tn.1 $before + do_execsql_test 6.3.$tn.2 $alter + do_execsql_test 6.3.$tn.3 { + SELECT sql FROM sqlite_schema WHERE type='table'; + } [list [string trim $after]] +} + +do_execsql_test 6.4.1 { + CREATE TABLE b1(a, b, CONSTRAINT abc CHECK (a!=2)); +} +do_catchsql_test 6.4.2 { + ALTER TABLE b1 ADD CONSTRAINT abc CHECK (a!=3); +} {1 {constraint abc already exists}} +do_execsql_test 6.4.1 { + SELECT sql FROM sqlite_schema WHERE tbl_name='b1' +} {{CREATE TABLE b1(a, b, CONSTRAINT abc CHECK (a!=2))}} + +do_execsql_test 6.5 { + CREATE TABLE abc(x,y); +} + +do_catchsql_test 6.6 { + ALTER TABLE abc ADD CHECK (z>=0); +} {1 {no such column: z}} + +#------------------------------------------------------------------------- +# Try attaching a NOT NULL to a generated column. +# +reset_db +do_execsql_test 7.0 { + CREATE TABLE x1(a, b AS (a+1)); + INSERT INTO x1 VALUES(1), (2), (3), (NULL); +} + +do_catchsql_test 7.1 { + ALTER TABLE x1 ALTER b SET NOT NULL; +} {1 {constraint failed}} + +do_catchsql_test 7.2 { + DELETE FROM x1 WHERE b IS NULL; + ALTER TABLE x1 ALTER b SET NOT NULL; +} {0 {}} + +do_execsql_test 7.3 { + SELECT b FROM x1 +} {2 3 4} + +do_catchsql_test 7.4 { + ALTER TABLE x1 ALTER rowid SET NOT NULL; +} {1 {no such column: rowid}} + +do_execsql_test 7.5 { + CREATE VIEW v1 AS SELECT a, b FROM x1; +} +do_catchsql_test 7.6 { + ALTER TABLE v1 RENAME a TO c; +} {1 {cannot rename columns of view "v1"}} +do_catchsql_test 7.7 { + ALTER TABLE v1 ALTER a SET NOT NULL; +} {1 {cannot edit constraints of view "v1"}} +do_catchsql_test 7.8 { + ALTER TABLE sqlite_schema ALTER sql SET NOT NULL; +} {1 {table sqlite_master may not be altered}} +do_catchsql_test 7.9 { + ALTER TABLE v1 ALTER a DROP NOT NULL +} {1 {cannot edit constraints of view "v1"}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 8.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b NOT NULL, c CHECK (c!=555), d); + INSERT INTO t1 VALUES(1, 1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3, 3); +} + +do_execsql_test 8.1.1 { + ALTER TABLE t1 ALTER a SET NOT NULL; + ALTER TABLE t1 ALTER b SET NOT NULL; + ALTER TABLE t1 ALTER c SET NOT NULL; + ALTER TABLE t1 ALTER d SET NOT NULL; +} + +do_execsql_test 8.1.2 { + SELECT sql FROM sqlite_schema WHERE tbl_name = 't1' +} {{CREATE TABLE t1(a INTEGER PRIMARY KEY NOT NULL, b NOT NULL, c CHECK (c!=555) NOT NULL, d NOT NULL)}} + +do_execsql_test 8.1.3 { + SELECT * FROM t1 WHERE a=2; +} {2 2 2 2} + +do_execsql_test 8.2.1 { + ALTER TABLE t1 ALTER a DROP NOT NULL; + ALTER TABLE t1 ALTER b DROP NOT NULL; + ALTER TABLE t1 ALTER c DROP NOT NULL; + ALTER TABLE t1 ALTER d DROP NOT NULL; +} + +do_execsql_test 8.2.2 { + SELECT sql FROM sqlite_schema WHERE tbl_name = 't1' +} {{CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c CHECK (c!=555), d)}} + +do_execsql_test 8.2.3 { + SELECT * FROM t1 WHERE a=3; +} {3 3 3 3} + +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +do_execsql_test 9.0 { + CREATE TABLE t1(x, y, z); + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.t1(x, y, z); + INSERT INTO aux.t1 VALUES(1, 1, 1); + INSERT INTO aux.t1 VALUES(2, 2, 2); + INSERT INTO aux.t1 VALUES(3, 3, NULL); + + CREATE TABLE aux.t2(x, y, z); +} + +do_catchsql_test 9.1.1 { + ALTER TABLE aux.t1 ALTER COLUMN z SET NOT NULL +} {1 {constraint failed}} +do_execsql_test 9.1.2 { + UPDATE aux.t1 SET z=x; + ALTER TABLE aux.t1 ALTER COLUMN z SET NOT NULL; + SELECT sql FROM aux.sqlite_schema WHERE name='t1'; +} {{CREATE TABLE t1(x, y, z NOT NULL)}} +do_execsql_test 9.1.3 { + ALTER TABLE aux.t1 ALTER z DROP NOT NULL; + SELECT sql FROM aux.sqlite_schema WHERE name='t1'; +} {{CREATE TABLE t1(x, y, z)}} +do_execsql_test 9.1.4 { + ALTER TABLE t2 ALTER x SET NOT NULL; + SELECT sql FROM aux.sqlite_schema WHERE name='t2'; +} {{CREATE TABLE t2(x NOT NULL, y, z)}} +do_execsql_test 9.1.5 { + ALTER TABLE t2 ALTER x DROP NOT NULL; + SELECT sql FROM aux.sqlite_schema WHERE name='t2'; +} {{CREATE TABLE t2(x, y, z)}} + +do_catchsql_test 9.2.1 { + ALTER TABLE aux.t1 ADD CONSTRAINT bill CHECK (y!=2); +} {1 {constraint failed}} +do_execsql_test 9.2.2 { + UPDATE aux.t1 SET y=4 WHERE y=2; + ALTER TABLE aux.t1 ADD CONSTRAINT bill CHECK (y!=2); + SELECT sql FROM aux.sqlite_schema WHERE name='t1'; +} {{CREATE TABLE t1(x, y, z, CONSTRAINT bill CHECK (y!=2))}} +do_execsql_test 9.2.3 { + ALTER TABLE aux.t1 DROP CONSTRAINT bill; + SELECT sql FROM aux.sqlite_schema WHERE name='t1'; +} {{CREATE TABLE t1(x, y, z)}} +do_execsql_test 9.2.4 { + ALTER TABLE t2 ADD CONSTRAINT william CHECK (z!=''); + SELECT sql FROM aux.sqlite_schema WHERE name='t2'; +} {{CREATE TABLE t2(x, y, z, CONSTRAINT william CHECK (z!=''))}} +do_execsql_test 9.2.5 { + ALTER TABLE t2 DROP CONSTRAINT william; + SELECT sql FROM aux.sqlite_schema WHERE name='t2'; +} {{CREATE TABLE t2(x, y, z)}} + +finish_test + diff --git a/test/altercons2.test b/test/altercons2.test new file mode 100644 index 000000000..a5bbaf6fc --- /dev/null +++ b/test/altercons2.test @@ -0,0 +1,247 @@ +# 2025 September 18 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#************************************************************************* +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix altercons2 + +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + +foreach {tn newsql alter res final} { + 1 "CREATE TABLE t1(a, b" + "ALTER TABLE t1 ALTER c SET NOT NULL" + {1 {database disk image is malformed}} + "CREATE TABLE t1(a, b" + + 2 "CREATE TABLE t1(a, b, " + "ALTER TABLE t1 ALTER c DROP NOT NULL" + {0 {}} + "CREATE TABLE t1(a, b, " + + 3 "CREATE TABLE t1(a, b, CHECK( ..." + "ALTER TABLE t1 ALTER c DROP NOT NULL" + {0 {}} + "CREATE TABLE t1(a, b, CHECK( ..." + + 4 "CREATE TABLE t1(a, b, c NOT NULL" + "ALTER TABLE t1 ALTER c DROP NOT NULL" + {0 {}} + "CREATE TABLE t1(a, b, c " + + 5 "CREATE TABLE" + "ALTER TABLE t1 ALTER c DROP NOT NULL" + {1 {database disk image is malformed}} + "CREATE TABLE" + + 6 "CREATE TABLE" + "ALTER TABLE t1 ADD CONSTRAINT nn CHECK (a!=0)" + {1 {database disk image is malformed}} + "CREATE TABLE" + +} { + reset_db + do_execsql_test 1.$tn.0 { + CREATE TABLE t1(a, b, c NOT NULL, CONSTRAINT xyz CHECK( a!=0 )); + } + do_execsql_test 1.$tn.1 { + PRAGMA writable_schema = 1; + UPDATE sqlite_schema SET sql = $::newsql + } + do_catchsql_test 1.$tn.2 $alter $res + + do_execsql_test 1.$tn.3 { + SELECT sql FROM sqlite_schema WHERE name='t1' + } [list $final] +} + +#------------------------------------------------------------------------- + +reset_db +proc xAuth {t args} { + if {$t=="SQLITE_ALTER_TABLE"} { + return "SQLITE_DENY" + } + return "SQLITE_OK" +} +sqlite3 db test.db +db auth xAuth + +do_execsql_test 2.0 { + CREATE TABLE x1(a PRIMARY KEY, b CHECK(a!=b) NOT NULL, c); +} + +do_catchsql_test 2.1.1 { + ALTER TABLE x1 ADD CONSTRAINT ccc CHECK (a!='a') +} {1 {not authorized}} +do_execsql_test 2.1.2 { + SELECT sql FROM sqlite_schema WHERE name='x1' +} {{CREATE TABLE x1(a PRIMARY KEY, b CHECK(a!=b) NOT NULL, c)}} + +do_catchsql_test 2.2.1 { + ALTER TABLE x1 ALTER c SET NOT NULL +} {1 {not authorized}} +do_execsql_test 2.2.2 { + SELECT sql FROM sqlite_schema WHERE name='x1' +} {{CREATE TABLE x1(a PRIMARY KEY, b CHECK(a!=b) NOT NULL, c)}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(x); +} +do_execsql_test 3.1 { + ALTER TABLE t1 ALTER x SET NOT NULL; +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 4.0 { + CREATE TABLE abc(a, b, c, CONSTRAINT one CONSTRAINT two CHECK (b!=c)); +} +do_execsql_test 4.1 { + ALTER TABLE abc DROP CONSTRAINT one +} +do_execsql_test 4.2 { + SELECT sql FROM sqlite_schema +} { + {CREATE TABLE abc(a, b, c, CONSTRAINT two CHECK (b!=c))} +} + +#------------------------------------------------------------------------- +reset_db + +# The columns must come before the table constraints in a CREATE TABLE +# statement. This is useful, as it means the DROP CONSTRAINT code does +# not have to handle the constraint immediately following the '(' at +# the start of the column-list. +do_catchsql_test 5.0 { + CREATE TABLE abc(a, b, c, CONSTRAINT two CHECK (b!=c), d) +} {1 {near "d": syntax error}} +do_catchsql_test 5.1 { + CREATE TABLE def(CONSTRAINT abc CHECK( b!=c ), a, b, c); +} {1 {near "CONSTRAINT": syntax error}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE abc(a, b CONSTRAINT two COLLATE nocase CHECK (a!=b), c CONSTRAINT one DEFAULT 'abc'); +} + +do_execsql_test 6.1 { + ALTER TABLE abc DROP CONSTRAINT one; + ALTER TABLE abc DROP CONSTRAINT two; +} + +do_execsql_test 6.2 { + SELECT sql FROM sqlite_schema +} { + {CREATE TABLE abc(a, b COLLATE nocase CHECK (a!=b), c DEFAULT 'abc')} +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 7.0 { + CREATE TABLE abc(a, b, c, CONSTRAINT one CHECK (a>b) FOREIGN KEY(a) REFERENCES abc); +} +do_execsql_test 7.1 { + ALTER TABLE abc DROP CONSTRAINT one +} +do_execsql_test 7.2 { + SELECT sql FROM sqlite_schema +} { + {CREATE TABLE abc(a, b, c, FOREIGN KEY(a) REFERENCES abc)} +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 8.0 { + CREATE TABLE abc(a, b, c, CONSTRAINT one FOREIGN KEY(a) REFERENCES abc); +} +do_catchsql_test 8.1 { + ALTER TABLE abc DROP CONSTRAINT one +} {1 {constraint may not be dropped: one}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 9.0 { + CREATE TABLE abc(a, b NOT NULL AS (a+1)) +} +do_execsql_test 9.1 { + ALTER TABLE abc ALTER b DROP NOT NULL; + SELECT sql FROM sqlite_schema; +} {{CREATE TABLE abc(a, b AS (a+1))}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 10.0 { + CREATE TABLE abc(a, b GENERATED ALWAYS AS (a+1)); + INSERT INTO abc VALUES(1), (2); + SELECT * FROM abc; +} {1 2 2 3} + +do_execsql_test 10.1 { + ALTER TABLE abc ALTER b SET NOT NULL; +} +do_catchsql_test 10.2 { + INSERT INTO abc VALUES(NULL); +} {1 {NOT NULL constraint failed: abc.b}} +do_execsql_test 10.3 { + INSERT INTO abc VALUES(3); + ALTER TABLE abc ALTER COLUMN b DROP NOT NULL; +} +do_execsql_test 10.4 { + INSERT INTO abc VALUES(NULL); +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 11.0 { + CREATE TABLE t1(a, b, c); +} + +do_execsql_test 11.1.1 { + ALTER TABLE t1 ADD CONSTRAINT c1 CHECK(a=b) --comment + ; +} + +do_execsql_test 11.1.2 {ALTER TABLE t1 ADD CONSTRAINT c2 CHECK(a=b) --comment} + +do_execsql_test 11.1.3 { + SELECT sql FROM sqlite_schema; +} { + {CREATE TABLE t1(a, b, c, CONSTRAINT c1 CHECK(a=b), CONSTRAINT c2 CHECK(a=b))} +} + +do_execsql_test 11.2.1 { + CREATE TABLE t2(a, b); +} +do_execsql_test 11.2.2 {ALTER TABLE t2 ALTER b SET NOT NULL --new cons} +do_execsql_test 11.2.3 { + SELECT sql FROM sqlite_schema WHERE name='t2'; +} { + {CREATE TABLE t2(a, b NOT NULL)} +} +do_execsql_test 11.2.3 { + ALTER TABLE t2 ALTER b DROP NOT NULL; + SELECT sql FROM sqlite_schema WHERE name='t2'; +} { + {CREATE TABLE t2(a, b)} +} +do_execsql_test 11.2.2 {ALTER TABLE t2 ALTER b SET NOT NULL --new cons +; +} +finish_test + diff --git a/test/alterfault.test b/test/alterfault.test index b6b42973e..c9c0d77ba 100644 --- a/test/alterfault.test +++ b/test/alterfault.test @@ -23,6 +23,9 @@ ifcapable !altertable { do_execsql_test 1.0 { CREATE TABLE t1(a); + CREATE TEMP TRIGGER tr1 AFTER INSERT ON t1 BEGIN + SELECT 123; + END; } faultsim_save_and_close @@ -36,6 +39,40 @@ do_faultsim_test 1.1 -faults oom* -prep { faultsim_test_result {0 {}} } +reset_db +do_execsql_test 2.0 { + CREATE TABLE x1(d, e CONSTRAINT abc NOT NULL, f); +} +faultsim_save_and_close + +foreach {tn sql} { + 1 { ALTER TABLE x1 ADD CHECK (d!=1) } + 2 { ALTER TABLE x1 ADD CONSTRAINT xyz CHECK (f>d+e); } + 3 { ALTER TABLE x1 DROP CONSTRAINT abc } + 4 { ALTER TABLE x1 ALTER f SET NOT NULL } + 5 { ALTER TABLE x1 ALTER e DROP NOT NULL } +} { + do_faultsim_test 2.$tn -faults oom* -prep { + faultsim_restore_and_reopen + } -body { + execsql $::sql + } -test { + faultsim_test_result {0 {}} + } +} +# Test an OOM when returning an error. +# +do_faultsim_test 2.e -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + ALTER TABLE x1 DROP CONSTRAINT nosuchconstraint + } +} -test { + faultsim_test_result \ + {1 {no such constraint: nosuchconstraint}} \ + {1 {SQL logic error}} +} finish_test diff --git a/test/altertab3.test b/test/altertab3.test index 92060fb41..36e08c769 100644 --- a/test/altertab3.test +++ b/test/altertab3.test @@ -778,4 +778,52 @@ do_execsql_test 31.2 { SELECT rr FROM t1 LIMIT 1 } {5.0} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 32.1.0 { + CREATE TABLE t1( + a INT, + b INT, + -- comment with comma + c INT + ); +} +do_execsql_test 32.1.1 { + ALTER TABLE t1 DROP COLUMN c; +} +do_execsql_test 32.1.2 { + SELECT sql FROM sqlite_schema +} {{CREATE TABLE t1( + a INT, + b INT)}} + +reset_db +do_execsql_test 32.2.0 { + CREATE TABLE t1( + a INT, + b INT, + -- comment with, comma + c INT + ); +} +do_execsql_test 32.2.1 { + ALTER TABLE t1 DROP COLUMN c; +} +do_execsql_test 32.2.2 { + SELECT sql FROM sqlite_schema +} {{CREATE TABLE t1( + a INT, + b INT)}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 33.1 { + CREATE TABLE x1(a TEXT, b INTEGER, c CHECK(c!=0)); +} + +do_execsql_test 33.2 { + ALTER TABLE x1 DROP COLUMN b; + SELECT sql FROM sqlite_schema; +} {{CREATE TABLE x1(a TEXT, c CHECK(c!=0))}} + finish_test diff --git a/test/altertrig.test b/test/altertrig.test index 556dc3fea..223feaf8f 100644 --- a/test/altertrig.test +++ b/test/altertrig.test @@ -41,7 +41,7 @@ do_execsql_test 1.0 { CREATE TABLE t4(a); CREATE TRIGGER r1 INSERT ON t1 BEGIN - UPDATE t1 SET d='xyz' FROM t2, t3; + UPDATE t1 SET d='xyz' FROM t2, t3; END; } diff --git a/test/atof2.test b/test/atof2.test new file mode 100644 index 000000000..5a68d1352 --- /dev/null +++ b/test/atof2.test @@ -0,0 +1,35 @@ +# 2026-02-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. +# +#*********************************************************************** +# +# Tests of the sqlite3AtoF() function. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Rounding cases: +# +do_execsql_test atof2-1.0 { + SELECT format('%g',192.496475); +} 192.496 +do_execsql_test atof2-1.1 { + SELECT format('%g',192.496501); +} 192.497 + +load_static_extension db ieee754 +do_execsql_test atof2-2.1 { + SELECT format('%!.30f',ieee754_inc(100.0,-1)); +} 99.9999999999999858 +do_execsql_test atof2-2.2 { + SELECT format('%!.30f',ieee754_inc(100.0,-2)); +} 99.9999999999999716 + +finish_test diff --git a/test/autoindex1.test b/test/autoindex1.test index 1c8ce007f..be41d702c 100644 --- a/test/autoindex1.test +++ b/test/autoindex1.test @@ -185,8 +185,7 @@ do_eqp_test autoindex1-500.1 { QUERY PLAN |--SEARCH t501 USING INTEGER PRIMARY KEY (rowid=?) `--LIST SUBQUERY xxxxxx - |--SCAN t502 - `--CREATE BLOOM FILTER + `--SCAN t502 } do_eqp_test autoindex1-501 { SELECT b FROM t501 diff --git a/test/avfs.test b/test/avfs.test index ffd6b309f..1503e8613 100644 --- a/test/avfs.test +++ b/test/avfs.test @@ -332,9 +332,11 @@ do_test 4.3 { set ofd [open $shdo w] if {$::cliDoesAr} { puts $ofd ".ar -u $shdo" + puts $ofd ".mode list" puts $ofd "select count(*) from sqlar where name = '$shdo';" } else { puts $ofd "insert into sqlar values (1);" + puts $ofd ".mode list" puts $ofd "select count(*) from sqlar;" } puts $ofd ".q" diff --git a/test/bestindex8.test b/test/bestindex8.test index 3ed7f6703..43d9cf6af 100644 --- a/test/bestindex8.test +++ b/test/bestindex8.test @@ -80,17 +80,17 @@ do_execsql_test 1.0 { foreach {tn sql bDistinct idxinsert bConsumed res} { 1 "SELECT a, b FROM vt1" 0 0 0 {a b c d a b c d} - 2 "SELECT DISTINCT a, b FROM vt1" 2 1 1 {a b c d} - 3 "SELECT DISTINCT a FROM vt1" 2 1 1 {a c} + 2 "SELECT DISTINCT a, b FROM vt1" 2 0 1 {a b c d} + 3 "SELECT DISTINCT a FROM vt1" 2 0 1 {a c} 4 "SELECT DISTINCT b FROM vt1" 2 1 0 {b d} - 5 "SELECT DISTINCT b FROM vt1 ORDER BY a" 0 1 1 {b d} - 6 "SELECT DISTINCT t0.c0 FROM vt1, t0 ORDER BY vt1.a" 0 1 1 {1 0} + 5 "SELECT DISTINCT b FROM vt1 ORDER BY a" 3 1 1 {b d} + 6 "SELECT DISTINCT t0.c0 FROM vt1, t0 ORDER BY vt1.a" 3 1 1 {1 0} 7 "SELECT DISTINCT a, b FROM vt1 ORDER BY a, b" 3 0 1 {a b c d} - 8 "SELECT DISTINCT a, b FROM vt1 ORDER BY a" 0 1 1 {a b c d} - 9 "SELECT DISTINCT a FROM vt1 ORDER BY a, b" 0 1 1 {a c} + 8 "SELECT DISTINCT a, b FROM vt1 ORDER BY a" 3 1 1 {a b c d} + 9 "SELECT DISTINCT a FROM vt1 ORDER BY a, b" 3 1 1 {a c} - 10 "SELECT DISTINCT a, b FROM vt1 WHERE b='b'" 2 1 1 {a b} - 11 "SELECT DISTINCT a, b FROM vt1 WHERE +b='b'" 2 1 1 {a b} + 10 "SELECT DISTINCT a, b FROM vt1 WHERE b='b'" 2 0 1 {a b} + 11 "SELECT DISTINCT a, b FROM vt1 WHERE +b='b'" 2 0 1 {a b} } { set ::lBestIndexDistinct "" set ::lOrderByConsumed 0 diff --git a/test/bestindexB.test b/test/bestindexB.test index b50e74fee..5850e35bd 100644 --- a/test/bestindexB.test +++ b/test/bestindexB.test @@ -34,7 +34,7 @@ proc vtab_command {method args} { set orderby [$hdl orderby] if {[info exists ::xbestindex_sql]} { - explain_i $::xbestindex_sql + # explain_i $::xbestindex_sql set ::xbestindex_res [ execsql $::xbestindex_sql ] } diff --git a/test/bestindexF.test b/test/bestindexF.test new file mode 100644 index 000000000..4f49f610d --- /dev/null +++ b/test/bestindexF.test @@ -0,0 +1,294 @@ +# 2025-12-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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix bestindexF + +ifcapable !vtab { + finish_test + return +} + + +proc vtab_command {method args} { + switch -- $method { + xConnect { + return "CREATE TABLE t1(a, b, c)" + } + + xBestIndex { + set hdl [lindex $args 0] + set ::vtab_orderby [$hdl orderby] + set ::vtab_distinct [$hdl distinct] + + if {$::vtab_orderby == "{column 0 desc 0} {column 1 desc 0}" + || $::vtab_orderby == "{column 0 desc 0}" + } { + return [list orderby 1] + } + + return "" + } + + xFilter { + set sql { + SELECT 1, 1, 'a', 555 + UNION ALL + SELECT 2, 1, 'a', NULL + UNION ALL + SELECT 3, 1, 'b', 'text' + UNION ALL + SELECT 4, 2, 'a', 3.14 + UNION ALL + SELECT 5, 2, 'b', 0 + } + return [list sql $sql] + } + } + + return {} +} + +register_tcl_module db + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING tcl(vtab_command) +} + +proc uses_idxinsert {sql} { + return [expr [lsearch [db eval "explain $sql"] IdxInsert]>=0] +} +proc do_idxinsert_test {tn sql res} { + set uses [uses_idxinsert $sql] + uplevel [list do_execsql_test $tn "SELECT $uses ; $sql" $res] +} + +do_idxinsert_test 1.1.1 { + SELECT DISTINCT a, b FROM t1 +} {0 1 a 1 b 2 a 2 b} + +do_test 1.1.2 { + list $::vtab_distinct $::vtab_orderby +} {2 {{column 0 desc 0} {column 1 desc 0}}} + +do_execsql_test 1.3 { + CREATE TABLE t0(c0); + INSERT INTO t0 VALUES(0); + INSERT INTO t0 VALUES(1); +} + +do_idxinsert_test 1.4.1 { + SELECT DISTINCT t0.c0 FROM t1, t0 ORDER BY t1.a; +} {1 0 1} + +do_test 1.4.2 { + list $::vtab_distinct $::vtab_orderby +} {3 {{column 0 desc 0}}} + +#------------------------------------------------------------------------- +# +reset_db +proc vtab_command {method args} { + switch -- $method { + xConnect { + return "CREATE TABLE t1(a, b, c)" + } + + xBestIndex { + set hdl [lindex $args 0] + set ::vtab_orderby [$hdl orderby] + set ::vtab_distinct [$hdl distinct] + + # Set idxNum to 1 if DISTINCT is to be used in xFilter. + # + set idxStr [list ""] + if {$::vtab_distinct==2 || $::vtab_distinct==3} { + set idxStr [list DISTINCT] + } + + set orderby 0 + if {$::vtab_orderby == "{column 0 desc 1}" + || $::vtab_orderby == "{column 0 desc 0}" + } { + set orderby 1 + if {$::vtab_distinct==1 || $::vtab_distinct==2} { + lappend idxStr "ORDER BY ((a+2)%5)" + } else { + set sort "ORDER BY a" + if {$::vtab_orderby == "{column 0 desc 1}"} { + append sort " DESC" + } + lappend idxStr $sort + } + } else { + lappend idxStr "" + } + + return [list orderby $orderby idxstr $idxStr] + return "" + } + + xFilter { + set idxstr [lindex $args 1] + + set distinct [lindex $idxstr 0] + set orderby [lindex $idxstr 1] + set sql " + SELECT $distinct 0, a, b FROM real_t1 $orderby + " + return [list sql $sql] + } + } + + return {} +} + +do_execsql_test 2.0 { + CREATE TABLE real_t1(a, b); + + INSERT INTO real_t1 VALUES (1, 'a'); + INSERT INTO real_t1 VALUES (2, 'a'); + INSERT INTO real_t1 VALUES (1, 'a'); + + INSERT INTO real_t1 VALUES (2, 'b'); + INSERT INTO real_t1 VALUES (1, 'b'); + INSERT INTO real_t1 VALUES (2, 'b'); + + INSERT INTO real_t1 VALUES (3, 'a'); + INSERT INTO real_t1 VALUES (4, 'b'); + INSERT INTO real_t1 VALUES (3, 'a'); + + INSERT INTO real_t1 VALUES (4, 'b'); + INSERT INTO real_t1 VALUES (3, 'a'); + INSERT INTO real_t1 VALUES (4, 'b'); +} + +register_tcl_module db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING tcl(vtab_command) +} + +do_execsql_test 2.1 { + SELECT a, b FROM t1 +} { + 1 a 2 a 1 a + 2 b 1 b 2 b + 3 a 4 b 3 a + 4 b 3 a 4 b +} + +# This is like do_execsql_test, except one value is prepended to the +# expected result - the P4 (idxStr) of the VFilter opcode. It is an error +# if $sql generates two or more VFilter instructions. +# +proc do_vtabsorter_test {tn sql expect} { + set vm [db eval "EXPLAIN $sql"] + + set ii [lsearch $vm VFilter] + set ::res [lindex $vm [expr $ii+4]] + + set ::idx [expr [lsearch $vm IdxInsert]>=0] + + set iSort [lsearch $vm SorterSort] + if {$iSort>=0} { + error "query is using sorter" + } + + uplevel [list do_test $tn.0 { set ::idx } [lindex $expect 0]] + uplevel [list do_test $tn.1 { set ::res } [lindex $expect 1]] + uplevel [list do_execsql_test $tn.2 $sql [lrange $expect 2 end]] +} + +do_vtabsorter_test 2.2 { + SELECT a, b FROM t1 +} { 0 "{} {}" + 1 a 2 a 1 a + 2 b 1 b 2 b + 3 a 4 b 3 a + 4 b 3 a 4 b +} + +do_vtabsorter_test 2.3 { + SELECT DISTINCT a FROM t1 +} { 0 "DISTINCT {ORDER BY ((a+2)%5)}" + 3 4 1 2 +} + +do_vtabsorter_test 2.4 { + SELECT DISTINCT a FROM t1 ORDER BY a +} { 0 "DISTINCT {ORDER BY a}" + 1 2 3 4 +} + +do_vtabsorter_test 2.5 { + SELECT DISTINCT a FROM t1 ORDER BY a DESC +} { 0 "DISTINCT {ORDER BY a DESC}" + 4 3 2 1 +} + +do_vtabsorter_test 2.6 { + SELECT a FROM t1 ORDER BY a +} { 0 "{} {ORDER BY a}" + 1 1 1 + 2 2 2 + 3 3 3 + 4 4 4 +} + +do_vtabsorter_test 2.7 { + SELECT a FROM t1 ORDER BY a DESC +} { 0 "{} {ORDER BY a DESC}" + 4 4 4 + 3 3 3 + 2 2 2 + 1 1 1 +} + +do_vtabsorter_test 2.8 { + SELECT a, count(*) FROM t1 GROUP BY a ORDER BY a +} { 0 "{} {ORDER BY a}" + 1 3 + 2 3 + 3 3 + 4 3 +} + +do_vtabsorter_test 2.9 { + SELECT a, count(*) FROM t1 GROUP BY a ORDER BY a DESC +} { 0 "{} {ORDER BY a DESC}" + 4 3 + 3 3 + 2 3 + 1 3 +} + +do_vtabsorter_test 2.10 { + SELECT a, count(*) FROM t1 GROUP BY a +} { 0 "{} {ORDER BY ((a+2)%5)}" + 3 3 + 4 3 + 1 3 + 2 3 +} + +do_vtabsorter_test 2.11 { + SELECT DISTINCT a, count(*) FROM t1 GROUP BY a +} { 1 "{} {ORDER BY ((a+2)%5)}" + 3 3 + 4 3 + 1 3 + 2 3 +} + +finish_test + diff --git a/test/carray01.test b/test/carray01.test index 86ea06996..b17a481e1 100644 --- a/test/carray01.test +++ b/test/carray01.test @@ -51,6 +51,10 @@ do_test 101 { run_stmt $STMT } {1} do_test 102 { + sqlite3_carray_bind -v2 -malloc $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT +} {1} +do_test 103 { set STMT2 [sqlite3_prepare_v2 db { SELECT DISTINCT typeof(value) FROM carray(?3)} -1] sqlite3_carray_bind $STMT2 3 1 2 3 4 5 6 7 @@ -124,6 +128,10 @@ do_test 160 { sqlite3_carray_bind -double $STMT 3 1 2 3 4 5 6 7 run_stmt $STMT } {1} +do_test 161 { + sqlite3_carray_bind -double -v2 $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT +} {1} do_test 170 { sqlite3_carray_bind -text -static $STMT 3 1 2 3 4 6 7 run_stmt $STMT diff --git a/test/collate5.test b/test/collate5.test index 71d4efe25..b474c39d5 100644 --- a/test/collate5.test +++ b/test/collate5.test @@ -122,9 +122,9 @@ do_test collate5-2.0 { } {} do_test collate5-2.1.1 { - execsql { + string toupper [execsql { SELECT a FROM collate5t1 UNION select a FROM collate5t2; - } + }] } {A B N} do_test collate5-2.1.2 { execsql { @@ -132,10 +132,10 @@ do_test collate5-2.1.2 { } } {A B N a b n} do_test collate5-2.1.3 { - execsql { + string tolower [execsql { SELECT a, b FROM collate5t1 UNION select a, b FROM collate5t2; - } -} {A Apple A apple B Banana b banana N {}} + }] +} {a apple a apple b banana b banana n {}} do_test collate5-2.1.4 { execsql { SELECT a, b FROM collate5t2 UNION select a, b FROM collate5t1; @@ -143,9 +143,9 @@ do_test collate5-2.1.4 { } {A Apple B banana N {} a apple b banana n {}} do_test collate5-2.2.1 { - execsql { + string toupper [execsql { SELECT a FROM collate5t1 EXCEPT select a FROM collate5t2; - } + }] } {N} do_test collate5-2.2.2 { execsql { @@ -153,10 +153,10 @@ do_test collate5-2.2.2 { } } {A a} do_test collate5-2.2.3 { - execsql { + string tolower [execsql { SELECT a, b FROM collate5t1 EXCEPT select a, b FROM collate5t2; - } -} {A Apple N {}} + }] +} {a apple n {}} do_test collate5-2.2.4 { execsql { SELECT a, b FROM collate5t2 EXCEPT select a, b FROM collate5t1 @@ -165,9 +165,9 @@ do_test collate5-2.2.4 { } {A apple a apple} do_test collate5-2.3.1 { - execsql { + string toupper [execsql { SELECT a FROM collate5t1 INTERSECT select a FROM collate5t2; - } + }] } {A B} do_test collate5-2.3.2 { execsql { @@ -175,10 +175,10 @@ do_test collate5-2.3.2 { } } {B b} do_test collate5-2.3.3 { - execsql { + string tolower [execsql { SELECT a, b FROM collate5t1 INTERSECT select a, b FROM collate5t2; - } -} {a apple B banana} + }] +} {a apple b banana} do_test collate5-2.3.4 { execsql { SELECT a, b FROM collate5t2 INTERSECT select a, b FROM collate5t1; diff --git a/test/corruptL.test b/test/corruptL.test index 52adf6fd7..8e3dd6012 100644 --- a/test/corruptL.test +++ b/test/corruptL.test @@ -234,7 +234,7 @@ do_execsql_test 2.1 { do_catchsql_test 2.2 { SELECT b,c FROM t1 ORDER BY a; -} {1 {database disk image is malformed}} +} {1 {out of memory}} #------------------------------------------------------------------------- reset_db diff --git a/test/cost.test b/test/cost.test index 6106caba8..5ef84c2b2 100644 --- a/test/cost.test +++ b/test/cost.test @@ -203,8 +203,8 @@ do_eqp_test 8.2 { } { QUERY PLAN |--SCAN track - |--SEARCH album USING INTEGER PRIMARY KEY (rowid=?) |--SEARCH composer USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH album USING INTEGER PRIMARY KEY (rowid=?) `--USE TEMP B-TREE FOR DISTINCT } diff --git a/test/dblwidth-a.sql b/test/dblwidth-a.sql index 38c219698..bcd60359f 100644 --- a/test/dblwidth-a.sql +++ b/test/dblwidth-a.sql @@ -1,20 +1,50 @@ +#!sqlite3 /* ** Run this script using "sqlite3" to confirm that the command-line ** shell properly handles the output of double-width characters. ** ** https://sqlite.org/forum/forumpost/008ac80276 */ +.testcase 100 .mode box CREATE TABLE data(word TEXT, description TEXT); INSERT INTO data VALUES('〈οὐκέτι〉','Greek without dblwidth <...>'); -.print .mode box SELECT * FROM data; +.check <<END +╭────────────┬──────────────────────────────╮ +│ word │ description │ +╞════════════╪══════════════════════════════╡ +│ 〈οὐκέτι〉 │ Greek without dblwidth <...> │ +╰────────────┴──────────────────────────────╯ +END + +.testcase 200 .mode table -.print .mode table SELECT * FROM data; +.check <<END ++------------+------------------------------+ +| word | description | ++------------+------------------------------+ +| 〈οὐκέτι〉 | Greek without dblwidth <...> | ++------------+------------------------------+ +END + +.testcase 300 .mode qbox -.print .mode qbox SELECT * FROM data; +.check <<END +╭──────────────┬────────────────────────────────╮ +│ word │ description │ +╞══════════════╪════════════════════════════════╡ +│ '〈οὐκέτι〉' │ 'Greek without dblwidth <...>' │ +╰──────────────┴────────────────────────────────╯ +END + +.testcase 400 .mode column -.print .mode column SELECT * FROM data; +.check <<END + word description +---------- ---------------------------- +〈οὐκέτι〉 Greek without dblwidth <...> +END diff --git a/test/distinct2.test b/test/distinct2.test index 980b0b1e3..5de4d3094 100644 --- a/test/distinct2.test +++ b/test/distinct2.test @@ -311,7 +311,7 @@ do_execsql_test 4010 { } do_execsql_test 4020 { SELECT b FROM t1 UNION SELECT 1; -} {1 { }} +} {1 {}} #------------------------------------------------------------------------- # diff --git a/test/dotcmd01.sql b/test/dotcmd01.sql new file mode 100644 index 000000000..751d32659 --- /dev/null +++ b/test/dotcmd01.sql @@ -0,0 +1,63 @@ +#!sqlite3 +# +# 2026-01-30 +# +# 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. +# +#*********************************************************************** +# +# Miscellaneous tests for dot-commands +# +# ./sqlite3 test/dotcmd01.sql +# + +.testcase setup +.open :memory: +.mode tty +.check '' + +# The ".eqp on" setting does not affect the output from .fullschema +# and similar. +# +.testctrl opt -Stat4 +.testcase 100 +CREATE TABLE t1(a,b,c); +WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<300) + INSERT INTO t1(a,b,c) + SELECT n%10, n%30, n%100 FROM c; +CREATE INDEX t1a ON t1(a); +CREATE INDEX t1b ON t1(b); +ANALYZE; +.eqp on +.fullschema +.check <<END +CREATE TABLE t1(a,b,c); +CREATE INDEX t1a ON t1(a); +CREATE INDEX t1b ON t1(b); +ANALYZE sqlite_schema; +INSERT INTO sqlite_stat1 VALUES('t1','t1b','300 10'); +INSERT INTO sqlite_stat1 VALUES('t1','t1a','300 30'); +ANALYZE sqlite_schema; +END + +.testcase 110 +.schema +.check <<END +CREATE TABLE t1(a,b,c); +CREATE INDEX t1a ON t1(a); +CREATE INDEX t1b ON t1(b); +CREATE TABLE sqlite_stat1(tbl,idx,stat); +END + +.testcase 120 +.tables +.check --glob "t1" + +.testcase 130 +.indexes +.check --glob t1a*t1b diff --git a/test/e_expr.test b/test/e_expr.test index 81d2fd172..5c0bfb0c1 100644 --- a/test/e_expr.test +++ b/test/e_expr.test @@ -1744,10 +1744,10 @@ do_execsql_test e_expr-32.2.8 { integer 9223372036854775807 \ integer 9223372036854775807 \ integer 9223372036854775807 \ - real 9.22337203685478e+18 \ - real 9.22337203685478e+18 \ - real 9.22337203685478e+18 \ - real 9.22337203685478e+18 \ + real 9.2233720368547758e+18 \ + real 9.2233720368547758e+18 \ + real 9.2233720368547758e+18 \ + real 9.2233720368547758e+18 \ integer -5 \ integer -5 \ ] diff --git a/test/e_update.test b/test/e_update.test index a13b059b3..2b3341dc7 100644 --- a/test/e_update.test +++ b/test/e_update.test @@ -325,6 +325,8 @@ foreach {tn sql error ac data } { # EVIDENCE-OF: R-43190-62442 In other words, the schema-name. prefix on # the table name of the UPDATE is not allowed within triggers. # +# Update: Unless the trigger is in the temp schema. +# do_update_tests e_update-2.1 -error { qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers } { @@ -339,12 +341,14 @@ do_update_tests e_update-2.1 -error { UPDATE aux.t1 SET a=1, b=2; END; } {} +} - 3 { - CREATE TRIGGER tr1 AFTER DELETE ON t4 BEGIN - UPDATE main.t1 SET a=1, b=2; - END; - } {} +# Qualified table name is allowed as t4 is a temp table. +do_execsql_test e_update-2.1.3 { + CREATE TRIGGER tr1 AFTER DELETE ON t4 BEGIN + UPDATE main.t1 SET a=1, b=2; + END; + DROP TRIGGER tr1; } # EVIDENCE-OF: R-06085-13761 Unless the table to which the trigger is diff --git a/test/e_walckpt.test b/test/e_walckpt.test index 3b1f3b015..4aae52a78 100644 --- a/test/e_walckpt.test +++ b/test/e_walckpt.test @@ -605,14 +605,15 @@ foreach {tn script} { sqlite3 db test.db foreach {tn mode res} { 0 -1001 {1 {SQLITE_MISUSE - not an error}} - 1 -1 {1 {SQLITE_MISUSE - not an error}} - 2 0 {0 {0 -1 -1}} - 3 1 {0 {0 -1 -1}} - 4 2 {0 {0 -1 -1}} - 5 3 {0 {0 -1 -1}} - 6 4 {1 {SQLITE_MISUSE - not an error}} - 7 114 {1 {SQLITE_MISUSE - not an error}} - 8 1000000 {1 {SQLITE_MISUSE - not an error}} + 1 -2 {1 {SQLITE_MISUSE - not an error}} + 2 -1 {0 {0 -1 -1}} + 3 0 {0 {0 -1 -1}} + 4 1 {0 {0 -1 -1}} + 5 2 {0 {0 -1 -1}} + 6 3 {0 {0 -1 -1}} + 7 4 {1 {SQLITE_MISUSE - not an error}} + 8 114 {1 {SQLITE_MISUSE - not an error}} + 9 1000000 {1 {SQLITE_MISUSE - not an error}} } { do_test 4.$tn { list [catch "wal_checkpoint_v2 db $mode" msg] $msg diff --git a/test/eqp.test b/test/eqp.test index 147b5ceaf..d2bdc49e3 100644 --- a/test/eqp.test +++ b/test/eqp.test @@ -124,10 +124,10 @@ do_eqp_test 1.8 { } { QUERY PLAN |--CO-ROUTINE (subquery-xxxxxx) - | `--COMPOUND QUERY - | |--LEFT-MOST SUBQUERY + | `--MERGE (UNION) + | |--LEFT | | `--SCAN CONSTANT ROW - | `--UNION USING TEMP B-TREE + | `--RIGHT | `--SCAN CONSTANT ROW |--SCAN (subquery-xxxxxx) `--SCAN t3 @@ -137,24 +137,26 @@ do_eqp_test 1.9 { } { QUERY PLAN |--CO-ROUTINE abc - | `--COMPOUND QUERY - | |--LEFT-MOST SUBQUERY + | `--MERGE (EXCEPT) + | |--LEFT | | `--SCAN CONSTANT ROW - | `--EXCEPT USING TEMP B-TREE - | `--SCAN t3 - |--SCAN abc - `--SCAN t3 + | `--RIGHT + | |--SCAN t3 + | `--USE TEMP B-TREE FOR ORDER BY + |--SCAN t3 + `--SCAN abc } do_eqp_test 1.10 { SELECT * FROM t3 JOIN (SELECT 1 INTERSECT SELECT a FROM t3 LIMIT 17) AS abc } { QUERY PLAN |--CO-ROUTINE abc - | `--COMPOUND QUERY - | |--LEFT-MOST SUBQUERY + | `--MERGE (INTERSECT) + | |--LEFT | | `--SCAN CONSTANT ROW - | `--INTERSECT USING TEMP B-TREE - | `--SCAN t3 + | `--RIGHT + | |--SCAN t3 + | `--USE TEMP B-TREE FOR ORDER BY |--SCAN abc `--SCAN t3 } @@ -455,10 +457,11 @@ do_eqp_test 4.3.1 { SELECT x FROM t1 UNION SELECT x FROM t2 } { QUERY PLAN - `--COMPOUND QUERY - |--LEFT-MOST SUBQUERY - | `--SCAN t1 - `--UNION USING TEMP B-TREE + `--MERGE (UNION) + |--LEFT + | |--SCAN t1 + | `--USE TEMP B-TREE FOR ORDER BY + `--RIGHT `--SCAN t2 USING COVERING INDEX t2i1 } @@ -466,13 +469,17 @@ do_eqp_test 4.3.2 { SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 } { QUERY PLAN - `--COMPOUND QUERY - |--LEFT-MOST SUBQUERY - | `--SCAN t1 - |--UNION USING TEMP B-TREE - | `--SCAN t2 USING COVERING INDEX t2i1 - `--UNION USING TEMP B-TREE - `--SCAN t1 + `--MERGE (UNION) + |--LEFT + | `--MERGE (UNION) + | |--LEFT + | | |--SCAN t1 + | | `--USE TEMP B-TREE FOR ORDER BY + | `--RIGHT + | `--SCAN t2 USING COVERING INDEX t2i1 + `--RIGHT + |--SCAN t1 + `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 4.3.3 { SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 ORDER BY 1 diff --git a/test/filectrl.test b/test/filectrl.test index 9b1a1c758..eea9a4773 100644 --- a/test/filectrl.test +++ b/test/filectrl.test @@ -36,13 +36,11 @@ do_test filectrl-1.5 { sqlite3 db test_control_lockproxy.db file_control_lockproxy_test db [get_pwd] } {} -ifcapable !winrt { - do_test filectrl-1.6 { - sqlite3 db test.db - set fn [file_control_tempfilename db] - set fn - } {/etilqs_/} -} +do_test filectrl-1.6 { + sqlite3 db test.db + set fn [file_control_tempfilename db] + set fn +} {/etilqs_/} db close forcedelete .test_control_lockproxy.db-conch test.proxy forcedelete test.db test2.db diff --git a/test/fpconv1.test b/test/fpconv1.test index 195fdf990..a93489907 100644 --- a/test/fpconv1.test +++ b/test/fpconv1.test @@ -17,28 +17,82 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl + +# Unusual rendering cases: +# +do_execsql_test fpconv1-1.0 { + SELECT 1.23 - 2.34; +} {-1.1099999999999999} +# ^--- Not -1.11 as you would expect. -1.11 has a different bit pattern + +do_execsql_test fpconv1-1.1 { + SELECT 1.23 * 2.34; +} {2.8781999999999996} +# ^--- Not 2.8782 as you would expect. 2.8782 has a different bit pattern + +# Change significant digits to 15 and get a different result. +sqlite3_db_config db FP_DIGITS 15 +do_execsql_test fpconv1-1.2 { + SELECT 1.23 - 2.34; +} {-1.11} +do_execsql_test fpconv1-1.3 { + SELECT 1.23 * 2.34; +} {2.8782} +sqlite3_db_config db FP_DIGITS 17 + + if {[catch {load_static_extension db decimal} error]} { puts "Skipping decimal tests, hit load error: $error" finish_test; return } sqlite3_create_function db -do_execsql_test fpconv1-1.0 { +do_execsql_test fpconv1-2.0 { WITH RECURSIVE /* Number of random floating-point values to try. - ** On a circa 2016 x64 linux box, this test runs at - ** about 80000 cases per second -------------------vvvvvv */ - c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100000), + ** On a circa 2021 Ryzen 5950X running Mint Linux, and + ** compiled with -O0 -DSQLITE_DEBUG, this test runs at + ** about 150000 cases per second ------------------vvvvvvv */ + c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<500_000), fp(y) AS MATERIALIZED ( SELECT CAST( format('%+d.%019d0e%+03d', random()%10,abs(random()),random()%200) AS real) FROM c ) SELECT y FROM fp - WHERE -log10(abs(decimal_sub(dtostr(y,24),format('%!.24e',y))/y))<15.0; + WHERE -log10(abs(decimal_sub(dtostr(y,24),format('%!.24e',y))/y))<17.0; /* Number of digits of accuracy required -------^^^^ */ } {} # ^---- Expect a empty set as the result. The output is all tested numbers -# that fail to preserve at least 15 significant digits of accuracy. +# that fail to preserve at least 16 significant digits of accuracy. + +######################################################################## +# Random test to ensure that double -> text -> double conversions +# round-trip exactly. +# + +load_static_extension db ieee754 + +do_execsql_test fpconv1-3.0 { + WITH RECURSIVE + c(x,s) AS MATERIALIZED (VALUES(1,random()&0xffefffffffffffff) + UNION ALL + SELECT x+1,random()&0xffefffffffffffff + FROM c WHERE x<1_000_000), + fp(y,s) AS ( + SELECT ieee754_from_int(s),s FROM c + ), + fp2(yt,y,s) AS ( + SELECT y||'', y, s FROM fp + ) + SELECT format('%#016x',s) aS 'orig-hex', + format('%#016x',ieee754_to_int(CAST(yt AS real))) AS 'full-roundtrip', + yt AS 'rendered-as', + decimal_exp(yt,30) AS 'text-decimal', + decimal_exp(ieee754_from_int(s),30) AS 'float-decimal' + FROM fp2 + WHERE ieee754_to_int(CAST(yt AS real))<>s; +} {} +# ^---- Values that fail to round-trip will be reported finish_test diff --git a/test/fptest01.sql b/test/fptest01.sql new file mode 100644 index 000000000..6221760cf --- /dev/null +++ b/test/fptest01.sql @@ -0,0 +1,76 @@ +#!sqlite3 +# +# 2026-03-01 +# +# 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. +# +#*********************************************************************** +# +# Floating-point to text conversions +# + +# Verify that binary64 -> text -> binary64 conversions round-trip +# successfully for 98,256 different edge-case binary64 values. The +# query result is all cases that do not round-trip without change, +# and so the query result should be an empty set. +# +.testcase 100 +.mode list +WITH + i1(i) AS (VALUES(0) UNION ALL SELECT i+1 FROM i1 WHERE i<15), + i2(j) AS (VALUES(0) UNION ALL SELECT j+1 FROM i2 WHERE j<0x7fe), + i3(k) AS (VALUES(0x0000000000000000), + (0x000ffffffffffff0), + (0x0008080808080800)), + fpint(n) AS (SELECT (j<<52)+i+k FROM i2, i1, i3), + fp(n,r) AS (SELECT n, ieee754_from_int(n) FROM fpint) +SELECT n, r FROM fp WHERE r<>(0.0 + (r||'')); +.check '' + +# Another batch of 106,444 edge cases: All postiive floating point +# values that have only a single bit set in the mantissa part of the +# number. +# +.testcase 110 +WITH + i1(i) AS (VALUES(0) UNION ALL SELECT i+1 FROM i1 WHERE i<51), + i2(j) AS (VALUES(0) UNION ALL SELECT j+1 FROM i2 WHERE j<0x7fe), + fpint(n) AS (SELECT (j<<52)+(1<<i) FROM i2, i1), + fp(n,r) AS (SELECT n, ieee754_from_int(n) FROM fpint) +SELECT n, r FROM fp WHERE r<>(0.0 + (r||'')); +.check '' + +# Verify that text -> binary64 conversions agree with system strtod(). +# for 98,256 different edge-cases. +# +.testcase 200 +.mode list +WITH + i1(i) AS (VALUES(0) UNION ALL SELECT i+1 FROM i1 WHERE i<15), + i2(j) AS (VALUES(0) UNION ALL SELECT j+1 FROM i2 WHERE j<0x7fe), + i3(k) AS (VALUES(0x0000000000000000), + (0x000ffffffffffff0), + (0x0008080808080800)), + fpint(n) AS (SELECT (j<<52)+i+k FROM i2, i1, i3), + fp(r) AS (SELECT ieee754_from_int(n) FROM fpint) +SELECT r FROM fp WHERE r<>strtod(r||''); +.check '' + + +# Another batch of 106,444 edge cases: All postiive floating point +# values that have only a single bit set in the mantissa part of the +# number. +# +.testcase 210 +WITH + i1(i) AS (VALUES(0) UNION ALL SELECT i+1 FROM i1 WHERE i<51), + i2(j) AS (VALUES(0) UNION ALL SELECT j+1 FROM i2 WHERE j<0x7fe), + fpint(n) AS (SELECT (j<<52)+(1<<i) FROM i2, i1), + fp(r) AS (SELECT ieee754_from_int(n) FROM fpint) +SELECT r FROM fp WHERE r<>strtod(r||''); +.check '' diff --git a/test/fts3comp1.test b/test/fts3comp1.test index 9f13aaa64..b5077a355 100644 --- a/test/fts3comp1.test +++ b/test/fts3comp1.test @@ -112,4 +112,81 @@ do_catchsql_test 2.2 { CREATE VIRTUAL TABLE t2 USING fts4(x, uncompress=unzip) } {1 {missing compress parameter in fts4 constructor}} +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + PRAGMA trusted_schema = OFF; +} + +set ::myfunc_invoked 0 +proc myfunc {data} { + incr ::myfunc_invoked + return $data +} +db func myfunc myfunc + +do_execsql_test 3.1 { + CREATE VIEW v1 AS SELECT myfunc('xyz'); +} + +do_catchsql_test 3.2 { + SELECT * FROM v1 +} {1 {unsafe use of myfunc()}} + +do_execsql_test 3.3 { + CREATE VIRTUAL TABLE f1 USING fts4(x, compress=myfunc, uncompress=myfunc); +} + +do_catchsql_test 3.4 { + INSERT INTO f1(rowid, x) VALUES(123, 'one two three'); +} {1 {SQL logic error}} + +do_test 3.5 { + set ::myfunc_invoked +} {0} + +do_execsql_test 3.6.1 { + CREATE TABLE t1(x); + CREATE TABLE t2(y); + + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + INSERT INTO t2 VALUES( myfunc(new.x) ); + END; +} + +do_catchsql_test 3.6.2 { + INSERT INTO t1 VALUES('hello world'); +} {1 {unsafe use of myfunc()}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE v1 USING fts4(x, compress=comp, uncompress=uncomp); +} + +proc comp {data} { return $data } +proc uncomp {data} { return $data } + +db func comp comp +db func uncomp uncomp + +do_catchsql_test 4.1 { + INSERT INTO v1 VALUES('one two three'); +} {0 {}} + +db close +sqlite3 db test.db +db func comp -directonly comp + +do_catchsql_test 4.2 { + INSERT INTO v1 VALUES('one two three'); +} {1 {SQL logic error}} + +db func uncomp -directonly uncomp + +do_catchsql_test 4.3 { + SELECT * FROM v1 +} {1 {SQL logic error}} + + finish_test diff --git a/test/fts4content.test b/test/fts4content.test index 980586ea3..8268e734a 100644 --- a/test/fts4content.test +++ b/test/fts4content.test @@ -638,7 +638,6 @@ do_catchsql_test 11.1 { # Check that an fts4 table cannot be its own content table. # reset_db -breakpoint do_execsql_test 12.1.1 { CREATE VIRTUAL TABLE t1 USING fts4(a, content=t1 ); INSERT INTO t1(rowid, a) VALUES(1, 'abc'); @@ -669,6 +668,64 @@ do_catchsql_test 12.2.4 { SELECT count(*) FROM t1; } {1 {SQL logic error}} +#--------------------------------------------------------------------------- +# Check that an fts4 table cannot read from an unsafe vtab in a non-trusted +# schema. +reset_db +do_execsql_test 13.0 { + PRAGMA trusted_schema = off; + CREATE VIRTUAL TABLE t1 USING fts4(data, content=sqlite_dbpage); +} + +do_catchsql_test 13.1 { + INSERT INTO t1(t1) VALUES('rebuild'); +} {1 {SQL logic error}} + +proc vtab_command {method args} { + switch -- $method { + xConnect { + return "CREATE TABLE t1(a)" + } + + xBestIndex { + return "" + } + + xFilter { + return [list sql {SELECT 1, 123}] + } + + xUpdate { + return 123 + } + } + + return {} +} + +register_tcl_module db xyz + +do_execsql_test 13.2.0 { + CREATE VIRTUAL TABLE aa USING tcl(vtab_command); +} + +do_execsql_test 13.2.1 { + INSERT INTO aa VALUES('one two three'); +} + +do_test 13.2.2 { + set ::stmt [sqlite3_prepare_v3 db \ + "INSERT INTO aa VALUES('one two three');" -1 0x00 + ] + sqlite3_finalize $::stmt +} {SQLITE_OK} +do_test 13.2.2 { + list [catch { + set ::stmt [sqlite3_prepare_v3 db \ + "INSERT INTO aa VALUES('one two three');" -1 0x20 + ] + } msg] $msg +} {1 {(1) unsafe use of virtual table "aa"}} finish_test diff --git a/test/fts4merge5.test b/test/fts4merge5.test index 1fad778b9..90870ca34 100644 --- a/test/fts4merge5.test +++ b/test/fts4merge5.test @@ -53,6 +53,9 @@ for {set tn 1} {1} {incr tn} { } } +do_catchsql_test 1.5 { + INSERT INTO x1(x1) VALUES('maxpendinAB64'); +} {1 {SQL logic error}} finish_test diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index a3377770a..f056d2d93 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -1954,6 +1954,7 @@ int main(int argc, char **argv){ char **azSrcDb = 0; /* Array of source database names */ int iSrcDb; /* Loop over all source databases */ int nTest = 0; /* Total number of tests performed */ + int nSliceSkip = 0; /* Skipped due to --slice */ char *zDbName = ""; /* Appreviated name of a source database */ const char *zFailCode = 0; /* Value of the TEST_FAILURE env variable */ int cellSzCkFlag = 0; /* --cell-size-check */ @@ -2520,6 +2521,7 @@ int main(int argc, char **argv){ if( isDbSql(pSql->a, pSql->sz) ){ if( iSliceSz>0 && (nTest%iSliceSz)!=iSliceIdx ){ nTest++; + nSliceSkip++; continue; } sqlite3_snprintf(sizeof(g.zTestName), g.zTestName, "sqlid=%d",pSql->id); @@ -2582,6 +2584,7 @@ int main(int argc, char **argv){ const char *zVfs = "inmem"; if( iSliceSz>0 && (nTest%iSliceSz)!=iSliceIdx ){ nTest++; + nSliceSkip++; continue; } sqlite3_snprintf(sizeof(g.zTestName), g.zTestName, "sqlid=%d,dbid=%d", @@ -2693,12 +2696,13 @@ int main(int argc, char **argv){ if( briefFlag ){ sqlite3_int64 iElapse = timeOfDay() - iBegin; if( iSliceSz>0 ){ - printf("%s %s: slice %d/%d of %d tests, %d.%03d seconds\n", - pathTail(argv[0]), pathTail(g.zDbFile), - iSliceIdx, iSliceSz, nTest, - (int)(iElapse/1000), (int)(iElapse%1000)); + printf( + "%s %s: 0 errors out of %d tests, slice %d/%d, %d.%03d seconds\n", + pathTail(argv[0]), pathTail(g.zDbFile), + nTest - nSliceSkip, iSliceIdx, iSliceSz, + (int)(iElapse/1000), (int)(iElapse%1000)); }else{ - printf("%s %s: 0 errors, %d tests, %d.%03d seconds\n", + printf("%s %s: 0 errors out of %d tests, %d.%03d seconds\n", pathTail(argv[0]), pathTail(g.zDbFile), nTest, (int)(iElapse/1000), (int)(iElapse%1000)); } diff --git a/test/fuzzinvariants.c b/test/fuzzinvariants.c index 6a5cfda68..9341b2056 100644 --- a/test/fuzzinvariants.c +++ b/test/fuzzinvariants.c @@ -261,9 +261,12 @@ int fuzz_invariant( sqlite3_finalize(pCk); /* Invariants do not necessarily work if there are virtual tables - ** involved in the query */ - rc = sqlite3_prepare_v2(db, - "SELECT 1 FROM bytecode(?1) WHERE opcode='VOpen'", -1, &pCk, 0); + ** or scalar subqueries involved in the query */ + rc = sqlite3_prepare_v2(db, + "SELECT 1 FROM bytecode(?1)" + " WHERE opcode='VOpen' OR" + " (opcode='Explain' AND p4 GLOB 'SCALAR SUBQUERY*')", + -1, &pCk, 0); if( rc==SQLITE_OK ){ if( eVerbosity>=2 ){ char *zSql = sqlite3_expanded_sql(pCk); diff --git a/test/import01.sql b/test/import01.sql new file mode 100644 index 000000000..6e029b8b2 --- /dev/null +++ b/test/import01.sql @@ -0,0 +1,217 @@ +#!sqlite3 +# +# 2025-12-28 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases for the ".import" command of the CLI. +# To run these tests: +# +# ./sqlite3 test/import01.sql +# + +.testcase setup +.open :memory: +.mode tty +.check '' + +.testcase 100 +CREATE TABLE t1(a,b,c); +.import -csv <<END t1 +111,222,333 +abc,def,ghi +END +SELECT * FROM t1; +.check <<END +╭───────┬───────┬───────╮ +│ a │ b │ c │ +╞═══════╪═══════╪═══════╡ +│ '111' │ '222' │ '333' │ +│ abc │ def │ ghi │ +╰───────┴───────┴───────╯ +END + +.testcase 110 +DELETE FROM t1; +.import -colsep ";" <<END t1 +this;is a;test +one;two;three +END +SELECT * FROM t1; +.check <<END +╭──────┬──────┬───────╮ +│ a │ b │ c │ +╞══════╪══════╪═══════╡ +│ this │ is a │ test │ +│ one │ two │ three │ +╰──────┴──────┴───────╯ +END + +.testcase 120 +DELETE FROM t1; +.import -colsep "," -rowsep ';' <<END t1 +this,is a,test;one,two,three; +END +SELECT * FROM t1; +.check <<END +╭──────┬──────┬───────╮ +│ a │ b │ c │ +╞══════╪══════╪═══════╡ +│ this │ is a │ test │ +│ one │ two │ three │ +╰──────┴──────┴───────╯ +END + +.testcase 130 +DELETE FROM t1; +.import -csv -colsep "," -rowsep "\n" <<END t1 +this,"is a","test ""with quotes""" +"second",,"line" +END +SELECT * FROM t1; +.check <<END +╭────────┬──────┬────────────────────╮ +│ a │ b │ c │ +╞════════╪══════╪════════════════════╡ +│ this │ is a │ test "with quotes" │ +│ second │ │ line │ +╰────────┴──────┴────────────────────╯ +END +.testcase 131 +DELETE FROM t1; +.import -ascii -colsep "," -rowsep "\n" <<END t1 +this,"is a","test ""with quotes""" +"second",,"line" +END +SELECT * FROM t1; +.check <<END +╭──────────┬────────┬────────────────────────╮ +│ a │ b │ c │ +╞══════════╪════════╪════════════════════════╡ +│ this │ "is a" │ "test ""with quotes""" │ +│ "second" │ │ "line" │ +╰──────────┴────────┴────────────────────────╯ +END + +.testcase 140 +DROP TABLE t1; +.import -csv <<END t1 +"abc","def","xy z" +"This","is","a" +"test","...", +END +SELECT * FROM t1; +.check <<END +╭──────┬─────┬──────╮ +│ abc │ def │ xy z │ +╞══════╪═════╪══════╡ +│ This │ is │ a │ +│ test │ ... │ │ +╰──────┴─────┴──────╯ +END +.testcase 141 +SELECT sql FROM sqlite_schema WHERE name='t1'; +.check <<END +╭───────────────────────────────────╮ +│ sql │ +╞═══════════════════════════════════╡ +│ CREATE TABLE "t1"( │ +│ "abc" ANY, "def" ANY, "xy z" ANY) │ +╰───────────────────────────────────╯ +END + +.testcase 150 +DROP TABLE t1; +.import -csv -v <<END t1 +"abc","def","xy z" +"This","is","a" +"test","...", +END +SELECT * FROM t1; +.check <<END +Column separator ",", row separator "\n" +CREATE TABLE "main"."t1"( +"abc" ANY, "def" ANY, "xy z" ANY) + +Added 2 rows with 0 errors using 3 lines of input +╭──────┬─────┬──────╮ +│ abc │ def │ xy z │ +╞══════╪═════╪══════╡ +│ This │ is │ a │ +│ test │ ... │ │ +╰──────┴─────┴──────╯ +END + +.testcase 160 +DROP TABLE t1; +.import -csv -schema TEMP <<END t2 +"x" +"abcdef" +END +SELECT * FROM t2; +.tables +.check <<END +╭────────╮ +│ x │ +╞════════╡ +│ abcdef │ +╰────────╯ +temp.t2 +END + +.testcase 170 +.import -csv -skip 2 <<END t3 +a,b,c +d,e,f +g,h,i +j,k,l +m,n,o +END +SELECT * FROM t3; +.check <<END +╭───┬───┬───╮ +│ g │ h │ i │ +╞═══╪═══╪═══╡ +│ j │ k │ l │ +│ m │ n │ o │ +╰───┴───┴───╯ +END + +.testcase 180 +DELETE FROM t3; +.import -csv -skip 7 <<END t3 +a,b,c +d,e,f +g,h,i +j,k,l +m,n,o +END +SELECT * FROM t3; +.check <<END +END + +.testcase 200 --error-prefix ERROR: +.import -csv +.check 'ERROR: Missing FILE argument' +.testcase 201 +.import -csv file1.csv +.check 'ERROR: Missing TABLE argument' +.testcase --error-prefix test-error: 202 +.import -csvxyzzy file1.csv +.check <<END +test-error: .import -csvxyzzy file1.csv +test-error: ^--- unknown option +END +.testcase 203 +.import -csv file1.csv t4 -colsep +.check <<END +test-error: .import -csv file1.csv t4 -colsep +test-error: missing argument ---^ +END diff --git a/test/imposter1.sql b/test/imposter1.sql new file mode 100644 index 000000000..9604b43f4 --- /dev/null +++ b/test/imposter1.sql @@ -0,0 +1,32 @@ +#!sqlite3 +# +# 2025-12-16 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases for the .imposter command. +# +.mode box -reset +.testcase 100 +CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c INT); +INSERT INTO t1 VALUES(1,'two',3),(4,'five',6); +CREATE INDEX t1bc ON t1(b,c); +.imposter T1BC x1 +----------^^^^--- Different case that the original +SELECT * FROM x1; +.check <<END +CREATE TABLE "x1"("b","c","_ROWID_",PRIMARY KEY("b","c","_ROWID_"))WITHOUT ROWID; +╭──────┬───┬─────────╮ +│ b │ c │ _ROWID_ │ +╞══════╪═══╪═════════╡ +│ five │ 6 │ 4 │ +│ two │ 3 │ 1 │ +╰──────┴───┴─────────╯ +END diff --git a/test/insert5.test b/test/insert5.test index 1e58902e0..3ae99a5d2 100644 --- a/test/insert5.test +++ b/test/insert5.test @@ -96,22 +96,19 @@ do_test insert5-2.8 { } } {1} -# UPDATE: Using a column from the outer query (main.id) in the GROUP BY -# or ORDER BY of a sub-query is no longer supported. -# -# do_test insert5-2.9 { -# uses_temp_table { -# INSERT INTO b -# SELECT * FROM main -# WHERE id > 10 AND (SELECT count(*) FROM v2 GROUP BY main.id) -# } -# } {} do_test insert5-2.9 { catchsql { INSERT INTO b SELECT * FROM main WHERE id > 10 AND (SELECT count(*) FROM v2 GROUP BY main.id) } -} {1 {no such column: main.id}} +} {0 {}} +do_execsql_test insert5-2.10 { + CREATE TABLE t1(a INT); + INSERT INTO t1 VALUES(2); + CREATE TABLE t2(c INT, d INT); + INSERT INTO t2 VALUES(3,4),(10,NULL); + SELECT (SELECT c FROM t2 ORDER BY coalesce(d,a) LIMIT 1) FROM t1; +} {10} finish_test diff --git a/test/intck01.sql b/test/intck01.sql new file mode 100644 index 000000000..b1996aeeb --- /dev/null +++ b/test/intck01.sql @@ -0,0 +1,23 @@ +#!sqlite3 +# +# 2026-03-01 +# +# 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. +# +#*********************************************************************** +# +# Bug report sqlite.org/forum/forumpost/efc9bc9cb3 +# +.testcase 100 +.mode quote +.intck 1 +SELECT parse_create_index('CREATE IDEX i ON t("x',0); +.check <<END +1 steps, 0 errors +NULL +END diff --git a/test/join.test b/test/join.test index b33a7560a..a1ce7da0c 100644 --- a/test/join.test +++ b/test/join.test @@ -1369,4 +1369,45 @@ do_execsql_test join-32.3 { INNER JOIN t2 ON +y IS z; } {NULL NULL 123 NULL} +# 2025-12-24 https://sqlite.org/forum/forumpost/11a53f2bad +# +# Chained omit-noop-join optimization +# +reset_db +db null NULL +do_execsql_test join-33.1 { + CREATE TABLE t1(a1 INTEGER PRIMARY KEY, b1); + CREATE TABLE t2(a2 INTEGER PRIMARY KEY, b2); + CREATE TABLE t3(a3 INTEGER PRIMARY KEY, b3); + CREATE TABLE t4(a4 INTEGER PRIMARY KEY, b4); + INSERT INTO t1 VALUES(1,11),(2,12),(3,13), (5,15); + INSERT INTO t2 VALUES(1,21), (3,23),(4,24),(5,25); + INSERT INTO t3 VALUES (2,32),(3,33), (5,35); + INSERT INTO t4 VALUES(1,41),(2,42), (4,44),(5,45); + CREATE VIEW vchain AS + SELECT a1, b1, b2, b3, b4 + FROM t1 LEFT JOIN t2 ON a1=a2 + LEFT JOIN t3 ON a2=a3 + LEFT JOIN t4 ON a3=a4; +} +do_execsql_test join-33.2 { + SELECT a1 FROM vchain ORDER BY a1; +} {1 2 3 5} +do_eqp_test join-33.2-eqp { + SELECT a1 FROM vchain ORDER BY a1; +} { + QUERY PLAN + `--SCAN t1 +} +do_execsql_test join-33.3 { + SELECT a1, b2 FROM vchain ORDER BY a1; +} {1 21 2 NULL 3 23 5 25} +do_eqp_test join-33.3-eqp { + SELECT a1, b2 FROM vchain ORDER BY a1; +} { + QUERY PLAN + |--SCAN t1 + `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN +} + finish_test diff --git a/test/joinI.test b/test/joinI.test index 577ca4c2c..3390afc6e 100644 --- a/test/joinI.test +++ b/test/joinI.test @@ -121,5 +121,52 @@ do_execsql_test 5.1 { INNER JOIN child2 ON child2.child2key = parent1.child2key; } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t5(a, d); + CREATE TABLE t6(a, e); + INSERT INTO t5 VALUES(1, 'red'); + INSERT INTO t6 VALUES(0, 1000); + + CREATE TABLE t7(x); + CREATE TABLE t8(y); +} + +do_catchsql_test 6.1 { + SELECT * FROM t6 CROSS JOIN (t7 RIGHT JOIN t8 ON (t6.a)); +} {1 {no such column: t6.a}} + +do_catchsql_test 6.4 { + SELECT * FROM t7 RIGHT JOIN t8 ON (t6.a) CROSS JOIN t6; +} {1 {ON clause references tables to its right}} + +do_catchsql_test 6.5 { + SELECT * FROM + (SELECT * FROM t7 RIGHT JOIN t8 ON (t6.a) CROSS JOIN t6); +} {1 {ON clause references tables to its right}} + +do_catchsql_test 6.6 { + SELECT *, NOT EXISTS ( + SELECT * FROM t7 RIGHT JOIN t8 ON (t6.a) CROSS JOIN t6 + ) FROM t5; +} {1 {ON clause references tables to its right}} + +do_catchsql_test 6.7 { + SELECT *, NOT EXISTS ( + SELECT 1 + EXCEPT + SELECT 11 FROM t7 RIGHT JOIN t8 ON (t6.a) CROSS JOIN t6 + ) FROM t5; +} {1 {ON clause references tables to its right}} + +do_catchsql_test 6.8 { + SELECT *, NOT EXISTS ( + SELECT 11 FROM t7 RIGHT JOIN t8 ON (t6.a) CROSS JOIN t6 + EXCEPT + SELECT 1 + ) FROM t5; +} {1 {ON clause references tables to its right}} + finish_test diff --git a/test/json102.test b/test/json102.test index 54a0e1e0e..54f66a83f 100644 --- a/test/json102.test +++ b/test/json102.test @@ -375,6 +375,27 @@ do_execsql_test json102-440-3 { do_execsql_test json102-440-4 { SELECT json(jsonb_remove(jsonb('[0,1,2,3,4]'),'$[2]')); } {{[0,1,3,4]}} +do_execsql_test json102-445-1 { + SELECT json_remove('[0,1,2,3,4]','$[5]'); +} {{[0,1,2,3,4]}} +do_execsql_test json102-445-2 { + SELECT json_remove('[0,1,2,3,4]','$[6]'); +} {{[0,1,2,3,4]}} +do_execsql_test json102-445-3 { + SELECT json_remove('[0,1,2,3,4]','$[4294967295]'); +} {{[0,1,2,3,4]}} +do_execsql_test json102-445-4 { + SELECT json_remove('[0,1,2,3,4]','$[4294967296]'); +} {{[0,1,2,3,4]}} +do_execsql_test json102-445-5 { + SELECT json_remove('[0,1,2,3,4]','$[4294967297]'); +} {{[0,1,2,3,4]}} +do_execsql_test json102-445-6 { + SELECT json_remove('[0,1,2,3,4]','$[42949672950]'); +} {{[0,1,2,3,4]}} +do_execsql_test json102-445-7 { + SELECT json_remove('[0,1,2,3,4]','$[42949672960]'); +} {{[0,1,2,3,4]}} do_execsql_test json102-450 { SELECT json_remove('[0,1,2,3,4]','$[2]','$[0]'); } {{[1,3,4]}} diff --git a/test/json103.test b/test/json103.test index f94217ac1..9eadf29f8 100644 --- a/test/json103.test +++ b/test/json103.test @@ -27,6 +27,9 @@ do_execsql_test json103-100 { do_catchsql_test json103-101 { SELECT json_group_array(a) FROM t1; } {1 {JSON cannot hold BLOB values}} +do_execsql_test json103-102 { + SELECT quote(jsonb_group_array(a)) FROM t1 WHERE a<0 AND typeof(a)!='blob'; +} {X'0B'} do_execsql_test json103-110 { SELECT json_group_array(a) FROM t1 WHERE rowid BETWEEN 31 AND 39; @@ -45,6 +48,10 @@ do_execsql_test json103-200 { do_catchsql_test json103-201 { SELECT json_group_object(c,a) FROM t1; } {1 {JSON cannot hold BLOB values}} +do_execsql_test json103-202 { + SELECT quote(jsonb_group_object(c,a)) FROM t1 WHERE a<0 AND typeof(a)!='blob'; +} {X'0C'} + do_execsql_test json103-210 { SELECT json_group_object(c,a) FROM t1 diff --git a/test/json105.test b/test/json105.test index 509db94e1..4a5572cf0 100644 --- a/test/json105.test +++ b/test/json105.test @@ -29,6 +29,11 @@ json_extract_test 30 {'$.b[#-2]'} {'[2,3]'} json_extract_test 31 {'$.b[#-02]'} {'[2,3]'} json_extract_test 40 {'$.b[#-3]'} 1 json_extract_test 50 {'$.b[#-4]'} NULL +json_extract_test 51 {'$.b[#-4296967295]'} NULL +json_extract_test 52 {'$.b[#-4296967296]'} NULL +json_extract_test 53 {'$.b[#-4296967297]'} NULL +json_extract_test 54 {'$.b[#-42969672950]'} NULL +json_extract_test 55 {'$.b[#-42969672960]'} NULL json_extract_test 60 {'$.b[#-2][#-1]'} 3 json_extract_test 70 {'$.b[0]','$.b[#-1]'} {'[1,4]'} diff --git a/test/json109.test b/test/json109.test new file mode 100644 index 000000000..1631a1f0f --- /dev/null +++ b/test/json109.test @@ -0,0 +1,72 @@ +# 2026-01-17 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases for json_array_insert(). +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix json109 + +do_execsql_test 1.1 { + SELECT json_array_insert('[1,2,3]','$[0]',999,'$[0]',888); +} {{[888,999,1,2,3]}} +do_execsql_test 1.2 { + SELECT json_array_insert('[1,2,3]','$[0]',999,'$[#]',888); +} {{[999,1,2,3,888]}} +do_execsql_test 1.3 { + SELECT json_array_insert('[1,2,3]','$[1]',888); +} {{[1,888,2,3]}} +do_execsql_test 1.4 { + SELECT json_array_insert('[1,2,3]','$[2]',888); +} {{[1,2,888,3]}} +do_execsql_test 1.5 { + SELECT json_array_insert('[1,2,3]','$[3]',888); +} {{[1,2,3,888]}} +do_execsql_test 1.6 { + SELECT json_array_insert('[1,2,3]','$[#-1]',888); +} {{[1,2,888,3]}} +do_execsql_test 1.7 { + SELECT json_array_insert('[1,2,3]','$[#-2]',888); +} {{[1,888,2,3]}} +do_execsql_test 1.8 { + SELECT json_array_insert('[1,2,3]','$[#-3]',888); +} {{[888,1,2,3]}} +do_execsql_test 1.9 { + SELECT json_array_insert('[1,2,3]','$[#-4]',888); +} {{[1,2,3]}} + +do_catchsql_test 2.1 { + SELECT json_array_insert('{a:[1,2,3]}','$.a',888); +} {1 {not an array element: '$.a'}} +do_catchsql_test 2.2 { + SELECT json_array_insert('{a:[1,2,3]}','$.b',888); +} {1 {not an array element: '$.b'}} +do_catchsql_test 2.3 { + SELECT json_array_insert('{a:[1,2,3]}','$.b[0]',888); +} {0 {{{"a":[1,2,3],"b":[888]}}}} +do_catchsql_test 2.4 { + SELECT json_array_insert('{a:[1,2,3]}','$.b.c.d[0]',888); +} {0 {{{"a":[1,2,3],"b":{"c":{"d":[888]}}}}}} +do_catchsql_test 2.5 { + SELECT json_array_insert('{a:[1,2,3]}','$.b.c.d[0',888); +} {1 {not an array element: '$.b.c.d[0'}} +do_catchsql_test 2.6 { + SELECT json_array_insert('{a:[1,2,3]}','$.b.c.d',888); +} {1 {not an array element: '$.b.c.d'}} +do_catchsql_test 2.7 { + SELECT json_array_insert('{a:[1,2,3]}','$[0]',888); +} {0 {{{"a":[1,2,3]}}}} +do_catchsql_test 2.8 { + SELECT json_array_insert('{a:[1,2,3]}','$.b[0]',888,'$.a[1]','999','$.c',0); +} {1 {not an array element: '$.c'}} + +finish_test diff --git a/test/misc5.test b/test/misc5.test index 43ee2781a..80b8d3c67 100644 --- a/test/misc5.test +++ b/test/misc5.test @@ -595,7 +595,7 @@ do_test misc5-7.1.2 { } append sql "0$tail); SELECT * FROM t1;" catchsql $sql -} {0 900} +} {1 {Recursion limit}} # Parser stack overflow is silently ignored when it occurs while parsing the diff --git a/test/modeA.sql b/test/modeA.sql new file mode 100644 index 000000000..4e62093b2 --- /dev/null +++ b/test/modeA.sql @@ -0,0 +1,303 @@ +#!sqlite3 +# +# 2025-11-12 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases for the ".mode" command of the CLI. +# To run these tests: +# +# ./sqlite3 test/modeA.sql +# +# +.open :memory: +CREATE TABLE t1(a,b,c,d,e); +INSERT INTO t1 VALUES(1,2.5,'three',x'4444',NULL); +INSERT INTO t1 SELECT b,c,d,e,a FROM t1; +INSERT INTO t1 SELECT d,e,a,b,c FROM t1; +.mode box + +.testcase 100 +SELECT * FROM t1; +.check <<END +╭─────┬───────┬───────┬───────┬───────╮ +│ a │ b │ c │ d │ e │ +╞═════╪═══════╪═══════╪═══════╪═══════╡ +│ 1 │ 2.5 │ three │ DD │ │ +│ 2.5 │ three │ DD │ │ 1 │ +│ DD │ │ 1 │ 2.5 │ three │ +│ │ 1 │ 2.5 │ three │ DD │ +╰─────┴───────┴───────┴───────┴───────╯ +END + +.testcase 110 +.mode --null xyz +SELECT * FROM t1; +.check <<END +╭─────┬───────┬───────┬───────┬───────╮ +│ a │ b │ c │ d │ e │ +╞═════╪═══════╪═══════╪═══════╪═══════╡ +│ 1 │ 2.5 │ three │ DD │ xyz │ +│ 2.5 │ three │ DD │ xyz │ 1 │ +│ DD │ xyz │ 1 │ 2.5 │ three │ +│ xyz │ 1 │ 2.5 │ three │ DD │ +╰─────┴───────┴───────┴───────┴───────╯ +END + +# Default output mode is qbox --quote relaxed +# +.mode tty --wrap 10 +CREATE TABLE t2(a,b,c,d); +INSERT INTO t2 VALUES(1,2.5,'three',x'4444'); +INSERT INTO t2 VALUES('The quick fox jumps over the lazy brown dog',2,3,4); +INSERT INTO t2 VALUES('10','', -1.25,NULL); +INSERT INTO t2 VALUES('a,b,c','"Double-Quoted"','-1.25','NULL'); +.testcase 120 +SELECT * FROM t2; +.check <<END +╭────────────┬────────────┬─────────┬─────────╮ +│ a │ b │ c │ d │ +╞════════════╪════════════╪═════════╪═════════╡ +│ 1 │ 2.5 │ three │ x'4444' │ +├────────────┼────────────┼─────────┼─────────┤ +│ The quick │ 2 │ 3 │ 4 │ +│ fox jumps │ │ │ │ +│ over the │ │ │ │ +│ lazy brown │ │ │ │ +│ dog │ │ │ │ +├────────────┼────────────┼─────────┼─────────┤ +│ '10' │ │ -1.25 │ NULL │ +├────────────┼────────────┼─────────┼─────────┤ +│ a,b,c │ "Double- │ '-1.25' │ 'NULL' │ +│ │ Quoted" │ │ │ +╰────────────┴────────────┴─────────┴─────────╯ +END +.testcase 130 +.mode +.check <<END +.mode qbox --limits on --quote relaxed --sw auto --textjsonb on +END +.testcase 140 +.mode -v +.check <<END +.mode qbox --align "" --border on --blob-quote auto --colsep "" --escape auto --limits on --null "NULL" --quote relaxed --rowsep "" --sw auto --tablename "" --textjsonb on --titles on --widths "" --wordwrap off --wrap 10 +END +.testcase 150 --error-prefix "Error:" +.mode foo +.check <<END +Error: .mode foo +Error: ^--- unknown mode +Error: Use ".help .mode" for more info +END + +.testcase 160 +.mode --null xyzzy -v +.output -glob ' --null "xyzzy"' +.testcase 170 +.mode -null abcde -v +.output -glob ' --null "abcde"' + +# Test cases for the ".explain off" command +.mode box -reset +.testcase 180 +EXPLAIN SELECT * FROM t1; +.output --notglob *────* --keep +.output --notglob "* id │ parent │ notused │ detail *" --keep +.output --glob "* Init *" +.testcase 190 +EXPLAIN QUERY PLAN SELECT * FROM t1; +.output --glob "*`--SCAN *" +.explain off +.testcase 200 +EXPLAIN SELECT * FROM t1; +.output --glob *────* +.testcase 210 +EXPLAIN QUERY PLAN SELECT * FROM t1; +.output --glob "* id │ parent │ notused │ detail *" +.explain auto + +# Test cases for limit settings in the .mode command. +.testcase 300 +.mode box --reset +.mode +.check <<END +.mode box +END +.testcase 310 +.mode --limits 5,300,20 +.mode +.check <<END +.mode box --limits on +END +.testcase 320 +.mode --limits 5,300,19 +.mode +.check <<END +.mode box --limits 5,300,19 +END +.testcase 330 +.mode --limits 0,0,0 +.mode -v +.check <<END +.mode box --align "" --border on --blob-quote auto --colsep "" --escape auto --limits off --null "" --quote off --rowsep "" --sw 0 --tablename "" --textjsonb off --titles on --widths "" --wordwrap off +END + +.testcase 400 +.mode --linelimit 123 +.mode +.check <<END +.mode box --limits 123,0,0 +END + +.testcase 410 +.mode --linelimit 0 -charlimit 123 +.mode +.check <<END +.mode box --limits 0,123,0 +END + +.testcase 420 +.mode --charlimit 0 -titlelimit 123 +.mode +.check <<END +.mode box --limits 0,0,123 +END + +.testcase 430 +.mode list +.mode +.check <<END +.mode list +END + +.testcase 440 +.mode -limits 0,123,0 +.mode +.check <<END +.mode list --limits 0,123,0 +END + +.testcase 450 +.mode -limits 123,0,0 +.mode +.check <<END +.mode list +END + +# --titlelimit functionality +# +.testcase 500 +.mode line --limits off --titlelimit 20 +SELECT a AS 'abcdefghijklmnopqrstuvwxyz', b FROM t2 WHERE c=3; +.check <<END +abcdefghijklmnopq...: The quick fox jumps over the lazy brown dog + b: 2 +END +.testcase 510 +.mode line --titlelimit 10 +SELECT a AS 'abcdefghijklmnopqrstuvwxyz', b FROM t2 WHERE c=3; +.check <<END +abcdefg...: The quick fox jumps over the lazy brown dog + b: 2 +END +.testcase 520 +.mode line --titlelimit 2 +SELECT a AS 'abcdefghijklmnopqrstuvwxyz', b FROM t2 WHERE c=3; +.check <<END +ab: The quick fox jumps over the lazy brown dog + b: 2 +END +.testcase 530 +.mode line --titlelimit 4 +SELECT a AS 'abcd', b FROM t2 WHERE c=3; +.check <<END +abcd: The quick fox jumps over the lazy brown dog + b: 2 +END +.testcase 540 +.mode line --titlelimit 3 +SELECT a AS 'abcd', b FROM t2 WHERE c=3; +.check <<END +...: The quick fox jumps over the lazy brown dog + b: 2 +END + +# https://sqlite.org/forum/forumpost/2025-12-31T19:14:24z +# +# For legacy compatibility, ".header" settings are not changed +# by ".mode" unless the --title or --reset option is used on .mode. +# +.testcase 600 +DROP TABLE IF EXISTS t1; +CREATE TABLE t1(a,b,c); +INSERT INTO t1 VALUES(1,2,3); +.header on +.mode csv +SELECT * FROM t1; +.check --glob a,b,c* + +.testcase 610 +.mode csv -reset +SELECT * FROM t1; +.check 1,2,3 + +.testcase 620 +.mode tty +.mode csv +.header on +SELECT * FROM t1; +.check --glob a,b,c* + +.testcase 630 +.mode tty +.mode csv --title on +SELECT * FROM t1; +.check --glob a,b,c* +.testcase 631 +.mode tty +.mode csv --title off +SELECT * FROM t1; +.check 1,2,3 + +# Verification of claims about .insert mode in the climode.html +# documentation. +.testcase 700 +CREATE TABLE tbl1(one,two); +INSERT INTO tbl1 VALUES('hello!',10),('goodbye',20); +.mode insert new_table +SELECT * FROM tbl1; +.check <<END +INSERT INTO new_table VALUES('hello!',10); +INSERT INTO new_table VALUES('goodbye',20); +END +.testcase 710 +.mode insert new_table --titles on +SELECT * FROM tbl1; +.check <<END +INSERT INTO new_table(one,two) VALUES('hello!',10); +INSERT INTO new_table(one,two) VALUES('goodbye',20); +END +.testcase 720 +.mode insert new_table --titles off +SELECT * FROM tbl1; +.check <<END +INSERT INTO new_table VALUES('hello!',10); +INSERT INTO new_table VALUES('goodbye',20); +END + +# QRF reports an error if the string is too big. +# +.testcase 800 +.mode box +.limit length 1000 +WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<100) +SELECT hex(randomblob(100)) c; +.check -glob "*: string or blob too big" +.limit length 10000000 diff --git a/test/mutex1.test b/test/mutex1.test index cb189a7a8..de291f4c9 100644 --- a/test/mutex1.test +++ b/test/mutex1.test @@ -115,6 +115,10 @@ ifcapable threadsafe1&&shared_cache { } } { + ifcapable thread_misuse_warnings { + if {$mode ne "serialized"} continue + } + # For journal_mode=memory, the static_prng mutex is not required. This # is because the header of an in-memory journal does not contain # any random bytes, and so no call to sqlite3_randomness() is made. @@ -177,16 +181,18 @@ ifcapable threadsafe1&&shared_cache { # Open and use a connection in "nomutex" mode. Test that no recursive # mutexes are obtained. - do_test mutex1.3.1 { - catch {db close} - clear_mutex_counters - sqlite3 db test.db -nomutex 1 - execsql { SELECT * FROM abc } - } {1 2 3 1 2 3 1 2 3} - do_test mutex1.3.2 { - mutex_counters counters - set counters(recursive) - } {0} + ifcapable !thread_misuse_warnings { + do_test mutex1.3.1 { + catch {db close} + clear_mutex_counters + sqlite3 db test.db -nomutex 1 + execsql { SELECT * FROM abc } + } {1 2 3 1 2 3 1 2 3} + do_test mutex1.3.2 { + mutex_counters counters + set counters(recursive) + } {0} + } } # Test the sqlite3_db_mutex() function. diff --git a/test/notnull2.test b/test/notnull2.test index 67d7c26a8..f49a13b56 100644 --- a/test/notnull2.test +++ b/test/notnull2.test @@ -66,7 +66,7 @@ do_vmstep_test 1.5.2 { SELECT count(*) FROM t2 WHERE EXISTS( SELECT 1 FROM t1 WHERE t1.a=450 AND t2.c IS NULL ) -} 4000 {0} +} 5000 {0} #------------------------------------------------------------------------- reset_db diff --git a/test/offset1.test b/test/offset1.test index 5b04bd836..fb68f0d02 100644 --- a/test/offset1.test +++ b/test/offset1.test @@ -190,6 +190,20 @@ do_execsql_test offset1-2.0 { ORDER BY salary asc); } {} do_execsql_test offset1-2.1 { + SELECT * FROM v ORDER BY +id; +} { + 11 Diane London hr 70 + 12 Bob London hr 78 + 21 Emma London it 84 + 22 Grace Berlin it 90 + 23 Henry London it 104 + 24 Irene Berlin it 104 + 25 Frank Berlin it 120 + 31 Cindy Berlin sales 96 + 32 Dave London sales 96 + 33 Alice Berlin sales 100 +} +do_execsql_test offset1-2.2 { SELECT * FROM v LIMIT 5 OFFSET 2; } { 22 Grace Berlin it 90 @@ -198,5 +212,19 @@ do_execsql_test offset1-2.1 { 11 Diane London hr 70 33 Alice Berlin sales 100 } +do_execsql_test offset1-2.3 { + SELECT * FROM v LIMIT 3 OFFSET 6; +} { + 33 Alice Berlin sales 100 + 23 Henry London it 104 + 24 Irene Berlin it 104 +} +do_execsql_test offset1-2.4 { + SELECT * FROM v LIMIT 3 OFFSET 1; +} { + 32 Dave London sales 96 + 22 Grace Berlin it 90 + 21 Emma London it 84 +} finish_test diff --git a/test/qrf01.test b/test/qrf01.test new file mode 100644 index 000000000..3ae027957 --- /dev/null +++ b/test/qrf01.test @@ -0,0 +1,1167 @@ +# 2025-11-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. +# +#*********************************************************************** +# +# Test cases for the Query Result Formatter (QRF) +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix qrf01 + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES(1,2.5,'three'),(x'424c4f42',NULL,'Ἀμήν'); +} + +do_test 1.10 { + set result "\n[db format {SELECT * FROM t1}]" +} { +╭──────┬─────┬───────╮ +│ a │ b │ c │ +╞══════╪═════╪═══════╡ +│ 1 │ 2.5 │ three │ +│ BLOB │ │ Ἀμήν │ +╰──────┴─────┴───────╯ +} +do_test 1.11a { + set result "\n[db format -title off {SELECT * FROM t1}]" +} { +╭──────┬─────┬───────╮ +│ 1 │ 2.5 │ three │ +│ BLOB │ │ Ἀμήν │ +╰──────┴─────┴───────╯ +} +do_test 1.11b { + set result "\n[db format -text sql {SELECT * FROM t1}]" +} { +╭─────────────┬─────┬─────────╮ +│ a │ b │ c │ +╞═════════════╪═════╪═════════╡ +│ 1 │ 2.5 │ 'three' │ +│ x'424c4f42' │ │ 'Ἀμήν' │ +╰─────────────┴─────┴─────────╯ +} +do_test 1.11c { + set result "\n[db format -text sql -border off {SELECT * FROM t1}]" +} { + a │ b │ c +═════════════╪═════╪═════════ + 1 │ 2.5 │ 'three' + x'424c4f42' │ │ 'Ἀμήν' +} +do_test 1.11d { + set result "\n[db format -text relaxed -blob sql -border off \ + {SELECT * FROM t1}]" +} { + a │ b │ c +═════════════╪═════╪═══════ + 1 │ 2.5 │ three + x'424c4f42' │ │ Ἀμήν +} +do_test 1.12 { + set result "\n[db format -text csv {SELECT * FROM t1}]" +} { +╭────────────────────┬─────┬────────╮ +│ a │ b │ c │ +╞════════════════════╪═════╪════════╡ +│ 1 │ 2.5 │ three │ +│ "\102\114\117\102" │ │ "Ἀμήν" │ +╰────────────────────┴─────┴────────╯ +} +do_test 1.13 { + set result "\n[db format -text csv -blob hex {SELECT * FROM t1}]" +} { +╭──────────┬─────┬────────╮ +│ a │ b │ c │ +╞══════════╪═════╪════════╡ +│ 1 │ 2.5 │ three │ +│ 424c4f42 │ │ "Ἀμήν" │ +╰──────────┴─────┴────────╯ +} +do_test 1.14 { + catch {db format -text unk -blob hex {SELECT * FROM t1}} res + set res +} {bad -text "unk": must be auto, csv, html, json, plain, relaxed, sql, or tcl} +do_test 1.15 { + catch {db format -text sql -blob unk {SELECT * FROM t1}} res + set res +} {bad BLOB encoding (-blob) "unk": must be auto, hex, json, tcl, text, sql, or size} +do_test 1.16 { + catch {db format -text sql -style unk {SELECT * FROM t1}} res + set res +} {bad format style (-style) "unk": must be auto, box, column, count, csv, eqp, explain, html, insert, jobject, json, line, list, markdown, quote, stats, stats-est, stats-vm, or table} + + +do_test 1.20 { + set result "\n[db format -style box {SELECT * FROM t1}]" +} { +╭──────┬─────┬───────╮ +│ a │ b │ c │ +╞══════╪═════╪═══════╡ +│ 1 │ 2.5 │ three │ +│ BLOB │ │ Ἀμήν │ +╰──────┴─────┴───────╯ +} + +do_test 1.30 { + set result "\n[db format -style table {SELECT * FROM t1}]" +} { ++------+-----+-------+ +| a | b | c | ++------+-----+-------+ +| 1 | 2.5 | three | +| BLOB | | Ἀμήν | ++------+-----+-------+ +} +do_test 1.31 { + set result "\n[db format -style table -title off {SELECT * FROM t1}]" +} { ++------+-----+-------+ +| 1 | 2.5 | three | +| BLOB | | Ἀμήν | ++------+-----+-------+ +} +do_test 1.32 { + set result "\n[db format -style table -border off {SELECT * FROM t1}]" +} { + a | b | c +------+-----+------- + 1 | 2.5 | three + BLOB | | Ἀμήν +} +do_test 1.33 { + set result "\n[db format -style table -border off \ + -screenwidth 15 \ + {SELECT * FROM t1}]" +} { + a | b | c +----+---+----- + 1|2.5|three +BLOB| |Ἀμήν +} +do_test 1.34 { + set result "\n[db format -style box -border off \ + -screenwidth 30 \ + {SELECT * FROM t1}]" +} { + a │ b │ c +══════╪═════╪═══════ + 1 │ 2.5 │ three + BLOB │ │ Ἀμήν +} +do_test 1.35 { + set result "\n[db format -style box -border off \ + -screenwidth 15 \ + {SELECT * FROM t1}]" +} { + a │ b │ c +════╪═══╪═════ + 1│2.5│three +BLOB│ │Ἀμήν +} + +do_test 1.40 { + set result "\n[db format -style column {SELECT * FROM t1}]" +} { +a b c +---- --- ----- +1 2.5 three +BLOB Ἀμήν +} +do_test 1.41 { + set result "\n[db format -style column -title off {SELECT * FROM t1}]" +} { +1 2.5 three +BLOB Ἀμήν +} + +do_test 1.50 { + db format -style count {SELECT * FROM t1} +} 2 + +do_test 1.60a { + db format -style list -columnsep , -rowsep \r\n -text csv -blob tcl {SELECT * FROM t1} +} "1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" +do_test 1.60b { + db format -style csv -columnsep xyz -rowsep pqr -text sql -blob sql {SELECT * FROM t1} +} "1,2.5,three\r\nx'424c4f42',,\"Ἀμήν\"\r\n" +do_test 1.61a { + db format -style list -columnsep , -rowsep \r\n -text csv -title auto -blob tcl {SELECT * FROM t1} +} "a,b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" +do_test 1.61b { + db format -style csv -title auto -blob tcl {SELECT * FROM t1} +} "a,b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" +do_test 1.62a { + db format -style list -columnsep , -rowsep \r\n -text csv -title csv -blob tcl {SELECT a AS 'a x y', b, c FROM t1} +} "\"a x y\",b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" +do_test 1.62b { + db format -style csv -title csv -blob tcl {SELECT a AS 'a x y', b, c FROM t1} +} "\"a x y\",b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" + +do_test 1.70 { + set result "\n[db format -style html {SELECT * FROM t1}]" +} { +<TR> +<TD>1 +<TD>2.5 +<TD>three +</TR> +<TR> +<TD>BLOB +<TD>null +<TD>Ἀμήν +</TR> +} +do_test 1.71 { + set result "\n[db format -style html -title auto {SELECT * FROM t1}]" +} { +<TR> +<TH>a +<TH>b +<TH>c +</TR> +<TR> +<TD>1 +<TD>2.5 +<TD>three +</TR> +<TR> +<TD>BLOB +<TD>null +<TD>Ἀμήν +</TR> +} + +do_test 1.80 { + set result "\n[db format -style insert {SELECT * FROM t1}]" +} { +INSERT INTO tab VALUES(1,2.5,'three'); +INSERT INTO tab VALUES(x'424c4f42',NULL,'Ἀμήν'); +} +do_test 1.81 { + set result "\n[db format -style insert -tablename t1 {SELECT * FROM t1}]" +} { +INSERT INTO t1 VALUES(1,2.5,'three'); +INSERT INTO t1 VALUES(x'424c4f42',NULL,'Ἀμήν'); +} +do_test 1.82 { + set result "\n[db format -style insert -tablename t1 -title auto \ + {SELECT * FROM t1}]" +} { +INSERT INTO t1(a,b,c) VALUES(1,2.5,'three'); +INSERT INTO t1(a,b,c) VALUES(x'424c4f42',NULL,'Ἀμήν'); +} +do_test 1.83 { + set result "\n[db format -style insert -tablename drop -title on \ + {SELECT a AS "a-b", b, c AS "123" FROM t1}]" +} { +INSERT INTO "drop"("a-b",b,"123") VALUES(1,2.5,'three'); +INSERT INTO "drop"("a-b",b,"123") VALUES(x'424c4f42',NULL,'Ἀμήν'); +} + +do_test 1.90 { + set result "\n[db format -style json {SELECT * FROM t1}]" +} { +[{"a":1,"b":2.5,"c":"three"}, +{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν"}] +} +do_test 1.91 { + set result "\n[db format -style jobject {SELECT * FROM t1}]" +} { +{"a":1,"b":2.5,"c":"three"} +{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν"} +} +do_test 1.92 { + set result "\n[db format -style jobject {SELECT *, unistr('abc\u000a123\u000d\u000axyz') AS xyz FROM t1}]" +} { +{"a":1,"b":2.5,"c":"three","xyz":"abc\n123\r\nxyz"} +{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν","xyz":"abc\n123\r\nxyz"} +} + +do_test 1.100 { + set result "\n[db format -style line {SELECT * FROM t1}]" +} { +a: 1 +b: 2.5 +c: three + +a: BLOB +b: +c: Ἀμήν +} +do_test 1.101 { + set result "\n[db format -style line -null (NULL) {SELECT * FROM t1}]" +} { +a: 1 +b: 2.5 +c: three + +a: BLOB +b: (NULL) +c: Ἀμήν +} +do_test 1.102 { + set result "\n[db format -style line -null (NULL) -columnsep { = } \ + -text sql {SELECT * FROM t1}]" +} { +a = 1 +b = 2.5 +c = 'three' + +a = x'424c4f42' +b = (NULL) +c = 'Ἀμήν' +} + +do_test 1.110 { + set result "\n[db format -style list {SELECT * FROM t1}]" +} { +1|2.5|three +BLOB||Ἀμήν +} +do_test 1.111 { + set result "\n[db format -style list -title on {SELECT * FROM t1}]" +} { +a|b|c +1|2.5|three +BLOB||Ἀμήν +} +do_test 1.112 { + set result "\n[db format -style list -title on -text sql -null NULL \ + -title plain {SELECT * FROM t1}]" +} { +a|b|c +1|2.5|'three' +x'424c4f42'|NULL|'Ἀμήν' +} +do_test 1.118 { + set rc [catch {db format -style list -title unk {SELECT * FROM t1}} res] + lappend rc $res +} {1 {bad -title "unk": must be off, on, auto, csv, html, json, plain, relaxed, sql, or tcl}} + + +do_test 1.120 { + set result "\n[db format -style markdown {SELECT * FROM t1}]" +} { +| a | b | c | +|------|-----|-------| +| 1 | 2.5 | three | +| BLOB | | Ἀμήν | +} +do_test 1.121 { + set result "\n[db format -style markdown -title off {SELECT * FROM t1}]" +} { +| 1 | 2.5 | three | +| BLOB | | Ἀμήν | +} + +do_test 1.130 { + set result "\n[db format -style quote {SELECT * FROM t1}]" +} { +1,2.5,'three' +x'424c4f42',NULL,'Ἀμήν' +} +do_test 1.131 { + set result "\n[db format -style quote -title on {SELECT * FROM t1}]" +} { +'a','b','c' +1,2.5,'three' +x'424c4f42',NULL,'Ἀμήν' +} + + +do_execsql_test 2.0 { + DELETE FROM t1; + INSERT INTO t1 VALUES(1,2,'The quick fox jumps over the lazy brown dog.'); +} +do_test 2.1 { + set result "\n[db format -widths {5 -5 19} -wordwrap on \ + {SELECT * FROM t1}]" +} { +╭───────┬───────┬─────────────────────╮ +│ a │ b │ c │ +╞═══════╪═══════╪═════════════════════╡ +│ 1 │ 2 │ The quick fox jumps │ +│ │ │ over the lazy brown │ +│ │ │ dog. │ +╰───────┴───────┴─────────────────────╯ +} +do_test 2.2 { + set result "\n[db format -widths {5 -5 19} -wordwrap off \ + {SELECT * FROM t1}]" +} { +╭───────┬───────┬─────────────────────╮ +│ a │ b │ c │ +╞═══════╪═══════╪═════════════════════╡ +│ 1 │ 2 │ The quick fox jumps │ +│ │ │ over the lazy brown │ +│ │ │ dog. │ +╰───────┴───────┴─────────────────────╯ +} +do_test 2.3 { + set result "\n[db format -widths {5 -5 18} -wordwrap on \ + {SELECT * FROM t1}]" +} { +╭───────┬───────┬────────────────────╮ +│ a │ b │ c │ +╞═══════╪═══════╪════════════════════╡ +│ 1 │ 2 │ The quick fox │ +│ │ │ jumps over the │ +│ │ │ lazy brown dog. │ +╰───────┴───────┴────────────────────╯ +} +do_test 2.4 { + set result "\n[db format -widths {5 -5 -18} -wordwrap on \ + {SELECT * FROM t1}]" +} { +╭───────┬───────┬────────────────────╮ +│ a │ b │ c │ +╞═══════╪═══════╪════════════════════╡ +│ 1 │ 2 │ The quick fox │ +│ │ │ jumps over the │ +│ │ │ lazy brown dog. │ +╰───────┴───────┴────────────────────╯ +} +do_test 2.5 { + set result "\n[db format -widths {5 -5 19} -wordwrap off \ + {SELECT * FROM t1}]" +} { +╭───────┬───────┬─────────────────────╮ +│ a │ b │ c │ +╞═══════╪═══════╪═════════════════════╡ +│ 1 │ 2 │ The quick fox jumps │ +│ │ │ over the lazy brown │ +│ │ │ dog. │ +╰───────┴───────┴─────────────────────╯ +} +do_test 2.6 { + set result "\n[db format -widths {5 -5 18} -wordwrap off \ + {SELECT * FROM t1}]" +} { +╭───────┬───────┬────────────────────╮ +│ a │ b │ c │ +╞═══════╪═══════╪════════════════════╡ +│ 1 │ 2 │ The quick fox jump │ +│ │ │ s over the lazy br │ +│ │ │ own dog. │ +╰───────┴───────┴────────────────────╯ +} +do_test 2.7 { + set result "\n[db format -widths {5 5 18} -wordwrap yes \ + -align {left center right} -titlealign right \ + {SELECT * FROM t1}]" +} { +╭───────┬───────┬────────────────────╮ +│ a │ b │ c │ +╞═══════╪═══════╪════════════════════╡ +│ 1 │ 2 │ The quick fox │ +│ │ │ jumps over the │ +│ │ │ lazy brown dog. │ +╰───────┴───────┴────────────────────╯ +} +do_test 2.8 { + set result "\n[db format -widths {5 8 11} -wordwrap yes \ + -align {auto auto center} -titlealign left \ + -defaultalign right \ + {SELECT * FROM t1}]" +} { +╭───────┬──────────┬─────────────╮ +│ a │ b │ c │ +╞═══════╪══════════╪═════════════╡ +│ 1 │ 2 │ The quick │ +│ │ │ fox jumps │ +│ │ │ over the │ +│ │ │ lazy brown │ +│ │ │ dog. │ +╰───────┴──────────┴─────────────╯ +} +do_test 2.9 { + catch {db format -align {auto xyz 123} {SELECT * FROM t1}} res + set res +} {bad column alignment (-align) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w} +do_test 2.10 { + catch {db format -defaultalign xyz {SELECT * FROM t1}} res + set res +} {bad default alignment (-defaultalign) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w} +do_test 2.11 { + catch {db format -titlealign xyz {SELECT * FROM t1}} res + set res +} {bad title alignment (-titlealign) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w} + + +do_execsql_test 2.30 { + UPDATE t1 SET c='Η γρήγορη αλεπού πηδάει πάνω από το τεμπέλικο καφέ σκυλί'; + SELECT hex(c) FROM t1; +} {CE9720CEB3CF81CEAECEB3CEBFCF81CEB720CEB1CEBBCEB5CF80CEBFCF8D20CF80CEB7CEB4CEACCEB5CEB920CF80CEACCEBDCF8920CEB1CF80CF8C20CF84CEBF20CF84CEB5CEBCCF80CEADCEBBCEB9CEBACEBF20CEBACEB1CF86CEAD20CF83CEBACF85CEBBCEAF} +do_test 2.31 { + set result "\n[db format -widths {5 -5 18} -wordwrap on \ + {SELECT * FROM t1}]" +} { +╭───────┬───────┬────────────────────╮ +│ a │ b │ c │ +╞═══════╪═══════╪════════════════════╡ +│ 1 │ 2 │ Η γρήγορη αλεπού │ +│ │ │ πηδάει πάνω από το │ +│ │ │ τεμπέλικο καφέ │ +│ │ │ σκυλί │ +╰───────┴───────┴────────────────────╯ +} +do_test 2.32 { + set result "\n[db format -widths {5 5 18} -align {left center center} -wordwrap on \ + {SELECT * FROM t1}]" +} { +╭───────┬───────┬────────────────────╮ +│ a │ b │ c │ +╞═══════╪═══════╪════════════════════╡ +│ 1 │ 2 │ Η γρήγορη αλεπού │ +│ │ │ πηδάει πάνω από το │ +│ │ │ τεμπέλικο καφέ │ +│ │ │ σκυλί │ +╰───────┴───────┴────────────────────╯ +} + + +do_execsql_test 3.0 { + DELETE FROM t1; + INSERT INTO t1 VALUES(1,2,unistr('abc\u001b[1;31m123\u001b[0mxyz')); +} +do_test 3.1 { + set result "\n[db format {SELECT * FROM t1}]" +} { +╭───┬───┬────────────────────────╮ +│ a │ b │ c │ +╞═══╪═══╪════════════════════════╡ +│ 1 │ 2 │ abc^[[1;31m123^[[0mxyz │ +╰───┴───┴────────────────────────╯ +} +do_test 3.2 { + set result "\n[db format -esc off {SELECT * FROM t1}]" + string map [list \033 X] $result +} { +╭───┬───┬───────────╮ +│ a │ b │ c │ +╞═══╪═══╪═══════════╡ +│ 1 │ 2 │ abcX[1;31m123X[0mxyz │ +╰───┴───┴───────────╯ +} +do_test 3.3 { + set result "\n[db format -esc symbol {SELECT * FROM t1}]" +} { +╭───┬───┬──────────────────────╮ +│ a │ b │ c │ +╞═══╪═══╪══════════════════════╡ +│ 1 │ 2 │ abc␛[1;31m123␛[0mxyz │ +╰───┴───┴──────────────────────╯ +} +do_test 3.4 { + set result "\n[db format -esc ascii {SELECT * FROM t1}]" +} { +╭───┬───┬────────────────────────╮ +│ a │ b │ c │ +╞═══╪═══╪════════════════════════╡ +│ 1 │ 2 │ abc^[[1;31m123^[[0mxyz │ +╰───┴───┴────────────────────────╯ +} +do_test 3.5 { + catch {db format -esc unk {SELECT * FROM t1}} res + set res +} {bad control character escape (-esc) "unk": must be ascii, auto, off, or symbol} + +do_execsql_test 4.0 { + DELETE FROM t1; + INSERT INTO t1 VALUES(json('{a:5,b:6}'), jsonb('{c:1,d:2}'), 99); +} +do_test 4.1 { + set result "\n[db format -text sql {SELECT * FROM t1}]" +} { +╭─────────────────┬───────────────────────┬────╮ +│ a │ b │ c │ +╞═════════════════╪═══════════════════════╪════╡ +│ '{"a":5,"b":6}' │ x'8c1763133117641332' │ 99 │ +╰─────────────────┴───────────────────────┴────╯ +} +do_test 4.2 { + set result "\n[db format -text sql -textjsonb on {SELECT * FROM t1}]" +} { +╭─────────────────┬────────────────────────┬────╮ +│ a │ b │ c │ +╞═════════════════╪════════════════════════╪════╡ +│ '{"a":5,"b":6}' │ jsonb('{"c":1,"d":2}') │ 99 │ +╰─────────────────┴────────────────────────┴────╯ +} +do_test 4.3 { + set result "\n[db format -text plain -textjsonb on -wrap 11 \ + {SELECT a AS json, b AS jsonb, c AS num FROM t1}]" +} { +╭─────────────┬─────────────┬─────╮ +│ json │ jsonb │ num │ +╞═════════════╪═════════════╪═════╡ +│ {"a":5,"b": │ {"c":1,"d": │ 99 │ +│ 6} │ 2} │ │ +╰─────────────┴─────────────┴─────╯ +} + +do_execsql_test 5.0 { + DROP TABLE t1; + CREATE TABLE t1(name, mtime, value); + INSERT INTO t1 VALUES + ('entry-one',1708791504,zeroblob(300)), + (unistr('one\u000atwo\u000athree'),1333206973,NULL), + ('sample-jsonb',1333101221,jsonb('{ + "alpha":53.11688723, + "beta":"qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth);", + "zeta":[15,null,1333206973,"fd8ffe000104a46494600010101"]}')); +} +do_test 5.1 { + set sql {SELECT name, mtime, datetime(mtime,'unixepoch') AS time, + value FROM t1 ORDER BY mtime} + set result "\n[db format -style line -screenwidth 60 -blob sql \ + -text sql -wordwrap off -linelimit 77 \ + -columnsep { = } $sql]" +} { + name = 'sample-jsonb' +mtime = 1333101221 + time = '2012-03-30 09:53:41' +value = x'cc7c57616c706861b535332e31313638383732334762657461 + c73071726657696474685072696e7428702c20702d3e704f7574 + 2c202d702d3e752e734c696e652e6d78436f6c577468293b477a + 657461cb2c23313500a331333333323036393733c71b66643866 + 6665303030313034613436343934363030303130313031' + + name = unistr('one\u000atwo\u000athree') +mtime = 1333206973 + time = '2012-03-31 15:16:13' +value = + + name = 'entry-one' +mtime = 1708791504 + time = '2024-02-24 16:18:24' +value = x'00000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000' +} +do_test 5.2a { + set sql {SELECT name, mtime, datetime(mtime,'unixepoch') AS time, + value FROM t1 ORDER BY mtime} + set result "\n[db format -style line -screenwidth 60 -blob sql \ + -text plain -esc off -textjsonb yes -columnsep { = }\ + -wordwrap yes -linelimit 3 $sql]" +} { + name = sample-jsonb +mtime = 1333101221 + time = 2012-03-30 09:53:41 +value = {"alpha":53.11688723,"beta":"qrfWidthPrint(p, + p->pOut, -p->u.sLine.mxColWth);","zeta":[15,null, + 1333206973,"fd8ffe000104a46494600010101"]} + + name = one + two + three +mtime = 1333206973 + time = 2012-03-31 15:16:13 +value = + + name = entry-one +mtime = 1708791504 + time = 2024-02-24 16:18:24 +value = x'00000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000 + ... +} +set sqlnolabel "SELECT name, mtime, datetime(mtime,'unixepoch'),\ + value FROM t1 ORDER BY mtime" +do_test 5.2b { + set result "\n[db format -style line -screenwidth 60 -blob sql \ + -text plain -esc off -textjsonb no -titlelimit 12 \ + -wordwrap yes -linelimit 3 $sqlnolabel ]" +} { + name: sample-jsonb + mtime: 1333101221 +datetime(...: 2012-03-30 09:53:41 + value: x'cc7c57616c706861b535332e31313638383732334762 + 657461c73071726657696474685072696e7428702c2070 + 2d3e704f75742c202d702d3e752e734c696e652e6d7843 + ... + + name: one + two + three + mtime: 1333206973 +datetime(...: 2012-03-31 15:16:13 + value: + + name: entry-one + mtime: 1708791504 +datetime(...: 2024-02-24 16:18:24 + value: x'00000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000 + ... +} +set sql "SELECT name, mtime, datetime(mtime,'unixepoch') AS time,\ + value FROM t1 ORDER BY mtime" +do_test 5.3a { + set result "\n[db format -style box -widths {0 10 10 14}\ + -align {left right right center} \ + -blob sql \ + -text plain -esc off -textjsonb no \ + -wordwrap yes -linelimit 2 $sql]" +} { +╭──────────────┬────────────┬────────────┬────────────────╮ +│ name │ mtime │ time │ value │ +╞══════════════╪════════════╪════════════╪════════════════╡ +│ sample-jsonb │ 1333101221 │ 2012-03-30 │ x'cc7c57616c70 │ +│ │ │ 09:53:41 │ 6861b535332e31 │ +│ │ │ │ ... │ +├──────────────┼────────────┼────────────┼────────────────┤ +│ one │ 1333206973 │ 2012-03-31 │ │ +│ two │ │ 15:16:13 │ │ +│ ... │ │ │ │ +├──────────────┼────────────┼────────────┼────────────────┤ +│ entry-one │ 1708791504 │ 2024-02-24 │ x'000000000000 │ +│ │ │ 16:18:24 │ 00000000000000 │ +│ │ │ │ ... │ +╰──────────────┴────────────┴────────────┴────────────────╯ +} +do_test 5.3b { + set result "\n[db format -style box -widths {0 10 0 14} \ + -align {left right right center} \ + -blob sql -titlelimit 12 \ + -text plain -esc off -textjsonb no \ + -wordwrap yes -linelimit 2 $sqlnolabel ]" +} { +╭──────────────┬────────────┬─────────────────────┬────────────────╮ +│ name │ mtime │ datetime(... │ value │ +╞══════════════╪════════════╪═════════════════════╪════════════════╡ +│ sample-jsonb │ 1333101221 │ 2012-03-30 09:53:41 │ x'cc7c57616c70 │ +│ │ │ │ 6861b535332e31 │ +│ │ │ │ ... │ +├──────────────┼────────────┼─────────────────────┼────────────────┤ +│ one │ 1333206973 │ 2012-03-31 15:16:13 │ │ +│ two │ │ │ │ +│ ... │ │ │ │ +├──────────────┼────────────┼─────────────────────┼────────────────┤ +│ entry-one │ 1708791504 │ 2024-02-24 16:18:24 │ x'000000000000 │ +│ │ │ │ 00000000000000 │ +│ │ │ │ ... │ +╰──────────────┴────────────┴─────────────────────┴────────────────╯ +} +do_test 5.3c { + set result "\n[db format -style table -widths {0 10 10 14}\ + -align {center right right right} \ + -blob sql \ + -text plain -esc off -textjsonb no \ + -wordwrap yes -linelimit 2 $sql]" +} { ++--------------+------------+------------+----------------+ +| name | mtime | time | value | ++--------------+------------+------------+----------------+ +| sample-jsonb | 1333101221 | 2012-03-30 | x'cc7c57616c70 | +| | | 09:53:41 | 6861b535332e31 | +| | | | ... | ++--------------+------------+------------+----------------+ +| one | 1333206973 | 2012-03-31 | | +| two | | 15:16:13 | | +| ... | | | | ++--------------+------------+------------+----------------+ +| entry-one | 1708791504 | 2024-02-24 | x'000000000000 | +| | | 16:18:24 | 00000000000000 | +| | | | ... | ++--------------+------------+------------+----------------+ +} +do_test 5.3c { + set result "\n[db format -style column -widths {0 10 10 14}\ + -align {center right right right} \ + -blob sql \ + -text plain -esc off -textjsonb no \ + -wordwrap yes -linelimit 2 $sql]" +} { + name mtime time value +------------ ---------- ---------- -------------- +sample-jsonb 1333101221 2012-03-30 x'cc7c57616c70 + 09:53:41 6861b535332e31 + ... + + one 1333206973 2012-03-31 + two 15:16:13 + ... + + entry-one 1708791504 2024-02-24 x'000000000000 + 16:18:24 00000000000000 + ... +} +do_test 5.4 { + db eval { + CREATE TABLE t2(a,b,c,d,e); + WITH v(x) AS (SELECT 'abcdefghijklmnopqrstuvwxyz') + INSERT INTO t2 SELECT x,x,x,x,x FROM v; + } + set sql {SELECT char(0x61,0xa,0x62,0xa,0x63,0xa,0x64) a, + mtime b, mtime c, mtime d, mtime e FROM t1} + set result "\n[db format -style box -widths {1 2 3 4 5}\ + -linelimit 3 -wordwrap off {SELECT *, 'x' AS x FROM t2}]" +} { +╭────┬────┬─────┬──────┬───────┬───╮ +│ a │ b │ c │ d │ e │ x │ +╞════╪════╪═════╪══════╪═══════╪═══╡ +│ ab │ ab │ abc │ abcd │ abcde │ x │ +│ cd │ cd │ def │ efgh │ fghij │ │ +│ ef │ ef │ ghi │ ijkl │ klmno │ │ +│ .. │ .. │ ... │ ... │ ... │ │ +╰────┴────┴─────┴──────┴───────┴───╯ +} + +do_execsql_test 6.0 { + DELETE FROM t2; + INSERT INTO t2 VALUES + (1, 2.5, 'three', x'342028666f757229', null); +} +do_test 6.1a { + set result "\n[db format -style list -null NULL \ + -text tcl -columnsep , \ + {SELECT * FROM t2}]" +} { +1,2.5,"three","\064\040\050\146\157\165\162\051",NULL +} + +do_execsql_test 7.0 { + CREATE TABLE t7(a,b); + INSERT INTO t7 VALUES('abcdefghijklmnop', + 'abcぁdefかghiのjklはmnop'); +} +do_test 7.1 { + set result "\n[db format -style list -charlimit 13 \ + {SELECT * FROM t7}]" +} { +abcdefghijklm...|abcぁdefかghi... +} +do_test 7.2 { + set result "\n[db format -style list -charlimit 14 \ + {SELECT * FROM t7}]" +} { +abcdefghijklmn...|abcぁdefかghi... +} +do_test 7.3 { + set result "\n[db format -style list -charlimit 15 \ + {SELECT * FROM t7}]" +} { +abcdefghijklmno...|abcぁdefかghiの... +} +do_test 7.4 { + set result "\n[db format -style list -charlimit 16 \ + {SELECT * FROM t7}]" +} { +abcdefghijklmnop|abcぁdefかghiのj... +} + +do_test 8.0 { + set result "\n[db format -style table { + WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<10) + SELECT 'aaa' AS 'a', format('%.*c',n,'b')||char(9)||'xx' AS x FROM c}]" +} { ++-----+--------------------+ +| a | x | ++-----+--------------------+ +| aaa | b xx | +| aaa | bb xx | +| aaa | bbb xx | +| aaa | bbbb xx | +| aaa | bbbbb xx | +| aaa | bbbbbb xx | +| aaa | bbbbbbb xx | +| aaa | bbbbbbbb xx | +| aaa | bbbbbbbbb xx | +| aaa | bbbbbbbbbb xx | ++-----+--------------------+ +} +do_test 8.1 { + set result "\n[db format -style table { + WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<10) + SELECT 'aaaa' AS 'a', format('%.*c',n,'b')||char(9)||'xx' AS x FROM c}]" +} { ++------+--------------------+ +| a | x | ++------+--------------------+ +| aaaa | b xx | +| aaaa | bb xx | +| aaaa | bbb xx | +| aaaa | bbbb xx | +| aaaa | bbbbb xx | +| aaaa | bbbbbb xx | +| aaaa | bbbbbbb xx | +| aaaa | bbbbbbbb xx | +| aaaa | bbbbbbbbb xx | +| aaaa | bbbbbbbbbb xx | ++------+--------------------+ +} +do_test 8.3 { + set result "\n[db format -style table -esc off { + WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<15) + SELECT 'aaa' AS 'a',n, 'xx'||char(n)||'yy' AS xy FROM c + WHERE n NOT IN (8,10,13,14)}]" +} { ++-----+----+------------+ +| a | n | xy | ++-----+----+------------+ +| aaa | 1 | xx␁yy | +| aaa | 2 | xx␂yy | +| aaa | 3 | xx␃yy | +| aaa | 4 | xx␄yy | +| aaa | 5 | xx␅yy | +| aaa | 6 | xx␆yy | +| aaa | 7 | xx␇yy | +| aaa | 9 | xx yy | +| aaa | 11 | xx␋yy | +| aaa | 12 | xx␌yy | +| aaa | 15 | xx␏yy | ++-----+----+------------+ +} +do_test 8.4 { + set result "\n[db format -style table -esc off { + WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<15) + SELECT 'aaa' AS 'a',n, 'xx'||char(n)||'yy'||char(9)||'zz' AS xyz FROM c + WHERE n NOT IN (8,10,13,14)}]" +} { ++-----+----+--------------------+ +| a | n | xyz | ++-----+----+--------------------+ +| aaa | 1 | xx␁yy zz | +| aaa | 2 | xx␂yy zz | +| aaa | 3 | xx␃yy zz | +| aaa | 4 | xx␄yy zz | +| aaa | 5 | xx␅yy zz | +| aaa | 6 | xx␆yy zz | +| aaa | 7 | xx␇yy zz | +| aaa | 9 | xx yy zz | +| aaa | 11 | xx␋yy zz | +| aaa | 12 | xx␌yy zz | +| aaa | 15 | xx␏yy zz | ++-----+----+--------------------+ +} + +do_test 9.1 { + db eval { + CREATE TABLE t9(x); + INSERT INTO t9 VALUES + (x'4331323334'), + (x'c30431323334'), + (x'd3000431323334'), + (x'e30000000431323334'), + (x'f3000000000000000431323334'); + } + db format -style list -text plain -rowsep , -textjsonb on \ + {SELECT * FROM t9} +} {1234,1234,1234,1234,1234,} +do_test 9.2 { + db format -style list -text sql -rowsep , -textjsonb on \ + {SELECT * FROM t9} +} {jsonb('1234'),jsonb('1234'),jsonb('1234'),jsonb('1234'),jsonb('1234'),} +do_test 9.3 { + db format -style json {SELECT * FROM t9 WHERE rowid<0} +} {} +do_test 9.4 { + db format -style jobject {SELECT * FROM t9 WHERE rowid<0} +} {} + +do_test 10.1 { + db eval { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(x); + INSERT INTO t1(x) VALUES + ('alice'), + ('bob'), + ('cinderella-cinderella'), + ('daniel'), + ('emma'), + ('fred'), + ('gertrude'), + ('harold'), + ('ingrid'), + ('jake'), + ('lisa'), + ('mike'), + ('nina'), + ('octavian'), + ('paula'), + ('quintus'), + ('rita'), + ('sam'), + ('tammy'), + ('ulysses'), + ('violet'), + ('william'), + ('xanthippe'), + ('yates'), + ('zoe'); + } + set result "\n[db format -style column -title off -screenwidth 41 -splitcolumn on \ + {SELECT x FROM t1}]" +} { +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +} +do_test 10.2 { + set result "\n[db format -style column -title off -screenwidth 42 -splitcolumn on \ + {SELECT x FROM t1}]" +} { +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +} +do_test 10.3 { + set result "\n[db format -style column -title off -screenwidth 51 -splitcolumn on \ + {SELECT x FROM t1}]" +} { +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +} +do_test 10.4 { + set result "\n[db format -style column -title off -screenwidth 61 -splitcolumn on \ + {SELECT x FROM t1}]" +} { +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +} +do_test 10.5 { + set result "\n[db format -style column -title off -screenwidth 74 -splitcolumn on \ + {SELECT x FROM t1}]" +} { +alice emma ingrid nina rita violet zoe +bob fred jake octavian sam william +cinderella-cinderella gertrude lisa paula tammy xanthippe +daniel harold mike quintus ulysses yates +} + +do_test 11.1 { + set result "\n[db format -style table -blob size {SELECT randomblob(1234)}]" +} { ++------------------+ +| randomblob(1234) | ++------------------+ +| (1234-byte blob) | ++------------------+ +} + +do_test 12.1 { + set result "\n[db format -style box -text html \ + {SELECT 'abc','','xyz'}]" +} { +╭───────┬────┬───────╮ +│ 'abc' │ '' │ 'xyz' │ +╞═══════╪════╪═══════╡ +│ abc │ │ xyz │ +╰───────┴────┴───────╯ +} + +# Tests for "relaxed" quoting +# +do_test 13.2 { + db eval { + CREATE TABLE t13(a,b); + INSERT INTO t13(a,b) VALUES + (1,'NULL'), + (0,'-NULL-'), + (0,''), + (1,'''abcde'), + (1,'abcde'''), + (0,'abcde'), + (1,' abcde'), + (1,'abcde '), + (1,'+0'), + (1,'-0'), + (1,'012345'), + (0,'012xyz345'), + (1,'0123.45'), + (0,'12.34.56'), + (0,'12.3e'), + (1,'12.3e+123'), + (1,'12.3e-34'), + (1,'12.3E56'), + (1,'12E56'), + (0,'12.5E5.6'), + (0,'12.5e+'), + (0,'12.5e-'), + (1,'+Inf'),(1,'-Inf'),(1,'Inf'); + } + set result \n[db format -style box -text relaxed -null NULL \ + -align {center left} \ + {SELECT if(a,'yes','') AS 'quoted?', b AS string + FROM t13 ORDER BY rowid}] +} { +╭─────────┬─────────────╮ +│ quoted? │ string │ +╞═════════╪═════════════╡ +│ yes │ 'NULL' │ +│ │ -NULL- │ +│ │ │ +│ yes │ '''abcde' │ +│ yes │ 'abcde''' │ +│ │ abcde │ +│ yes │ ' abcde' │ +│ yes │ 'abcde ' │ +│ yes │ '+0' │ +│ yes │ '-0' │ +│ yes │ '012345' │ +│ │ 012xyz345 │ +│ yes │ '0123.45' │ +│ │ 12.34.56 │ +│ │ 12.3e │ +│ yes │ '12.3e+123' │ +│ yes │ '12.3e-34' │ +│ yes │ '12.3E56' │ +│ yes │ '12E56' │ +│ │ 12.5E5.6 │ +│ │ 12.5e+ │ +│ │ 12.5e- │ +│ yes │ '+Inf' │ +│ yes │ '-Inf' │ +│ yes │ 'Inf' │ +╰─────────┴─────────────╯ +} + +db close + +finish_test diff --git a/test/qrf02.test b/test/qrf02.test new file mode 100644 index 000000000..07e1568f7 --- /dev/null +++ b/test/qrf02.test @@ -0,0 +1,47 @@ +# 2025-11-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. +# +#*********************************************************************** +# +# Test cases for the Query Result Formatter (QRF) +# +# These tests are for EXPLAIN and EXPLAIN QUERY PLAN formatting, the +# output of which can change when enhancments are made to the query +# planner. So expect to have to modify the expected results of these +# test cases in the future. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix qrf02 + +do_execsql_test 1.0 { + CREATE TABLE t1(a); + INSERT INTO t1 VALUES(1); +} + +set result [db format {EXPLAIN SELECT * FROM t1}] +do_test 1.10 { + set result +} {/*addr opcode p1 p2 p3 p4 p5 comment +---- ------------- ---- ---- ---- ------------- -- ------------- +0 Init */} +regsub -all {\d+} $result {N} result2 +do_test 1.11 { + set result2 +} "/.*\nN Rewind .*\nN Column .*/" + +do_test 1.20 { + set result "\n[db format {EXPLAIN QUERY PLAN SELECT * FROM t1}]" +} { +QUERY PLAN +`--SCAN t1 +} + +finish_test diff --git a/test/qrf03.test b/test/qrf03.test new file mode 100644 index 000000000..c0457df7f --- /dev/null +++ b/test/qrf03.test @@ -0,0 +1,176 @@ +# 2025-11-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. +# +#*********************************************************************** +# +# Test cases for the Query Result Formatter (QRF) +# +# Format narrowing due to nScreenWidth +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix qrf03 + +do_execsql_test 1.0 { + CREATE TABLE mlink( + mid INTEGER, + fid INTEGER, + pmid INTEGER, + pid INTEGER, + fnid INTEGER REFERENCES filename, + pfnid INTEGER, + mperm INTEGER, + isaux BOOLEAN DEFAULT 0 + ); + INSERT INTO mlink VALUES(28775,28774,28773,28706,1,0,0,0); + INSERT INTO mlink VALUES(28773,28706,28770,28685,1,0,0,0); + INSERT INTO mlink VALUES(28770,28736,28769,28695,2,0,0,0); + INSERT INTO mlink VALUES(28770,28697,28769,28698,3,0,0,0); + INSERT INTO mlink VALUES(28767,28768,28759,28746,4,0,0,0); + CREATE TABLE event( + type TEXT, + mtime DATETIME, + objid INTEGER PRIMARY KEY, + tagid INTEGER, + uid INTEGER REFERENCES user, + bgcolor TEXT, + euser TEXT, + user TEXT, + ecomment TEXT, + comment TEXT, + brief TEXT, + omtime DATETIME + ); + INSERT INTO event VALUES('ci',2460994.978048461023,126223,NULL,NULL,NULL,NULL,'drh',NULL,unistr('Data structure improvements on columnar layout. Prep work for getting\u000acolumnar layouts to respond to nScreenWidth.'),NULL,2460994.978048461023); + INSERT INTO event VALUES('ci',2460994.836955601816,126218,NULL,NULL,NULL,NULL,'stephan',NULL,'API doc typo fix.',NULL,2460994.836955601816); + INSERT INTO event VALUES('ci',2460994.88823369192,126212,NULL,NULL,NULL,NULL,'stephan',NULL,'Move sqlite3-api-cleanup.js into post-js-footer.js to remove the final direct Emscripten dependency from the intermediary build product sqlite3-api.js (the whole library, waiting to be bootstrapped). This is partly in response to [forum:4b7d45433731d2e0|forum post 4b7d45433731d2e0], which demonstrates a potential use case for a standalone sqlite3-api.js. This is a build/doc change, not a functional one.',NULL,2460994.88823369192); + INSERT INTO event VALUES('ci',2460994.516081551089,126211,NULL,NULL,NULL,NULL,'drh',NULL,unistr('Improve columnar layout in QRF so that it correctly deals with control\u000acharacters, and especially tabs.'),NULL,2460994.516081551089); + INSERT INTO event VALUES('ci',2460994.409343171865,126208,NULL,NULL,NULL,NULL,'drh',NULL,'Make use of the new sqlite3_str_free() interface in the CLI.',NULL,2460994.409343171865); +} + +do_test 1.10 { + set x "\n[db format -style box -screenwidth 68 \ + {SELECT * FROM mlink ORDER BY rowid}]" + +} { +╭───────┬───────┬───────┬───────┬──────┬───────┬───────┬───────╮ +│ mid │ fid │ pmid │ pid │ fnid │ pfnid │ mperm │ isaux │ +╞═══════╪═══════╪═══════╪═══════╪══════╪═══════╪═══════╪═══════╡ +│ 28775 │ 28774 │ 28773 │ 28706 │ 1 │ 0 │ 0 │ 0 │ +│ 28773 │ 28706 │ 28770 │ 28685 │ 1 │ 0 │ 0 │ 0 │ +│ 28770 │ 28736 │ 28769 │ 28695 │ 2 │ 0 │ 0 │ 0 │ +│ 28770 │ 28697 │ 28769 │ 28698 │ 3 │ 0 │ 0 │ 0 │ +│ 28767 │ 28768 │ 28759 │ 28746 │ 4 │ 0 │ 0 │ 0 │ +╰───────┴───────┴───────┴───────┴──────┴───────┴───────┴───────╯ +} +do_test 1.11 { + set x "\n[db format -style box -screenwidth 52 \ + {SELECT * FROM mlink ORDER BY rowid}]" + +} { +╭─────┬─────┬─────┬─────┬────┬─────┬─────┬─────╮ +│ mid │ fid │pmid │ pid │fnid│pfnid│mperm│isaux│ +╞═════╪═════╪═════╪═════╪════╪═════╪═════╪═════╡ +│28775│28774│28773│28706│ 1│ 0│ 0│ 0│ +│28773│28706│28770│28685│ 1│ 0│ 0│ 0│ +│28770│28736│28769│28695│ 2│ 0│ 0│ 0│ +│28770│28697│28769│28698│ 3│ 0│ 0│ 0│ +│28767│28768│28759│28746│ 4│ 0│ 0│ 0│ +╰─────┴─────┴─────┴─────┴────┴─────┴─────┴─────╯ +} + +do_test 1.20 { + set x "\n[db format -style table -screenwidth 68 \ + {SELECT * FROM mlink ORDER BY rowid}]" + +} { ++-------+-------+-------+-------+------+-------+-------+-------+ +| mid | fid | pmid | pid | fnid | pfnid | mperm | isaux | ++-------+-------+-------+-------+------+-------+-------+-------+ +| 28775 | 28774 | 28773 | 28706 | 1 | 0 | 0 | 0 | +| 28773 | 28706 | 28770 | 28685 | 1 | 0 | 0 | 0 | +| 28770 | 28736 | 28769 | 28695 | 2 | 0 | 0 | 0 | +| 28770 | 28697 | 28769 | 28698 | 3 | 0 | 0 | 0 | +| 28767 | 28768 | 28759 | 28746 | 4 | 0 | 0 | 0 | ++-------+-------+-------+-------+------+-------+-------+-------+ +} +do_test 1.21 { + set x "\n[db format -style table -screenwidth 52 \ + {SELECT * FROM mlink ORDER BY rowid}]" + +} { ++-----+-----+-----+-----+----+-----+-----+-----+ +| mid | fid |pmid | pid |fnid|pfnid|mperm|isaux| ++-----+-----+-----+-----+----+-----+-----+-----+ +|28775|28774|28773|28706| 1| 0| 0| 0| +|28773|28706|28770|28685| 1| 0| 0| 0| +|28770|28736|28769|28695| 2| 0| 0| 0| +|28770|28697|28769|28698| 3| 0| 0| 0| +|28767|28768|28759|28746| 4| 0| 0| 0| ++-----+-----+-----+-----+----+-----+-----+-----+ +} + +do_test 1.30 { + set x "\n[db format -style markdown -screenwidth 68 \ + {SELECT * FROM mlink ORDER BY rowid}]" + +} { +| mid | fid | pmid | pid | fnid | pfnid | mperm | isaux | +|-------|-------|-------|-------|------|-------|-------|-------| +| 28775 | 28774 | 28773 | 28706 | 1 | 0 | 0 | 0 | +| 28773 | 28706 | 28770 | 28685 | 1 | 0 | 0 | 0 | +| 28770 | 28736 | 28769 | 28695 | 2 | 0 | 0 | 0 | +| 28770 | 28697 | 28769 | 28698 | 3 | 0 | 0 | 0 | +| 28767 | 28768 | 28759 | 28746 | 4 | 0 | 0 | 0 | +} +do_test 1.31 { + set x "\n[db format -style markdown -screenwidth 52 \ + {SELECT * FROM mlink ORDER BY rowid}]" + +} { +| mid | fid |pmid | pid |fnid|pfnid|mperm|isaux| +|-----|-----|-----|-----|----|-----|-----|-----| +|28775|28774|28773|28706| 1| 0| 0| 0| +|28773|28706|28770|28685| 1| 0| 0| 0| +|28770|28736|28769|28695| 2| 0| 0| 0| +|28770|28697|28769|28698| 3| 0| 0| 0| +|28767|28768|28759|28746| 4| 0| 0| 0| +} + +do_test 1.40 { + set x "\n[db format -style column -screenwidth 68 \ + {SELECT * FROM mlink ORDER BY rowid}]" + +} { + mid fid pmid pid fnid pfnid mperm isaux +----- ----- ----- ----- ---- ----- ----- ----- +28775 28774 28773 28706 1 0 0 0 +28773 28706 28770 28685 1 0 0 0 +28770 28736 28769 28695 2 0 0 0 +28770 28697 28769 28698 3 0 0 0 +28767 28768 28759 28746 4 0 0 0 +} +do_test 1.41 { + set x "\n[db format -style column -screenwidth 52 \ + {SELECT * FROM mlink ORDER BY rowid}]" + +} { + mid fid pmid pid fnid pfnid mperm isaux +----- ----- ----- ----- ---- ----- ----- ----- +28775 28774 28773 28706 1 0 0 0 +28773 28706 28770 28685 1 0 0 0 +28770 28736 28769 28695 2 0 0 0 +28770 28697 28769 28698 3 0 0 0 +28767 28768 28759 28746 4 0 0 0 +} + + + +finish_test diff --git a/test/qrf04.test b/test/qrf04.test new file mode 100644 index 000000000..0b231d921 --- /dev/null +++ b/test/qrf04.test @@ -0,0 +1,750 @@ +# 2025-11-23 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases for the Query Result Formatter (QRF), and especially +# the bSplitColumn feature. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix qrf01 + +# The expected output from test 1.1. The "do_test" procedure normally +# ignores differences in whitespace, but whitespace is important for +# this test, so we have to do the comparison ourselves. +# +set expected { +<---- 22 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 23 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 24 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 25 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 26 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 27 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 28 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 29 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 30 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 31 ----> +alice +bob +cinderella-cinderella +daniel +emma +fred +gertrude +harold +ingrid +jake +lisa +mike +nina +octavian +paula +quintus +rita +sam +tammy +ulysses +violet +william +xanthippe +yates +zoe +<---- 32 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 33 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 34 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 35 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 36 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 37 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 38 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 39 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 40 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 41 ----> +alice octavian +bob paula +cinderella-cinderella quintus +daniel rita +emma sam +fred tammy +gertrude ulysses +harold violet +ingrid william +jake xanthippe +lisa yates +mike zoe +nina +<---- 42 ----> +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +<---- 43 ----> +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +<---- 44 ----> +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +<---- 45 ----> +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +<---- 46 ----> +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +<---- 47 ----> +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +<---- 48 ----> +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +<---- 49 ----> +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +<---- 50 ----> +alice jake tammy +bob lisa ulysses +cinderella-cinderella mike violet +daniel nina william +emma octavian xanthippe +fred paula yates +gertrude quintus zoe +harold rita +ingrid sam +<---- 51 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 52 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 53 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 54 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 55 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 56 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 57 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 58 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 59 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 60 ----> +alice harold paula william +bob ingrid quintus xanthippe +cinderella-cinderella jake rita yates +daniel lisa sam zoe +emma mike tammy +fred nina ulysses +gertrude octavian violet +<---- 61 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 62 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 63 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 64 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 65 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 66 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 67 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 68 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 69 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 70 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 71 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 72 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 73 ----> +alice fred lisa quintus violet +bob gertrude mike rita william +cinderella-cinderella harold nina sam xanthippe +daniel ingrid octavian tammy yates +emma jake paula ulysses zoe +<---- 74 ----> +alice emma ingrid nina rita violet zoe +bob fred jake octavian sam william +cinderella-cinderella gertrude lisa paula tammy xanthippe +daniel harold mike quintus ulysses yates +<---- 75 ----> +alice emma ingrid nina rita violet zoe +bob fred jake octavian sam william +cinderella-cinderella gertrude lisa paula tammy xanthippe +daniel harold mike quintus ulysses yates +<---- 76 ----> +alice emma ingrid nina rita violet zoe +bob fred jake octavian sam william +cinderella-cinderella gertrude lisa paula tammy xanthippe +daniel harold mike quintus ulysses yates +<---- 77 ----> +alice emma ingrid nina rita violet zoe +bob fred jake octavian sam william +cinderella-cinderella gertrude lisa paula tammy xanthippe +daniel harold mike quintus ulysses yates +<---- 78 ----> +alice emma ingrid nina rita violet zoe +bob fred jake octavian sam william +cinderella-cinderella gertrude lisa paula tammy xanthippe +daniel harold mike quintus ulysses yates +<---- 79 ----> +alice emma ingrid nina rita violet zoe +bob fred jake octavian sam william +cinderella-cinderella gertrude lisa paula tammy xanthippe +daniel harold mike quintus ulysses yates +<---- 80 ----> +alice emma ingrid nina rita violet zoe +bob fred jake octavian sam william +cinderella-cinderella gertrude lisa paula tammy xanthippe +daniel harold mike quintus ulysses yates +} + +do_test 1.0 { + db eval { + CREATE TABLE t1(x); + INSERT INTO t1(x) VALUES + ('alice'), + ('bob'), + ('cinderella-cinderella'), + ('daniel'), + ('emma'), + ('fred'), + ('gertrude'), + ('harold'), + ('ingrid'), + ('jake'), + ('lisa'), + ('mike'), + ('nina'), + ('octavian'), + ('paula'), + ('quintus'), + ('rita'), + ('sam'), + ('tammy'), + ('ulysses'), + ('violet'), + ('william'), + ('xanthippe'), + ('yates'), + ('zoe'); + } + set res \n + for {set i 22} {$i<=80} {incr i} { + set sp [expr {$i-13}] + append res [format "<----%*s%3d%*s---->\n" \ + [expr {$sp/2}] {} $i [expr {$sp-$sp/2}] {}] + append res [db format -style column -title off \ + -screenwidth $i -splitcolumn on \ + {SELECT x FROM t1 ORDER BY x ASC}] + } + expr {$res eq $::expected} +} {1} diff --git a/test/qrf05.test b/test/qrf05.test new file mode 100644 index 000000000..0d5a4d7f9 --- /dev/null +++ b/test/qrf05.test @@ -0,0 +1,37 @@ +# 2025-12-02 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases for the Query Result Formatter (QRF) +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix qrf05 + +do_execsql_test 1.0 { + CREATE TABLE t1(a INT NOT NULL); +} +do_test 1.1 { + set rc [catch {db format -style list \ + {INSERT INTO t1 VALUES(123) RETURNING *}} msg] + list $rc [string trim $msg] +} {0 123} +do_test 1.2 { + set rc [catch {db format -style list \ + {INSERT INTO t1 VALUES(NULL) RETURNING *}} msg] + list $rc [string trim $msg] +} {1 {NOT NULL constraint failed: t1.a}} +do_test 1.3 { + set rc [catch {db format -version 99 {SELECT * FROM t1}} msg] + list $rc [string trim $msg] +} {1 {unusable sqlite3_qrf_spec.iVersion (99)}} + +finish_test diff --git a/test/qrf06.test b/test/qrf06.test new file mode 100644 index 000000000..5fa62c26f --- /dev/null +++ b/test/qrf06.test @@ -0,0 +1,576 @@ +# 2025-12-02 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases for the Query Result Formatter (QRF), and especially +# the sqlite3_qrf_wcwidth() function and its utilization. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix qrf06 + +# Data +db eval { + BEGIN TRANSACTION; + CREATE TABLE language(name TEXT); + INSERT INTO language(name) VALUES + ('العربية'), + ('Deutsch'), + ('English'), + ('Español'), + ('فارسی'), + ('Français'), + ('Italiano'), + ('مصرى'), + ('Nederlands'), + ('日本語'), + ('Polski'), + ('Português'), + ('Sinugboanong Binisaya'), + ('Svenska'), + ('Українська'), + ('Tiếng Việt'), + ('Winaray'), + ('中文'), + ('Русский'), + ('Afrikaans'), + ('Shqip'), + ('Asturianu'), + ('Azərbaycanca'), + ('Български'), + ('閩南語 / Bân-lâm-gú'), + ('বাংলা'), + ('Беларуская'), + ('Català'), + ('Čeština'), + ('Cymraeg'), + ('Dansk'), + ('Eesti'), + ('Ελληνικά'), + ('Esperanto'), + ('Euskara'), + ('Galego'), + ('한국어'), + ('Հայերեն'), + ('हिन्दी'), + ('Hrvatski'), + ('Bahasa Indonesia'), + ('עברית'), + ('ქართული'), + ('Ladin'), + ('Latina'), + ('Latviešu'), + ('Lietuvių'), + ('Magyar'), + ('Македонски'), + ('Malagasy'), + ('मराठी'), + ('Bahasa Melayu'), + ('Bahaso Minangkabau'), + ('မြန်မာဘာသာ'), + ('Norskbokmålnynorsk'), + ('Нохчийн'), + ('Oʻzbekcha / Ўзбекча'), + ('Қазақша / Qazaqşa / قازاقشا'), + ('Română'), + ('Simple English'), + ('Slovenčina'), + ('Slovenščina'), + ('Српски / Srpski'), + ('Srpskohrvatski / Српскохрватски'), + ('Suomi'), + ('Kiswahili'), + ('தமிழ்'), + ('Татарча / Tatarça'), + ('తెలుగు'), + ('ภาษาไทย'), + ('Тоҷикӣ'), + ('تۆرکجه'), + ('Türkçe'), + ('اردو'), + ('粵語'), + ('Bahsa Acèh'), + ('Alemannisch'), + ('አማርኛ'), + ('Aragonés'), + ('Արեւմտահայերէն'), + ('Bahasa Hulontalo'), + ('Basa Bali'), + ('Bahasa Banjar'), + ('Basa Banyumasan'), + ('Башҡортса'), + ('Беларуская (тарашкевіца)'), + ('Bikol Central'), + ('বিষ্ণুপ্রিয়া মণিপুরী'), + ('Boarisch'), + ('Bosanski'), + ('Brezhoneg'), + ('Чӑвашла'), + ('Dagbanli'), + ('الدارجة'), + ('Diné Bizaad'), + ('Emigliàn–Rumagnòl'), + ('Fiji Hindi'), + ('Føroyskt'), + ('Frysk'), + ('Fulfulde'), + ('Gaeilge'), + ('Gàidhlig'), + ('گیلکی'), + ('ગુજરાતી'), + ('Hak-kâ-ngî / 客家語'), + ('Hausa'), + ('Hornjoserbsce'), + ('Ido'), + ('Igbo'), + ('Ilokano'), + ('Interlingua'), + ('Interlingue'), + ('Ирон'), + ('Íslenska'), + ('Jawa'), + ('ಕನ್ನಡ'), + ('Kapampangan'), + ('ភាសាខ្មែរ'), + ('Kotava'), + ('Kreyòl Ayisyen'), + ('Kurdî / كوردی'), + ('کوردیی ناوەندی'), + ('Кыргызча'), + ('Кырык мары'), + ('Lëtzebuergesch'), + ('Lìgure'), + ('Limburgs'), + ('Lombard'), + ('मैथिली'), + ('മലയാളം'), + ('მარგალური'), + ('مازِرونی'), + ('Mìng-dĕ̤ng-ngṳ̄ / 閩東語'), + ('Монгол'), + ('Napulitano'), + ('नेपाल भाषा'), + ('Nordfriisk'), + ('Occitan'), + ('Олык марий'), + ('ଓଡି଼ଆ'), + ('অসমীযা়'), + ('ਪੰਜਾਬੀ'), + ('پنجابی (شاہ مکھی)'), + ('پښتو'), + ('Piemontèis'), + ('Plattdüütsch'), + ('Qaraqalpaqsha'), + ('Qırımtatarca'), + ('Runa Simi'), + ('Русиньскый'), + ('संस्कृतम्'), + ('ᱥᱟᱱᱛᱟᱲᱤ'), + ('سرائیکی'), + ('Саха Тыла'), + ('Scots'), + ('ChiShona'), + ('Sicilianu'), + ('සිංහල'), + ('سنڌي'), + ('Ślůnski'), + ('Basa Sunda'), + ('Taclḥit'), + ('Tagalog'), + ('ၽႃႇသႃႇတႆး'), + ('ⵜⴰⵎⴰⵣⵉⵖⵜ ⵜⴰⵏⴰⵡⴰⵢⵜ'), + ('tolışi'), + ('chiTumbuka'), + ('Basa Ugi'), + ('Vèneto'), + ('Volapük'), + ('Walon'), + ('文言'), + ('吴语'), + ('ייִדיש'), + ('Yorùbá'), + ('Zazaki'), + ('žemaitėška'), + ('isiZulu'), + ('नेपाली'), + ('ꯃꯤꯇꯩ ꯂꯣꯟ'), + ('Dzhudezmo / לאדינו'), + ('Адыгэбзэ'), + ('Ænglisc'), + ('Anarâškielâ'), + ('अंगिका'), + ('Аԥсшәа'), + ('armãneashti'), + ('Arpitan'), + ('atikamekw'), + ('ܐܬܘܪܝܐ'), + ('Avañe’ẽ'), + ('Авар'), + ('Aymar'), + ('Batak Toba'), + ('Betawi'), + ('भोजपुरी'), + ('Bislama'), + ('བོད་ཡིག'), + ('Буряад'), + ('Chavacano de Zamboanga'), + ('Chichewa'), + ('Corsu'), + ('Vahcuengh / 話僮'), + ('Dagaare'), + ('Davvisámegiella'), + ('Deitsch'), + ('ދިވެހިބަސް'), + ('Dolnoserbski'), + ('Dusun Bundu-liwan'), + ('Эрзянь'), + ('Estremeñu'), + ('Eʋegbe'), + ('Farefare'), + ('Fɔ̀ngbè'), + ('Furlan'), + ('Gaelg'), + ('Gagauz'), + ('ГӀалгӀай'), + ('Ghanaian Pidgin'), + ('Gĩkũyũ'), + ('赣语 / 贛語'), + ('Gungbe'), + ('Хальмг'), + ('ʻŌlelo Hawaiʻi'), + ('Ikinyarwanda'), + ('Jaku Iban'), + ('Kabɩyɛ'), + ('Yerwa Kanuri'), + ('Kaszëbsczi'), + ('Kernewek'), + ('Коми'), + ('Перем коми'), + ('Kongo'), + ('कोंकणी / Konknni'), + ('كٲشُر'), + ('Kriyòl Gwiyannen'), + ('Kumoring'), + ('Kʋsaal'), + ('ພາສາລາວ'), + ('Лакку'), + ('Latgaļu'), + ('Лезги'), + ('Li Niha'), + ('Lingála'), + ('Lingua Franca Nova'), + ('livvinkarjala'), + ('lojban'), + ('Luganda'), + ('Madhurâ'), + ('Malti'), + ('Mandailing'), + ('Māori'), + ('Mfantse'), + ('Mirandés'), + ('Мокшень'), + ('ဘာသာ မန်'), + ('Moore'), + ('ߒߞߏ'), + ('Na Vosa Vaka-Viti'), + ('Nāhuatlahtōlli'), + ('Naijá'), + ('Nedersaksisch'), + ('Nouormand / Normaund'), + ('Novial'), + ('Afaan Oromoo'), + ('ပအိုဝ်ႏဘာႏသာႏ'), + ('Pangasinán'), + ('Pangcah'), + ('Papiamentu'), + ('Patois'), + ('Pfälzisch'), + ('Picard'), + ('Къарачай–малкъар'), + ('Ripoarisch'), + ('Rumantsch'), + ('Sakizaya'), + ('Gagana Sāmoa'), + ('Sardu'), + ('Seediq'), + ('Seeltersk'), + ('Sesotho'), + ('Sesotho sa Leboa'), + ('Setswana'), + ('ꠍꠤꠟꠐꠤ'), + ('Словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ'), + ('Soomaaliga'), + ('Sranantongo'), + ('SiSwati'), + ('Reo tahiti'), + ('Taqbaylit'), + ('Tarandíne'), + ('Tayal'), + ('Tetun'), + ('Tok Pisin'), + ('faka Tonga'), + ('Türkmençe'), + ('Twi'), + ('Tyap'), + ('Тыва дыл'), + ('Удмурт'), + ('ئۇيغۇرچه'), + ('Vepsän'), + ('võro'), + ('West-Vlams'), + ('Wolof'), + ('isiXhosa'), + ('Zeêuws'), + ('алтай тил'), + ('अवधी'), + ('डोटेली'), + ('ತುಳು'), + ('ရခိုင်'), + ('Bajau Sama'), + ('Bamanankan'), + ('Chamoru'), + ('རྫོང་ཁ'), + ('𐌲𐌿𐍄𐌹𐍃𐌺x'), + ('Igala'), + ('ᐃᓄᒃᑎᑐᑦ / Inuktitut'), + ('Iñupiak'), + ('isiNdebele seSewula'), + ('Kalaallisut'), + ('Nupe'), + ('Obolo'), + ('पालि'), + ('pinayuanan'), + ('Ποντιακά'), + ('romani čhib'), + ('Ikirundi'), + ('руски'), + ('Sängö'), + ('ᥖᥭᥰᥖᥬᥳᥑᥨᥒᥰ'), + ('ትግርኛ'), + ('Thuɔŋjäŋ'), + ('ᏣᎳᎩ'), + ('Tsėhesenėstsestotse'), + ('Xitsonga'), + ('Tshivenḓa'), + ('Wayuunaiki'), + ('адыгабзэ'); + COMMIT; +} + +do_test 1.2 { + set res \n[db format -style box { + SELECT name, rowid AS id FROM language + WHERE length(name)=2 + ORDER BY name + }] + set exp { +╭──────┬─────╮ +│ name │ id │ +╞══════╪═════╡ +│ 中文 │ 18 │ +│ 吴语 │ 173 │ +│ 文言 │ 172 │ +│ 粵語 │ 75 │ +╰──────┴─────╯ +} + if {$res ne $exp} { + puts [list $res] + puts [list $exp] + } + string compare $res $exp +} {0} + +do_test 1.3 { + set res \n[db format -style box { + SELECT name, rowid AS id FROM language + WHERE length(name)=3 + ORDER BY name + }] + set exp { +╭────────┬─────╮ +│ name │ id │ +╞════════╪═════╡ +│ Ido │ 108 │ +│ Twi │ 297 │ +│ ߒߞߏ │ 258 │ +│ ᏣᎳᎩ │ 335 │ +│ 日本語 │ 10 │ +│ 한국어 │ 37 │ +╰────────┴─────╯ +} + if {$res ne $exp} { + puts [list $res] + puts [list $exp] + } + string compare $res $exp +} {0} + +do_test 1.4 { + set res \n[db format -style box { + SELECT name, rowid AS id FROM language + WHERE length(name)=4 + ORDER BY name + }] + set exp { +╭──────┬─────╮ +│ name │ id │ +╞══════╪═════╡ +│ Igbo │ 109 │ +│ Jawa │ 115 │ +│ Nupe │ 323 │ +│ Tyap │ 298 │ +│ võro │ 303 │ +│ Авар │ 192 │ +│ Ирон │ 113 │ +│ Коми │ 231 │ +│ اردو │ 74 │ +│ سنڌي │ 159 │ +│ مصرى │ 8 │ +│ پښتو │ 144 │ +│ अवधी │ 309 │ +│ पालि │ 325 │ +│ ತುಳು │ 311 │ +│ ትግርኛ │ 333 │ +│ አማርኛ │ 78 │ +╰──────┴─────╯ +} + if {$res ne $exp} { + puts [list $res] + puts [list $exp] + } + string compare $res $exp +} {0} + +do_test 1.5 { + set res \n[db format -style box { + SELECT name, rowid AS id FROM language + WHERE length(name)=5 + ORDER BY name + }] + set exp { +╭───────┬─────╮ +│ name │ id │ +╞═══════╪═════╡ +│ Aymar │ 193 │ +│ Corsu │ 202 │ +│ Dansk │ 31 │ +│ Eesti │ 32 │ +│ Frysk │ 99 │ +│ Gaelg │ 216 │ +│ Hausa │ 106 │ +│ Igala │ 318 │ +│ Kongo │ 233 │ +│ Ladin │ 44 │ +│ Malti │ 250 │ +│ Moore │ 257 │ +│ Māori │ 252 │ +│ Naijá │ 261 │ +│ Obolo │ 324 │ +│ Sardu │ 278 │ +│ Scots │ 155 │ +│ Shqip │ 21 │ +│ Suomi │ 65 │ +│ Sängö │ 331 │ +│ Tayal │ 292 │ +│ Tetun │ 293 │ +│ Walon │ 171 │ +│ Wolof │ 305 │ +│ Лакку │ 240 │ +│ Лезги │ 242 │ +│ руски │ 330 │ +│ עברית │ 42 │ +│ فارسی │ 5 │ +│ كٲشُر │ 235 │ +│ گیلکی │ 103 │ +│ मराठी │ 51 │ +│ বাংলা │ 26 │ +│ ଓଡି଼ଆ │ 140 │ +│ தமிழ் │ 67 │ +│ ಕನ್ನಡ │ 116 │ +│ සිංහල │ 158 │ +│ ꠍꠤꠟꠐꠤ │ 284 │ +╰───────┴─────╯ +} + if {$res ne $exp} { + puts [list $res] + puts [list $exp] + } + string compare $res $exp +} {0} + +do_test 1.6 { + set res \n[db format -style box { + SELECT name, rowid AS id FROM language + WHERE length(name)=6 + ORDER BY name + }] + set exp { +╭────────┬─────╮ +│ name │ id │ +╞════════╪═════╡ +│ Betawi │ 195 │ +│ Català │ 28 │ +│ Eʋegbe │ 212 │ +│ Furlan │ 215 │ +│ Gagauz │ 217 │ +│ Galego │ 36 │ +│ Gungbe │ 222 │ +│ Gĩkũyũ │ 220 │ +│ Kabɩyɛ │ 227 │ +│ Kotava │ 119 │ +│ Kʋsaal │ 238 │ +│ Latina │ 45 │ +│ Lìgure │ 126 │ +│ Magyar │ 48 │ +│ Novial │ 264 │ +│ Patois │ 270 │ +│ Picard │ 272 │ +│ Polski │ 11 │ +│ Română │ 59 │ +│ Seediq │ 279 │ +│ Türkçe │ 73 │ +│ Vepsän │ 302 │ +│ Vèneto │ 169 │ +│ Yorùbá │ 175 │ +│ Zazaki │ 176 │ +│ Zeêuws │ 307 │ +│ lojban │ 247 │ +│ tolışi │ 166 │ +│ Аԥсшәа │ 186 │ +│ Буряад │ 199 │ +│ Монгол │ 134 │ +│ Тоҷикӣ │ 71 │ +│ Удмурт │ 300 │ +│ Хальмг │ 223 │ +│ Эрзянь │ 210 │ +│ ייִדיש │ 174 │ +│ تۆرکجه │ 72 │ +│ ܐܬܘܪܝܐ │ 190 │ +│ अंगिका │ 185 │ +│ डोटेली │ 310 │ +│ नेपाली │ 179 │ +│ मैथिली │ 129 │ +│ हिन्दी │ 39 │ +│ ਪੰਜਾਬੀ │ 142 │ +│ తెలుగు │ 69 │ +│ മലയാളം │ 130 │ +│ རྫོང་ཁ │ 316 │ +│ ရခိုင် │ 312 │ +╰────────┴─────╯ +} + if {$res ne $exp} { + puts [list $res] + puts [list $exp] + } + string compare $res $exp +} {0} + +finish_test diff --git a/test/recover.test b/test/recover.test index ad6b7298d..7035e407c 100644 --- a/test/recover.test +++ b/test/recover.test @@ -42,7 +42,7 @@ proc compare_dbs {db1 db2} { proc recover_with_opts {opts} { set cmd ".recover $opts" - set fd [open [list |$::CLI test.db $cmd]] + set fd [open [list |$::CLI -noinit test.db $cmd]] fconfigure $fd -translation binary set sql [read $fd] close $fd diff --git a/test/regexp1.sql b/test/regexp1.sql new file mode 100644 index 000000000..c1938885a --- /dev/null +++ b/test/regexp1.sql @@ -0,0 +1,32 @@ +#!sqlite3 +# +# 2025-12-16 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases for the oversized patterns in the REGEXP extension found +# at ext/misc/regexp.c. +# +.mode list +.testcase 100 +-- 0 1 2 3 4 +-- 123456789 123456789 123456789 123456789 123 +SELECT 'abcdefg' REGEXP '((((((((((((((((((abcdefg))))))))))))))))))'; +.check 1 + +.testcase 110 +.limit like_pattern_length 42 +SELECT 'abcdefg' REGEXP '((((((((((((((((((abcdefg))))))))))))))))))'; +.check -glob "Error near line #: REGEXP pattern too big*" + +.testcase 120 +.limit like_pattern_length 43 +SELECT 'abcdefg' REGEXP '((((((((((((((((((abcdefg))))))))))))))))))'; +.check 1 diff --git a/test/regexp1.test b/test/regexp1.test index 0401b13d7..fb123284b 100644 --- a/test/regexp1.test +++ b/test/regexp1.test @@ -331,5 +331,29 @@ do_execsql_test regexp1-7.12 { SELECT char(0x61,0x10ffff,0x62) REGEXP char(0x10ffff); } 1 +do_execsql_test regexp1-8.0 { + CREATE TABLE t2(a); + INSERT INTO t2 VALUES('abc-def'); + SELECT length(a) FROM t2; +} {7} + +do_execsql_test regexp1-8.1 { + SELECT rowid FROM t2 WHERE a REGEXP '[1-5]'; +} {} +do_execsql_test regexp1-8.2 { + SELECT rowid FROM t2 WHERE a REGEXP '[1\-5]'; +} {1} +do_execsql_test regexp1-8.3 { + SELECT rowid FROM t2 WHERE a REGEXP '[x\-]'; +} {1} +do_catchsql_test regexp1-8.4 { + SELECT rowid FROM t2 WHERE a REGEXP '[x-]'; +} {1 {unclosed '['}} +do_execsql_test regexp1-8.5 { + SELECT rowid FROM t2 WHERE a REGEXP '-'; +} {1} +do_execsql_test regexp1-8.6 { + SELECT rowid FROM t2 WHERE a REGEXP '\-'; +} {1} finish_test diff --git a/test/rowvalue4.test b/test/rowvalue4.test index 1ef5fc292..5e02f0fc2 100644 --- a/test/rowvalue4.test +++ b/test/rowvalue4.test @@ -236,8 +236,7 @@ do_eqp_test 5.1 { QUERY PLAN |--SEARCH d2 USING INDEX d2ab (a=? AND b=?) |--LIST SUBQUERY xxxxxx - | |--SCAN d1 - | `--CREATE BLOOM FILTER + | `--SCAN d1 `--LIST SUBQUERY xxxxxx |--SCAN d1 `--CREATE BLOOM FILTER diff --git a/test/rowvalueA.test b/test/rowvalueA.test index 8760c2c39..16429f985 100644 --- a/test/rowvalueA.test +++ b/test/rowvalueA.test @@ -73,4 +73,29 @@ do_catchsql_test 2.3 { SELECT 2 IN ( (1, 2), (3, 4), (5, 6) ) } {1 {row value misused}} +#------------------------------------------------------------------------- +# Test the fix for forum post https://sqlite.org/forum/forumpost/6ceca07fc3 +# +do_execsql_test 3.0 { + CREATE TABLE x2 (x, y); + INSERT INTO x2 VALUES (1234, 'abc'); + + CREATE TABLE x1 (a, b PRIMARY KEY COLLATE NOCASE) WITHOUT ROWID; + INSERT INTO x1 VALUES (1234, 'ABCD'); +} + +do_execsql_test 3.1 { + SELECT * FROM x2 CROSS JOIN x1 WHERE (1234, x2.y) > (x1.a, x1.b); +} {1234 abc 1234 ABCD} + +do_execsql_test 3.2 { + CREATE INDEX x1a ON x1(a); +} + +do_execsql_test 3.3 { + SELECT * FROM x2 CROSS JOIN x1 WHERE (1234, x2.y) > (x1.a, x1.b); +} {1234 abc 1234 ABCD} + + + finish_test diff --git a/test/schema.test b/test/schema.test index c7daef20b..a6564293b 100644 --- a/test/schema.test +++ b/test/schema.test @@ -227,10 +227,10 @@ ifcapable auth { set ::STMT [sqlite3_prepare $::DB {SELECT * FROM sqlite_master} -1 TAIL] db auth {} sqlite3_step $::STMT - } {SQLITE_ROW} + } {SQLITE_ERROR} do_test schema-8.12 { sqlite3_finalize $::STMT - } {SQLITE_OK} + } {SQLITE_SCHEMA} } diff --git a/test/select9.test b/test/select9.test index bbed8e18f..bef56d83f 100644 --- a/test/select9.test +++ b/test/select9.test @@ -406,7 +406,7 @@ do_test select9-4.4 { do_test select9-4.5 { execsql { CREATE VIEW v1 AS SELECT a FROM t1 UNION SELECT d FROM t2 } cksort { SELECT a FROM v1 ORDER BY 1 LIMIT 5 } -} {1 2 3 4 5 sort} +} {1 2 3 4 5 nosort} do_test select9-4.X { execsql { DROP INDEX i1; diff --git a/test/shell1.test b/test/shell1.test index abf214a90..1d111d616 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -216,10 +216,14 @@ do_test shell1-2.2.4 { } {0 {}} do_test shell1-2.2.5 { catchcmd "test.db" ".mode \"insert FOO" -} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}} +} {1 {line 1: .mode "insert FOO +line 1: ^--- unknown mode +line 1: Use ".help .mode" for more info}} do_test shell1-2.2.6 { catchcmd "test.db" ".mode \'insert FOO" -} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}} +} {1 {line 1: .mode 'insert FOO +line 1: ^--- unknown mode +line 1: Use ".help .mode" for more info}} # check multiple tokens, and quoted tokens do_test shell1-2.3.1 { @@ -247,7 +251,9 @@ do_test shell1-2.3.7 { # check quoted args are unquoted do_test shell1-2.4.1 { catchcmd "test.db" ".mode FOO" -} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}} +} {1 {line 1: .mode FOO +line 1: ^--- unknown mode +line 1: Use ".help .mode" for more info}} do_test shell1-2.4.2 { catchcmd "test.db" ".mode csv" } {0 {}} @@ -296,9 +302,7 @@ do_test shell1-3.2.4 { catchcmd "test.db" ".bail OFF BAD" } {1 {Usage: .bail on|off}} -# This test will not work on winrt, as winrt has no concept of the absolute -# paths that the test expects in the result. It uses relative paths only. -ifcapable vtab&&!winrt { +ifcapable vtab { # .databases List names and files of attached databases do_test shell1-3.3.1 { catchcmd "-csv test.db" ".databases" @@ -364,7 +368,6 @@ do_test shell1-3.7.4 { catchcmd "test.db" ".explain OFF BAD" } {0 {}} - # .header(s) ON|OFF Turn display of headers on or off do_test shell1-3.9.1 { catchcmd "test.db" ".header" @@ -400,7 +403,7 @@ do_test shell1-3.10.1 { # look for a few of the possible help commands list [regexp {.help} $res] \ [regexp {.quit} $res] \ - [regexp {.show} $res] + [regexp {.mode} $res] } {1 1 1} do_test shell1-3.10.2 { # we allow .help to take extra args (it is help after all) @@ -408,20 +411,24 @@ do_test shell1-3.10.2 { # look for a few of the possible help commands list [regexp {.help} $res] \ [regexp {.quit} $res] \ - [regexp {.show} $res] + [regexp {.mode} $res] } {1 1 1} # .import FILE TABLE Import data from FILE into TABLE do_test shell1-3.11.1 { catchcmd "test.db" ".import" -} {/1 .ERROR: missing FILE argument.*/} +} {/1 .line 1: Missing FILE argument.*/} do_test shell1-3.11.2 { catchcmd "test.db" ".import FOO" -} {/1 .ERROR: missing TABLE argument.*/} +} {/1 .line 1: Missing TABLE argument.*/} do_test shell1-3.11.3 { # too many arguments catchcmd "test.db" ".import FOO BAR BAD" -} {/1 .ERROR: extra argument: "BAD".*./} +} {1 {line 1: .import FOO BAR BAD +line 1: ^--- unknown argument}} +do_test shell1-3.11.4 { + catchcmd "test.db" ".import <<END t1\na,b,c\n1,2,3" +} {1 {line 1: Content terminator "END" not found.}} # .indexes ?TABLE? Show names of all indexes # If TABLE specified, only show indexes for tables @@ -451,11 +458,13 @@ do_test shell1-3.12.3 { # tabs Tab-separated values # tcl TCL list elements do_test shell1-3.13.1 { - catchcmd "test.db" ".mode" -} {0 {current output mode: list --escape ascii}} + catchcmd "test.db" ".mode batch\n.mode" +} {0 {.mode list}} do_test shell1-3.13.2 { catchcmd "test.db" ".mode FOO" -} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}} +} {1 {line 1: .mode FOO +line 1: ^--- unknown mode +line 1: Use ".help .mode" for more info}} do_test shell1-3.13.3 { catchcmd "test.db" ".mode csv" } {0 {}} @@ -503,7 +512,7 @@ do_test shell1-3.15.1 { .print x" } {0 x} do_test shell1-3.15.2 { - catchcmd "test.db" ".output FOO + catchcmd "test.db" ".mode batch\n.output FOO .print x .output SELECT readfile('FOO');" @@ -512,17 +521,8 @@ SELECT readfile('FOO');" do_test shell1-3.15.3 { # too many arguments catchcmd "test.db" ".output FOO BAD" -} {1 {ERROR: extra parameter: "BAD". Usage: -.output ?FILE? Send output to FILE or stdout if FILE is omitted - If FILE begins with '|' then open it as a pipe. - If FILE is 'off' then output is disabled. - Options: - --bom Prefix output with a UTF8 byte-order mark - -e Send output to the system text editor - --plain Use text/plain for -w option - -w Send output to a web browser - -x Send output as CSV to a spreadsheet -child process exited abnormally}} +} {1 {line 1: .output FOO BAD +line 1: ^--- surplus argument}} # .output stdout Send output to the screen do_test shell1-3.16.1 { @@ -531,17 +531,8 @@ do_test shell1-3.16.1 { do_test shell1-3.16.2 { # too many arguments catchcmd "test.db" ".output stdout BAD" -} {1 {ERROR: extra parameter: "BAD". Usage: -.output ?FILE? Send output to FILE or stdout if FILE is omitted - If FILE begins with '|' then open it as a pipe. - If FILE is 'off' then output is disabled. - Options: - --bom Prefix output with a UTF8 byte-order mark - -e Send output to the system text editor - --plain Use text/plain for -w option - -w Send output to a web browser - -x Send output as CSV to a spreadsheet -child process exited abnormally}} +} {1 {line 1: .output stdout BAD +line 1: ^--- surplus argument}} # .prompt MAIN CONTINUE Replace the standard prompts do_test shell1-3.17.1 { @@ -625,6 +616,20 @@ CREATE VIEW v1 AS SELECT y+1 FROM v2 catch {db eval {DROP VIEW v1; DROP VIEW v2; DROP TABLE t1;}} } +do_test shell1-3.21.5 { + exec {*}$CLI -noinit test.db \ + {CREATE TABLE t2(a INTEGER PRIMARY KEY, b BLOB DEFAULT(jsonb('[]')),c TEXT NOT NULL)STRICT;} \ + {.schema -indent t2} +} {CREATE TABLE t2( + a INTEGER PRIMARY KEY, + b BLOB DEFAULT(jsonb('[]')), + c TEXT NOT NULL +)STRICT;} +do_test shell1-3.21.6 { + exec {*}$CLI -noinit test.db \ + {DROP TABLE t2;} \ + {.schema -indent t2} +} {} # .separator STRING Change column separator used by output and .import do_test shell1-3.22.1 { @@ -643,7 +648,7 @@ do_test shell1-3.22.4 { # .show Show the current values for various settings do_test shell1-3.23.1 { - set res [catchcmd "test.db" ".show"] + set res [catchcmd "test.db" ".mode batch\n.show"] list [regexp {echo:} $res] \ [regexp {explain:} $res] \ [regexp {headers:} $res] \ @@ -679,7 +684,7 @@ do_test shell1-3.23b.4 { # Adverse interaction between .stats and .eqp # do_test shell1-3.23b.5 { - catchcmd "test.db" [string map {"\n " "\n"} { + catchcmd "test.db" [string map {"\n " "\n"} {.mode batch CREATE TEMP TABLE t1(x); INSERT INTO t1 VALUES(1),(2); .stats on @@ -741,30 +746,27 @@ do_test shell1-3.26.5 { do_test shell1-3.26.6 { catchcmd "test.db" ".mode column\n.header off\n.width -10 10\nSELECT 'abcdefg', 123456;" # this should be treated the same as a '1' width for col 1 and 2 -} {0 { abcdefg 123456 }} +} {0 { abcdefg 123456}} # .timer ON|OFF Turn the CPU timer measurement on or off do_test shell1-3.27.1 { catchcmd "test.db" ".timer" -} {1 {Usage: .timer on|off}} -ifcapable !winrt { - # No timer support on winrt. - do_test shell1-3.27.2 { - catchcmd "test.db" ".timer ON" - } {0 {}} -} +} {1 {Usage: .timer on|off|once}} +do_test shell1-3.27.2 { + catchcmd "test.db" ".timer ON" +} {0 {}} do_test shell1-3.27.3 { catchcmd "test.db" ".timer OFF" } {0 {}} do_test shell1-3.27.4 { # too many arguments catchcmd "test.db" ".timer OFF BAD" -} {1 {Usage: .timer on|off}} +} {1 {Usage: .timer on|off|once}} -do_test shell1-3-28.1 { +do_test shell1-3.28.1 { catchcmd test.db \ - ".log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');" + ".mode batch\n.log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');" } "0 {(123) hello\n456}" do_test shell1-3-29.1 { @@ -803,14 +805,14 @@ INSERT INTO t1 VALUES(''); INSERT INTO t1 VALUES(1); INSERT INTO t1 VALUES(2.25); INSERT INTO t1 VALUES('hello'); -INSERT INTO t1 VALUES(X'807f'); +INSERT INTO t1 VALUES(x'807f'); CREATE TABLE t3(x,y); INSERT INTO t3 VALUES(1,NULL); INSERT INTO t3 VALUES(2,''); INSERT INTO t3 VALUES(3,1); INSERT INTO t3 VALUES(4,2.25); INSERT INTO t3 VALUES(5,'hello'); -INSERT INTO t3 VALUES(6,X'807f'); +INSERT INTO t3 VALUES(6,x'807f'); COMMIT;}} @@ -828,14 +830,14 @@ INSERT INTO t1(rowid,x) VALUES(2,''); INSERT INTO t1(rowid,x) VALUES(3,1); INSERT INTO t1(rowid,x) VALUES(4,2.25); INSERT INTO t1(rowid,x) VALUES(5,'hello'); -INSERT INTO t1(rowid,x) VALUES(6,X'807f'); +INSERT INTO t1(rowid,x) VALUES(6,x'807f'); CREATE TABLE t3(x,y); INSERT INTO t3(rowid,x,y) VALUES(1,1,NULL); INSERT INTO t3(rowid,x,y) VALUES(2,2,''); INSERT INTO t3(rowid,x,y) VALUES(3,3,1); INSERT INTO t3(rowid,x,y) VALUES(4,4,2.25); INSERT INTO t3(rowid,x,y) VALUES(5,5,'hello'); -INSERT INTO t3(rowid,x,y) VALUES(6,6,X'807f'); +INSERT INTO t3(rowid,x,y) VALUES(6,6,x'807f'); COMMIT;}} # If the table contains an INTEGER PRIMARY KEY, do not record a separate @@ -854,12 +856,12 @@ do_test shell1-4.1.2 { } {0 {PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE t1(x INTEGER PRIMARY KEY, y); -INSERT INTO t1 VALUES(1,NULL); -INSERT INTO t1 VALUES(2,''); -INSERT INTO t1 VALUES(3,1); -INSERT INTO t1 VALUES(4,2.25); -INSERT INTO t1 VALUES(5,'hello'); -INSERT INTO t1 VALUES(6,X'807f'); +INSERT INTO t1(x,y) VALUES(1,NULL); +INSERT INTO t1(x,y) VALUES(2,''); +INSERT INTO t1(x,y) VALUES(3,1); +INSERT INTO t1(x,y) VALUES(4,2.25); +INSERT INTO t1(x,y) VALUES(5,'hello'); +INSERT INTO t1(x,y) VALUES(6,x'807f'); COMMIT;}} # Verify that the table named [table] is correctly quoted and that @@ -883,7 +885,7 @@ INSERT INTO "table"(rowid,x,y) VALUES(2,12,''); INSERT INTO "table"(rowid,x,y) VALUES(3,23,1); INSERT INTO "table"(rowid,x,y) VALUES(4,34,2.25); INSERT INTO "table"(rowid,x,y) VALUES(5,45,'hello'); -INSERT INTO "table"(rowid,x,y) VALUES(6,56,X'807f'); +INSERT INTO "table"(rowid,x,y) VALUES(6,56,x'807f'); COMMIT;}} # Do not record rowids for a WITHOUT ROWID table. Also check correct quoting @@ -902,12 +904,12 @@ do_test shell1-4.1.4 { } {0 {PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE [ta<>ble](x INTEGER PRIMARY KEY, y) WITHOUT ROWID; -INSERT INTO "ta<>ble" VALUES(1,NULL); -INSERT INTO "ta<>ble" VALUES(12,''); -INSERT INTO "ta<>ble" VALUES(23,1); -INSERT INTO "ta<>ble" VALUES(34,2.25); -INSERT INTO "ta<>ble" VALUES(45,'hello'); -INSERT INTO "ta<>ble" VALUES(56,X'807f'); +INSERT INTO "ta<>ble"(x,y) VALUES(1,NULL); +INSERT INTO "ta<>ble"(x,y) VALUES(12,''); +INSERT INTO "ta<>ble"(x,y) VALUES(23,1); +INSERT INTO "ta<>ble"(x,y) VALUES(34,2.25); +INSERT INTO "ta<>ble"(x,y) VALUES(45,'hello'); +INSERT INTO "ta<>ble"(x,y) VALUES(56,x'807f'); COMMIT;}} # Do not record rowids if the rowid is inaccessible @@ -924,9 +926,9 @@ do_test shell1-4.1.5 { } {0 {PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE t1(_ROWID_,rowid,oid); -INSERT INTO t1 VALUES(1,NULL,'alpha'); -INSERT INTO t1 VALUES(12,'',99); -INSERT INTO t1 VALUES(23,1,X'b0b1b2'); +INSERT INTO t1(_ROWID_,rowid,oid) VALUES(1,NULL,'alpha'); +INSERT INTO t1(_ROWID_,rowid,oid) VALUES(12,'',99); +INSERT INTO t1(_ROWID_,rowid,oid) VALUES(23,1,x'b0b1b2'); COMMIT;}} } else { @@ -941,7 +943,7 @@ do_test shell1-4.1.6 { (4,2.25), (5,'hello'), (6,x'807f'); } catchcmd test2.db {.dump --preserve-rowids} -} {1 {The --preserve-rowids option is not compatible with SQLITE_OMIT_VIRTUALTABLE}} +} {/.* --preserve-rowids option is not compatible with SQLITE_OMIT_VIRTUALTABLE/} } @@ -1020,7 +1022,7 @@ INSERT INTO t1 VALUES(''); INSERT INTO t1 VALUES(1); INSERT INTO t1 VALUES(2.25); INSERT INTO t1 VALUES('hello'); -INSERT INTO t1 VALUES(X'807f');}} +INSERT INTO t1 VALUES(x'807f');}} # Test the output of ".mode insert" with headers # @@ -1031,7 +1033,7 @@ INSERT INTO t1(x) VALUES(''); INSERT INTO t1(x) VALUES(1); INSERT INTO t1(x) VALUES(2.25); INSERT INTO t1(x) VALUES('hello'); -INSERT INTO t1(x) VALUES(X'807f');}} +INSERT INTO t1(x) VALUES(x'807f');}} # Test the output of ".mode insert" # @@ -1042,7 +1044,7 @@ INSERT INTO t3 VALUES(2,''); INSERT INTO t3 VALUES(3,1); INSERT INTO t3 VALUES(4,2.25); INSERT INTO t3 VALUES(5,'hello'); -INSERT INTO t3 VALUES(6,X'807f');}} +INSERT INTO t3 VALUES(6,x'807f');}} # Test the output of ".mode insert" with headers # @@ -1053,7 +1055,7 @@ INSERT INTO t3(x,y) VALUES(2,''); INSERT INTO t3(x,y) VALUES(3,1); INSERT INTO t3(x,y) VALUES(4,2.25); INSERT INTO t3(x,y) VALUES(5,'hello'); -INSERT INTO t3(x,y) VALUES(6,X'807f');}} +INSERT INTO t3(x,y) VALUES(6,x'807f');}} # Test the output of ".mode tcl" # @@ -1069,8 +1071,8 @@ do_test shell1-4.3 { catchcmd test.db ".mode tcl\nselect * from t1;" } {0 {"" "" -"1" -"2.25" +1 +2.25 "hello" "\200\177"}} @@ -1083,15 +1085,15 @@ do_test shell1-4.4 { } catchcmd test.db ".mode tcl\nselect * from t2;" } {0 {"" "" -"1" "2.25" +1 2.25 "hello" "\200\177"}} # Test the output of ".mode tcl" with ".nullvalue" # do_test shell1-4.5 { catchcmd test.db ".mode tcl\n.nullvalue NULL\nselect * from t2;" -} {0 {"NULL" "" -"1" "2.25" +} {0 {NULL "" +1 2.25 "hello" "\200\177"}} # Test the output of ".mode tcl" with Tcl reserved characters @@ -1115,10 +1117,11 @@ do_test shell1-4.6 { # do_test shell1-4.7 { catchcmd test.db ".mode quote\nselect x'0123456789ABCDEF';" -} {0 X'0123456789abcdef'} +} {0 x'0123456789abcdef'} # Test using arbitrary byte data with the shell via standard input/output. # +if 0 { # Causes a valgrind error in TCL. Seems to be a TCL problem. do_test shell1-5.0 { # # NOTE: Skip NUL byte because it appears to be incompatible with command @@ -1185,6 +1188,7 @@ do_test shell1-5.0 { } } } {} +} # These test cases do not work on MinGW if 0 { @@ -1274,7 +1278,7 @@ do_test shell1-7.1.7 { # information. # do_test shell1-8.1 { - catchcmd ":memory:" { + catchcmd ":memory:" {.mode batch -- The pow2 table will hold all the necessary powers of two. CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT); WITH RECURSIVE c(x,v) AS ( @@ -1300,20 +1304,20 @@ do_test_with_ansi_output shell1-8.2 { .mode box SELECT ieee754(47.49) AS x; } -} {0 {┌───────────────────────────────┐ +} {0 {╭───────────────────────────────╮ │ x │ -├───────────────────────────────┤ +╞═══════════════════════════════╡ │ ieee754(6683623321994527,-47) │ -└───────────────────────────────┘}} +╰───────────────────────────────╯}} do_test_with_ansi_output shell1-8.3 { catchcmd ":memory: --box" { select ieee754(6683623321994527,-47) as x; } -} {0 {┌───────┐ +} {0 {╭───────╮ │ x │ -├───────┤ +╞═══════╡ │ 47.49 │ -└───────┘}} +╰───────╯}} do_test shell1-8.4 { catchcmd ":memory: --table" {SELECT ieee754_mantissa(47.49) AS M, ieee754_exponent(47.49) AS E;} } {0 {+------------------+-----+ @@ -1321,35 +1325,42 @@ do_test shell1-8.4 { +------------------+-----+ | 6683623321994527 | -47 | +------------------+-----+}} +do_test shell1-8.4b { + catchcmd ":memory: --psql" \ + {SELECT ieee754_mantissa(47.49) AS M, ieee754_exponent(47.49) AS E;} +} {0 { M | E +------------------+----- + 6683623321994527 | -47}} do_test_with_ansi_output shell1-8.5 { catchcmd ":memory: --box" { create table t(a text, b int); insert into t values ('too long for one line', 1), ('shorter', NULL); .header on +.mode box --wordwrap off .width 10 10 .nullvalue NADA select * from t;} -} {0 {┌────────────┬────────────┐ +} {0 {╭────────────┬────────────╮ │ a │ b │ -├────────────┼────────────┤ -│ too long f │ 1 │ +╞════════════╪════════════╡ +│ too long f │ 1 │ │ or one lin │ │ │ e │ │ ├────────────┼────────────┤ │ shorter │ NADA │ -└────────────┴────────────┘}} +╰────────────┴────────────╯}} #---------------------------------------------------------------------------- # Test cases shell1-9.*: Basic test that "dot" commands and SQL intermix ok. # do_test shell1-9.1 { catchcmd :memory: { -.mode csv +.mode csv --rowsep "\n" /* x */ select 1,2; --x -- .nada ; -.mode csv +.mode csv --rowsep "\n" --x select 2,1; select 3,4; } @@ -1387,4 +1398,30 @@ select base85(zeroblob(2000000000)); } } {/1.*too big.*/} +#---------------------------------------------------------------------------- +# As of 2025-11-17, the default mode is: +# +# qbox --screenwidth auto --linelimit 5 --charlimit 300 --textjsonb on +# +do_test shell1-12.1 { + catchcmd :memory: {.mode tty -quote sql +.print +SELECT jsonb(1234) AS x;} +} {0 { +╭───────────────╮ +│ x │ +╞═══════════════╡ +│ jsonb('1234') │ +╰───────────────╯}} +do_test shell1-12.2 { + catchcmd :memory: {.mode box --textjsonb on +.print +SELECT jsonb(1234) AS x;} +} {0 { +╭──────╮ +│ x │ +╞══════╡ +│ 1234 │ +╰──────╯}} + finish_test diff --git a/test/shell2.test b/test/shell2.test index 5f700a9a1..7141c4d49 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -44,7 +44,7 @@ do_test shell2-1.1.1 { # Shell silently ignores extra parameters. # Ticket [f5cb008a65]. do_test shell2-1.2.1 { - catchcmdex {:memory: "select+3" "select+4"} + catchcmdex {:memory: -list "select+3" "select+4"} } {0 {3 4 }} @@ -64,7 +64,7 @@ do_test shell2-1.3 { UPDATE OR REPLACE t5 SET a = 4 WHERE a = 1; } -} {1 {Runtime error near line 9: too many levels of trigger recursion}} +} {1 {Error near line 9: too many levels of trigger recursion}} @@ -75,7 +75,8 @@ do_test shell2-1.3 { # NB. whitespace is important do_test shell2-1.4.1 { forcedelete foo.db - catchcmd "foo.db" {CREATE TABLE foo(a); + catchcmd "foo.db" {.mode batch +CREATE TABLE foo(a); INSERT INTO foo(a) VALUES(1); SELECT * FROM foo;} } {0 1} @@ -96,7 +97,9 @@ SELECT * FROM foo; # NB. whitespace is important do_test shell2-1.4.3 { forcedelete foo.db - catchcmd "foo.db" {.echo ON + catchcmd "foo.db" { +.mode batch +.echo ON CREATE TABLE foo(a); INSERT INTO foo(a) VALUES(1); SELECT * FROM foo;} @@ -110,7 +113,9 @@ SELECT * FROM foo; # NB. whitespace is important do_test shell2-1.4.4 { forcedelete foo.db - catchcmd "foo.db" {.echo ON + catchcmd "foo.db" { +.mode batch +.echo ON CREATE TABLE foo(a); .echo OFF INSERT INTO foo(a) VALUES(1); @@ -124,7 +129,9 @@ SELECT * FROM foo;} # NB. whitespace is important do_test shell2-1.4.5 { forcedelete foo.db - catchcmdex "foo.db" {.echo ON + catchcmdex "foo.db" { +.mode batch +.echo ON CREATE TABLE foo1(a); INSERT INTO foo1(a) VALUES(1); CREATE TABLE foo2(b); @@ -153,7 +160,9 @@ SELECT * FROM foo1; SELECT * FROM foo2; # NB. whitespace is important do_test shell2-1.4.6 { forcedelete foo.db - catchcmdex "foo.db" {.echo ON + catchcmdex "foo.db" { +.mode batch +.echo ON .headers ON CREATE TABLE foo1(a); INSERT INTO foo1(a) VALUES(1); @@ -208,6 +217,7 @@ do_test shell2-1.4.9 { do_test shell2-1.4.9 { forcedelete clone.db set res [catchcmd :memory: [string trim { +.mode batch CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT); INSERT INTO t VALUES (1),(2); .clone clone.db @@ -222,6 +232,7 @@ ifcapable vtab { # See overflow report at https://sqlite.org/forum/forumpost/5d34ce5280 do_test shell2-1.4.10 { set res [catchcmd :memory: [string trim { + .mode batch SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1); SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1); SELECT avg(value),min(value),max(value) FROM generate_series( @@ -248,6 +259,65 @@ do_test shell2-1.4.10 { 0 1 2}} +do_test shell2-1.4.10b { + set res [catchcmd :memory: [string trim { + .mode tty +.print + SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1); + SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1); + SELECT avg(value),min(value),max(value) FROM generate_series( + -9223372036854775808,9223372036854775807,1085102592571150095); + SELECT * FROM generate_series(-9223372036854775808,9223372036854775807, + 9223372036854775807); + SELECT value FROM generate_series(-4611686018427387904, + 4611686018427387904, 4611686018427387904) ORDER BY value DESC; + SELECT * FROM generate_series(0,-2,-1); + SELECT * FROM generate_series(0,-2); + SELECT * FROM generate_series(0,2) LIMIT 3;}]] +} {0 { +╭─────────────────────╮ +│ value │ +╞═════════════════════╡ +│ 9223372036854775807 │ +╰─────────────────────╯ +╭─────────────────────╮ +│ value │ +╞═════════════════════╡ +│ 9223372036854775807 │ +╰─────────────────────╯ +╭────────────┬──────────────────────┬─────────────────────╮ +│ avg(value) │ min(value) │ max(value) │ +╞════════════╪══════════════════════╪═════════════════════╡ +│ -0.5 │ -9223372036854775808 │ 9223372036854775807 │ +╰────────────┴──────────────────────┴─────────────────────╯ +╭──────────────────────╮ +│ value │ +╞══════════════════════╡ +│ -9223372036854775808 │ +│ -1 │ +│ 9223372036854775806 │ +╰──────────────────────╯ +╭──────────────────────╮ +│ value │ +╞══════════════════════╡ +│ 4611686018427387904 │ +│ 0 │ +│ -4611686018427387904 │ +╰──────────────────────╯ +╭───────╮ +│ value │ +╞═══════╡ +│ 0 │ +│ -1 │ +│ -2 │ +╰───────╯ +╭───────╮ +│ value │ +╞═══════╡ +│ 0 │ +│ 1 │ +│ 2 │ +╰───────╯}} } ;# ifcapable vtab ifcapable vtab { @@ -260,17 +330,17 @@ do_test shell2-1.4.11 { close $df set res [catchcmd :memory: [string trim { CREATE TABLE t(line text); -.mode ascii -.separator "\377" "\n" +.mode ascii -colsep "\377" -rowsep "\n" .import dummy.csv t SELECT count(*) FROM t;}]] -} {0 1} +} {1 {0 +Error: .import column separator must be ASCII}} } ;# ifcapable vtab # Bug from forum post 7cbe081746dd3803 # Keywords as column names were producing an error message. do_test shell2-1.4.12 { - set res [catchcmd :memory: [string trim { + set res [catchcmd :memory: [string trim {.mode batch CREATE TABLE "group"("order" text); INSERT INTO "group" VALUES ('ABC'); .sha3sum}]] diff --git a/test/shell4.test b/test/shell4.test index 3ced0702e..3614909c7 100644 --- a/test/shell4.test +++ b/test/shell4.test @@ -136,15 +136,15 @@ SELECT * FROM t1;} do_test shell4-3.1 { set fd [open t1.txt wb] - puts $fd "SELECT 'squirrel';" + puts $fd ".mode list\nSELECT 'squirrel';" close $fd - exec $::CLI_ONLY :memory: --interactive ".read t1.txt" + exec $::CLI_ONLY --noinit :memory: --interactive ".read t1.txt" } {squirrel} do_test_with_ansi_output shell4-3.2 { set fd [open t1.txt wb] - puts $fd "SELECT 'pound: \302\243';" + puts $fd ".mode list\nSELECT 'pound: \302\243';" close $fd - exec $::CLI_ONLY :memory: --interactive ".read t1.txt" + exec $::CLI_ONLY --noinit :memory: --interactive ".read t1.txt" } {pound: £} do_test shell4-4.1 { diff --git a/test/shell5.test b/test/shell5.test index 70a2298bc..559dc3ce7 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -33,14 +33,15 @@ forcedelete test.db test.db-journal test.db-wal # .import FILE TABLE Import data from FILE into TABLE do_test shell5-1.1.1 { catchcmd "test.db" ".import" -} {/1 .ERROR: missing FILE argument.*/} +} {/1 .line 1: Missing FILE argument.*/} do_test shell5-1.1.2 { catchcmd "test.db" ".import FOO" -} {/1 .ERROR: missing TABLE argument.*/} +} {/1 .line 1: Missing TABLE argument.*/} do_test shell5-1.1.3 { # too many arguments catchcmd "test.db" ".import FOO BAR BAD" -} {/1 .ERROR: extra argument.*/} +} {1 {line 1: .import FOO BAR BAD +line 1: ^--- unknown argument}} # .separator STRING Change separator used by output mode and .import do_test shell5-1.2.1 { @@ -59,13 +60,13 @@ do_test shell5-1.2.4 { # column separator should default to "|" do_test shell5-1.3.1.1 { - set res [catchcmd "test.db" ".show"] + set res [catchcmd "test.db" ".mode list\n.show"] list [regexp {colseparator: \"\|\"} $res] } {1} # row separator should default to "\n" do_test shell5-1.3.1.2 { - set res [catchcmd "test.db" ".show"] + set res [catchcmd "test.db" ".mode list\n.show"] list [regexp {rowseparator: \"\\n\"} $res] } {1} @@ -82,7 +83,7 @@ do_test shell5-1.4.1 { forcedelete FOO set res [catchcmd "test.db" {CREATE TABLE t1(a, b); .import FOO t1}] -} {1 {Error: cannot open "FOO"}} +} {1 {line 2: cannot open "FOO"}} # the remainder of these test cases require virtual tables. # @@ -97,7 +98,9 @@ do_test shell5-1.4.2 { forcedelete shell5.csv set in [open shell5.csv w] close $in - set res [catchcmd ":memory:" {ATTACH 'test.db' AS test; + set res [catchcmd ":memory:" { +.mode list +ATTACH 'test.db' AS test; .import -schema test shell5.csv t1 SELECT COUNT(*) FROM test.t1;}] } {0 0} @@ -116,7 +119,7 @@ do_test shell5-1.4.4 { set in [open shell5.csv w] puts $in "1|2|3" close $in - set res [catchcmd ":memory:" {ATTACH 'test.db' AS test; + set res [catchcmd ":memory: --list" {ATTACH 'test.db' AS test; .import --schema test shell5.csv t1}] } {1 {shell5.csv:1: expected 2 columns but found 3 - extras ignored}} @@ -125,7 +128,7 @@ do_test shell5-1.4.5 { set in [open shell5.csv w] puts $in "1|2" close $in - set res [catchcmd "test.db" {DELETE FROM t1; + set res [catchcmd "test.db -list" {DELETE FROM t1; .import shell5.csv t1 SELECT COUNT(*) FROM t1;}] } {0 1} @@ -138,7 +141,9 @@ do_test shell5-1.4.6 { puts $in "2|3" puts $in "3|4" close $in - set res [catchcmd ":memory:" {ATTACH 'test.db' AS test; + set res [catchcmd ":memory:" { +.mode list +ATTACH 'test.db' AS test; .import -schema test shell5.csv t1 SELECT COUNT(*) FROM test.t1;}] } {0 3} @@ -148,7 +153,9 @@ do_test shell5-1.4.7 { set in [open shell5.csv w] puts $in "4,5" close $in - set res [catchcmd ":memory:" {ATTACH 'test.db' AS test; + set res [catchcmd ":memory:" { +.mode list +ATTACH 'test.db' AS test; .separator , .import --schema test shell5.csv t1 SELECT COUNT(*) FROM test.t1;}] @@ -159,12 +166,13 @@ do_test shell5-1.4.8.1 { set in [open shell5.csv w] puts $in "5|Now is the time for all good men to come to the aid of their country." close $in - set res [catchcmd "test.db" {.import shell5.csv t1 + set res [catchcmd "test.db" {.mode list +.import shell5.csv t1 SELECT COUNT(*) FROM t1;}] } {0 5} do_test shell5-1.4.8.2 { - catchcmd "test.db" {SELECT b FROM t1 WHERE a='5';} + catchcmd "test.db -list" {SELECT b FROM t1 WHERE a='5';} } {0 {Now is the time for all good men to come to the aid of their country.}} # import file with 1 row, 2 columns, quoted text data @@ -174,12 +182,12 @@ do_test shell5-1.4.9.1 { set in [open shell5.csv w] puts $in "6|'Now is the time for all good men to come to the aid of their country.'" close $in - set res [catchcmd "test.db" {.import shell5.csv t1 + set res [catchcmd "test.db -list" {.import shell5.csv t1 SELECT COUNT(*) FROM t1;}] } {0 6} do_test shell5-1.4.9.2 { - catchcmd "test.db" {SELECT b FROM t1 WHERE a='6';} + catchcmd "test.db -list" {SELECT b FROM t1 WHERE a='6';} } {0 {'Now is the time for all good men to come to the aid of their country.'}} # import file with 1 row, 2 columns, quoted text data @@ -187,12 +195,12 @@ do_test shell5-1.4.10.1 { set in [open shell5.csv w] puts $in "7|\"Now is the time for all good men to come to the aid of their country.\"" close $in - set res [catchcmd "test.db" {.import shell5.csv t1 + set res [catchcmd "test.db -list" {.import shell5.csv t1 SELECT COUNT(*) FROM t1;}] } {0 7} do_test shell5-1.4.10.2 { - catchcmd "test.db" {SELECT b FROM t1 WHERE a='7';} + catchcmd "test.db -list" {SELECT b FROM t1 WHERE a='7';} } {0 {Now is the time for all good men to come to the aid of their country.}} # import file with 2 rows, 2 columns and an initial BOM @@ -203,7 +211,7 @@ do_test shell5-1.4.11 { puts $in "2|3" puts $in "4|5" close $in - set res [catchcmd "test.db" {CREATE TABLE t2(x INT, y INT); + set res [catchcmd "test.db -list" {CREATE TABLE t2(x INT, y INT); .import shell5.csv t2 .mode quote .header on @@ -218,7 +226,7 @@ do_test shell5-1.4.12 { puts $in "\xef\xbb\xbf\"two\"|3" puts $in "4|5" close $in - set res [catchcmd "test.db" {DELETE FROM t2; + set res [catchcmd "test.db -list" {DELETE FROM t2; .import shell5.csv t2 .mode quote .header on @@ -232,7 +240,7 @@ do_test shell5-1.5.1 { set in [open shell5.csv w] puts $in "8|$str" close $in - set res [catchcmd "test.db" {.import shell5.csv t1 + set res [catchcmd "test.db -list" {.import shell5.csv t1 SELECT length(b) FROM t1 WHERE a='8';}] } {0 999} @@ -252,7 +260,7 @@ do_test shell5-1.6.1 { set in [open shell5.csv w] puts $in $data close $in - set res [catchcmd "test.db" {DROP TABLE IF EXISTS t2; + set res [catchcmd "test.db -list" {DROP TABLE IF EXISTS t2; .import shell5.csv t2 SELECT COUNT(*) FROM t2;}] } {0 1} @@ -268,6 +276,7 @@ do_test shell5-1.7.1 { close $in set res [catchcmd "test.db" {.mode csv .import shell5.csv t3 +.mode quote SELECT COUNT(*) FROM t3;}] } [list 0 $rows] @@ -329,6 +338,24 @@ do_test shell5-1.10 { db eval {SELECT hex(c) FROM t1 ORDER BY rowid} } {636F6C756D6E33 783320220D0A64617461222033 783320220A64617461222033} +# The --escape option +# +do_test shell5-1.10.1 { + set out [open shell5.csv w] + fconfigure $out -translation lf + puts $out {column1,column2,column3,column4} + puts $out "x1,x2%\"x3,\"x3\\\"data\\\"3\",x4" + close $out + db close + forcedelete test.db + catchcmd test.db { + CREATE TABLE t1(a,b,c,d); +.import --csv --qesc \\ --esc % shell5.csv t1 + } + sqlite3 db test.db + db eval {SELECT b, c FROM t1 ORDER BY rowid} +} {column2 column3 x2\"x3 x3\"data\"3} + # Blank last column with \r\n line endings. do_test shell5-1.11 { set out [open shell5.csv w] @@ -495,9 +522,10 @@ do_test shell5-4.4 { CREATE TEMP TABLE t8(a, b, c); .import shell5.csv t8 .nullvalue # +.mode quote SELECT * FROM temp.t8 }] -} {0 1,2,3} +} {0 '1','2','3'} #---------------------------------------------------------------------------- # Tests for the shell automatic column rename. @@ -513,7 +541,7 @@ do_test shell5-5.1 { close $out forcedelete test.db catchcmd test.db {.import -csv shell5.csv t1 -.mode line +.mode line --colsep ' = ' SELECT * FROM t1;} } {1 { ? = 0 x_02 = x2 @@ -529,7 +557,7 @@ Columns renamed during .import shell5.csv due to duplicates: "z" to "z_05", "z" to "z_08"}} -do_test shell5-5.1 { +do_test shell5-5.1b { set out [open shell5.csv w] fconfigure $out -translation lf puts $out {"COW","cow","CoW","cOw"} @@ -539,10 +567,10 @@ do_test shell5-5.1 { catchcmd test.db {.import -csv shell5.csv t1 .mode line SELECT * FROM t1;} -} {1 {COW_1 = uuu -cow_2 = lll -CoW_3 = ulu -cOw_4 = lul +} {1 {COW_1: uuu +cow_2: lll +CoW_3: ulu +cOw_4: lul Columns renamed during .import shell5.csv due to duplicates: "COW" to "COW_1", "cow" to "cow_2", @@ -561,7 +589,7 @@ do_test_with_ansi_output shell5-6.1 { close $out forcedelete test.db catchcmd test.db {.import -csv shell5.csv t1 -.mode line +.mode line --colsep " = " SELECT * FROM t1;} } {0 { あい = 1 うえお = 2}} @@ -576,8 +604,8 @@ do_test_with_ansi_output shell5-6.2 { catchcmd test.db {.import -csv shell5.csv t1 .mode line SELECT * FROM t1;} -} {0 { 1 = あい - 2 = うえお}} +} {0 {1: あい +2: うえお}} # 2024-03-11 https://sqlite.org/forum/forumpost/ca014d7358 # Import into a table that contains computed columns. @@ -588,7 +616,7 @@ do_test shell5-7.1 { puts $out {aaa|bbb} close $out forcedelete test.db - catchcmd :memory: {CREATE TABLE t1(a TEXT, b TEXT, c AS (a||b)); + catchcmd ":memory: -list" {CREATE TABLE t1(a TEXT, b TEXT, c AS (a||b)); .import shell5.csv t1 SELECT * FROM t1;} } {0 aaa|bbb|aaabbb} @@ -605,4 +633,36 @@ do_test shell5-8.1 { catchcmd :memory: {.import --csv shell5.csv '""""""""""""""""""""""""""""""""""""""""""""""'} } {0 {}} + +# 2025-12-29 https://sqlite.org/forum/forumpost/6c1c0e213d +# .import honor .bail +# +do_test shell5-9.1 { + catchcmd ":memory:" { + CREATE TABLE t1(a,b,c INT CHECK(c<>5)); +.bail on +.import -csv <<END t1 +1,2,3 +"a","b","c" +3,4,5 +"q","r","s" +END +SELECT * FROM t1;} +} {1 {<stdin>:7: INSERT failed: CHECK constraint failed: c<>5}} +do_test shell5-9.2 { + catchcmd ":memory:" { + CREATE TABLE t1(a,b,c INT CHECK(c<>5)); +.bail off +.import -csv <<END t1 +1,2,3 +"a","b","c" +3,4,5 +"q","r","s" +END +SELECT * FROM t1;} +} {1 {1|2|3 +a|b|c +q|r|s +<stdin>:7: INSERT failed: CHECK constraint failed: c<>5}} + finish_test diff --git a/test/shell8.test b/test/shell8.test index e55539636..40579a599 100644 --- a/test/shell8.test +++ b/test/shell8.test @@ -217,6 +217,46 @@ if {$tcl_platform(platform)=="unix"} { do_test 3.3 { catchcmd shell8.db {.ar -x} } {0 {}} + + # Test defenses against using symlinks to write outside + # of the destination directory. See forum thread at + # sqlite.org/forum/forumpost/2026-02-21T11:04:36z + # + forcedelete shell8.db + forcedelete ar1 + forcedelete ar2 + forcedelete ar3 + file mkdir ar2 + file mkdir ar3 + set pwd [pwd] + sqlite3 db shell8.db + db eval { + CREATE TABLE sqlar( + name TEXT PRIMARY KEY, -- name of the file + mode INT, -- access permissions + mtime INT, -- last modification time + sz INT, -- original file size + data BLOB -- compressed content + ); + INSERT INTO sqlar VALUES + ('abc',33188,0,-1,'content for abc'), + ('escape',40960,0,-1,$pwd||'/ar3'), + ('escape/def',33188,0,-1,'content for escape/def'), + ('ghi',33188,0,-1,'content for ghi'); + } + do_test 3.4.1 { + catchcmd shell8.db {.ar -x --directory ar2} + lsort [glob -tails -directory ar2 -nocomplain *] + } {abc escape ghi} + do_test 3.4.2 { + lsort [glob -tails -directory ar3 -nocomplain *] + } {} + # ^^--- An extraction into ar2 should not leak any files into ar3 + + forcedelete shell8.db + forcedelete ar2 + forcedelete ar3 + } finish_test diff --git a/test/shellA.test b/test/shellA.test index f3959d428..3b28c921c 100644 --- a/test/shellA.test +++ b/test/shellA.test @@ -36,11 +36,11 @@ do_execsql_test shellA-1.0 { # and that our calls to the CLI are working. # do_test_with_ansi_output shellA-1.2 { - exec {*}$CLI test.db {.mode box --escape symbol} {SELECT * FROM t1;} + exec {*}$CLI -noinit test.db {.mode box -quote off --escape symbol} {SELECT * FROM t1;} } { -┌───┬──────────────────────────┐ +╭───┬──────────────────────────╮ │ a │ x │ -├───┼──────────────────────────┤ +╞═══╪══════════════════════════╡ │ 1 │ line with ' single quote │ ├───┼──────────────────────────┤ │ 2 │ ␛[31mVT-100 codes␛[0m │ @@ -57,39 +57,39 @@ do_test_with_ansi_output shellA-1.2 { │ 7 │ carriage␍return │ ├───┼──────────────────────────┤ │ 8 │ last line │ -└───┴──────────────────────────┘ +╰───┴──────────────────────────╯ } # ".mode list" # do_test shellA-1.3 { - exec {*}$CLI test.db {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI -noinit -list test.db {SELECT x FROM t1 WHERE a=2;} } { ^[[31mVT-100 codes^[[0m } do_test_with_ansi_output shellA-1.4 { - exec {*}$CLI test.db --escape symbol {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI -noinit -list test.db --escape symbol {SELECT x FROM t1 WHERE a=2;} } { ␛[31mVT-100 codes␛[0m } do_test shellA-1.5 { - exec {*}$CLI test.db --escape ascii {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI -noinit -list test.db --escape ascii {SELECT x FROM t1 WHERE a=2;} } { ^[[31mVT-100 codes^[[0m } do_test_with_ansi_output shellA-1.6 { - exec {*}$CLI test.db {.mode list --escape symbol} {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI -noinit test.db {.mode list --escape symbol} {SELECT x FROM t1 WHERE a=2;} } { ␛[31mVT-100 codes␛[0m } do_test shellA-1.7 { - exec {*}$CLI test.db {.mode list --escape ascii} {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI -noinit test.db {.mode list --escape ascii} {SELECT x FROM t1 WHERE a=2;} } { ^[[31mVT-100 codes^[[0m } do_test shellA-1.8 { file delete -force out.txt - exec {*}$CLI test.db {.mode list --escape off} {SELECT x FROM t1 WHERE a=7;} \ + exec {*}$CLI -noinit test.db {.mode list --escape off} {SELECT x FROM t1 WHERE a=7;} \ >out.txt set fd [open out.txt rb] set res [read $fd] @@ -98,92 +98,93 @@ do_test shellA-1.8 { } "carriage\rreturn" do_test shellA-1.9 { set rc [catch { - exec {*}$CLI test.db {.mode test --escape xyz} + exec {*}$CLI -noinit test.db {.mode test --escape xyz} } msg] lappend rc $msg -} {1 {unknown control character escape mode "xyz" - choices: ascii symbol off}} +} {1 {argv[3]: .mode test --escape xyz +argv[3]: ^--- unknown mode +argv[3]: Use ".help .mode" for more info}} do_test shellA-1.10 { set rc [catch { - exec {*}$CLI --escape abc test.db .q + exec {*}$CLI --noinit --escape abc test.db .q } msg] lappend rc $msg -} {1 {unknown control character escape mode "abc" - choices: ascii symbol off}} +} {1 {unknown control character escape mode "abc" - choices: auto off ascii symbol}} # ".mode quote" # do_test shellA-2.1 { - exec {*}$CLI test.db --quote {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} + exec {*}$CLI -noinit test.db --quote {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { 1,'line with '' single quote' 2,unistr('\u001b[31mVT-100 codes\u001b[0m') -6,'new -line' +6,unistr('new\u000aline') 7,unistr('carriage\u000dreturn') 8,'last line' } do_test shellA-2.2 { - exec {*}$CLI test.db --quote {.mode} -} {current output mode: quote --escape ascii} + exec {*}$CLI -noinit test.db --quote {.mode -v} +} {/*.mode quote* --escape auto*/} do_test shellA-2.3 { - exec {*}$CLI test.db --quote --escape SYMBOL {.mode} -} {current output mode: quote --escape symbol} + exec {*}$CLI -noinit test.db --quote --escape SYMBOL {.mode} +} {.mode quote --escape symbol} do_test shellA-2.4 { - exec {*}$CLI test.db --quote --escape OFF {.mode} -} {current output mode: quote --escape off} + exec {*}$CLI -noinit test.db --quote --escape OFF {.mode} +} {.mode quote --escape off} # ".mode line" # do_test_with_ansi_output shellA-3.1 { - exec {*}$CLI test.db --line --escape symbol \ + exec {*}$CLI -noinit test.db --line --escape symbol \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { - a = 1 - x = line with ' single quote + a: 1 + x: line with ' single quote - a = 2 - x = ␛[31mVT-100 codes␛[0m + a: 2 + x: ␛[31mVT-100 codes␛[0m - a = 6 - x = new -line + a: 6 + x: new + line - a = 7 - x = carriage␍return + a: 7 + x: carriage␍return - a = 8 - x = last line + a: 8 + x: last line } do_test shellA-3.2 { - exec {*}$CLI test.db --line --escape ascii \ + exec {*}$CLI -noinit test.db --line --escape ascii \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { - a = 1 - x = line with ' single quote + a: 1 + x: line with ' single quote - a = 2 - x = ^[[31mVT-100 codes^[[0m + a: 2 + x: ^[[31mVT-100 codes^[[0m - a = 6 - x = new -line + a: 6 + x: new + line - a = 7 - x = carriage^Mreturn + a: 7 + x: carriage^Mreturn - a = 8 - x = last line + a: 8 + x: last line } # ".mode box" # do_test_with_ansi_output shellA-4.1 { - exec {*}$CLI test.db --box --escape ascii \ + exec {*}$CLI -noinit test.db --box --escape ascii \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { -┌───┬──────────────────────────┐ +╭───┬──────────────────────────╮ │ a │ x │ -├───┼──────────────────────────┤ +╞═══╪══════════════════════════╡ │ 1 │ line with ' single quote │ ├───┼──────────────────────────┤ │ 2 │ ^[[31mVT-100 codes^[[0m │ @@ -194,31 +195,56 @@ do_test_with_ansi_output shellA-4.1 { │ 7 │ carriage^Mreturn │ ├───┼──────────────────────────┤ │ 8 │ last line │ -└───┴──────────────────────────┘ +╰───┴──────────────────────────╯ +} +do_test_with_ansi_output shellA-4.1b { + exec {*}$CLI -noinit test.db --box --escape ascii \ + {.mode -border off} \ + {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} +} { + a │ x +═══╪══════════════════════════ + 1 │ line with ' single quote +───┼────────────────────────── + 2 │ ^[[31mVT-100 codes^[[0m +───┼────────────────────────── + 6 │ new + │ line +───┼────────────────────────── + 7 │ carriage^Mreturn +───┼────────────────────────── + 8 │ last line } do_test_with_ansi_output shellA-4.2 { - exec {*}$CLI test.db {.mode qbox} {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} + exec {*}$CLI -noinit test.db {.mode qbox} {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { -┌───┬───────────────────────────────────────────┐ +╭───┬───────────────────────────────────────────╮ │ a │ x │ -├───┼───────────────────────────────────────────┤ +╞═══╪═══════════════════════════════════════════╡ │ 1 │ 'line with '' single quote' │ -├───┼───────────────────────────────────────────┤ │ 2 │ unistr('\u001b[31mVT-100 codes\u001b[0m') │ -├───┼───────────────────────────────────────────┤ -│ 6 │ 'new │ -│ │ line' │ -├───┼───────────────────────────────────────────┤ +│ 6 │ unistr('new\u000aline') │ │ 7 │ unistr('carriage\u000dreturn') │ -├───┼───────────────────────────────────────────┤ │ 8 │ 'last line' │ -└───┴───────────────────────────────────────────┘ +╰───┴───────────────────────────────────────────╯ +} +do_test_with_ansi_output shellA-4.2b { + exec {*}$CLI -noinit test.db {.mode qbox -border off} \ + {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} +} { + a │ x +═══╪═══════════════════════════════════════════ + 1 │ 'line with '' single quote' + 2 │ unistr('\u001b[31mVT-100 codes\u001b[0m') + 6 │ unistr('new\u000aline') + 7 │ unistr('carriage\u000dreturn') + 8 │ 'last line' } # ".mode insert" # do_test shellA-5.1 { - exec {*}$CLI test.db {.mode insert t1 --escape ascii} \ + exec {*}$CLI -noinit test.db {.mode insert t1 --escape ascii} \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { INSERT INTO t1 VALUES(1,'line with '' single quote'); @@ -228,7 +254,7 @@ INSERT INTO t1 VALUES(7,unistr('carriage\u000dreturn')); INSERT INTO t1 VALUES(8,'last line'); } do_test shellA-5.2 { - exec {*}$CLI test.db {.mode insert t1 --escape symbol} \ + exec {*}$CLI -noinit test.db {.mode insert t1 --escape symbol} \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { INSERT INTO t1 VALUES(1,'line with '' single quote'); @@ -239,7 +265,7 @@ INSERT INTO t1 VALUES(8,'last line'); } do_test shellA-5.3 { file delete -force out.txt - exec {*}$CLI test.db {.mode insert t1 --escape off} \ + exec {*}$CLI -noinit test.db {.mode insert t1 --escape off} \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} >out.txt set fd [open out.txt rb] set res [read $fd] @@ -254,4 +280,83 @@ INSERT INTO t1 VALUES(7,'carriage\rreturn'); INSERT INTO t1 VALUES(8,'last line'); " +# ".mode split" +# +do_test shellA-6.1 { + db eval { + CREATE TABLE t2(x); + INSERT INTO t2(x) VALUES + ('one'), ('two'), ('three'), ('four'), ('five'), + ('six'), ('seven'), ('eight'), ('nine'), ('ten'), + ('eleven'), ('twelve'), ('thirteen'), ('fourteen'); + } + exec {*}$CLI -noinit test.db \ + {.print} \ + {.mode split -screenwidth 30} \ + {SELECT x FROM t2} +} { +one five nine thirteen +two six ten fourteen +three seven eleven +four eight twelve} +# 3456789 123456789 123456789 + +do_test shellA-6.2 { + exec {*}$CLI -noinit test.db \ + {.print} \ + {.mode split -screenwidth 30} \ + {SELECT x FROM t2} \ + {.mode column -titles off} \ + {SELECT x FROM t2} +} { +one five nine thirteen +two six ten fourteen +three seven eleven +four eight twelve +one +two +three +four +five +six +seven +eight +nine +ten +eleven +twelve +thirteen +fourteen} + +do_test shellA-6.3 { + exec {*}$CLI -noinit test.db \ + {.print} \ + {.mode table} \ + {.mode --once split -screenwidth 30} \ + {SELECT x FROM t2} \ + {SELECT x FROM t2} +} { +one five nine thirteen +two six ten fourteen +three seven eleven +four eight twelve ++----------+ +| x | ++----------+ +| one | +| two | +| three | +| four | +| five | +| six | +| seven | +| eight | +| nine | +| ten | +| eleven | +| twelve | +| thirteen | +| fourteen | ++----------+} + finish_test diff --git a/test/shellB.test b/test/shellB.test new file mode 100644 index 000000000..d98a77cb5 --- /dev/null +++ b/test/shellB.test @@ -0,0 +1,53 @@ +# 2025-11-12 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# TESTRUNNER: shell +# +# Test cases for the command-line shell using the newly renovated +# ".testcase" and ".check" commands. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set CLI [test_cli_invocation] + +# Run an instance of the CLI on the file $name. +# Capture the number of test cases and the number of +# errors and increment the counts. +# +proc do_clitest {name} { + set mapping [list <NAME> $::testdir/$name <CLI> $::CLI] + set script [string map $mapping { + catch {exec <CLI> :memory: ".read <NAME>" 2>@stdout} res + set ntest 0 + set nerr 999 + regexp {(\d+) tests? run with (\d+) errors?} $res all ntest nerr + set_test_counter count [expr {[set_test_counter count]+$ntest-1}] + set_test_counter errors [expr {[set_test_counter errors]+$nerr}] + if {$nerr==0} {set res "error count: 0"} + set res + }] + # puts $script + do_test shellB-$name $script {error count: 0} +} + +do_clitest modeA.sql +do_clitest dblwidth-a.sql +do_clitest vt100-a.sql +do_clitest regexp1.sql +do_clitest imposter1.sql +do_clitest dotcmd01.sql +ifcapable vtab { + do_clitest import01.sql + do_clitest intck01.sql +} +do_clitest fptest01.sql + +finish_test diff --git a/test/speedtest.md b/test/speedtest.md index 135e562ae..98cba93ff 100644 --- a/test/speedtest.md +++ b/test/speedtest.md @@ -8,8 +8,9 @@ You will need: * valgrind * tclsh * A script or program named "open" that brings up *.txt files in an - editor for viewing. (Macs provide this by default. You'll need to - come up with your own on Linux and Windows.) + editor for viewing. (Macs provide this by default. On Linux it's + called xdg-open and some distributions symlink it to "open". You'll + need to come up with your own on Windows.) * An SQLite source tree The procedure described in this document is not the only way to make diff --git a/test/speedtest.tcl b/test/speedtest.tcl index 9cb81c0fc..26bc27530 100755 --- a/test/speedtest.tcl +++ b/test/speedtest.tcl @@ -27,7 +27,7 @@ Other options include: --lookaside N SZ Lookahead uses N slots of SZ bytes each. --osmalloc Use the OS native malloc() instead of MEMSYS5 --pagesize N Use N as the page size. - --quiet | -q "Quite". Put results in file but don't pop up editor + --quiet | -q "Quiet". Put results in file but don't pop up editor --size N Change the test size. 100 means 100%. Default: 5. --testset TEST Specify the specific testset to use. The default is "mix1". Other options include: "main", "json", diff --git a/test/tabfunc01.test b/test/tabfunc01.test index 9a2017c46..60f546ce4 100644 --- a/test/tabfunc01.test +++ b/test/tabfunc01.test @@ -668,6 +668,20 @@ do_execsql_test 1370 { SELECT * FROM generate_series(0,0,0); } {} +reset_db +load_static_extension db series +do_execsql_test 1400 { + CREATE TABLE t1(x); + CREATE TABLE t2(y); +} +do_catchsql_test 1410 { + SELECT x, y, value + FROM (t1 RIGHT JOIN generate_series(t2.y,5) AS value) JOIN t2; +} {1 {table-function argument references tables to its right}} +do_catchsql_test 1420 { + SELECT x, y, value + FROM t2 JOIN (t1 RIGHT JOIN generate_series(t2.y,5) AS value) +} {1 {no such column: t2.y}} # Free up memory allocations diff --git a/test/tclsqlite.test b/test/tclsqlite.test index 5f373ea18..6cababad3 100644 --- a/test/tclsqlite.test +++ b/test/tclsqlite.test @@ -42,7 +42,7 @@ do_test tcl-1.1.1 { do_test tcl-1.2 { set v [catch {db bogus} msg] lappend v $msg -} {1 {bad option "bogus": must be authorizer, backup, bind_fallback, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, config, copy, deserialize, enable_load_extension, errorcode, erroroffset, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, serialize, status, timeout, total_changes, trace, trace_v2, transaction, unlock_notify, update_hook, version, or wal_hook}} +} {1 {bad option "bogus": must be authorizer, backup, bind_fallback, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, config, copy, deserialize, enable_load_extension, errorcode, erroroffset, eval, exists, format, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, serialize, status, timeout, total_changes, trace, trace_v2, transaction, unlock_notify, update_hook, version, or wal_hook}} do_test tcl-1.2.1 { set v [catch {db cache bogus} msg] lappend v $msg diff --git a/test/temptrigfault.tes b/test/temptrigfault.tes new file mode 100644 index 000000000..4b124e1f7 --- /dev/null +++ b/test/temptrigfault.tes @@ -0,0 +1,120 @@ +# 2025 November 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix temptrigfault + +forcedelete test.db2 +do_execsql_test 1.0 { + CREATE TABLE t1(x, y); + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.t1(x, y); +} + +do_faultsim_test 1.1 -faults oom* -prep { +} -body { + execsql { + CREATE TEMP TRIGGER tmptrig AFTER INSERT ON t1 BEGIN + INSERT INTO aux.t1 VALUES(new.x, new.y); + END; + } +} -test { + faultsim_test_result {0 {}} + catchsql { DROP TRIGGER tmptrig } +} + +do_execsql_test 2.0 { + CREATE TEMP TRIGGER tmptrig AFTER INSERT ON t1 BEGIN + INSERT INTO aux.t1 VALUES(new.x, new.y); + END; +} + +do_faultsim_test 2 -faults oom* -prep { +} -body { + execsql { + INSERT INTO t1 VALUES('x', 'y'); + } +} -test { + faultsim_test_result {0 {}} +} + +do_execsql_test 3.0.1 { + SELECT * FROM t1; + DELETE FROM t1; + DELETE FROM aux.t1; +} [db eval {SELECT * FROM aux.t1}] + +do_execsql_test 3.0.2 { + CREATE TEMP TRIGGER tmptrig2 AFTER INSERT ON aux.t1 BEGIN + INSERT INTO t1 VALUES(new.x||'2', new.y||'2'); + END; +} + +do_faultsim_test 3 -faults oom* -prep { +} -body { + execsql { + INSERT INTO aux.t1 VALUES('aaa', 'bbb'); + } +} -test { + faultsim_test_result {0 {}} +} + +proc repeatlist {list n} { + set ret [list] + for {set i 0} {$i < $n} {incr i} { + set ret [concat $ret $list] + } + set ret +} + +do_execsql_test 3.x.1 { + SELECT * FROM main.t1; +} [repeatlist {aaa2 bbb2} 5] + +do_execsql_test 3.x.2 { + SELECT * FROM aux.t1; +} [repeatlist {aaa bbb aaa2 bbb2} 5] + +faultsim_save_and_close +do_faultsim_test 4 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { ATTACH 'test.db2' AS aux; } +} -body { + execsql { + CREATE TEMP TRIGGER xyz AFTER DELETE ON main.t1 BEGIN + DELETE FROM aux.t1 WHERE rowid=old.rowid; + END; + + DELETE FROM t1 WHERE rowid=2; + } +} -test { + faultsim_test_result {0 {}} {1 {unable to open a temporary database file for storing temporary tables}} +} + +faultsim_save_and_close +do_faultsim_test 5 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { ATTACH 'test.db2' AS aux; } +} -body { + execsql { + CREATE TEMP TRIGGER xyz AFTER UPDATE ON aux.t1 BEGIN + UPDATE main.t1 SET x=new.x, y=new.y WHERE rowid=new.rowid; + END; + UPDATE aux.t1 SET x=x||x WHERE rowid=1+abs(random() % 5); + } +} -test { + faultsim_test_result {0 {}} {1 {unable to open a temporary database file for storing temporary tables}} +} + + +finish_test diff --git a/test/temptrigger.test b/test/temptrigger.test index e4277adf6..74390f3b8 100644 --- a/test/temptrigger.test +++ b/test/temptrigger.test @@ -276,4 +276,191 @@ do_catchsql_test 6.3 { } {1 error} db2 close +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 + +do_execsql_test 7.0 { + CREATE TABLE m1(a, b); + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.a1(c, d); +} + +do_execsql_test 7.1 { + CREATE TEMP TRIGGER tr1 AFTER INSERT ON m1 BEGIN + INSERT INTO a1 VALUES(new.a, new.b); + END; + + INSERT INTO m1 VALUES(5, 6); + SELECT * FROM aux.a1; +} {5 6} + +do_execsql_test 7.2 { + CREATE TABLE a1(e, f); + INSERT INTO m1 VALUES(7, 8); +} + +do_execsql_test 7.3.1 { SELECT * FROM main.a1 } {7 8} +do_execsql_test 7.3.2 { SELECT * FROM aux.a1 } {5 6} + +do_execsql_test 7.4 { + DROP TRIGGER tr1; + CREATE TEMP TRIGGER tr1 AFTER INSERT ON m1 BEGIN + INSERT INTO a1 SELECT d, c FROM aux.a1; + END; + + DELETE FROM aux.a1; + DELETE FROM main.a1; + INSERT INTO aux.a1 VALUES('hello', 'world'); +} + +do_execsql_test 7.5 { + INSERT INTO m1 VALUES(9, 10); + SELECT * FROM main.a1; +} {world hello} + +do_catchsql_test 7.6 { + DROP TRIGGER tr1; + CREATE TRIGGER tr1 AFTER INSERT ON m1 BEGIN + INSERT INTO a1 SELECT d, c FROM aux.a1; + END; +} {1 {trigger tr1 cannot reference objects in database aux}} + +#------------------------------------------------------------------------- +# Check that temp triggers may INSERT/UPDATE/DELETE to fully qualified +# table names. +reset_db +forcedelete {*}[glob -nocomplain *mj*] +forcedelete test.db2 +do_execsql_test 8.0 { + ATTACH 'test.db2' AS aux; + CREATE TABLE t1(a, b); + CREATE TABLE t2(c, d); + CREATE TABLE aux.t1(e, f); + CREATE TABLE aux.t2(g, h); +} + +do_catchsql_test 8.1.1 { + CREATE TRIGGER tr1 AFTER INSERT ON t2 BEGIN + INSERT INTO aux.t1 VALUES(new.c, new.d); + END; +} {1 {qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers}} + +do_execsql_test 8.1.2 { + CREATE TEMP TRIGGER tr1 AFTER INSERT ON t2 BEGIN + INSERT INTO aux.t1 VALUES(new.c, new.d); + END; + + INSERT INTO main.t2 VALUES('x', 'y'); + SELECT * FROM aux.t1; +} {x y} + +do_execsql_test 8.1.3 { SELECT * FROM t1 } {} + +do_catchsql_test 8.2.1 { + CREATE TRIGGER aux.tr2 AFTER UPDATE ON aux.t1 BEGIN + UPDATE main.t2 SET c=new.e, d=new.f; + END; +} {1 {qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers}} + +do_execsql_test 8.2.2 { + CREATE TEMP TRIGGER tr2 AFTER UPDATE ON aux.t1 BEGIN + UPDATE main.t2 SET c=new.e, d=new.f; + END; + + UPDATE aux.t1 SET e=1, f=2; + SELECT * FROM t2; +} {1 2} + +do_execsql_test 8.2.3 { SELECT * FROM aux.t2 } {} + +do_catchsql_test 8.3.1 { + CREATE TRIGGER tr3 AFTER DELETE ON t2 BEGIN + DELETE FROM aux.t1; + END; +} {1 {qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers}} + +do_execsql_test 8.3.2 { + INSERT INTO main.t1 VALUES('a', 'b'); + CREATE TEMP TRIGGER tr3 AFTER DELETE ON t2 BEGIN + DELETE FROM aux.t1; + END; + + DELETE FROM main.t2; + SELECT * FROM aux.t1; +} {} + +do_execsql_test 8.3.3 { SELECT * FROM t1 } {a b} + +#------------------------------------------------------------------------- +reset_db +set nDb 8 +do_test 9.0 { + for {set ii 0} {$ii < $nDb} {incr ii} { + db eval "ATTACH ':memory:' AS db$ii" + db eval "CREATE TABLE db$ii.tbl(a, b, c)" + } + + for {set ii 0} {$ii < ($nDb-1)} {incr ii} { + set jj [expr $ii+1] + db eval " + CREATE TEMP TRIGGER tr$ii AFTER INSERT ON db$ii.tbl BEGIN + INSERT INTO db$jj.tbl VALUES(new.b, new.c, new.a); + END; + " + } +} {} + +do_execsql_test 9.1 { INSERT INTO db0.tbl VALUES('a', 'b', 'c'); } +do_execsql_test 9.1.1 { SELECT * FROM db0.tbl } {a b c} +do_execsql_test 9.1.2 { SELECT * FROM db1.tbl } {b c a} +do_execsql_test 9.1.3 { SELECT * FROM db2.tbl } {c a b} +do_execsql_test 9.1.1 { SELECT * FROM db3.tbl } {a b c} +do_execsql_test 9.1.2 { SELECT * FROM db4.tbl } {b c a} +do_execsql_test 9.1.3 { SELECT * FROM db5.tbl } {c a b} +do_execsql_test 9.1.1 { SELECT * FROM db6.tbl } {a b c} +do_execsql_test 9.1.2 { SELECT * FROM db7.tbl } {b c a} + +do_test 9.2 { + for {set ii 0} {$ii < ($nDb-1)} {incr ii} { + set jj [expr $ii+1] + db eval " + CREATE TEMP TRIGGER tru$ii AFTER UPDATE ON db$ii.tbl BEGIN + UPDATE db$jj.tbl SET a=new.b, b=new.c, c=new.a; + END; + " + } +} {} + +do_execsql_test 9.3 { UPDATE db0.tbl SET a=1, b=2, c=3 } +do_execsql_test 9.3.1 { SELECT * FROM db0.tbl } {1 2 3} +do_execsql_test 9.3.2 { SELECT * FROM db1.tbl } {2 3 1} +do_execsql_test 9.3.3 { SELECT * FROM db2.tbl } {3 1 2} +do_execsql_test 9.3.1 { SELECT * FROM db3.tbl } {1 2 3} +do_execsql_test 9.3.2 { SELECT * FROM db4.tbl } {2 3 1} +do_execsql_test 9.3.3 { SELECT * FROM db5.tbl } {3 1 2} +do_execsql_test 9.3.1 { SELECT * FROM db6.tbl } {1 2 3} +do_execsql_test 9.3.2 { SELECT * FROM db7.tbl } {2 3 1} + +do_test 9.4 { + for {set ii 0} {$ii < ($nDb-1)} {incr ii} { + set jj [expr $ii+1] + db eval " + CREATE TEMP TRIGGER trd$ii BEFORE DELETE ON db$ii.tbl BEGIN + DELETE FROM db$jj.tbl; + END; + " + } +} {} + +do_execsql_test 9.5 { DELETE FROM db0.tbl } +do_execsql_test 9.5.1 { SELECT * FROM db0.tbl } {} +do_execsql_test 9.5.2 { SELECT * FROM db1.tbl } {} +do_execsql_test 9.5.3 { SELECT * FROM db2.tbl } {} +do_execsql_test 9.5.1 { SELECT * FROM db3.tbl } {} +do_execsql_test 9.5.2 { SELECT * FROM db4.tbl } {} +do_execsql_test 9.5.3 { SELECT * FROM db5.tbl } {} +do_execsql_test 9.5.1 { SELECT * FROM db6.tbl } {} +do_execsql_test 9.5.2 { SELECT * FROM db7.tbl } {} + finish_test diff --git a/test/tester.tcl b/test/tester.tcl index 3fad39668..856df5421 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -1785,11 +1785,6 @@ proc crashsql {args} { # cfSync(), which can be different then what TCL uses by # default, so here we force it to the "nativename" format. set cfile [string map {\\ \\\\} [file nativename [file join [get_pwd] $crashfile]]] - ifcapable winrt { - # Except on winrt. Winrt has no way to transform a relative path into - # an absolute one, so it just uses the relative paths. - set cfile $crashfile - } set f [open crash.tcl w] puts $f "sqlite3_initialize ; sqlite3_shutdown" diff --git a/test/testrunner.tcl b/test/testrunner.tcl index 515036368..1f81690f2 100755 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -101,6 +101,7 @@ Usage: $a0 help $a0 joblist ?PATTERN? $a0 njob ?NJOB? + $a0 retest $a0 script ?-msvc? CONFIG $a0 status ?-d SECS? ?--cls? $a0 halt @@ -120,20 +121,21 @@ Usage: --stop-on-error Stop running after any reported error --zipvfs ZIPVFSDIR ZIPVFS source directory -Special values for PERMUTATION that work with plain tclsh: +Special values for PERMUTATION include: - list - show all allowed PERMUTATION arguments. + list - show allowed PERMUTATION arguments. mdevtest - tests recommended prior to normal development check-ins. + devtest - alias for "mdevtest" release - full release test with various builds. sdevtest - like mdevtest but using ASAN and UBSAN. - -Other PERMUTATION arguments must be run using testfixture, not tclsh: - all - all tcl test scripts, plus a subset of test scripts rerun with various permutations. full - all tcl test scripts. veryquick - a fast subset of the tcl test scripts. This is the default. +The interpreter that runs this script can be an ordinary "tclsh" as long +as "package require sqlite3" works, or it can be "testfixture". + If no PATTERN arguments are present, all tests specified by the PERMUTATION are run. Otherwise, each pattern is interpreted as a glob pattern. Only those tcl tests for which the final component of the filename matches at @@ -167,6 +169,9 @@ only the parts that contain the error messages. The --summary option just shows the jobs that failed. If PATTERN are provided, the error information is only provided for jobs that match PATTERN. +The "retest" command reruns tests that failed or were never completed +by a prior invocation of testrunner.tcl. + Full documentation here: https://sqlite.org/src/doc/trunk/doc/testrunner.md }]] @@ -212,7 +217,8 @@ proc default_njob {} { if {$nCore<=2} { set nHelper 1 } else { - set nHelper [expr int($nCore*0.5)] + set nHelper [expr int($nCore*0.8)] + if {$nHelper>20} {set nHelper 20} } return $nHelper } @@ -297,6 +303,8 @@ switch -nocase -glob -- $tcl_platform(os) { error "cannot determine platform!" } } +set TRG(testfixture-fullpath) [file join $dir $TRG(testfixture)] +set TRG(interp) [info nameofexec] #------------------------------------------------------------------------- #------------------------------------------------------------------------- @@ -706,9 +714,18 @@ if {[llength $argv]>=1 } } - if {![file readable $TRG(dbname)]} { - puts "Database missing: $TRG(dbname)" - exit + set once 1 + while {![file readable $TRG(dbname)]} { + if {$delay==0} { + puts "Database missing: $TRG(dbname)" + exit + } + if {$once} { + set once 0 + puts "Waiting for testing to start...." + flush stdout + } + after [expr {$delay*1000}] } sqlite3 mydb $TRG(dbname) mydb timeout 2000 @@ -1148,7 +1165,7 @@ proc add_tcl_jobs {build config patternlist {shelldepid ""}} { set testrunner_tcl [file normalize [info script]] if {$build==""} { - set testfixture [info nameofexec] + set testfixture $TRG(interp) } else { set testfixture [file join [lindex $build 1] $TRG(testfixture)] } @@ -1269,6 +1286,26 @@ proc add_fuzztest_jobs {buildname patternlist} { set subcmd [lrange $interpreter 1 end] set interpreter [lindex $interpreter 0] + # For fuzzcheck-asan and fuzzcheck-ubsan, break up some + # fuzzdata files into multiple slices, for improved + # concurrency. + # + if {[string match *fuzzcheck-*san $interpreter]} { + set newscripts {} + foreach s $scripts { + if {[string match {*fuzzdata[12].db} $s] + && ![string match slice $s]} { + set N 6 + for {set i 0} {$i<$N} {incr i} { + lappend newscripts [list --slice $i $N $s] + } + } else { + lappend newscripts $s + } + } + set scripts $newscripts + } + if {[string match fuzzcheck* $interpreter] && [info exists env(FUZZDB)] && [file readable $env(FUZZDB)] @@ -1359,14 +1396,30 @@ proc add_devtest_jobs {lBld patternlist} { } } -# Check to ensure that the interpreter is a full-blown "testfixture" -# build and not just a "tclsh". If this is not the case, issue an -# error message and exit. +# Check to ensure that TRG(interp) is a full-blown "testfixture" and +# not just a "tclsh". +# +# The value of TRG(interp) defaults to whatever interpreter is running +# this script, which might be either tclsh or testfixture. If tclsh is +# running this script, change $TRG(interp) to be an instance of testfixture. +# If no testfixture exists in the directory from which this script is run, +# attempt to build one. +# +# Do not return unless $TRG(interp) is a valid testfixture. If unable +# to find and/or construct one, abort with an error message. # proc must_be_testfixture {} { + global TRG if {[lsearch [info commands] sqlite3_soft_heap_limit]<0} { - puts "Use testfixture, not tclsh, for these arguments." - exit 1 + if {![file exec $TRG(testfixture-fullpath)]} { + puts "make testfixture" + catch {exec make testfixture >@stdout 2>@stderr} + } + if {![file exec $TRG(testfixture-fullpath)]} { + puts "Requires testfixture, and I was unable to build it." + exit 1 + } + set TRG(interp) $TRG(testfixture-fullpath) } } @@ -1441,11 +1494,15 @@ proc add_jobs_from_cmdline {patternlist} { list { set allperm [array names ::testspec] - lappend allperm all mdevtest sdevtest release list + lappend allperm all devtest mdevtest sdevtest release list puts "Allowed values for the PERMUTATION argument: [lsort $allperm]" exit 0 } + retest { + # no-op + } + default { must_be_testfixture if {[info exists ::testspec($first)]} { @@ -1482,6 +1539,8 @@ proc add_jobs_from_cmdline {patternlist} { } } +# Initializer, or reinitialize, the testrunner.db database file. +# proc make_new_testset {} { global TRG @@ -1525,7 +1584,8 @@ proc mark_job_as_finished {jobid output state endtm} { SET output=$output, state=$state, endtime=$endtm, span=$endtm-starttime, ntest=$ntest, nerr=$nerr, svers=$svers, pltfm=$pltfm WHERE jobid=$jobid; - UPDATE jobs SET state=$childstate WHERE depid=$jobid AND state!='halt'; + UPDATE jobs SET state=$childstate + WHERE depid=$jobid AND state!='halt' AND state!='done'; UPDATE config SET value=value+$nerr WHERE name='nfail'; UPDATE config SET value=value+$ntest WHERE name='ntest'; } @@ -1597,7 +1657,7 @@ proc launch_another_job {iJob} { global O global T - set testfixture [info nameofexec] + set testfixture $TRG(interp) set script $TRG(info_script) set O($iJob) "" @@ -1798,6 +1858,27 @@ proc run_testset {} { } +# If the argument is "retest", simply rerun all tests from the previous +# run that are marked as one of "ready", "running", "failed", or "omit" +# plus redo any build of dependencies those tests. +# +proc handle_retest {} { + set cnt 0 + if {[catch {trdb exists {SELECT jobid FROM jobs}} cnt] || $cnt==0} { + puts "No test available to rerun" + exit 1 + } + trdb eval {UPDATE jobs SET state='ready' + WHERE state IN ('running','failed','omit')} + for {set kk 0} {$kk<2} {incr kk} { + trdb eval { + UPDATE jobs SET state='ready' + WHERE jobid IN (SELECT depid FROM jobs WHERE state='ready'); + UPDATE jobs SET state='' WHERE state='ready' AND depid<>''; + } + } +} + # Handle the --buildonly option, if it was specified. # proc handle_buildonly {} { @@ -1836,14 +1917,21 @@ proc explain_tests {} { sqlite3 trdb $TRG(dbname) trdb timeout $TRG(timeout) -set tm [lindex [time { make_new_testset }] 0] +if {[llength $TRG(patternlist)]==1 && $TRG(patternlist) eq "retest"} { + set tm 0 + handle_retest +} else { + set tm [lindex [time { make_new_testset }] 0] +} if {$TRG(explain)} { explain_tests } else { if {$TRG(nJob)>1} { puts "splitting work across $TRG(nJob) cores" } - puts "built testset in [expr $tm/1000]ms.." + if {$tm>0} { + puts "built testset in [expr $tm/1000]ms.." + } handle_buildonly run_testset } diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index e74caee1d..4daee0274 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -37,7 +37,6 @@ namespace eval trd { set tcltest(win.Windows-Memdebug) veryquick set tcltest(win.Windows-Win32Heap) veryquick set tcltest(win.Windows-Sanitize) veryquick - set tcltest(win.Windows-WinRT) veryquick set tcltest(win.Default) {full win_unc_locking} # Extra [make xyz] tests that should be run for various builds. @@ -193,6 +192,7 @@ namespace eval trd { -DSQLITE_ENABLE_HIDDEN_COLUMNS -DSQLITE_MAX_ATTACHED=125 -DSQLITE_MUTATION_TEST + -DSQLITE_THREAD_MISUSE_ABORT --enable-fts5 } set build(Debug-Two) { @@ -364,12 +364,6 @@ namespace eval trd { set build(Windows-Sanitize) { ASAN=1 } - - set build(Windows-WinRT) { - FOR_WINRT=1 - ENABLE_SETLK=1 - -DSQLITE_TEMP_STORE=3 - } } diff --git a/test/testrunner_estwork.tcl b/test/testrunner_estwork.tcl index c139394a5..e02eb22dc 100644 --- a/test/testrunner_estwork.tcl +++ b/test/testrunner_estwork.tcl @@ -364,6 +364,7 @@ set estwork(shell6.test) 3 set estwork(shell8.test) 104 set estwork(shell9.test) 3 set estwork(shellA.test) 2 +set estwork(shellB.test) 2 set estwork(shmlock.test) 27 set estwork(sidedelete.test) 10 set estwork(skipscan1.test) 7 diff --git a/test/tkt-99378177930f87bd.test b/test/tkt-99378177930f87bd.test index ba9fdc702..495867280 100644 --- a/test/tkt-99378177930f87bd.test +++ b/test/tkt-99378177930f87bd.test @@ -33,6 +33,8 @@ do_execsql_test tkt-99378-100 { (2, '{"x":2}', 4, 5), (3, '{"x":1}', 6, 7); CREATE INDEX t1x ON t1(d, a, b->>'x', c); + CREATE TABLE t2(y); + INSERT INTO t2(y) VALUES(9); } {} do_execsql_test tkt-99378-110 { SELECT a, @@ -48,6 +50,20 @@ do_execsql_test tkt-99378-110 { 2 2 1 26 22 3 1 1 6 6 } +do_execsql_test tkt-99378-111 { + SELECT if(a,a,y), + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t2 CROSS JOIN t1 + WHERE d BETWEEN 0 and 10 + GROUP BY a; +} { + 1 2 1 16 12 + 2 2 1 26 22 + 3 1 1 6 6 +} # The proof that the index on the expression is being used is in the # fact that the byte code contains no "Function" opcodes. In other words, @@ -65,6 +81,17 @@ do_execsql_test tkt-99378-120 { WHERE d BETWEEN 0 and 10 GROUP BY a; } {~/Function/} +do_execsql_test tkt-99378-121 { + EXPLAIN + SELECT if(a,a,y), + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t2 CROSS JOIN t1 + WHERE d BETWEEN 0 and 10 + GROUP BY a; +} {~/Function/} do_execsql_test tkt-99378-130 { @@ -182,6 +209,7 @@ do_execsql_test tkt-99378-310 { # do_execsql_test tkt-99378-400 { DROP TABLE t1; + DROP TABLE t2; CREATE TABLE t0(w); INSERT INTO t0(w) VALUES(1); CREATE TABLE t1(x); diff --git a/test/tkt2339.test b/test/tkt2339.test index 41acd377c..4bbedb828 100644 --- a/test/tkt2339.test +++ b/test/tkt2339.test @@ -47,12 +47,12 @@ do_test tkt2339.2 { } } {4 3 14 13} do_test tkt2339.3 { - execsql { + lsort -integer [execsql { SELECT * FROM (SELECT * FROM t1 ORDER BY num DESC) UNION ALL SELECT * FROM (SELECT * FROM t2 ORDER BY num DESC LIMIT 2) - } -} {4 3 2 1 14 13} + }] +} {1 2 3 4 13 14} do_test tkt2339.4 { execsql { SELECT * FROM (SELECT * FROM t1 ORDER BY num DESC LIMIT 2) diff --git a/test/values.test b/test/values.test index c3c52ceb1..58e764ce6 100644 --- a/test/values.test +++ b/test/values.test @@ -21,9 +21,9 @@ do_execsql_test 1.0 { } -explain_i { - INSERT INTO x1(a, b, c) VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); -} +#explain_i { +# INSERT INTO x1(a, b, c) VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); +#} do_execsql_test 1.1.1 { INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); } diff --git a/test/vt02.c b/test/vt02.c index 06fade096..e08d649c1 100644 --- a/test/vt02.c +++ b/test/vt02.c @@ -45,8 +45,29 @@ ** a=... AND b=... AND c=... ** a=... AND b=... AND c=... AND d=... ** -** Various ORDER BY constraints are also recognized and consumed. The -** OFFSET constraint is recognized and consumed. +** The table will also recognize IN constraints on column D using the +** sqlite3_vtab_in_first() and sqlite3_vtab_in_next() interfaces: +** +** a=... AND d IN (...) +** a=... AND b=... AND d IN (...) +** a=... AND b=... AND c=... AND d IN (...) +** +** Various ORDER BY constraints are also recognized and consumed. +** +** ORDER BY x +** ORDER BY a +** ORDER BY a, b +** ORDER BY a, b, c +** ORDER BY a, b, c, d +** +** The above also work if every term is DESC rather than ASC. However, +** the orderByConsumed is not set if there are a mixture of ASC and DESC +** terms in the ORDER BY clause. +** +** The sqlite3_vtab_distinct() interface is used to recognize DISTINCT +** and GROUP BY constraints and consume the corresponding sorting requirements. +** +** The OFFSET constraint is recognized and consumed. ** ** ## TABLE-VALUED FUNCTION ** @@ -90,6 +111,13 @@ ** vector of results sent to xFilter. Only the first ** few are used, as required by idxNum. ** +** 0x80 If sqlite3_vtab_distinct() says that duplicate rows +** may be omitted (values 2 or 3), then maybe omit +** some but not all of the duplicate rows. +** +** 0x100 Do not omit any duplicate rows even if +** sqlite3_vtab_distinct() says that is ok to do. +** ** Because these flags take effect during xBestIndex, the RHS of the ** flag= constraint must be accessible. In other words, the RHS of flag= ** needs to be an integer literal, not another column of a join or a @@ -136,14 +164,12 @@ ** ## COMPILING AND RUNNING ** ** This file can also be compiled separately as a loadable extension -** for SQLite (as long as the -DTH3_VERSION is not defined). To compile as a -** loadable extension do his: -** -** gcc -Wall -g -shared -fPIC -I. -DSQLITE_DEBUG vt02.c -o vt02.so +** for SQLite (as long as the -DTH3_VERSION is not defined). To compile as a +** loadable extension do something like this: ** -** Or on Windows: -** -** cl vt02.c -link -dll -out:vt02.dll +** (linux) gcc -shared -fPIC -I. vt02.c -o vt02.so +** (mac) clang -dynamiclib -fPIC -I. vt02.c -o vt02.dylib +** (windows) cl vt02.c -link -dll -out:vt02.dll ** ** Then load into the CLI using: ** @@ -167,6 +193,7 @@ ** 2x increment by 100 ** 3x increment by 1000 ** 1xx Use offset provided by argv[N] +** 1xxx Reverse output order (to implement ORDER BY ... DESC) */ #ifndef TH3_VERSION /* These bits for separate compilation as a loadable extension, only */ @@ -195,6 +222,8 @@ struct vt02_vtab { #define VT02_NO_OFFSET 0x0004 /* Omit the offset optimization */ #define VT02_ALLOC_IDXSTR 0x0008 /* Alloate an idxStr */ #define VT02_BAD_IDXNUM 0x0010 /* Generate an invalid idxNum */ +#define VT02_PARTIAL_DEDUP 0x0080 /* Omit some but not all duplicate rows */ +#define VT02_NO_DEDUP 0x0100 /* Include all duplicate rows */ /* ** A cursor @@ -203,6 +232,7 @@ struct vt02_cur { sqlite3_vtab_cursor parent; /* Base class. Must be first */ sqlite3_int64 i; /* Current entry */ sqlite3_int64 iEof; /* Indicate EOF when reaching this value */ + sqlite3_int64 iMin; /* EOF if dropping below this value */ int iIncr; /* Amount by which to increment */ unsigned int mD; /* Mask of allowed D-column values */ }; @@ -292,7 +322,7 @@ static int vt02Close(sqlite3_vtab_cursor *pCursor){ */ static int vt02Eof(sqlite3_vtab_cursor *pCursor){ vt02_cur *pCur = (vt02_cur*)pCursor; - return pCur->i<0 || pCur->i>=pCur->iEof; + return pCur->i<pCur->iMin || pCur->i>=pCur->iEof; } /* Advance the cursor to the next row in the table @@ -301,8 +331,8 @@ static int vt02Next(sqlite3_vtab_cursor *pCursor){ vt02_cur *pCur = (vt02_cur*)pCursor; do{ pCur->i += pCur->iIncr; - if( pCur->i<0 ) pCur->i = pCur->iEof; - }while( (pCur->mD & (1<<(pCur->i%10)))==0 && pCur->i<pCur->iEof ); + if( pCur->i<pCur->iMin || pCur->i>=pCur->iEof ) break; + }while( (pCur->mD & (1<<(pCur->i%10)))==0 ); return SQLITE_OK; } @@ -324,6 +354,7 @@ static int vt02Next(sqlite3_vtab_cursor *pCursor){ ** 2x increment by 100 ** 3x increment by 1000 ** 1xx Use offset provided by argv[N] +** 1xxx Output rows in reverse order */ static int vt02Filter( sqlite3_vtab_cursor *pCursor, /* The cursor to rewind */ @@ -334,11 +365,17 @@ static int vt02Filter( ){ vt02_cur *pCur = (vt02_cur*)pCursor; /* The vt02 cursor */ int bUseOffset = 0; /* True to use OFFSET value */ + int bReverse = 0; /* Output rows in reverse order */ int iArg = 0; /* argv[] values used so far */ int iOrigIdxNum = idxNum; /* Original value for idxNum */ pCur->iIncr = 1; + pCur->iMin = 0; pCur->mD = 0x3ff; + if( idxNum>=1000 ){ + bReverse = 1; + idxNum -= 1000; + } if( idxNum>=100 ){ bUseOffset = 1; idxNum -= 100; @@ -416,9 +453,17 @@ static int vt02Filter( }else{ goto vt02_bad_idxnum; } + if( bReverse ){ + sqlite3_int64 x; + x = pCur->i + ((pCur->iEof - pCur->i)/pCur->iIncr)*pCur->iIncr; + if( x>=pCur->iEof ) x -= pCur->iIncr; + pCur->iIncr = -pCur->iIncr; + pCur->iMin = pCur->i; + pCur->i = x; + } if( bUseOffset ){ int nSkip = sqlite3_value_int(argv[iArg]); - while( nSkip-- > 0 && pCur->i<pCur->iEof ) vt02Next(pCursor); + while( nSkip-- > 0 && !vt02Eof(pCursor) ) vt02Next(pCursor); } return SQLITE_OK; @@ -838,35 +883,52 @@ static int vt02BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ ** the same answer. */ if( pInfo->nOrderBy>0 && (flags & VT02_NO_SORT_OPT)==0 ){ + int eDistinct = sqlite3_vtab_distinct(pInfo); if( pInfo->idxNum==1 ){ /* There will only be one row of output. So it is always sorted. */ pInfo->orderByConsumed = 1; }else - if( pInfo->aOrderBy[0].iColumn<=0 - && pInfo->aOrderBy[0].desc==0 - ){ - /* First column of order by is X ascending */ + if( pInfo->aOrderBy[0].iColumn<=0 ){ + /* First column of order by is X */ + if( pInfo->aOrderBy[0].desc ){ + pInfo->idxNum += 1000; /* Reverse output order */ + } pInfo->orderByConsumed = 1; }else - if( sqlite3_vtab_distinct(pInfo)>=1 ){ + if( eDistinct>=1 ){ unsigned int x = 0; + int nDesc = 0; + int nAsc = 0; for(i=0; i<pInfo->nOrderBy; i++){ int iCol = pInfo->aOrderBy[i].iColumn; if( iCol<0 ) iCol = 0; + if( pInfo->aOrderBy[i].desc ){ + nDesc++; + }else{ + nAsc++; + } x |= 1<<iCol; } - if( sqlite3_vtab_distinct(pInfo)==2 ){ + if( nDesc>0 && nAsc>0 ){ + if( eDistinct!=1 ) eDistinct = -999; /* Never set orderByConsumed */ + }else if( nAsc==0 ){ + pInfo->idxNum += 1000; /* Reverse output order */ + } + if( eDistinct>=2 && (flags & VT02_NO_DEDUP)!=0 ){ + eDistinct = 1; + } + if( eDistinct>=2 ){ /* DISTINCT or (DISTINCT and ORDER BY) */ if( x==0x02 ){ /* DISTINCT A */ - pInfo->idxNum += 30; + pInfo->idxNum += (flags & VT02_PARTIAL_DEDUP)!=0 ? 20 : 30; pInfo->orderByConsumed = 1; }else if( x==0x06 ){ /* DISTINCT A,B */ - pInfo->idxNum += 20; + pInfo->idxNum += (flags & VT02_PARTIAL_DEDUP)!=0 ? 10 : 20; pInfo->orderByConsumed = 1; }else if( x==0x0e ){ /* DISTINCT A,B,C */ - pInfo->idxNum += 10; + pInfo->idxNum += (flags & VT02_PARTIAL_DEDUP)!=0 ? 0 : 10; pInfo->orderByConsumed = 1; }else if( x & 0x01 ){ /* DISTINCT X */ @@ -875,7 +937,7 @@ static int vt02BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ /* DISTINCT A,B,C,D */ pInfo->orderByConsumed = 1; } - }else{ + }else if( eDistinct==1 ){ /* GROUP BY */ if( x==0x02 ){ /* GROUP BY A */ pInfo->orderByConsumed = 1; @@ -893,6 +955,21 @@ static int vt02BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ pInfo->orderByConsumed = 1; } } + }else{ + int nDesc = 0; + int nAsc = 0; + for(i=0; i<pInfo->nOrderBy; i++){ + if( pInfo->aOrderBy[i].iColumn!=i+1 ) break; + if( pInfo->aOrderBy[i].desc ){ + nDesc++; + }else{ + nAsc++; + } + } + if( i==pInfo->nOrderBy && (nDesc==0 || nAsc==0) ){ + pInfo->orderByConsumed = 1; + if( nDesc ) pInfo->idxNum += 1000; + } } } @@ -901,7 +978,7 @@ static int vt02BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ pInfo->needToFreeIdxStr = 1; } if( flags & VT02_BAD_IDXNUM ){ - pInfo->idxNum += 1000; + pInfo->idxNum += 10000; } if( iOffset>=0 ){ diff --git a/test/vt100-a.sql b/test/vt100-a.sql index a0d3f46be..f141f637f 100644 --- a/test/vt100-a.sql +++ b/test/vt100-a.sql @@ -9,11 +9,40 @@ INSERT INTO t1 VALUES ('one','twotwotwo','thirty-three'), (unistr('\u001b[91mRED\u001b[0m'),'fourfour','fifty-five'), ('six','seven','eighty-eight'); -.print With -escape off +.testcase 100 SELECT * FROM t1; +.check <<END +╭─────┬───────────┬──────────────╮ +│ a │ b │ c │ +╞═════╪═══════════╪══════════════╡ +│ one │ twotwotwo │ thirty-three │ +│ RED │ fourfour │ fifty-five │ +│ six │ seven │ eighty-eight │ +╰─────┴───────────┴──────────────╯ +END + .mode box -escape ascii -.print With -escape ascii +.testcase 200 SELECT * FROM t1; +.check <<END +╭────────────────┬───────────┬──────────────╮ +│ a │ b │ c │ +╞════════════════╪═══════════╪══════════════╡ +│ one │ twotwotwo │ thirty-three │ +│ ^[[91mRED^[[0m │ fourfour │ fifty-five │ +│ six │ seven │ eighty-eight │ +╰────────────────┴───────────┴──────────────╯ +END + +.testcase 300 .mode box -escape symbol -.print With -escape symbol SELECT * FROM t1; +.check <<END +╭──────────────┬───────────┬──────────────╮ +│ a │ b │ c │ +╞══════════════╪═══════════╪══════════════╡ +│ one │ twotwotwo │ thirty-three │ +│ ␛[91mRED␛[0m │ fourfour │ fifty-five │ +│ six │ seven │ eighty-eight │ +╰──────────────┴───────────┴──────────────╯ +END diff --git a/test/walckptnoop.test b/test/walckptnoop.test index 7ff8e90b8..89055316f 100644 --- a/test/walckptnoop.test +++ b/test/walckptnoop.test @@ -102,7 +102,11 @@ do_catchsql_test 1.8 { PRAGMA wal_checkpoint = noop; } {0 {0 5 0}} -do_execsql_test 1.9 { +do_test 1.9 { + sqlite3_wal_checkpoint_v2 db noop +} {0 5 0} + +do_execsql_test 1.10 { PRAGMA journal_mode = delete; PRAGMA wal_checkpoint = noop; } {delete 0 -1 -1} diff --git a/test/walrestart.test b/test/walrestart.test new file mode 100644 index 000000000..cf27a4098 --- /dev/null +++ b/test/walrestart.test @@ -0,0 +1,89 @@ +# 2026-03-03 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing a race condition in WAL restart. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +if {$::tcl_platform(platform) ne "unix"} { + # This test only works on unix + finish_test + return +} +set testprefix walrestart + +if {[permutation]=="memsubsys1"} { + # memsubsys1 configures a very small page-cache, which causes different + # numbers of frames to be written to the wal file for some transactions, + # which causes some of the tests in this file to fail. + finish_test + return +} + +db close +sqlite3_shutdown + +proc faultsim {n} { return 0 } +sqlite3_test_control_fault_install faultsim + +# Populate database. Create a large wal file and checkpoint it. +# +reset_db +do_execsql_test 1.0 { + PRAGMA auto_vacuum = 0; + PRAGMA journal_mode = wal; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<20 + ) + INSERT INTO t1 SELECT NULL, randomblob(600) FROM s; + CREATE INDEX i1 ON t1(b); + PRAGMA wal_checkpoint; +} {wal 0 49 49} +do_execsql_test 1.1 { + UPDATE t1 SET b=randomblob(600); + PRAGMA wal_checkpoint; +} {0 45 45} + +# We have a completely checkpointed wal file on disk. mxFrame=$large, +# nBackfill=$large. Do another checkpoint with [db]. This time, after [db] +# reads mxFrame but before it reads nBackfill, write to the db such +# that 0 < mxFrame < large. +# +proc faultsim {n} { + if {$n==660} { + db2 eval { UPDATE t1 SET b=randomblob(600) WHERE a<5 } + } + return 0 +} +sqlite3 db2 test.db +do_execsql_test 1.2 { + PRAGMA wal_checkpoint; +} {0 45 0} + +# Now write another big update to the wal file and checkpoint it. +# +do_execsql_test -db db2 1.3 { + UPDATE t1 SET b=randomblob(600); +} +proc faultsim {n} { return 0 } +do_execsql_test 1.4 { + PRAGMA wal_checkpoint; +} {/0 5. 5./} + +do_catchsql_test 1.5 { + PRAGMA integrity_check +} {0 ok} + +sqlite3_test_control_fault_install + +finish_test diff --git a/test/where2.test b/test/where2.test index 83740bffd..6045cb117 100644 --- a/test/where2.test +++ b/test/where2.test @@ -794,4 +794,41 @@ do_execsql_test where2-15.1 { ORDER BY a,EXISTS(SELECT 1 FROM t1 LEFT JOIN (SELECT x AS y FROM t2) AS s2 ON t1.b=s2.y),x; } {12 34 NULL | 56 78 78 | 90 12 12 |} +# Demonstrate that CROSS JOIN is a join reordering barrier. +# +reset_db +do_execsql_test where2-16.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT); + CREATE TABLE t2(c INTEGER PRIMARY KEY, d INT); + CREATE TABLE t3(e INTEGER PRIMARY KEY, f INT); + CREATE TABLE t4(g INTEGER PRIMARY KEY, h INT); +} + +# Here the query planner wants to move t4 forward so that it is in front of +# t1. Ensure that does not happen. +do_execsql_test where2-16.2 { + EXPLAIN QUERY PLAN + SELECT * + FROM t1, t2 CROSS JOIN t3, t4 + WHERE t4.g=1 + AND t1.a=t4.h + AND t2.c=t1.b + AND t3.e=t2.d; +} {~/.* t4 .* t[12] .*/} + +# In this case the planner wants to move t1 to come after t4. Ensure that +# does not happen. +do_execsql_test where2-16.2 { + EXPLAIN QUERY PLAN + SELECT * + FROM t1, t2 CROSS JOIN t3, t4 + WHERE t2.c=1 + AND t3.e=t2.d + AND t4.g=t3.f + AND t1.a=t4.h; +} {~/.* t[34] .* t1 .*/} + + + + finish_test diff --git a/test/whereK.test b/test/whereK.test index 060d470ff..995c08371 100644 --- a/test/whereK.test +++ b/test/whereK.test @@ -69,4 +69,17 @@ do_execsql_test 1.5eqp { ORDER BY +a; } {/SEARCH t1 USING INDEX t1bc/} +# https://sqlite.org/forum/forumpost/2026-01-16T11:35:28Z +do_execsql_test 2.1 { + DROP TABLE t1; + CREATE TABLE t0(x COLLATE NOCASE); + CREATE INDEX t0x ON t0(x); + CREATE TABLE t1(y); + INSERT INTO t0 VALUES('a'); + INSERT INTO t1 VALUES('AB'); + SELECT count(*) FROM t0, t1 WHERE (y BETWEEN 1 AND x) OR (x>=y AND x); + SELECT count(*) FROM t0, t1 WHERE (x>=y AND x) OR (y BETWEEN 1 AND x); +} {1 1} + + finish_test diff --git a/test/win32longpath.test b/test/win32longpath.test index 2545c55a1..43905f26e 100644 --- a/test/win32longpath.test +++ b/test/win32longpath.test @@ -115,11 +115,7 @@ do_test 1.6 { db3 close -# winrt platforms do not handle paths with unix-style '/' directory separators. -# set lUri [list 1a 1b 1c 1d 1e 1f] -ifcapable winrt { set lUri [list 1a 1c 1e] } - foreach tn $lUri { sqlite3 db3 $uri($tn) -vfs win32-longpath -uri 1 -translatefilename 0 diff --git a/test/with1.test b/test/with1.test index 5ddf9dce0..c87082583 100644 --- a/test/with1.test +++ b/test/with1.test @@ -1237,4 +1237,23 @@ do_execsql_test 27.1 { SELECT k, cte_map, main_map, '|' FROM log ORDER BY k; } {1 cte1 main1 | 2 cte2 main2 |} +# forum post https://sqlite.org/forum/forumpost/2026-03-04T05:06:26Z +# +db null NULL +do_execsql_test 28.1 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY); + INSERT INTO t1 VALUES(1),(4),(999); + SELECT ( + WITH RECURSIVE t2(y) AS ( + SELECT 4 + UNION + SELECT NULL + UNION + SELECT y+1 FROM t2 WHERE y=4 ORDER BY 1 + ) + SELECT 1 FROM t2 WHERE y=x + ) FROM t1; +} {NULL 1 NULL} + finish_test diff --git a/test/zipfile.test b/test/zipfile.test index 9bb35ea5d..f57170724 100644 --- a/test/zipfile.test +++ b/test/zipfile.test @@ -908,4 +908,10 @@ d42728f602000000020000000500ffff0000000000000000a4810000000068 do_catchsql_test 20.2 { SELECT * FROM zipfile(unhex('504b0304140000000000000000008b9ed9d30100000001000000010000007841504b01021e03140000000000000000008b9ed9d3010000000100000001001e000000000000000000a4810000000078504b050600000000010001002f000000200000000000')); } {1 {zip archive is corrupt}} + +# https://sqlite.org/forum/forumpost/2026-01-13T00:09:06Z +do_catchsql_test 21.0 { + SELECT * FROM zipfile(X'504B03040A0000000000000000000000000000000000000000000100000078504B010200000A0000000000000000000000000000000000000000000100000000000000000000000000E2FFFFFF78504B050600000000010001002F0000001F0000000000'); +} {1 {failed to read LFH at offset -30}} + finish_test diff --git a/test/zipfile2.test b/test/zipfile2.test index 8ee90d310..c1498872a 100644 --- a/test/zipfile2.test +++ b/test/zipfile2.test @@ -302,4 +302,19 @@ do_execsql_test 7.1 { SELECT length(name) FROM t1; } {60000} + +# https://sqlite.org/forum/forumpost/721a05d2c5 +# +if {[catch { load_static_extension db fileio }]==0} { + forcedelete test.zip + set fd [open test.zip wb] + fconfigure $fd -translation binary + puts -nonewline $fd [db one {SELECT X'504b0506000000000100010030000000160000000000504b01021400140000000000000000000000000000000000000000000100010000000000000000000000000000006100'}] + close $fd + + do_catchsql_test 8.0 { + SELECT name,sz FROM zipfile(readfile('test.zip')); + } {1 {failed to read LFH at offset 0}} +} + finish_test diff --git a/tool/build-all-msvc.bat b/tool/build-all-msvc.bat index 83d660deb..ae0d0e5b6 100755 --- a/tool/build-all-msvc.bat +++ b/tool/build-all-msvc.bat @@ -105,7 +105,6 @@ REM When set, these values are expanded and passed to the NMAKE command line, REM after its other arguments. These may be used to specify additional NMAKE REM options, for example: REM -REM SET NMAKE_ARGS=FOR_WINRT=1 REM SET NMAKE_ARGS_DEBUG=MEMDEBUG=1 REM SET NMAKE_ARGS_RETAIL=WIN32HEAP=1 REM @@ -861,4 +860,4 @@ GOTO no_errors GOTO end_of_file :end_of_file -%__ECHO% EXIT /B %ERRORLEVEL% +%__ECHO% EXIT /B %ERRORLEVEL% diff --git a/tool/dbtotxt.c b/tool/dbtotxt.c index fbd6e3d51..ed347840a 100644 --- a/tool/dbtotxt.c +++ b/tool/dbtotxt.c @@ -1,6 +1,12 @@ /* -** Copyright 2008 D. Richard Hipp and Hipp, Wyrick & Company, Inc. -** All Rights Reserved +** 2018-12-13 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. ** ****************************************************************************** ** diff --git a/tool/lemon.c b/tool/lemon.c index 324dda0c5..9071e7719 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -494,6 +494,7 @@ struct lemon { char *filename; /* Name of the input file */ char *outname; /* Name of the current output file */ char *tokenprefix; /* A prefix added to token names in the .h file */ + char *stackSizeLimit; /* Function to return the stack size limit */ char *reallocFunc; /* Function to use to allocate stack space */ char *freeFunc; /* Function to use to free stack space */ int nconflict; /* Number of parsing conflicts */ @@ -2638,6 +2639,9 @@ static void parseonetoken(struct pstate *psp) }else if( strcmp(x,"default_type")==0 ){ psp->declargslot = &(psp->gp->vartype); psp->insertLineMacro = 0; + }else if( strcmp(x,"stack_size_limit")==0 ){ + psp->declargslot = &(psp->gp->stackSizeLimit); + psp->insertLineMacro = 0; }else if( strcmp(x,"realloc")==0 ){ psp->declargslot = &(psp->gp->reallocFunc); psp->insertLineMacro = 0; @@ -3715,7 +3719,7 @@ PRIVATE int compute_action(struct lemon *lemp, struct action *ap) return act; } -#define LINESIZE 1000 +#define LINESIZE 10000 /* The next cluster of routines are for reading the template file ** and writing the results to the generated parser */ /* The first function transfers data from "in" to "out" until @@ -3750,12 +3754,9 @@ PRIVATE void tplt_xfer(char *name, FILE *in, FILE *out, int *lineno) /* Skip forward past the header of the template file to the first "%%" */ -PRIVATE void tplt_skip_header(FILE *in, int *lineno) -{ +PRIVATE void tplt_skip_header(FILE *in){ char line[LINESIZE]; - while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){ - (*lineno)++; - } + while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){} } /* The next function finds the template file and opens it, returning @@ -3825,12 +3826,14 @@ PRIVATE void tplt_linedir(FILE *out, int lineno, char *filename) filename++; } fprintf(out,"\"\n"); + fflush(out); } /* Print a string to the file and keep the linenumber up to date */ PRIVATE void tplt_print(FILE *out, struct lemon *lemp, char *str, int *lineno) { if( str==0 ) return; + fflush(out); while( *str ){ putc(*str,out); if( *str=='\n' ) (*lineno)++; @@ -3843,6 +3846,7 @@ PRIVATE void tplt_print(FILE *out, struct lemon *lemp, char *str, int *lineno) if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); } + fflush(out); return; } @@ -4407,6 +4411,13 @@ static void writeRuleText(FILE *out, struct rule *rp){ } } +/* +** Return true if the string is not NULL and not empty. +*/ +static int notnull(const char *z){ + return z && z[0]; +} + /* Generate C source code for the parser */ void ReportTable( @@ -4414,7 +4425,7 @@ void ReportTable( int mhflag, /* Output in makeheaders format if true */ int sqlFlag /* Generate the *.sql file too */ ){ - FILE *out, *in, *sql; + FILE *out, *in; int lineno; struct state *stp; struct action *ap; @@ -4439,18 +4450,10 @@ void ReportTable( in = tplt_open(lemp); if( in==0 ) return; - out = file_open(lemp,".c","wb"); - if( out==0 ){ - fclose(in); - return; - } - if( sqlFlag==0 ){ - sql = 0; - }else{ - sql = file_open(lemp, ".sql", "wb"); + if( sqlFlag ){ + FILE *sql = file_open(lemp, ".sql", "wb"); if( sql==0 ){ fclose(in); - fclose(out); return; } fprintf(sql, @@ -4515,6 +4518,12 @@ void ReportTable( } } fprintf(sql, "COMMIT;\n"); + fclose(sql); + } + out = file_open(lemp,".c","wb"); + if( out==0 ){ + fclose(in); + return; } lineno = 1; @@ -4543,7 +4552,7 @@ void ReportTable( } } if( lemp->include[0]=='/' ){ - tplt_skip_header(in,&lineno); + tplt_skip_header(in); }else{ tplt_xfer(lemp->name,in,out,&lineno); } @@ -4563,7 +4572,7 @@ void ReportTable( if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; }else{ - fprintf(out,"#ifndef %s%s\n", prefix, lemp->symbols[1]->name); + fprintf(out,"#ifndef %s%s\n", prefix, lemp->symbols[1]->name); lineno++; } for(i=1; i<lemp->nterminal; i++){ fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); @@ -4612,25 +4621,33 @@ void ReportTable( fprintf(out,"#define %sARG_FETCH\n",name); lineno++; fprintf(out,"#define %sARG_STORE\n",name); lineno++; } + fprintf(out, "#undef YYREALLOC\n"); lineno++; if( lemp->reallocFunc ){ fprintf(out,"#define YYREALLOC %s\n", lemp->reallocFunc); lineno++; }else{ fprintf(out,"#define YYREALLOC realloc\n"); lineno++; } + fprintf(out, "#undef YYFREE\n"); lineno++; if( lemp->freeFunc ){ fprintf(out,"#define YYFREE %s\n", lemp->freeFunc); lineno++; }else{ fprintf(out,"#define YYFREE free\n"); lineno++; } + fprintf(out, "#undef YYDYNSTACK\n"); lineno++; if( lemp->reallocFunc && lemp->freeFunc ){ fprintf(out,"#define YYDYNSTACK 1\n"); lineno++; }else{ fprintf(out,"#define YYDYNSTACK 0\n"); lineno++; } - if( lemp->ctx && lemp->ctx[0] ){ + fprintf(out, "#undef YYSIZELIMIT\n"); lineno++; + if( notnull(lemp->ctx) ){ i = lemonStrlen(lemp->ctx); while( i>=1 && ISSPACE(lemp->ctx[i-1]) ) i--; while( i>=1 && (ISALNUM(lemp->ctx[i-1]) || lemp->ctx[i-1]=='_') ) i--; + if( notnull(lemp->stackSizeLimit) ){ + fprintf(out,"#define YYSIZELIMIT %s\n", lemp->stackSizeLimit); lineno++; + } + fprintf(out,"#define %sCTX(P) ((P)->%s)\n",name,&lemp->ctx[i]); lineno++; fprintf(out,"#define %sCTX_SDECL %s;\n",name,lemp->ctx); lineno++; fprintf(out,"#define %sCTX_PDECL ,%s\n",name,lemp->ctx); lineno++; fprintf(out,"#define %sCTX_PARAM ,%s\n",name,&lemp->ctx[i]); lineno++; @@ -4639,6 +4656,7 @@ void ReportTable( fprintf(out,"#define %sCTX_STORE yypParser->%s=%s;\n", name,&lemp->ctx[i],&lemp->ctx[i]); lineno++; }else{ + fprintf(out,"#define %sCTX(P) 0\n",name); lineno++; fprintf(out,"#define %sCTX_SDECL\n",name); lineno++; fprintf(out,"#define %sCTX_PDECL\n",name); lineno++; fprintf(out,"#define %sCTX_PARAM\n",name); lineno++; @@ -4648,10 +4666,13 @@ void ReportTable( if( mhflag ){ fprintf(out,"#endif\n"); lineno++; } + fprintf(out, "#undef YYERRORSYMBOL\n"); lineno++; + fprintf(out, "#undef YYERRSYMDT\n"); lineno++; if( lemp->errsym && lemp->errsym->useCnt ){ fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++; fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++; } + fprintf(out,"#undef YYFALLBACK\n"); lineno++; if( lemp->has_fallback ){ fprintf(out,"#define YYFALLBACK 1\n"); lineno++; } @@ -5003,7 +5024,6 @@ void ReportTable( sp2->destLineno = -1; /* Avoid emitting this destructor again */ } } - emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); fprintf(out," break;\n"); lineno++; } @@ -5103,7 +5123,6 @@ void ReportTable( acttab_free(pActtab); fclose(in); fclose(out); - if( sql ) fclose(sql); return; } diff --git a/tool/lempar.c b/tool/lempar.c index 74314efea..7b654e650 100644 --- a/tool/lempar.c +++ b/tool/lempar.c @@ -299,15 +299,24 @@ static int yyGrowStack(yyParser *p){ int newSize; int idx; yyStackEntry *pNew; +#ifdef YYSIZELIMIT + int nLimit = YYSIZELIMIT(ParseCTX(p)); +#endif newSize = oldSize*2 + 100; +#ifdef YYSIZELIMIT + if( newSize>nLimit ){ + newSize = nLimit; + if( newSize<=oldSize ) return 1; + } +#endif idx = (int)(p->yytos - p->yystack); if( p->yystack==p->yystk0 ){ - pNew = YYREALLOC(0, newSize*sizeof(pNew[0])); + pNew = YYREALLOC(0, newSize*sizeof(pNew[0]), ParseCTX(p)); if( pNew==0 ) return 1; memcpy(pNew, p->yystack, oldSize*sizeof(pNew[0])); }else{ - pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0])); + pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0]), ParseCTX(p)); if( pNew==0 ) return 1; } p->yystack = pNew; @@ -459,7 +468,9 @@ void ParseFinalize(void *p){ } #if YYGROWABLESTACK - if( pParser->yystack!=pParser->yystk0 ) YYFREE(pParser->yystack); + if( pParser->yystack!=pParser->yystk0 ){ + YYFREE(pParser->yystack, ParseCTX(pParser)); + } #endif } diff --git a/tool/mkautoconfamal.sh b/tool/mkautoconfamal.sh index 3835799a6..002a3b8ee 100644 --- a/tool/mkautoconfamal.sh +++ b/tool/mkautoconfamal.sh @@ -59,7 +59,8 @@ cp $TOP/src/sqlite3.rc $TMPSPACE cp $TOP/tool/Replace.cs $TMPSPACE cp $TOP/VERSION $TMPSPACE cp $TOP/main.mk $TMPSPACE - +cp $TOP/make.bat $TMPSPACE +tree $TMPSPACE cd $TMPSPACE #if true; then diff --git a/tool/mkcombo.tcl b/tool/mkcombo.tcl new file mode 100644 index 000000000..71368ec41 --- /dev/null +++ b/tool/mkcombo.tcl @@ -0,0 +1,106 @@ +#!/usr/bin/tclsh +# +# Use this script to combine multiple source code files into a single +# file. Example: +# +# tclsh mkcombo.tcl file1.c file2.c file3.c -o file123.c +# + +set help {Usage: tclsh mkcombo.tcl [OPTIONS] [FILELIST] + where OPTIONS is zero or more of the following with these effects: + --linemacros=? => Emit #line directives into output or not. (? = 1 or 0) + --o FILE => write to alternative output file named FILE + --help => See this. +} + +set linemacros 0 +set fname {} +set src [list] + + +for {set i 0} {$i<[llength $argv]} {incr i} { + set x [lindex $argv $i] + if {[regexp {^-?-linemacros(?:=([01]))?$} $x ma ulm]} { + if {$ulm == ""} {set ulm 1} + set linemacros $ulm + } elseif {[regexp {^-o$} $x]} { + incr i + if {$i==[llength $argv]} { + error "No argument following $x" + } + set fname [lindex $argv $i] + } elseif {[regexp {^-?-((help)|\?)$} $x]} { + puts $help + exit 0 + } elseif {[regexp {^-?-} $x]} { + error "unknown command-line option: $x" + } else { + lappend src $x + } +} + +# Open the output file and write a header comment at the beginning +# of the file. +# +if {![info exists fname]} { + set fname sqlite3.c + if {$enable_recover} { set fname sqlite3r.c } +} +set out [open $fname wb] + +# Return a string consisting of N "*" characters. +# +proc star N { + set r {} + for {set i 0} {$i<$N} {incr i} {append r *} + return $r +} + +# Force the output to use unix line endings, even on Windows. +fconfigure $out -translation binary +puts $out "/[star 78]" +puts $out {** The following is an amalgamation of these source code files:} +puts $out {**} +foreach s $src { + regsub {^.*/(src|ext)/} $s {\1/} s2 + puts $out "** $s2" +} +puts $out {**} +puts $out "[star 78]/" + +# Insert a comment into the code +# +proc section_comment {text} { + global out s78 + set n [string length $text] + set nstar [expr {60 - $n}] + puts $out "/************** $text [star $nstar]/" +} + +# Read the source file named $filename and write it into the +# sqlite3.c output file. The only transformation is the trimming +# of EOL whitespace. +# +proc copy_file_verbatim {filename} { + global out + set in [open $filename rb] + set tail [file tail $filename] + section_comment "Begin file $tail" + while {![eof $in]} { + set line [string trimright [gets $in]] + puts $out $line + } + section_comment "End of $tail" +} +set taillist "" +foreach file $src { + copy_file_verbatim $file + append taillist ", [file tail $file]" +} + +set taillist "End of the amalgamation of [string range $taillist 2 end]" +set n [string length $taillist] +set ns [expr {(75-$n)/2}] +if {$ns<3} {set ns 3} +puts $out "/[star $ns] $taillist [star $ns]/" +close $out diff --git a/tool/mkkeywordhash.c b/tool/mkkeywordhash.c index 188c0a29a..d6d54a5a4 100644 --- a/tool/mkkeywordhash.c +++ b/tool/mkkeywordhash.c @@ -668,8 +668,8 @@ int main(int argc, char **argv){ printf("/* Check to see if z[0..n-1] is a keyword. If it is, write the\n"); printf("** parser symbol code for that keyword into *pType. Always\n"); printf("** return the integer n (the length of the token). */\n"); - printf("static int keywordCode(const char *z, int n, int *pType){\n"); - printf(" int i, j;\n"); + printf("static i64 keywordCode(const char *z, i64 n, int *pType){\n"); + printf(" i64 i, j;\n"); printf(" const char *zKW;\n"); printf(" assert( n>=2 );\n"); printf(" i = ((charMap(z[0])*%d) %c", HASH_C0, HASH_CC); diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl index 2f7a6ea25..45452621b 100644 --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@ -13,28 +13,90 @@ set topdir [file dir [file dir [file normal $argv0]]] set out stdout fconfigure stdout -translation binary if {[lindex $argv 0]!=""} { - set out [open [lindex $argv 0] wb] + set output_file [lindex $argv 0] + file delete -force $output_file + set out [open $output_file wb] +} else { + set output_file {} } -puts $out {/* DO NOT EDIT! -** This file is automatically generated by the script in the canonical -** SQLite source tree at tool/mkshellc.tcl. That script combines source -** code from various constituent source files of SQLite into this single -** "shell.c" file used to implement the SQLite command-line shell. -** -** Most of the code found below comes from the "src/shell.c.in" file in -** the canonical SQLite source tree. That main file contains "INCLUDE" -** lines that specify other files in the canonical source tree that are -** inserted to getnerate this complete program source file. -** -** The code from multiple files is combined into this single "shell.c" -** source file to help make the command-line program easier to compile. -** -** To modify this program, get a copy of the canonical SQLite source tree, -** edit the src/shell.c.in" and/or some of the other files that are included -** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script. -*/} + +############################## FIRST PASS ################################ +# Read through the shell.c.in source file to gather information. Do not +# yet generate any code +# set in [open $topdir/src/shell.c.in] fconfigure $in -translation binary +set allSource(src/shell.c.in) 1 +set inUsage 0 +set dotcmd {} +while {1} { + set lx [gets $in] + if {[eof $in]} break; + if {[regexp {^INCLUDE } $lx]} { + set cfile [lindex $lx 1] + if {[string match ../* $cfile]} { + set xfile [string range $cfile 3 end] + } else { + set xfile "src/$cfile" + } + set allSource($xfile) 1 + } elseif {[regexp {^\*\* USAGE:\s+([^\s]+)} $lx all dotcmd]} { + set inUsage 1 + set details [string trim [string range $lx 2 end]] + } elseif {$inUsage} { + if {![regexp {^\*\*} $lx] || [regexp { DOT-COMMAND: } $lx]} { + set inUsage 0 + set Usage($dotcmd) [string trim $details] + } else { + append details \n + append details [string range [string trimright $lx] 3 end] + } + } +} + +# Generate dot-command usage text based on the data accumulated in +# the Usage() array. +# +proc generate_usage {out} { + global Usage + puts $out "/**************************************************************" + puts $out "** \"Usage\" help text automatically generated from comments */" + puts $out "static const struct \173" + puts $out " const char *zCmd; /* Name of the dot-command */" + puts $out " const char *zUsage; /* Documentation */" + puts $out "\175 aUsage\[\] = \173" + foreach dotcmd [array names Usage] { + puts $out " \173 \"$dotcmd\"," + foreach line [split $Usage($dotcmd) \n] { + set x [string map [list \\ \\\\ \" \\\"] $line] + puts $out "\"$x\\n\"" + } + puts $out " \175," + } + puts $out "\175;" +} +# generate_usage stderr + +###### SECOND PASS ####### +# Make a second pass through shell.c.in to generate the the final +# output, based on data gathered during the first pass. +# + +puts $out {/* +** This is the amalgamated source code to the "sqlite3" or "sqlite3.exe" +** command-line shell (CLI) for SQLite. This file is automatically +** generated by the tool/mkshellc.tcl script from the following sources: +**} +foreach fn [lsort [array names allSource]] { + puts $out "** $fn" +} +puts $out {** +** To modify this program, get a copy of the canonical SQLite source tree, +** edit the src/shell.c.in file and/or some of the other files that are +** listed above, then rerun the command "make shell.c". +*/} +seek $in 0 start +puts $out "/************************* Begin src/shell.c.in ******************/" proc omit_redundant_typedefs {line} { global typedef_seen if {[regexp {^typedef .* ([a-zA-Z0-9_]+);} $line all typename]} { @@ -53,9 +115,14 @@ while {1} { incr iLine if {[regexp {^INCLUDE } $lx]} { set cfile [lindex $lx 1] - puts $out "/************************* Begin $cfile ******************/" -# puts $out "#line 1 \"$cfile\"" - set in2 [open $topdir/src/$cfile] + if {[string match ../* $cfile]} { + set xfile [string range $cfile 3 end] + } else { + set xfile "src/$cfile" + } + puts $out "/************************* Begin $xfile ******************/" +# puts $out "#line 1 \"$xfile\"" + set in2 [open $topdir/$xfile] fconfigure $in2 -translation binary while {![eof $in2]} { set lx [omit_redundant_typedefs [gets $in2]] @@ -69,11 +136,14 @@ while {1} { puts $out $lx } close $in2 - puts $out "/************************* End $cfile ********************/" + puts $out "/************************* End $xfile ********************/" # puts $out "#line [expr $iLine+1] \"shell.c.in\"" - continue + } elseif {[regexp {^INSERT-USAGE-TEXT-HERE} $lx]} { + generate_usage $out + } else { + puts $out $lx } - puts $out $lx } +puts $out "/************************* End src/shell.c.in ******************/" close $in close $out diff --git a/tool/omittest.tcl b/tool/omittest.tcl index 0452a4c6f..03c9220cd 100644 --- a/tool/omittest.tcl +++ b/tool/omittest.tcl @@ -123,7 +123,6 @@ set CompileOptionsToTest { SQLITE_ENABLE_MEMSYS SQLITE_ENABLE_MODULE_COMMENTS SQLITE_ENABLE_MULTIPLEX - SQLITE_ENABLE_MULTITHREADED_CHECKS SQLITE_ENABLE_NORMALIZE SQLITE_ENABLE_NULL_TRIM SQLITE_ENABLE_OFFSET_SQL_FUNC @@ -152,6 +151,7 @@ set CompileOptionsToTest { SQLITE_ENABLE_VFSTRACE SQLITE_ENABLE_WHERETRACE SQLITE_ENABLE_ZIPVFS + SQLITE_THREAD_MISUSE_WARNINGS } # Parse command-line options. diff --git a/tool/showdb.c b/tool/showdb.c index f0bd9737c..84813b839 100644 --- a/tool/showdb.c +++ b/tool/showdb.c @@ -8,6 +8,7 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> +#include <stdint.h> #if !defined(_MSC_VER) #include <unistd.h> @@ -28,13 +29,21 @@ typedef sqlite3_uint64 u64; /* unsigned 64-bit */ static struct GlobalData { i64 pagesize; /* Size of a database page */ + i64 usablesize; /* pagesize-nRes */ int dbfd; /* File descriptor for reading the DB */ u32 mxPage; /* Last page number */ + u32 nRes; /* Amount of reserve space */ int perLine; /* HEX elements to print per line */ int bRaw; /* True to access db file via OS APIs */ + int bCSV; /* CSV output for "pgidx" */ + int bTmstmp; /* Interpret tmstmpvfs tags on "pgidx" */ sqlite3_file *pFd; /* File descriptor for non-raw mode */ sqlite3 *pDb; /* Database handle that owns pFd */ -} g = {1024, -1, 0, 16, 0, 0, 0}; + char **zPageUse; /* Use for each page */ + struct TmstmpTag { + unsigned char a[16]; /* tmstmpvfs tag for each page */ + } *aPageTag; +} g = {4096, 4096, -1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0}; /* ** Convert the var-int format into i64. Return the number of bytes @@ -142,11 +151,12 @@ static void fileClose(){ static unsigned char *fileRead(sqlite3_int64 ofst, int nByte){ unsigned char *aData; int got; + int rc; aData = sqlite3_malloc64(32+(i64)nByte); if( aData==0 ) out_of_memory(); memset(aData, 0, nByte+32); if( g.bRaw==0 ){ - int rc = g.pFd->pMethods->xRead(g.pFd, (void*)aData, nByte, ofst); + rc = g.pFd->pMethods->xRead(g.pFd, (void*)aData, nByte, ofst); if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){ fprintf(stderr, "error in xRead() - %d\n", rc); exit(1); @@ -154,7 +164,21 @@ static unsigned char *fileRead(sqlite3_int64 ofst, int nByte){ }else{ lseek(g.dbfd, (long)ofst, SEEK_SET); got = read(g.dbfd, aData, nByte); - if( got>0 && got<nByte ) memset(aData+got, 0, nByte-got); + if( got==nByte ){ + rc = SQLITE_OK; + }else if( got>0 && got<nByte ){ + memset(aData+got, 0, nByte-got); + rc = SQLITE_IOERR_SHORT_READ; + }else{ + memset(aData,0,nByte); + rc = SQLITE_IOERR; + } + } + if( g.aPageTag && nByte==(int)g.pagesize ){ + unsigned int pgno = (unsigned int)(ofst/g.pagesize) + 1; + if( pgno>0 && pgno<=g.mxPage ){ + memcpy(g.aPageTag[pgno].a, &aData[nByte-16], 16); + } } return aData; } @@ -382,14 +406,14 @@ static i64 localPayload(i64 nPayload, char cType){ i64 nLocal; if( cType==13 ){ /* Table leaf */ - maxLocal = g.pagesize-35; - minLocal = (g.pagesize-12)*32/255-23; + maxLocal = g.usablesize-35; + minLocal = (g.usablesize-12)*32/255-23; }else{ - maxLocal = (g.pagesize-12)*64/255-23; - minLocal = (g.pagesize-12)*32/255-23; + maxLocal = (g.usablesize-12)*64/255-23; + minLocal = (g.usablesize-12)*32/255-23; } if( nPayload>maxLocal ){ - surplus = minLocal + (nPayload-minLocal)%(g.pagesize-4); + surplus = minLocal + (nPayload-minLocal)%(g.usablesize-4); if( surplus<=maxLocal ){ nLocal = surplus; }else{ @@ -751,7 +775,7 @@ static void decode_trunk_page( print_decode_line(a, 4, 4, "Number of entries on this page"); if( detail ){ n = decodeInt32(&a[4]); - for(i=0; i<n && i<g.pagesize/4; i++){ + for(i=0; i<n && i<g.usablesize/4; i++){ u32 x = decodeInt32(&a[8+4*i]); char zIdx[13]; sprintf(zIdx, "[%d]", i); @@ -769,11 +793,6 @@ static void decode_trunk_page( } } -/* -** A short text comment on the use of each page. -*/ -static char **zPageUse; - /* ** Add a comment on the use of a page. */ @@ -790,13 +809,13 @@ static void page_usage_msg(u32 pgno, const char *zFormat, ...){ sqlite3_free(zMsg); return; } - if( zPageUse[pgno]!=0 ){ + if( g.zPageUse[pgno]!=0 ){ printf("ERROR: page %d used multiple times:\n", pgno); - printf("ERROR: previous: %s\n", zPageUse[pgno]); + printf("ERROR: previous: %s\n", g.zPageUse[pgno]); printf("ERROR: current: %s\n", zMsg); - sqlite3_free(zPageUse[pgno]); + sqlite3_free(g.zPageUse[pgno]); } - zPageUse[pgno] = zMsg; + g.zPageUse[pgno] = zMsg; } /* @@ -837,7 +856,7 @@ static void page_usage_cell( while( ovfl && (cnt++)<g.mxPage ){ page_usage_msg(ovfl, "overflow %d from cell %d of page %u", cnt, cellno, pgno); - a = fileRead((ovfl-1)*(sqlite3_int64)g.pagesize, 4); + a = fileRead((ovfl-1)*(sqlite3_int64)g.pagesize, g.pagesize); ovfl = decodeInt32(a); sqlite3_free(a); } @@ -916,12 +935,12 @@ static void page_usage_btree( u32 ofst; cellidx = cellstart + i*2; - if( cellidx+1 >= g.pagesize ){ + if( cellidx+1 >= g.usablesize ){ printf("ERROR: page %d too many cells (%d)\n", pgno, nCell); break; } ofst = a[cellidx]*256 + a[cellidx+1]; - if( ofst<cellidx+2 || ofst+4>=g.pagesize ){ + if( ofst<cellidx+2 || ofst+4>=g.usablesize ){ printf("ERROR: page %d cell %d out of bounds\n", pgno, i); continue; } @@ -959,9 +978,9 @@ static void page_usage_freelist(u32 pgno){ a = fileRead((pgno-1)*g.pagesize, g.pagesize); iNext = decodeInt32(a); n = decodeInt32(a+4); - if( n>(g.pagesize - 8)/4 ){ + if( n>(g.usablesize - 8)/4 ){ printf("ERROR: page %d too many freelist entries (%d)\n", pgno, n); - n = (g.pagesize - 8)/4; + n = (g.usablesize - 8)/4; } for(i=0; i<n; i++){ int child = decodeInt32(a + (i*4+8)); @@ -990,6 +1009,63 @@ static void page_usage_ptrmap(u8 *a){ } } +/* +** The six bytes at a[] are a big-endian unsigned integer which is the +** number of milliseconds since 1970. Decode that value into an ISO 8601 +** date/time string stored in static space and return a pointer to that +** string. +*/ +static const char *decodeTimestamp(const unsigned char *a){ + uint64_t ms; /* Milliseconds since 1970 */ + uint64_t days; /* Days since 1970-01-01 */ + uint64_t sod; /* Start of date specified by ms */ + uint64_t z; /* Days since 0000-03-01 */ + uint64_t era; /* 400-year era */ + int i; /* Loop counter */ + 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 */ + static char zOut[50]; /* Return results here */ + + for(ms=0, i=0; i<=5; i++) ms = (ms<<8) + a[i]; + if( ms==0 ){ + return " "; + }else if( ms>4102444800000LL ){ /* 2100-01-01 */ + /* YYYY-MM-DD HH:MM:SS.SSS */ + return " (bad date) "; + } + days = ms/86400000; + sod = (ms%86400000)/1000; + f = (int)(ms%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); + snprintf(zOut, sizeof(zOut), + "%04d-%02d-%02d %02d:%02d:%02d.%03d", + Y, M, D, h, m, s, f); + return zOut; +} + /* ** Try to figure out how every page in the database file is being used. */ @@ -1010,14 +1086,22 @@ static void page_usage_report(const char *zPrg, const char *zDbName){ /* Open the database file */ db = openDatabase(zPrg, zDbName); - /* Set up global variables zPageUse[] and g.mxPage to record page + /* Set up global variables g.zPageUse[] and g.mxPage to record page ** usages */ - zPageUse = sqlite3_malloc64( sizeof(zPageUse[0])*(g.mxPage+1) ); - if( zPageUse==0 ) out_of_memory(); - memset(zPageUse, 0, sizeof(zPageUse[0])*(g.mxPage+1)); + g.zPageUse = sqlite3_malloc64( sizeof(g.zPageUse[0])*(g.mxPage+1) ); + if( g.zPageUse==0 ) out_of_memory(); + memset(g.zPageUse, 0, sizeof(g.zPageUse[0])*(g.mxPage+1)); /* Discover the usage of each page */ a = fileRead(0, 100); + if( g.bTmstmp && a[20]==16 ){ + g.aPageTag = sqlite3_malloc64( sizeof(struct TmstmpTag)*(g.mxPage+1) ); + if( g.aPageTag==0 ) out_of_memory(); + memset(g.aPageTag, 0, sizeof(struct TmstmpTag)*(g.mxPage+1) ); + }else{ + g.bTmstmp = 0; + g.aPageTag = 0; + } page_usage_freelist(decodeInt32(a+32)); page_usage_ptrmap(a); sqlite3_free(a); @@ -1042,15 +1126,68 @@ static void page_usage_report(const char *zPrg, const char *zDbName){ sqlite3_close(db); /* Print the report and free memory used */ + if( g.bCSV ){ + if( g.bTmstmp ){ + printf("pgno,tm,frame,flg,salt,parent,child,ovfl,txt\r\n"); + }else{ + printf("pgno,parent,child,ovfl,txt\r\n"); + } + } for(i=1; i<=g.mxPage; i++){ - if( zPageUse[i]==0 ) page_usage_btree(i, -1, 0, 0); - printf("%5u: %s\n", i, zPageUse[i] ? zPageUse[i] : "???"); + if( g.zPageUse[i]==0 ){ + g.zPageUse[i] = sqlite3_mprintf("???"); + if( g.zPageUse[i]==0 ) continue; + } + if( g.bCSV ){ + const char *z = g.zPageUse[i]; + const char *s; + printf("%u,", i); + if( g.bTmstmp ){ + const unsigned char *a = g.aPageTag[i].a; + sqlite3_uint64 tm = 0; + unsigned int x; + int k; + for(k=2; k<=7; k++) tm = (tm<<8)+a[k]; + printf("%llu.%03u,", tm/1000, (unsigned int)(tm%1000)); + for(x=0, k=8; k<=11; k++) x = (x<<8)+a[k]; + printf("%u,", x); + printf("%u,", a[12]); + for(x=0, k=13; k<=15; k++) x = (x<<8)+a[k]; + printf("%u,", x); + } + if( (s = strstr(z, " of page "))!=0 ){ + printf("%d,", atoi(s+9)); + }else if( (s = strstr(z, " of trunk page "))!=0 ){ + printf("%d,", atoi(s+15)); + }else{ + printf("0,"); + } + if( (s = strstr(z, "], child "))!=0 ){ + printf("%d,", atoi(s+9)); + }else if( (s = strstr(z, " from cell "))!=0 ){ + printf("%d,", atoi(s+12)); + }else{ + printf("-1,"); + } + if( strncmp(z,"overflow ", 9)==0 ){ + printf("%d,", atoi(z+9)); + }else{ + printf("-1,"); + } + printf("\"%s\"\r\n", z); + }else if( g.bTmstmp ){ + printf("%5u: %s %s\n", i, + decodeTimestamp(&g.aPageTag[i].a[2]), + g.zPageUse[i]); + }else{ + printf("%5u: %s\n", i, g.zPageUse[i]); + } } for(i=1; i<=g.mxPage; i++){ - sqlite3_free(zPageUse[i]); + sqlite3_free(g.zPageUse[i]); } - sqlite3_free(zPageUse); - zPageUse = 0; + sqlite3_free(g.zPageUse); + g.zPageUse = 0; } /* @@ -1083,7 +1220,7 @@ static void ptrmap_coverage_report(const char *zDbName){ for(pgno=2; pgno<=g.mxPage; pgno += perPage+1){ printf("%5llu: PTRMAP page covering %llu..%llu\n", pgno, pgno+1, pgno+perPage); - a = fileRead((pgno-1)*g.pagesize, usable); + a = fileRead((pgno-1)*g.pagesize, g.pagesize); for(i=0; i+5<=usable; i+=5){ const char *zType; u32 iFrom = decodeInt32(&a[i+1]); @@ -1115,7 +1252,7 @@ static void ptrmap_coverage_report(const char *zDbName){ ** Check the range validity for a page number. Print an error and ** exit if the page is out of range. */ -static void checkPageValidity(int iPage){ +static void checkPageValidity(unsigned int iPage){ if( iPage<1 || iPage>g.mxPage ){ fprintf(stderr, "Invalid page number %d: valid range is 1..%d\n", iPage, g.mxPage); @@ -1130,7 +1267,9 @@ static void usage(const char *argv0){ fprintf(stderr, "Usage %s ?--uri? FILENAME ?args...?\n\n", argv0); fprintf(stderr, "switches:\n" + " --csv CSV output for \"pgidx\"\n" " --raw Read db file directly, bypassing SQLite VFS\n" + " --tmstmp Interpret tmstmpvfs tags\n" "args:\n" " dbheader Show database header\n" " pgidx Index of how each page is used\n" @@ -1154,14 +1293,28 @@ int main(int argc, char **argv){ char **azArg = argv; int nArg = argc; - /* Check for the "--uri" or "-uri" switch. */ - if( nArg>1 ){ - if( sqlite3_stricmp("-raw", azArg[1])==0 - || sqlite3_stricmp("--raw", azArg[1])==0 - ){ + /* Check for the switches. */ + while( nArg>1 && azArg[1][0]=='-' ){ + const char *z = azArg[1]; + if( z[1]=='-' && z[2]!=0 ) z++; + if( sqlite3_stricmp("-raw", z)==0 ){ g.bRaw = 1; azArg++; nArg--; + }else + if( strcmp("-csv", z)==0 ){ + g.bCSV = 1; + azArg++; + nArg--; + }else + if( strcmp("-tmstmp", z)==0 ){ + g.bTmstmp = 1; + azArg++; + nArg--; + }else + { + usage(zPrg); + exit(1); } } @@ -1173,15 +1326,19 @@ int main(int argc, char **argv){ fileOpen(zPrg, azArg[1]); szFile = fileGetsize(); - zPgSz = fileRead(16, 2); - g.pagesize = zPgSz[0]*256 + zPgSz[1]*65536; - if( g.pagesize==0 ) g.pagesize = 1024; + zPgSz = fileRead(0, 24); + g.pagesize = zPgSz[16]*256 + zPgSz[17]*65536; + if( g.pagesize==0 ) g.pagesize = 4096; + g.nRes = zPgSz[20]; + g.usablesize = g.pagesize - g.nRes; sqlite3_free(zPgSz); - - printf("Pagesize: %d\n", (int)g.pagesize); g.mxPage = (u32)((szFile+g.pagesize-1)/g.pagesize); - printf("Available pages: 1..%u\n", g.mxPage); + if( !g.bCSV ){ + printf("Pagesize: %d\n", (int)g.pagesize); + if( g.nRes ) printf("Useable-size: %d\n", (int)g.usablesize); + printf("Available pages: 1..%u\n", g.mxPage); + } if( nArg==2 ){ u32 i; for(i=1; i<=g.mxPage; i++) print_page(i); diff --git a/tool/showtmlog.c b/tool/showtmlog.c new file mode 100644 index 000000000..4c35b777b --- /dev/null +++ b/tool/showtmlog.c @@ -0,0 +1,254 @@ +/* +** A utility program to decode tmstmpvfs log files. +*/ +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <stdint.h> +#include <stdlib.h> + +/* +** The six bytes at a[] are a big-endian unsigned integer which is the +** number of milliseconds since 1970. Decode that value into an ISO 8601 +** date/time string stored in static space and return a pointer to that +** string. +*/ +static const char *decodeTimestamp(const unsigned char *a){ + uint64_t ms; /* Milliseconds since 1970 */ + uint64_t days; /* Days since 1970-01-01 */ + uint64_t sod; /* Start of date specified by ms */ + uint64_t z; /* Days since 0000-03-01 */ + uint64_t era; /* 400-year era */ + int i; /* Loop counter */ + 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 */ + static char zOut[50]; /* Return results here */ + + for(ms=0, i=0; i<=5; i++) ms = (ms<<8) + a[i]; + if( ms==0 ){ + return " "; + }else if( ms>4102444800000LL ){ /* 2100-01-01 */ + /* YYYY-MM-DD HH:MM:SS.SSS */ + return " (bad date) "; + } + days = ms/86400000; + sod = (ms%86400000)/1000; + f = (int)(ms%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); + snprintf(zOut, sizeof(zOut), + "%04d-%02d-%02d %02d:%02d:%02d.%03d", + Y, M, D, h, m, s, f); + return zOut; +} + +/* +** Render a single 16-byte tmstmpvfs log record as a line to a CSV file. +** +** Columns: tmstmp,fileno,op,pid,pgno,frame,salt,txn +*/ +static void renderCSV(int iFile, unsigned char *a){ + unsigned int a2, a3; + int j; + uint64_t ms; + + for(ms=0, j=2; j<=7; j++) ms = (ms<<8) + a[j]; + printf("%u.%03u,%d,", (unsigned int)(ms/1000), (unsigned)(ms%1000), iFile); + for(a2=0, j=8; j<=11; j++) a2 = (a2<<8)+a[j]; + for(a3=0, j=12; j<=15; j++) a3 = (a3<<8)+a[j]; + switch( a[0] ){ + case 0x01: { + printf("\"open-db\",%u,,,,\r\n",a2); + break; + } + case 0x02: { + printf("\"open-wal\",%u,,,,\r\n", a2); + break; + } + case 0x03: { + printf("\"wal-page\",,%u,%u,,%d\r\n", a2, a3, a[1]); + break; + } + case 0x04: { + printf("\"db-page\",,%u,,,\r\n", a2); + break; + } + case 0x05: { + printf("\"ckpt-start\",,,,,\r\n"); + break; + } + case 0x06: { + printf("\"ckpt-page\",,%u,%u,,\r\n", a2, a3); + break; + } + case 0x07: { + printf("\"ckpt-end\",,,,,\r\n"); + break; + } + case 0x08: { + printf("\"wal-reset\",,,,%u,\r\n", a3); + break; + } + case 0x0e: { + printf("\"close-wal\",,,,,\r\n"); + break; + } + case 0x0f: { + printf("\"close-db\",,,,,\r\n"); + break; + } + default: { + printf("\"invalid-record\",,,,,\r\n"); + break; + } + } +} + +/* +** Render a single 16-byte tmstmpvfs log record as human-readable text +** on stdout. +*/ +static void renderText(unsigned char *a){ + unsigned int a2, a3; + int j; + + printf("%s ", decodeTimestamp(a+2)); + for(a2=0, j=8; j<=11; j++) a2 = (a2<<8)+a[j]; + for(a3=0, j=12; j<=15; j++) a3 = (a3<<8)+a[j]; + switch( a[0] ){ + case 0x01: { + printf("open-db pid %u\n", a2); + break; + } + case 0x02: { + printf("open-wal pid %u\n", a2); + break; + } + case 0x03: { + printf("wal-page pgno %-8u frame %-8u%s\n", a2, a3, + a[1]==1 ? " txn" : ""); + break; + } + case 0x04: { + printf("db-page pgno %-8u\n", a2); + break; + } + case 0x05: { + printf("ckpt-start\n"); + break; + } + case 0x06: { + printf("ckpt-page pgno %-8u frame %-8u\n", a2, a3); + break; + } + case 0x07: { + printf("ckpt-end\n"); + break; + } + case 0x08: { + printf("wal-reset salt1 0x%08x\n", a3); + break; + } + case 0x0e: { + printf("close-wal\n"); + break; + } + case 0x0f: { + printf("close-db\n"); + break; + } + default: { + printf("invalid-record\n"); + break; + } + } +} + +static void usage(const char *argv0){ + printf("Usage: %s [--csv] LOGFILE ...\n", argv0); + printf("Decode one or more tmstmpvfs log files and display the results\n" + "on stdout. Render as CSV if the --csv option is used.\n"); +} + +int main(int argc, char **argv){ + int i; + FILE *in; + unsigned char a[16]; + int bCSV = 0; + const char *z; + int nFile = 0; + int iFile; + for(i=1; i<argc; i++){ + z = argv[i]; + if( z[0]=='-' ){ + if( z[1]=='-' ) z++; + if( strcmp(z,"-csv")==0 ){ + bCSV = 1; + }else + if( strcmp(z,"-help")==0 || strcmp(z,"-?")==0 ){ + usage(argv[0]); + return 0; + }else + { + printf("unknown command-line option: \"%s\"\n", + argv[i]); + usage(argv[0]); + return 1; + } + }else{ + nFile++; + } + } + if( nFile==0 ){ + usage(argv[0]); + return 1; + } + iFile = 0; + if( bCSV ){ + printf("tmstmp,fileno,op,pid,pgno,frame,salt,txn\r\n"); + } + for(i=1; i<argc; i++){ + z = argv[i]; + if( z[0]=='-' ) continue; + in = fopen(z, "rb"); + if( in==0 ){ + printf("%s: can't open\n", z); + continue; + } + iFile++; + if( nFile>1 && !bCSV ){ + printf("*** %s ***\n", z); + } + while( 16==fread(a, 1, 16, in) ){ + if( bCSV ){ + renderCSV(iFile, a); + }else{ + renderText(a); + } + } + fclose(in); + } + return 0; +} diff --git a/tool/sqldiff.c b/tool/sqldiff.c index 44bf488f8..d27a62e14 100644 --- a/tool/sqldiff.c +++ b/tool/sqldiff.c @@ -28,6 +28,9 @@ #include "sqlite3.h" #include "sqlite3_stdio.h" +typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; + /* ** All global variables are gathered into the "g" singleton. */ @@ -202,12 +205,12 @@ static char **columnNames( int *pbRowid /* OUT: True if PK is an implicit rowid */ ){ char **az = 0; /* List of column names to be returned */ - int naz = 0; /* Number of entries in az[] */ + i64 naz = 0; /* Number of entries in az[] */ sqlite3_stmt *pStmt; /* SQL statement being run */ char *zPkIdxName = 0; /* Name of the PRIMARY KEY index */ int truePk = 0; /* PRAGMA table_info identifies the PK to use */ - int nPK = 0; /* Number of PRIMARY KEY columns */ - int i, j; /* Loop counters */ + i64 nPK = 0; /* Number of PRIMARY KEY columns */ + i64 i, j; /* Loop counters */ if( g.bSchemaPK==0 ){ /* Normal case: Figure out what the true primary key is for the table. @@ -271,7 +274,7 @@ static char **columnNames( } *pnPKey = nPK; naz = nPK; - az = sqlite3_malloc( sizeof(char*)*(nPK+1) ); + az = sqlite3_malloc64( sizeof(char*)*(nPK+1) ); if( az==0 ) runtimeError("out of memory"); memset(az, 0, sizeof(char*)*(nPK+1)); if( g.bSchemaCompare ){ @@ -288,7 +291,7 @@ static char **columnNames( || !(strcmp(sid,"rootpage")==0 ||strcmp(sid,"name")==0 ||strcmp(sid,"type")==0)){ - az = sqlite3_realloc(az, sizeof(char*)*(naz+2) ); + az = sqlite3_realloc64(az, sizeof(char*)*(naz+2) ); if( az==0 ) runtimeError("out of memory"); az[naz++] = sid; } @@ -954,7 +957,7 @@ static int rbuDeltaCreate( unsigned int i, base; char *zOrigDelta = zDelta; hash h; - int nHash; /* Number of hash table entries */ + i64 nHash; /* Number of hash table entries */ int *landmark; /* Primary hash table */ int *collide; /* Collision chain */ int lastRead = -1; /* Last byte of zSrc read by a COPY command */ @@ -982,7 +985,7 @@ static int rbuDeltaCreate( ** source file. */ nHash = lenSrc/NHASH; - collide = sqlite3_malloc( nHash*2*sizeof(int) ); + collide = sqlite3_malloc64( nHash*2*sizeof(int) ); landmark = &collide[nHash]; memset(landmark, -1, nHash*sizeof(int)); memset(collide, -1, nHash*sizeof(int)); @@ -1286,9 +1289,9 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ } }else{ char *zOtaControl; - int nOtaControl = sqlite3_column_bytes(pStmt, nCol); + i64 nOtaControl = sqlite3_column_bytes(pStmt, nCol); - zOtaControl = (char*)sqlite3_malloc(nOtaControl+1); + zOtaControl = (char*)sqlite3_malloc64(nOtaControl+1); memcpy(zOtaControl, sqlite3_column_text(pStmt, nCol), nOtaControl+1); for(i=0; i<nCol; i++){ @@ -1300,11 +1303,11 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ const char *aSrc = sqlite3_column_blob(pStmt, nCol+1+i); int nSrc = sqlite3_column_bytes(pStmt, nCol+1+i); const char *aFinal = sqlite3_column_blob(pStmt, i); - int nFinal = sqlite3_column_bytes(pStmt, i); + i64 nFinal = sqlite3_column_bytes(pStmt, i); char *aDelta; int nDelta; - aDelta = sqlite3_malloc(nFinal + 60); + aDelta = sqlite3_malloc64(nFinal + 60); nDelta = rbuDeltaCreate(aSrc, nSrc, aFinal, nFinal, aDelta); if( nDelta<nFinal ){ int j; @@ -1549,10 +1552,10 @@ static void changeset_one_table(const char *zTab, FILE *out){ sqlite3_stmt *pStmt; /* SQL statment */ char *zId = safeId(zTab); /* Escaped name of the table */ char **azCol = 0; /* List of escaped column names */ - int nCol = 0; /* Number of columns */ + i64 nCol = 0; /* Number of columns */ int *aiFlg = 0; /* 0 if column is not part of PK */ int *aiPk = 0; /* Column numbers for each PK column */ - int nPk = 0; /* Number of PRIMARY KEY columns */ + i64 nPk = 0; /* Number of PRIMARY KEY columns */ sqlite3_str *pSql; /* SQL for the diff query */ int i, k; /* Loop counters */ const char *zSep; /* List separator */ @@ -1564,16 +1567,16 @@ static void changeset_one_table(const char *zTab, FILE *out){ pStmt = db_prepare("PRAGMA main.table_info=%Q", zTab); while( SQLITE_ROW==sqlite3_step(pStmt) ){ nCol++; - azCol = sqlite3_realloc(azCol, sizeof(char*)*nCol); + azCol = sqlite3_realloc64(azCol, sizeof(char*)*nCol); if( azCol==0 ) runtimeError("out of memory"); - aiFlg = sqlite3_realloc(aiFlg, sizeof(int)*nCol); + aiFlg = sqlite3_realloc64(aiFlg, sizeof(int)*nCol); if( aiFlg==0 ) runtimeError("out of memory"); azCol[nCol-1] = safeId((const char*)sqlite3_column_text(pStmt,1)); aiFlg[nCol-1] = i = sqlite3_column_int(pStmt,5); if( i>0 ){ if( i>nPk ){ nPk = i; - aiPk = sqlite3_realloc(aiPk, sizeof(int)*nPk); + aiPk = sqlite3_realloc64(aiPk, sizeof(int)*nPk); if( aiPk==0 ) runtimeError("out of memory"); } aiPk[i-1] = nCol-1; @@ -1896,6 +1899,11 @@ static void showHelp(void){ ); } +/* work-around the Microsoft "WorstFit" bug */ +#ifdef _WIN32 +#define main utf8_main +#endif + int main(int argc, char **argv){ const char *zDb1 = 0; const char *zDb2 = 0; @@ -1908,7 +1916,7 @@ int main(int argc, char **argv){ FILE *out = stdout; void (*xDiff)(const char*,FILE*) = diff_one_table; #ifndef SQLITE_OMIT_LOAD_EXTENSION - int nExt = 0; + i64 nExt = 0; char **azExt = 0; #endif int useTransaction = 0; diff --git a/tool/sqlite3_rsync.c b/tool/sqlite3_rsync.c index ad9f132bb..b10224b2f 100644 --- a/tool/sqlite3_rsync.c +++ b/tool/sqlite3_rsync.c @@ -32,6 +32,7 @@ static const char zUsage[] = "\n" " --exe PATH Name of the sqlite3_rsync program on the remote side\n" " --help Show this help screen\n" + " -p|--port PORT Run SSH over TCP port PORT instead of the default 22\n" " --protocol N Use sync protocol version N.\n" " --ssh PATH Name of the SSH program used to reach the remote side\n" " -v Verbose. Multiple v's for increasing output\n" @@ -324,15 +325,29 @@ static int popen2( ** Close the connection to a child process previously created using ** popen2(). */ -static void pclose2(FILE *pIn, FILE *pOut, int childPid){ +static int pclose2(FILE *pIn, FILE *pOut, int childPid){ #ifdef _WIN32 /* Not implemented, yet */ fclose(pIn); fclose(pOut); + return 0; #else + int wp, rc = 0; fclose(pIn); fclose(pOut); - while( waitpid(0, 0, WNOHANG)>0 ) {} + do{ + wp = waitpid(0, &rc, WNOHANG); + if( wp>0 ){ + if( WIFEXITED(rc) ){ + rc = WEXITSTATUS(rc); + }else if( WIFSIGNALED(rc) ){ + rc = WTERMSIG(rc); + }else{ + rc = 0/*???*/; + } + } + } while( wp>0 ); + return rc; #endif } /***************************************************************************** @@ -2054,6 +2069,7 @@ int main(int argc, char const * const *argv){ FILE *pOut = 0; int childPid = 0; const char *zSsh = "ssh"; + int iPort = 0; const char *zExe = "sqlite3_rsync"; char *zCmd = 0; sqlite3_int64 tmStart; @@ -2094,6 +2110,15 @@ int main(int argc, char const * const *argv){ zSsh = cli_opt_val; continue; } + if( strcmp(z, "-port")==0 || strcmp(z, "-p")==0 ){ + const char *zPort = cli_opt_val; + iPort = atoi(zPort); + if( iPort<1 || iPort>65535 ){ + fprintf(stderr, "invalid TCP port number: \"%s\"\n", zPort); + return 1; + } + continue; + } if( strcmp(z, "-exe")==0 ){ zExe = cli_opt_val; continue; @@ -2235,6 +2260,7 @@ int main(int argc, char const * const *argv){ for(iRetry=0; 1 /*exit-via-break*/; iRetry++){ sqlite3_str *pStr = sqlite3_str_new(0); append_escaped_arg(pStr, zSsh, 1); + if( iPort>0 ) sqlite3_str_appendf(pStr, " -p %d", iPort); sqlite3_str_appendf(pStr, " -e none"); append_escaped_arg(pStr, ctx.zOrigin, 0); if( iRetry ) add_path_argument(pStr); @@ -2283,6 +2309,7 @@ int main(int argc, char const * const *argv){ for(iRetry=0; 1 /*exit-by-break*/; iRetry++){ sqlite3_str *pStr = sqlite3_str_new(0); append_escaped_arg(pStr, zSsh, 1); + if( iPort>0 ) sqlite3_str_appendf(pStr, " -p %d", iPort); sqlite3_str_appendf(pStr, " -e none"); append_escaped_arg(pStr, ctx.zReplica, 0); if( iRetry==1 ) add_path_argument(pStr); @@ -2349,7 +2376,7 @@ int main(int argc, char const * const *argv){ } originSide(&ctx); } - pclose2(ctx.pIn, ctx.pOut, childPid); + ctx.nErr += !!pclose2(ctx.pIn, ctx.pOut, childPid); if( ctx.pLog ) fclose(ctx.pLog); tmEnd = currentTime(); tmElapse = tmEnd - tmStart; /* Elapse time in milliseconds */ diff --git a/tool/sqltclsh.c.in b/tool/sqltclsh.c.in index da354ee93..3e4d38b2d 100644 --- a/tool/sqltclsh.c.in +++ b/tool/sqltclsh.c.in @@ -33,7 +33,7 @@ INCLUDE $ROOT/ext/misc/appendvfs.c INCLUDE $ROOT/ext/misc/zipfile.c INCLUDE $ROOT/ext/misc/sqlar.c #endif -INCLUDE $ROOT/src/tclsqlite.c +INCLUDE tclsqlite-ex.c const char *sqlite3_tclapp_init_proc(Tcl_Interp *interp){ (void)interp; diff --git a/tool/winmain.c b/tool/winmain.c new file mode 100644 index 000000000..72ae00d5c --- /dev/null +++ b/tool/winmain.c @@ -0,0 +1,79 @@ +/* +** 2025-12-26 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements a substitute process entry point for windows +** that correctly interprets command-line arguments as UTF-8. This is +** a work-around for the (unfixed) Windows bug known as "WorstFit" and +** described at: +** +** * https://news.ycombinator.com/item?id=42647101 +** +** USAGE: +** +** If you have a command-line program coded to C-language standard in which +** the entry point is: +** +** int main(int argc, char **argv){...} +** +** That does not work on Windows if there are non-ASCII characters on the +** command line. Programs are expected to use an alternative entry point: +** +** int wmain(int wargc, wchar_t **wargv){...} +** +** This file implements _wmain() but then converts all of the wargv elements +** to UTF-8 and passes them off to utf8_main(). +** +** So, a command-line tool that implements the standard entry point can be +** modified by adding the following lines just prior to main(): +** +** #ifdef _WIN32 +** #define main utf8_main +** #endif +** +** Then, include this file in the list of files that implement the program, +** and the program will work correctly, even on Windows. +*/ +#include <windows.h> +#include <stdio.h> + +extern int utf8_main(int,char**); +int wmain(int argc, wchar_t **wargv){ + int rc, i; + char **argv = malloc( sizeof(char*) * (argc+1) ); + char **orig = argv; + if( argv==0 ){ + fprintf(stderr, "malloc failed\n"); + exit(1); + } + for(i=0; i<argc; i++){ + int nByte = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, 0, 0, 0, 0); + if( nByte==0 ){ + argv[i] = 0; + }else{ + argv[i] = malloc( nByte ); + if( argv[i]==0 ){ + fprintf(stderr, "malloc failed\n"); + exit(1); + } + nByte = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i],nByte,0,0); + if( nByte==0 ){ + free(argv[i]); + argv[i] = 0; + } + } + } + argv[argc] = 0; + rc = utf8_main(argc, argv); + for(i=0; i<argc; i++) free(orig[i]); + free(argv); + return rc; +} From b3444ee185afd513840d568c51a82f46d172ee13 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo <sjlombardo@zetetic.net> Date: Fri, 6 Mar 2026 21:59:35 -0500 Subject: [PATCH 13/15] Updates CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 446d65cae..51fcfd558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ Notable changes to this project are documented in this file. ## [4.14.0] - (? 2026 - [4.14.0 changes]) +- Updates baseline to SQLite 3.52.0 +- Restores and improves upon LibTomCrypto provder +- Minor test improvements ## [4.13.0] - (January 2026 - [4.13.0 changes]) - Updates baseline to SQLite 3.51.2 From 2b30a9fdc1d348cf45e4ae252308cfb753e1c7ed Mon Sep 17 00:00:00 2001 From: Stephen Lombardo <sjlombardo@zetetic.net> Date: Fri, 13 Mar 2026 09:24:56 -0400 Subject: [PATCH 14/15] Snapshot of upstream SQLite 3.51.3 --- 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 | 70 +- 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 | 69 - 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/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 | 7 +- 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 | 4 +- 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.c-pp => EXPORTED_FUNCTIONS.sqlite3-core} | 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.c-pp.html => index.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 | 511 +- manifest.tags | 7 +- manifest.uuid | 2 +- src/alter.c | 779 +- src/attach.c | 2 +- src/auth.c | 2 +- src/btree.c | 28 +- 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/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 | 10 +- src/select.c | 707 +- src/shell.c.in | 7966 +++++++++-------- src/sqlite.h.in | 241 +- src/sqlite3ext.h | 11 +- src/sqliteInt.h | 127 +- src/sqliteLimit.h | 29 +- 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 | 50 +- src/vdbeaux.c | 2 +- src/vdbemem.c | 194 +- src/where.c | 227 +- src/wherecode.c | 11 +- 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/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 | 43 + 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 | 4 +- test/where2.test | 37 - test/whereK.test | 13 - test/win32longpath.test | 4 + test/with1.test | 19 - 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 - 274 files changed, 11464 insertions(+), 26556 deletions(-) delete mode 100644 ext/misc/tmstmpvfs.c delete mode 100644 ext/qrf/README.md delete mode 100644 ext/qrf/dev-notes.md delete mode 100644 ext/qrf/qrf.c delete mode 100644 ext/qrf/qrf.h create mode 100644 ext/repair/README.md create mode 100644 ext/repair/checkfreelist.c create mode 100644 ext/repair/checkindex.c create mode 100644 ext/repair/sqlite3_checker.c.in create mode 100644 ext/repair/sqlite3_checker.tcl create mode 100644 ext/repair/test/README.md create mode 100644 ext/repair/test/checkfreelist01.test create mode 100644 ext/repair/test/checkindex01.test create mode 100644 ext/repair/test/test.tcl create mode 100644 ext/wasm/EXPORTED_FUNCTIONS.fiddle.in rename ext/wasm/api/{EXPORTED_FUNCTIONS.c-pp => EXPORTED_FUNCTIONS.sqlite3-core} (61%) create mode 100644 ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras create mode 100644 ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see create mode 100644 ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api create mode 100644 ext/wasm/api/sqlite3-api-cleanup.js delete mode 100644 ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js rename ext/wasm/fiddle/{index.c-pp.html => index.html} (95%) delete mode 100644 make.bat delete mode 100644 test/altercons.test delete mode 100644 test/altercons2.test delete mode 100644 test/atof2.test delete mode 100644 test/bestindexF.test delete mode 100644 test/dotcmd01.sql delete mode 100644 test/fptest01.sql delete mode 100644 test/import01.sql delete mode 100644 test/imposter1.sql delete mode 100644 test/intck01.sql delete mode 100644 test/json109.test delete mode 100644 test/modeA.sql delete mode 100644 test/qrf01.test delete mode 100644 test/qrf02.test delete mode 100644 test/qrf03.test delete mode 100644 test/qrf04.test delete mode 100644 test/qrf05.test delete mode 100644 test/qrf06.test delete mode 100644 test/regexp1.sql delete mode 100644 test/shellB.test delete mode 100644 test/temptrigfault.tes delete mode 100644 tool/mkcombo.tcl delete mode 100644 tool/showtmlog.c delete mode 100644 tool/winmain.c diff --git a/Makefile.msc b/Makefile.msc index 763616ce9..52807ff7f 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -11,8 +11,6 @@ TOP = . # <<mark>> -# ^^^^^^^^-- 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 @@ -103,6 +101,13 @@ 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. # @@ -111,8 +116,14 @@ SPLIT_AMALGAMATION = 0 !ENDIF # <<mark>> -# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc +# 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. # +!IFNDEF USE_TCLSH_IN_PATH +USE_TCLSH_IN_PATH = 0 +!ENDIF # Set this non-0 to use zlib, possibly compiling it from source code. # @@ -175,6 +186,14 @@ 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). @@ -190,8 +209,6 @@ FOR_WIN10 = 0 !ENDIF # <<mark>> -# ^^^^^^^^-- 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. # @@ -248,8 +265,6 @@ DEBUG = 0 !ENDIF # <<mark>> -# ^^^^^^^^-- 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. @@ -345,8 +360,6 @@ SQLITE3EXEPDB = /pdb:sqlite3sh.pdb !ENDIF # <<mark>> -# ^^^^^^^^-- 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. @@ -628,24 +641,16 @@ 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 - # <<mark>> -# ^^^^^^^^-- 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 # <</mark>> - !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 - # <<mark>> -# ^^^^^^^^-- 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 # <</mark>> - !ELSE CORE_CCONV_OPTS = SHELL_CCONV_OPTS = @@ -772,6 +777,18 @@ 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 @@ -784,7 +801,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 $(USE_CRT_DLL)!=0 +!IF $(FOR_WINRT)!=0 || $(USE_CRT_DLL)!=0 !IF $(DEBUG)>1 TCC = $(TCC) -MDd BCC = $(BCC) -MDd @@ -807,8 +824,6 @@ ZLIBCFLAGS = -nologo -MT -W3 -O2 -Oy- -Zi !ENDIF # <<mark>> -# ^^^^^^^^-- 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. @@ -859,6 +874,16 @@ MKSQLITE3H_ARGS = !ENDIF # <</mark>> +# 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 @@ -924,8 +949,6 @@ TCC = $(TCC) /fsanitize=address !ENDIF # <<mark>> -# ^^^^^^^^-- 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 @@ -1165,8 +1188,6 @@ BCC = $(BCC) -Zi !ENDIF # <<mark>> -# ^^^^^^^^-- 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 @@ -1219,6 +1240,56 @@ 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. # @@ -1242,14 +1313,12 @@ LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib # If either debugging or symbols are enabled, enable PDBs. # !IF $(DEBUG)>1 || $(SYMBOLS)!=0 -LDFLAGS = /NODEFAULTLIB:msvcrt /DEBUG $(LDOPTS) +LDFLAGS = /DEBUG $(LDOPTS) !ELSE -LDFLAGS = /NODEFAULTLIB:msvcrt $(LDOPTS) +LDFLAGS = $(LDOPTS) !ENDIF # <<mark>> -# ^^^^^^^^-- content between mark and /mark omitted from autoconf/Makefile.msc -# # Start with the Tcl related linker options. # !IF $(NO_TCL)==0 @@ -1276,8 +1345,6 @@ LTLIBS = $(LTLIBS) $(LIBICU) ############################################################################### # <<mark>> -# ^^^^^^^^-- 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 \ @@ -1396,7 +1463,7 @@ SRC01 = \ $(TOP)\src\status.c \ $(TOP)\src\table.c \ $(TOP)\src\threads.c \ - tclsqlite-ex.c \ + $(TOP)\src\tclsqlite.c \ $(TOP)\src\tokenize.c \ $(TOP)\src\treeview.c \ $(TOP)\src\trigger.c \ @@ -1707,7 +1774,6 @@ 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 @@ -1866,8 +1932,8 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT /link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) # <<mark>> -sqldiff.exe: $(TOP)\tool\sqldiff.c $(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) +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) dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) @@ -1957,18 +2023,8 @@ 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 $@ # <</mark>> -tclsqlite-ex.c: - # Rule to build the amalgamation # sqlite3.lo: $(SQLITE3C) @@ -2268,11 +2324,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: 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.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-shell.lo: tclsqlite-ex.c $(HDR) $(SQLITE_TCL_DEP) - $(LTCOMPILE) $(NO_WARN) -DTCLSH -DBUILD_sqlite -I$(TCLINCDIR) -c tclsqlite-ex.c +tclsqlite-shell.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP) + $(LTCOMPILE) $(NO_WARN) -DTCLSH -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c tclsqlite3.exe: tclsqlite-shell.lo $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) $(LTLINK) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) /OUT:$@ tclsqlite-shell.lo $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS) @@ -2325,8 +2381,6 @@ 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 \ @@ -2490,9 +2544,9 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_STRICT_SUBTYPE=1 TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2) TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C) !IF $(USE_AMALGAMATION)==0 -TESTFIXTURE_SRC = $(TESTSRC) tclsqlite-ex.c $(TESTFIXTURE_SRC0) +TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC0) !ELSE -TESTFIXTURE_SRC = $(TESTSRC) tclsqlite-ex.c $(TESTFIXTURE_SRC1) +TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC1) !ENDIF !IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 @@ -2601,22 +2655,6 @@ 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. # @@ -2640,17 +2678,17 @@ smoketest: $(TESTPROGS) @set PATH=$(LIBTCLPATH);$(PATH) .\testfixture.exe $(TOP)\test\main.test $(TESTOPTS) -shelltest: - $(TCLSH_CMD) $(TOP)\test\testrunner.tcl release shell +shelltest: $(TESTPROGS) + .\testfixture.exe $(TOP)\test\permutations.test shell -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) +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) $(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 tclsqlite-ex.c $(TOP)\tool\sqltclsh.tcl $(TOP)\ext\misc\appendvfs.c $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in +sqltclsh.c: sqlite3.c $(TOP)\src\tclsqlite.c $(TOP)\tool\sqltclsh.tcl $(TOP)\ext\misc\appendvfs.c $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in $(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in >sqltclsh.c sqltclsh.exe: sqltclsh.c $(SHELL_CORE_DEP) $(LIBRESOBJS) @@ -2660,6 +2698,23 @@ 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) @@ -2692,9 +2747,6 @@ 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) @@ -2886,7 +2938,6 @@ 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 7ec5042e5..d44bf5cb7 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ <h1 align="center">SQLite Source Repository</h1> This repository contains the complete source code for the -[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 +[SQLite database engine](https://sqlite.org/), including +many tests. Additional tests and most documentation are managed separately. See the [on-line documentation](https://sqlite.org/) for more information @@ -100,16 +99,16 @@ script found at the root of the source tree. Then run "make". For example: - apt install gcc make tcl-dev ;# Install the necessary build tools + apt install gcc make tcl-dev ;# Make sure you have all the necessary build tools tar xzf sqlite.tar.gz ;# Unpack the source tree into "sqlite" - mkdir bld ;# Build happens in a sibling directory + mkdir bld ;# Build will occur in a sibling directory cd bld ;# Change to the build directory ../sqlite/configure ;# Run the configure script - 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 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 devtest ;# Run development tests make releasetest ;# Run full release tests make sqlite3_analyzer ;# Builds the "sqlite3_analyzer" tool @@ -117,15 +116,14 @@ For example: See the makefile for additional targets. For debugging builds, the core developers typically run "configure" with options like this: - ../sqlite/configure --all --debug CFLAGS='-O0 -g' + ../sqlite/configure --enable-all --enable-debug CFLAGS='-O0 -g' For release builds, the core developers usually do: - ../sqlite/configure --all + ../sqlite/configure --enable-all -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 +Almost all 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" @@ -135,20 +133,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 --all + ./configure --enable-all make OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3 -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. +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. ## 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, -though 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. +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. @@ -158,25 +156,24 @@ TCL library, using a command like this: set TCLDIR=c:\Tcl -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 +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 There are many other makefile targets. See comments in Makefile.msc for details. @@ -184,7 +181,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: - make OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3.exe + nmake /f Makefile.msc OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3.exe ## Source Tree Map @@ -197,7 +194,8 @@ 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`" to build that + a command like "`make testfixture`" (unix) or + "`nmake /f Makefile.msc testfixture.exe`" (windows) 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. @@ -379,11 +377,10 @@ 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**, **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/). + * **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/). There are many other source files. Each has a succinct header comment that describes its purpose and role within the larger system. @@ -409,6 +406,10 @@ 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 7ac0b0a68..f17126c72 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.52.0 +3.51.3 diff --git a/autoconf/Makefile.in b/autoconf/Makefile.in index 8e6b358be..c938ffe1b 100644 --- a/autoconf/Makefile.in +++ b/autoconf/Makefile.in @@ -117,6 +117,19 @@ 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 @@ -282,7 +295,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 make.bat \ + Makefile.in Makefile.msc Makefile.fallback \ sqlite3.rc sqlite3rc.h Replace.cs \ sqlite3.pc.in sqlite3.1 diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 34e41d8aa..365e1f0fb 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -101,6 +101,13 @@ 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. # @@ -149,6 +156,14 @@ 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). @@ -548,14 +563,10 @@ 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 = @@ -656,6 +667,18 @@ 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 @@ -668,7 +691,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 $(USE_CRT_DLL)!=0 +!IF $(FOR_WINRT)!=0 || $(USE_CRT_DLL)!=0 !IF $(DEBUG)>1 TCC = $(TCC) -MDd BCC = $(BCC) -MDd @@ -691,6 +714,16 @@ 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 @@ -880,6 +913,56 @@ 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. # @@ -903,9 +986,9 @@ LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib # If either debugging or symbols are enabled, enable PDBs. # !IF $(DEBUG)>1 || $(SYMBOLS)!=0 -LDFLAGS = /NODEFAULTLIB:msvcrt /DEBUG $(LDOPTS) +LDFLAGS = /DEBUG $(LDOPTS) !ELSE -LDFLAGS = /NODEFAULTLIB:msvcrt $(LDOPTS) +LDFLAGS = $(LDOPTS) !ENDIF @@ -941,7 +1024,6 @@ 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 @@ -990,8 +1072,6 @@ $(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 2560bfab9..c8da5c643 100644 --- a/autosetup/README.md +++ b/autosetup/README.md @@ -375,11 +375,6 @@ 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 @@ -393,11 +388,6 @@ 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). - - <a name="branch-customization"></a> Branch-specific Customization ======================================================================== diff --git a/autosetup/jimsh0.c b/autosetup/jimsh0.c index 0f0a89088..1a6453d0c 100644 --- a/autosetup/jimsh0.c +++ b/autosetup/jimsh0.c @@ -7409,13 +7409,11 @@ void *JimDefaultAllocator(void *ptr, size_t size) free(ptr); return NULL; } + else if (ptr) { + return realloc(ptr, size); + } else { - void *p = realloc(ptr, size); - if( p==0 ){ - fprintf(stderr,"Out of memory\n"); - exit(1); - } - return p; + return malloc(size); } } diff --git a/autosetup/proj.tcl b/autosetup/proj.tcl index caa679ad6..86f4df44e 100644 --- a/autosetup/proj.tcl +++ b/autosetup/proj.tcl @@ -1842,7 +1842,7 @@ proc proj-setup-autoreconfig {defName} { } # -# @prop-define-append defineName args... +# @prop-append-to 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-define-append +# See: proj-append-to # proc proj-define-amend {args} { set defName "" diff --git a/autosetup/sqlite-config.tcl b/autosetup/sqlite-config.tcl index fe1b355ab..7c798b31a 100644 --- a/autosetup/sqlite-config.tcl +++ b/autosetup/sqlite-config.tcl @@ -557,25 +557,7 @@ proc proc-debug {msg} { } define OPT_FEATURE_FLAGS {} ; # -DSQLITE_OMIT/ENABLE flags. -# -# 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 -} +define OPT_SHELL {} ; # Feature-related CFLAGS for the sqlite3 CLI app ######################################################################## # 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 @@ -679,7 +661,6 @@ 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 "" @@ -806,8 +787,7 @@ proc sqlite-handle-common-feature-flags {} { sqlite-add-feature-flag -DSQLITE_ENABLE_MEMSYS3 } } - bytecode-vtab -DSQLITE_ENABLE_BYTECODE_VTAB {} - scanstatus {-DSQLITE_ENABLE_STMT_SCANSTATUS -DSQLITE_ENABLE_BYTECODE_VTAB} {} + scanstatus -DSQLITE_ENABLE_STMT_SCANSTATUS {} 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 659bdb038..ce76b97ba 100644 --- a/doc/compile-for-unix.md +++ b/doc/compile-for-unix.md @@ -22,10 +22,9 @@ guidance on building for Windows. or <https://sqlite.org/tmp/tcl9.0.0.tar.gz>. <li>Untar the source archive. CD into the "unix/" subfolder of the source tree. - <li>Run: &ensp; `mkdir $HOME/local` - <li>Run: &ensp; `./configure --prefix=$HOME/local`<br> - SQLite deliverable builds add: &emsp; `--static CFLAGS=-Os` - <li>Run: &ensp; `make install` + <li>Run: `mkdir $HOME/local` + <li>Run: `./configure --prefix=$HOME/local` + <li>Run: `make install` </ol> <p> As of 2024-10-25, TCL is not longer required for many @@ -42,12 +41,6 @@ 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 - &ensp; `--with-linenoise=$HOME/linenoise` &ensp; 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: <ul> diff --git a/doc/compile-for-windows.md b/doc/compile-for-windows.md index 30536d5fd..0e59c83fe 100644 --- a/doc/compile-for-windows.md +++ b/doc/compile-for-windows.md @@ -180,39 +180,3 @@ 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: <blockquote><pre>dumpbin /dependents sqlite3_analyzer.exe</pre></blockquote> - -## 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: - <blockquote> - set USE_ZLIB=1<br> - set BUILD_ZLIB=0<br> - set ZLIBDIR=c:\\zlib-64 - </blockquote> - - 3. Because the settings in step 2 specify "BUILD_ZLIB=0", you will need - to build the library at least once. I recommand: - <blockquote> - make clean sqlite3.exe BUILD_ZLIB=1 - </blockquote> - - 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 a994b396b..965f305c0 100644 --- a/doc/lemon.html +++ b/doc/lemon.html @@ -696,7 +696,6 @@ <h3>4.4 Special Directives</h3> <li><tt><a href='#pright'>%right</a></tt> <li><tt><a href='#reallc'>%realloc</a></tt> <li><tt><a href='#stack_overflow'>%stack_overflow</a></tt> -<li><tt><a href='#reallc'>%stack_size_limit</a></tt> <li><tt><a href='#stack_size'>%stack_size</a></tt> <li><tt><a href='#start_symbol'>%start_symbol</a></tt> <li><tt><a href='#syntax_error'>%syntax_error</a></tt> @@ -1204,33 +1203,20 @@ <h4>4.4.25 The <tt>%wildcard</tt> directive</h4> The wildcard token is only matched if there are no alternatives.</p> <a id='reallc'></a> -<h4>4.4.26 The <tt>%realloc</tt>, <tt>%free</tt>, and -<tt>%stack_size_limit</tt> directives</h4> +<h4>4.4.26 The <tt>%realloc</tt> and <tt>%free</tt> directives</h4> <p>The <tt>%realloc</tt> and <tt>%free</tt> directives defines function -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. - -<p>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 +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. + +<p>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 is specified by either <tt>%stack_size</tt> or the -DYYSTACKDEPTH compile-time flag. -<p>The <tt>%stack_size_limit</tt> 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. - <a id='errors'></a> <h2>5.0 Error Processing</h2> diff --git a/doc/testrunner.md b/doc/testrunner.md index 90ef4b71f..d1696e9d1 100644 --- a/doc/testrunner.md +++ b/doc/testrunner.md @@ -4,12 +4,6 @@ <ul type=none> <li> 1. <a href=#overview>Overview</a> -<ul type=none> - <li> 1.1. <a href=#runtr>Running testrunner.tcl</a> - <li> 1.2. <a href=#runviamake>Run using "make"</a> - <li> 1.3. <a href=#outputs>Outputs from testrunner.tcl</a> - <li> 1.4. <a href=#help>Built-in help</a> -</ul> <li> 2. <a href=#binary_tests>Binary Tests</a> <ul type=none> <li> 2.1. <a href=#organization_tests>Organization of Tcl Tests</a> @@ -32,40 +26,17 @@ The testrunner.tcl program is a Tcl script used to run multiple SQLite tests in parallel, thus reducing testing time on multi-core machines. -The testrunner.tcl supports running tests that based on `testfixture`, -`sqlite3`, and `fuzzcheck`. - -<a name="runtr"></a> -## 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: -"<tt>test/testrunner.tcl</tt>". On Windows, you have to specify the -`tclsh` interpreter command first, like this: -"<tt>tclsh&nbsp;test/testrunner.tcl</tt>". - -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 "<tt>test/testrunner.tcl</tt>". If you -are in a different directory, then make appropriate adjustments to -the path. On Windows, add the "<tt>tclsh</tt>" interpreter command -up front. +It supports the following types of tests: -<a name="runviamake"></a> -## 1.2 Run using make + * Tcl test scripts. -The standard Makefiles for SQLite include targets that invoke -testrunner.tcl. So the following commands also run testrunner.tcl: + * Fuzzcheck tests, including using an external fuzzcheck database. - * `make devtest` - * `make releasetest` - * `make sdevtest` - * `make testrunner` - -<a name="outputs"></a> -## 1.3 Outputs from testrunner.tcl + * Tests run with `make` commands. Examples: + - `make devtest` + - `make releasetest` + - `make sdevtest` + - `make testrunner` The testrunner.tcl program stores output of all tests and builds run in log file **testrunner.log**, created in the current working directory. @@ -83,19 +54,17 @@ A useful query might be: ``` You can get a summary of errors in a prior run by invoking commands like -those shown below. Note that the testrunner.tcl script can be run directly -on unix systems (including Macs) but you will need to add <tt>tclsh</tt> -to the front on Windows. +these: ``` - test/testrunner.tcl errors - test/testrunner.tcl errors -v + tclsh $(TESTDIR)/testrunner.tcl errors + tclsh $(TESTDIR)/testrunner.tcl errors -v ``` Running the command: ``` - test/testrunner.tcl status + tclsh $(TESTDIR)/testrunner.tcl status ``` in the directory containing the testrunner.db database runs various queries @@ -104,43 +73,32 @@ A good way to keep and eye on test progress is to run either of the two following commands: ``` - watch test/testrunner.tcl status - test/testrunner.tcl status -d 2 + watch tclsh $(TESTDIR)/testrunner.tcl status + tclsh $(TESTDIR)/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` and `sqlite3` binaries that -are in the directory from which testrunner.tcl is run. -(see "Binary Tests" below). Sometimes it builds testfixture and +Sometimes testrunner.tcl uses the `testfixture` binary that it is run with +to run tests (see "Binary Tests" below). Sometimes it builds testfixture and other binaries in specific configurations to test (see "Source Tests"). -<a name=help></a> -## 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 - - <a name=binary_tests></a> # 2. Binary Tests The commands described in this section all run various combinations of the Tcl -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. +test scripts using the `testfixture` binary used to run the testrunner.tcl +script (i.e. they do not invoke the compiler to build new binaries, or the +`make` command to run tests that are not Tcl scripts). The procedure to run +these tests is therefore: + + 1. Build the "testfixture" (or "testfixture.exe" for windows) binary using + whatever method seems convenient. + + 2. Test the binary built in step 1 by running testrunner.tcl with it, + perhaps with various options. The following sub-sections describe the various options that can be passed to testrunner.tcl to test binary testfixture builds. @@ -182,22 +140,22 @@ are defined in file *testrunner_data.tcl*. To run the "veryquick" test set, use either of the following: ``` - test/testrunner.tcl - test/testrunner.tcl veryquick + ./testfixture $TESTDIR/testrunner.tcl + ./testfixture $TESTDIR/testrunner.tcl veryquick ``` To run the "full" test suite: ``` - test/testrunner.tcl full + ./testfixture $TESTDIR/testrunner.tcl full ``` To run the subset of the "full" test suite for which the test file name matches a specified pattern (e.g. all tests that start with "fts5"), either of: ``` - test/testrunner.tcl fts5% - test/testrunner.tcl 'fts5*' + ./testfixture $TESTDIR/testrunner.tcl fts5% + ./testfixture $TESTDIR/testrunner.tcl 'fts5*' ``` Strictly speaking, for a test to be run the pattern must match the script @@ -209,7 +167,7 @@ characters specified as part of the pattern are transformed to "\*". To run "all" tests (full + permutations): ``` - test/testrunner.tcl all + ./testfixture $TESTDIR/testrunner.tcl all ``` <a name=binary_test_failures></a> @@ -228,15 +186,10 @@ If there is no permutation, the individual test script may be run with: Or, if the failure occured as part of a permutation: ``` - test/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT + ./testfixture $TESTDIR/testrunner.tcl $PERMUTATION $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 -``` +TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT? <a name=source_code_tests></a> # 3. Source Code Tests @@ -251,9 +204,11 @@ 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 do not require that -the testfixture and/or sqlite3 binaries be built ahead of time. Those -binaries will be constructed automatically. +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. <a name=commands_to_run_tests></a> ## 3.1. Commands to Run SQLite Tests @@ -263,7 +218,7 @@ the `make fuzztest` target once for each of two --enable-all builds - one with debugging enabled and one without: ``` - test/testrunner.tcl mdevtest + tclsh $TESTDIR/testrunner.tcl mdevtest ``` In other words, it is equivalent to running: @@ -272,13 +227,13 @@ In other words, it is equivalent to running: $TOP/configure --enable-all --enable-debug make fuzztest make testfixture - $TOP/test/testrunner.tcl veryquick + ./testfixture $TOP/test/testrunner.tcl veryquick # Then, after removing files created by the tests above: $TOP/configure --enable-all OPTS="-O0" make fuzztest make testfixture - $TOP/test/testrunner.tcl veryquick + ./testfixture $TOP/test/testrunner.tcl veryquick ``` The **sdevtest** command is identical to the mdevtest command, except that the @@ -286,7 +241,7 @@ second of the two builds is a sanitizer build. Specifically, this means that OPTS="-fsanitize=address,undefined" is specified instead of OPTS="-O0": ``` - test/testrunner.tcl sdevtest + tclsh $TESTDIR/testrunner.tcl sdevtest ``` The **release** command runs lots of tests under lots of builds. It runs @@ -295,7 +250,7 @@ on Linux, Windows or OSX. Refer to *testrunner\_data.tcl* for the details of the specific tests run. ``` - test/testrunner.tcl release + tclsh $TESTDIR/testrunner.tcl release ``` As with <a href=#source code tests>source code tests</a>, one or more patterns @@ -303,7 +258,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. ``` - test/testrunner.tcl release rtree% + tclsh $TESTDIR/testrunner.tcl release rtree% ``` <a name=zipvfs_tests></a> @@ -313,14 +268,14 @@ testrunner.tcl can build a zipvfs-enabled testfixture and use it to run tests from the Zipvfs project with the following command: ``` - test/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS + tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS ``` This can be combined with any of "mdevtest", "sdevtest" or "release" to test both SQLite and Zipvfs with a single command: ``` - test/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest + tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest ``` <a name=source_code_test_failures></a> @@ -340,10 +295,10 @@ a dos \*.bat file on windows. For example: ``` # Create a script that recreates build configuration "Device-One" on # Linux or OSX: - test/testrunner.tcl script Device-One > make.sh + tclsh $TESTDIR/testrunner.tcl script Device-One > make.sh # Create a script that recreates build configuration "Have-Not" on Windows: - test/testrunner.tcl script Have-Not > make.bat + tclsh $TESTDIR/testrunner.tcl script Have-Not > make.bat ``` The generated bash or \*.bat file script accepts a single argument - a makefile @@ -366,7 +321,7 @@ Thus, for example, to run a full releasetest including an external dbsqlfuzz database, run a command like one of these: ``` - test/testrunner.tcl releasetest --fuzzdb ../fuzz/20250415.db + tclsh test/testrunner.tcl releasetest --fuzzdb ../fuzz/20250415.db FUZZDB=../fuzz/20250415.db make releasetest nmake /f Makefile.msc FUZZDB=../fuzz/20250415.db releasetest ``` @@ -376,7 +331,7 @@ databases. So if you want to run *only* tests involving the external database, you can use a command something like this: ``` - test/testrunner.tcl releasetest 20250415 --fuzzdb ../fuzz/20250415.db + tclsh test/testrunner.tcl releasetest 20250415 --fuzzdb ../fuzz/20250415.db ``` <a name=testrunner_options></a> @@ -390,7 +345,7 @@ required by a test, not to run any actual tests. For example: ``` # Build binaries required by release test. - test/testrunner.tcl --buildonly release" + tclsh $TESTDIR/testrunner.tcl --buildonly release" ``` The **--dryrun** option prevents testrunner.tcl from building any binaries @@ -399,7 +354,7 @@ would normally execute into the testrunner.log file. Example: ``` # Log the shell commmands that make up the mdevtest test. - test/testrunner.tcl --dryrun mdevtest" + tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest" ``` The **--explain** option is similar to --dryrun in that it prevents @@ -409,7 +364,7 @@ summary of all the builds and tests that would have been run. ``` # Show what builds and tests would have been run - test/testrunner.tcl --explain mdevtest + tclsh $TESTDIR/testrunner.tcl --explain mdevtest ``` The **--status** option uses VT100 escape sequences to display the test @@ -425,7 +380,7 @@ When running either binary or source code tests, testrunner.tcl reports the number of jobs it intends to use to stdout. e.g. ``` - $ test/testrunner.tcl + $ ./testfixture $TESTDIR/testrunner.tcl splitting work across 16 jobs ... more output ... ``` @@ -435,7 +390,7 @@ of real cores on the machine. This can be overridden using the "--jobs" (or -j) switch: ``` - $ test/testrunner.tcl --jobs 8 + $ ./testfixture $TESTDIR/testrunner.tcl --jobs 8 splitting work across 8 jobs ... more output ... ``` @@ -445,5 +400,5 @@ running by exucuting the following command from the directory containing the testrunner.log and testrunner.db files: ``` - $ test/testrunner.tcl njob $NEW_NUMBER_OF_JOBS + $ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS ``` diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test index aaea03711..0c3b512af 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 -noinit test.db ".expert" {$sql;}]] + squish [string trim [exec $::CLI test.db ".expert" {$sql;}]] }] uplevel [list do_test $tn $tst $res] } diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 368e9b189..f178abafe 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -1816,7 +1816,9 @@ static int fts3CursorSeekStmt(Fts3Cursor *pCsr){ zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist); if( !zSql ) return SQLITE_NOMEM; p->bLock++; - rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt); + rc = sqlite3_prepare_v3( + p->db, zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0 + ); p->bLock--; sqlite3_free(zSql); } @@ -3391,7 +3393,9 @@ static int fts3FilterMethod( } if( zSql ){ p->bLock++; - rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt); + rc = sqlite3_prepare_v3( + p->db,zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0 + ); p->bLock--; sqlite3_free(zSql); }else{ @@ -4014,7 +4018,6 @@ 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 556635def..e98b90a75 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -601,15 +601,6 @@ 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 1b8bca70f..19dff31f0 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 { - sqlite3_int64 nData; + int nData; char *aData; - sqlite3_int64 nSpace; + int nSpace; sqlite3_int64 iLastDocid; sqlite3_int64 iLastCol; sqlite3_int64 iLastPos; @@ -273,24 +273,6 @@ 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, @@ -416,12 +398,12 @@ static int fts3SqlStmt( pStmt = p->aStmt[eStmt]; if( !pStmt ){ - int bAllowVtab = 0; + int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB; 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 ){ - bAllowVtab = 1; + f &= ~SQLITE_PREPARE_NO_VTAB; zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist); }else{ zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName); @@ -429,7 +411,7 @@ static int fts3SqlStmt( if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3Fts3PrepareStmt(p, zSql, 1, bAllowVtab, &pStmt); + rc = sqlite3_prepare_v3(p->db, zSql, -1, f, &pStmt, NULL); sqlite3_free(zSql); assert( rc==SQLITE_OK || pStmt==0 ); p->aStmt[eStmt] = pStmt; @@ -778,9 +760,7 @@ static int fts3PendingTermsAddOne( pList = (PendingList *)fts3HashFind(pHash, zToken, nToken); if( pList ){ - assert( (i64)pList->nData+(i64)nToken+(i64)sizeof(Fts3HashElem) - <= (i64)p->nPendingData ); - p->nPendingData -= (int)(pList->nData + nToken + sizeof(Fts3HashElem)); + p->nPendingData -= (pList->nData + nToken + sizeof(Fts3HashElem)); } if( fts3PendingListAppend(&pList, p->iPrevDocid, iCol, iPos, &rc) ){ if( pList==fts3HashInsert(pHash, zToken, nToken, pList) ){ @@ -793,9 +773,7 @@ static int fts3PendingTermsAddOne( } } if( rc==SQLITE_OK ){ - assert( (i64)p->nPendingData + pList->nData + nToken - + sizeof(Fts3HashElem) <= 0x3fffffff ); - p->nPendingData += (int)(pList->nData + nToken + sizeof(Fts3HashElem)); + p->nPendingData += (pList->nData + nToken + sizeof(Fts3HashElem)); } return rc; } @@ -3596,7 +3574,7 @@ static int fts3DoRebuild(Fts3Table *p){ if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); } @@ -5349,7 +5327,7 @@ int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){ if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); } @@ -5479,7 +5457,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=", 11) ){ + }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){ 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 ee43ca6cc..95b33ea31 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_malloc64(nPhrase); + aSeen = sqlite3_malloc(nPhrase); if( aSeen==0 ){ rc = SQLITE_NOMEM; } diff --git a/ext/fts5/fts5_buffer.c b/ext/fts5/fts5_buffer.c index d799e34cb..afcd83b6b 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_malloc64((i64)nIn+1); + zRet = (char*)sqlite3_malloc(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 cea14b500..eea82b046 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_malloc64(sizeof(Fts5Config)); + *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config)); if( pRet==0 ) return SQLITE_NOMEM; memset(pRet, 0, sizeof(Fts5Config)); pRet->pGlobal = pGlobal; @@ -1123,3 +1123,5 @@ 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 8ecaca34f..352df81f4 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_malloc64(sizeof(Fts5Expr)); + *ppNew = pNew = sqlite3_malloc(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_realloc64( + Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc( p1->apExprPhrase, nPhrase * sizeof(Fts5ExprPhrase*) ); if( ap==0 ){ diff --git a/ext/fts5/fts5_hash.c b/ext/fts5/fts5_hash.c index ba4a030b7..a33dec9a9 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_malloc64(sizeof(Fts5Hash)); + *ppNew = pNew = (Fts5Hash*)sqlite3_malloc(sizeof(Fts5Hash)); if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 164d61388..b10df893f 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 ){ - i64 nNew = pIter->nRowidOffset + 8; + int nNew = pIter->nRowidOffset + 8; int *aNew = (int*)sqlite3_realloc64(pIter->aRowidOffset,nNew*sizeof(int)); if( aNew==0 ){ p->rc = SQLITE_NOMEM; @@ -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. */ - u64 nPrefix = 0; - u64 nSuffix = 0; - u64 nPrefix2 = 0; - u64 nSuffix2 = 0; + int nPrefix = 0; + int nSuffix = 0; + int nPrefix2 = 0; + int nSuffix2 = 0; iDelKeyOff = iNextOff; - iNextOff += fts5GetVarint(&aPg[iNextOff], &nPrefix2); - iNextOff += fts5GetVarint(&aPg[iNextOff], &nSuffix2); + iNextOff += fts5GetVarint32(&aPg[iNextOff], nPrefix2); + iNextOff += fts5GetVarint32(&aPg[iNextOff], nSuffix2); if( iKey!=1 ){ - iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nPrefix); + iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nPrefix); } - iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nSuffix); + iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nSuffix); nPrefix = MIN(nPrefix, nPrefix2); nSuffix = (nPrefix2 + nSuffix2) - nPrefix; - if( (iKeyOff+nSuffix)>(u64)iPgIdx || (iNextOff+nSuffix2)>(u64)iPgIdx ){ + if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){ FTS5_CORRUPT_IDX(p); }else{ if( iKey!=1 ){ iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix); } iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix); - if( nPrefix2>(u64)pSeg->term.n ){ + if( nPrefix2>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; - i64 iTermOff = 0; + int iTermOff = 0; while( 1 ){ u32 iVal = 0; @@ -5465,15 +5465,12 @@ static void fts5DoSecureDelete( } nTermIdx = iTermIdx; - 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); - } + 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); @@ -5496,9 +5493,7 @@ static void fts5DoSecureDelete( int iPrevKeyOut = 0; int iKeyIn = 0; - if( nMove>0 ){ - memmove(&aPg[iOff], &aPg[iNextOff], nMove); - } + memmove(&aPg[iOff], &aPg[iNextOff], nMove); iPgIdx -= nShift; nPg = iPgIdx; fts5PutU16(&aPg[2], iPgIdx); @@ -6418,16 +6413,16 @@ struct Fts5TokenDataMap { ** aMap[] variables. */ struct Fts5TokenDataIter { - i64 nMapAlloc; /* Allocated size of aMap[] in entries */ - i64 nMap; /* Number of valid entries in aMap[] */ + int nMapAlloc; /* Allocated size of aMap[] in entries */ + int 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. */ - i64 nIter; - i64 nIterAlloc; + int nIter; + int nIterAlloc; Fts5PoslistReader *aPoslistReader; int *aPoslistToIter; Fts5Iter *apIter[FLEXARRAY]; @@ -6483,11 +6478,11 @@ static void fts5TokendataIterAppendMap( ){ if( p->rc==SQLITE_OK ){ if( pT->nMap==pT->nMapAlloc ){ - i64 nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; - i64 nAlloc = nNew * sizeof(Fts5TokenDataMap); + int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; + int nAlloc = nNew * sizeof(Fts5TokenDataMap); Fts5TokenDataMap *aNew; - aNew = (Fts5TokenDataMap*)sqlite3_realloc64(pT->aMap, nAlloc); + aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nAlloc); if( aNew==0 ){ p->rc = SQLITE_NOMEM; return; @@ -6513,7 +6508,7 @@ static void fts5TokendataIterAppendMap( */ static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){ Fts5TokenDataMap *aTmp = 0; - i64 nByte = pT->nMap * sizeof(Fts5TokenDataMap); + int nByte = pT->nMap * sizeof(Fts5TokenDataMap); aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte); if( aTmp ){ @@ -7047,10 +7042,9 @@ static Fts5TokenDataIter *fts5AppendTokendataIter( if( p->rc==SQLITE_OK ){ if( pIn==0 || pIn->nIter==pIn->nIterAlloc ){ - i64 nAlloc = pIn ? pIn->nIterAlloc*2 : 16; - i64 nByte = SZ_FTS5TOKENDATAITER(nAlloc+1); - Fts5TokenDataIter *pNew; - pNew = (Fts5TokenDataIter*)sqlite3_realloc64(pIn, nByte); + int nAlloc = pIn ? pIn->nIterAlloc*2 : 16; + int nByte = SZ_FTS5TOKENDATAITER(nAlloc+1); + Fts5TokenDataIter *pNew = (Fts5TokenDataIter*)sqlite3_realloc(pIn, nByte); if( pNew==0 ){ p->rc = SQLITE_NOMEM; @@ -7147,8 +7141,8 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){ /* Ensure the token-mapping is large enough */ if( eDetail==FTS5_DETAIL_FULL && pT->nMapAlloc<(pT->nMap + nByte) ){ - i64 nNew = (pT->nMapAlloc + nByte) * 2; - Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc64( + int nNew = (pT->nMapAlloc + nByte) * 2; + Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc( pT->aMap, nNew*sizeof(Fts5TokenDataMap) ); if( aNew==0 ){ diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index cf033ab5d..f45b9ef90 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_malloc64((i64)pInfo->nConstraint * 8 + 1); + idxStr = (char*)sqlite3_malloc(pInfo->nConstraint * 8 + 1); if( idxStr==0 ) return SQLITE_NOMEM; pInfo->idxStr = idxStr; pInfo->needToFreeIdxStr = 1; @@ -2081,7 +2081,6 @@ static int fts5UpdateMethod( } update_out: - sqlite3Fts5IndexCloseReader(pTab->p.pIndex); pTab->p.pConfig->pzErrmsg = 0; return rc; } @@ -3763,7 +3762,7 @@ static int fts5Init(sqlite3 *db){ int rc; Fts5Global *pGlobal = 0; - pGlobal = (Fts5Global*)sqlite3_malloc64(sizeof(Fts5Global)); + pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global)); if( pGlobal==0 ){ rc = SQLITE_NOMEM; }else{ diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c index f5d8705ff..25cd5c063 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_malloc64(sizeof(F5tAuxData)); + F5tAuxData *pData = (F5tAuxData*)sqlite3_malloc(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_malloc64(nText); + pCopy = sqlite3_malloc(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_malloc64(sizeof(OriginTextTokenizer)); + pTok = (OriginTextTokenizer*)sqlite3_malloc(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_malloc64(nReq*2); + p->aBuf = sqlite3_malloc(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 c77c49de7..994d304dc 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_malloc64(sizeof(Fts5tokTable)); + pTab = (Fts5tokTable*)sqlite3_malloc(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_malloc64(sizeof(Fts5tokCursor)); + pCsr = (Fts5tokCursor *)sqlite3_malloc(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_malloc64((sqlite3_int64)nToken+1); + pRow->zToken = sqlite3_malloc(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]); - sqlite3_int64 nByte = sqlite3_value_bytes(apVal[0]); - pCsr->zInput = sqlite3_malloc64(nByte+1); + int nByte = sqlite3_value_bytes(apVal[0]); + pCsr->zInput = sqlite3_malloc(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 990810239..b8a113646 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_malloc64(sizeof(AsciiTokenizer)); + p = sqlite3_malloc(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_malloc64(sizeof(Unicode61Tokenizer)); + p = (Unicode61Tokenizer*)sqlite3_malloc(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_malloc64(sizeof(PorterTokenizer)); + pRet = (PorterTokenizer*)sqlite3_malloc(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_malloc64(sizeof(*pNew)); + pNew = (TrigramTokenizer*)sqlite3_malloc(sizeof(*pNew)); if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ diff --git a/ext/fts5/fts5_vocab.c b/ext/fts5/fts5_vocab.c index 295ace6ba..3a6a968f7 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_malloc64((i64)pCsr->nLeTerm+1); + pCsr->zLeTerm = sqlite3_malloc(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 index 6cf06f836..102a4135b 100644 --- a/ext/fts5/test/fts5corrupt9.test +++ b/ext/fts5/test/fts5corrupt9.test @@ -54,75 +54,6 @@ 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 9b2720faf..4bf120c44 100644 --- a/ext/fts5/test/fts5integrity.test +++ b/ext/fts5/test/fts5integrity.test @@ -379,6 +379,9 @@ 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 87b232b05..67ef5f7e9 100644 --- a/ext/fts5/test/fts5interrupt.test +++ b/ext/fts5/test/fts5interrupt.test @@ -64,21 +64,4 @@ 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 e3fef7763..5f645fae6 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( z[iRet] ){ + while( 1 ){ if( z[iRet]==c ){ iRet++; if( z[iRet]!=c ) break; diff --git a/ext/jni/README.md b/ext/jni/README.md index 0bdbde91e..5ad79fce9 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -13,14 +13,11 @@ Technical support is available in the forum: <https://sqlite.org/forum> -> **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. +> **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. Project goals/requirements: @@ -165,13 +162,11 @@ 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, 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. +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`. Client-defined callbacks _must never throw exceptions_ unless _very explicitly documented_ as being throw-safe. Exceptions are generally @@ -199,8 +194,7 @@ 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" for -usability's sake... +demonstration of where such changes are "really necessary"... ### Custom Collations @@ -292,9 +286,12 @@ binding. The Java API has only one core function-registration function: ```java int sqlite3_create_function(sqlite3 db, String funcName, int nArgs, - int flags, SQLFunction func); + int encoding, 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: @@ -316,4 +313,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 describes. +provide the same semantics as the C API documentation suggests. diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 8cdba9bcf..f130eff04 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -133,9 +133,11 @@ ** Which sqlite3.c we're using needs to be configurable to enable ** building against a custom copy, e.g. the SEE variant. We have to ** include sqlite3.c, as opposed to sqlite3.h, in order to get access -** to some internal details like SQLITE_MAX_... and friends, and keep -** those consistent with this build. This increases the rebuild time -** considerably, however. +** 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. */ #ifndef SQLITE_C # define SQLITE_C sqlite3.c @@ -333,7 +335,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 zName. + ** klazz is a global ref to the class represented by pRef. ** ** According to: ** @@ -997,20 +999,19 @@ static S3JniEnv * S3JniEnv__get(JNIEnv * const env){ ** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not ** from client 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. +** Returns err_code. */ -static int s3jni_db_error(JNIEnv * env, sqlite3* const db, - int err_code, const char * const zMsg){ +static int s3jni_db_error(sqlite3* const db, int err_code, + const char * const zMsg){ if( db!=0 ){ - int const rc = sqlite3_set_errmsg(db, err_code, zMsg); - s3jni_oom_fatal(0==rc); - if( rc && !err_code ) err_code=rc; + if( 0==zMsg ){ + sqlite3Error(db, err_code); + }else{ + const int nMsg = sqlite3Strlen30(zMsg); + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + } } return err_code; } @@ -1234,11 +1235,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(env, pDb, errCode, zMsg ? zMsg : zDfltMsg); + s3jni_db_error(pDb, errCode, zMsg ? zMsg : zDfltMsg); sqlite3_free(zMsg); S3JniUnrefLocal(ex); }else if( zDfltMsg ){ - s3jni_db_error(env, pDb, errCode, zDfltMsg); + s3jni_db_error(pDb, errCode, zDfltMsg); } return errCode; } @@ -1955,6 +1956,15 @@ 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 @@ -1996,7 +2006,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 -** (argc,argv) are (0,NULL) for UDF types with no arguments. This +** latter will be 0-and-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 @@ -2089,19 +2099,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; - jobject jcx = 0 /* sqlite3_context */; - jobjectArray jargv = 0 /* sqlite3_value[] */; - int rc = udf_args(env, pCx, argc, argv, &jcx, &jargv); + udf_jargs args = {0,0}; + int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv); + if( 0 == rc ){ - (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx, jargv); + (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); S3JniIfThrew{ rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx, s->zFuncName, zFuncType); } - udf_unargs(env, jcx, argc, jargv); + udf_unargs(env, args.jcx, argc, args.jargv); } - S3JniUnrefLocal(jcx); - S3JniUnrefLocal(jargv); + S3JniUnrefLocal(args.jcx); + S3JniUnrefLocal(args.jargv); return rc; } @@ -3290,7 +3300,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(env, ps->pDb, SQLITE_MISUSE, 0); + s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); S3JniDb_mutex_leave; return 0; } @@ -3310,14 +3320,13 @@ 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(env, ps->pDb, SQLITE_ERROR, + s3jni_db_error(ps->pDb, SQLITE_ERROR, "Cannot not find matching call() method in" "hook object."); }else{ @@ -3597,7 +3606,7 @@ S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(), (*env)->GetMethodID(env, klazz, "call", "([B[B)I"); S3JniUnrefLocal(klazz); S3JniIfThrew{ - rc = s3jni_db_error(env, ps->pDb, SQLITE_ERROR, + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "Could not get call() method from " "CollationCallback object."); }else{ @@ -3636,15 +3645,15 @@ S3JniApi(sqlite3_create_function() sqlite3_create_function_v2() if( !pDb || !jFuncName ){ return SQLITE_MISUSE; - }else if( !encodingTypeIsValid(eTextRep & 0x0f) ){ - return s3jni_db_error(env, pDb, SQLITE_FORMAT, + }else if( !encodingTypeIsValid(eTextRep) ){ + return s3jni_db_error(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(env, pDb, SQLITE_MISUSE, + rc = s3jni_db_error(pDb, SQLITE_MISUSE, "Cannot unambiguously determine function type."); S3JniUdf_free(env, s, 1); goto error_cleanup; @@ -4003,7 +4012,7 @@ S3JniApi(sqlite3_jni_db_error(), jint, 1jni_1db_1error)( zStr = jStr ? s3jni_jstring_to_utf8( jStr, 0) : NULL; - rc = s3jni_db_error(env, ps->pDb, (int)jRc, zStr ); + rc = s3jni_db_error( ps->pDb, (int)jRc, zStr ); sqlite3_free(zStr); } return rc; @@ -4312,7 +4321,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(env, ps->pDb, SQLITE_NOMEM, 0); + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); }else{ assert( hook.jObj ); assert( hook.midCallback ); @@ -4414,7 +4423,7 @@ static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jlong jpDb, jobject S3JniUnrefLocal(klazz); S3JniIfThrew { S3JniExceptionClear; - s3jni_db_error(env, ps->pDb, SQLITE_ERROR, + s3jni_db_error(ps->pDb, SQLITE_ERROR, "Cannot not find matching callback on " "(pre)update hook object."); }else{ @@ -4523,7 +4532,7 @@ S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)( S3JniUnrefLocal(klazz); S3JniIfThrew { S3JniExceptionClear; - s3jni_db_error(env, ps->pDb, SQLITE_ERROR, + s3jni_db_error(ps->pDb, SQLITE_ERROR, "Cannot not find matching xCallback() on " "ProgressHandler object."); }else{ @@ -4897,9 +4906,8 @@ S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)( ")I"); S3JniUnrefLocal(klazz); S3JniIfThrew { - rc = s3jni_db_error(env, ps->pDb, SQLITE_ERROR, - "Error setting up Java parts of " - "authorizer hook."); + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Error setting up Java parts of authorizer hook."); }else{ rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps); } @@ -5183,7 +5191,7 @@ S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)( S3JniUnrefLocal(klazz); S3JniIfThrew { S3JniExceptionClear; - rc = s3jni_db_error(env, ps->pDb, SQLITE_ERROR, + rc = s3jni_db_error(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 c326fa8ea..81af5cbde 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -245,10 +245,8 @@ 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 13L +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX 12L #undef org_sqlite_jni_capi_CApi_SQLITE_UTF8 #define org_sqlite_jni_capi_CApi_SQLITE_UTF8 1L #undef org_sqlite_jni_capi_CApi_SQLITE_UTF16LE diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java index 1bdc5300d..0b840c362 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CApi.java +++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java @@ -115,9 +115,8 @@ 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. 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. + decisions. Its return type and semantics are not considered + stable and may change at any time. */ public static native boolean sqlite3_java_uncache_thread(); @@ -2586,8 +2585,7 @@ 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_TEMPBUF_SPILL = 13; - public static final int SQLITE_DBSTATUS_MAX = 13; + public static final int SQLITE_DBSTATUS_MAX = 12; // 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 891bdea54..9d14c954b 100644 --- a/ext/jni/src/org/sqlite/jni/capi/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java @@ -815,9 +815,7 @@ public void xDestroy(){ }; // Register and use the function... - int rc = sqlite3_create_function(db, "myfunc", -1, - SQLITE_UTF8 | SQLITE_INNOCUOUS, - func); + int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); affirm(0 == rc); affirm(0 == xFuncAccum.value); final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)"); diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java index ba2ffd119..d259e0ce6 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java @@ -171,7 +171,6 @@ 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 24645f226..9c726f5f1 100644 --- a/ext/misc/btreeinfo.c +++ b/ext/misc/btreeinfo.c @@ -306,10 +306,6 @@ 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{ @@ -343,7 +339,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_errstr(rc)); + pCursor->pVtab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); return SQLITE_ERROR; } } diff --git a/ext/misc/csv.c b/ext/misc/csv.c index f44a30001..1caaaec87 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 be4321ca8..f87699f96 100644 --- a/ext/misc/decimal.c +++ b/ext/misc/decimal.c @@ -291,36 +291,12 @@ 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; nZero<p->nDigit && 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, int N){ +static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p){ char *z; /* The output buffer */ int i; /* Loop counter */ int nZero; /* Number of leading zeros */ @@ -338,8 +314,7 @@ static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p, int N){ sqlite3_result_null(pCtx); return; } - if( N<1 ) N = 0; - for(nDigit=p->nDigit; nDigit>N && p->a[nDigit-1]==0; nDigit--){} + for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){} for(nZero=0; nZero<nDigit && p->a[nZero]==0; nZero++){} nFrac = p->nFrac + (nDigit - p->nDigit); nDigit -= nZero; @@ -702,16 +677,10 @@ static void decimalFunc( sqlite3_value **argv ){ Decimal *p = decimal_new(context, argv[0], 0); - int N; - if( argc==2 ){ - N = sqlite3_value_int(argv[1]); - if( N>0 ) decimal_round(p, N); - }else{ - N = 0; - } + UNUSED_PARAMETER(argc); if( p ){ if( sqlite3_user_data(context)!=0 ){ - decimal_result_sci(context, p, N); + decimal_result_sci(context, p); }else{ decimal_result(context, p); } @@ -881,7 +850,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, 0); + decimal_result_sci(context, pA); decimal_free(pA); } } @@ -902,9 +871,7 @@ 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 51b748291..6cc2ae008 100644 --- a/ext/misc/fileio.c +++ b/ext/misc/fileio.c @@ -94,16 +94,12 @@ SQLITE_EXTENSION_INIT1 # include <utime.h> # include <sys/time.h> # define STRUCT_STAT struct stat -# include <limits.h> -# include <stdlib.h> #else # include "windirent.h" # include <direct.h> # 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 <time.h> #include <errno.h> @@ -135,9 +131,12 @@ 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; @@ -149,9 +148,12 @@ 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; @@ -264,34 +266,69 @@ static sqlite3_uint64 fileTimeToUnixTime( return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000; } -#endif /* _WIN32 */ + + +#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 is used in place of stat(). On Windows, special handling -** is required in order for the included time to be returned as UTC. On all -** other systems, this function simply calls stat(). +** 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 int fileStat( +static void statTimesToUtc( const char *zPath, STRUCT_STAT *pStatBuf ){ -#if defined(_WIN32) - int rc; - wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath); - if( b1==0 ) return 1; - rc = _wstat(b1, pStatBuf); - if( rc==0 ){ - HANDLE hFindFile; - WIN32_FIND_DATAW fd; + 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(b1, &fd); + 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 + +/* +** This function is used in place of stat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls stat(). +*/ +static int fileStat( + const char *zPath, + STRUCT_STAT *pStatBuf +){ +#if defined(_WIN32) + sqlite3_int64 sz = strlen(zPath); + wchar_t *b1 = sqlite3_malloc64( (sz+1)*sizeof(b1[0]) ); + int rc; + 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); sqlite3_free(b1); return rc; #else @@ -423,6 +460,7 @@ static int writeFile( if( mtime>=0 ){ #if defined(_WIN32) +#if !SQLITE_OS_WINRT /* Windows */ FILETIME lastAccess; FILETIME lastWrite; @@ -453,6 +491,7 @@ static int writeFile( }else{ return 1; } +#endif #elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */ /* Recent unix */ struct timespec times[2]; @@ -1056,154 +1095,6 @@ 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; i<n; i++){ - if( zOut[i]=='/' ){ - if( zOut[i+1]=='/' ) continue; - if( zOut[i+1]=='.' && i+2<n && zOut[i+2]=='/' ){ - i += 1; - continue; - } - if( zOut[i+1]=='.' && i+3<n && zOut[i+2]=='.' && zOut[i+3]=='/' ){ - while( j>0 && 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 @@ -1230,10 +1121,13 @@ 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/fuzzer.c b/ext/misc/fuzzer.c index 3dcf1d667..e16d005d9 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; - sqlite3_int64 n; /* Size of output term without nul-term */ + int 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 f551b2265..7f1c6d9e1 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,38 +259,6 @@ 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) ** @@ -343,8 +311,6 @@ 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 e1826caf3..f1babf4ab 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,18 +740,11 @@ static const char *re_compile( } /* -** The value of LIMIT_MAX_PATTERN_LENGTH. +** Compute a reasonable limit on the length of the REGEXP NFA. */ static int re_maxlen(sqlite3_context *context){ sqlite3 *db = sqlite3_context_db_handle(context); - 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; + return 75 + sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH,-1)/2; } /* @@ -777,17 +770,10 @@ 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; - 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); - } + zErr = re_compile(&pRe, zPattern, re_maxlen(context), + sqlite3_user_data(context)!=0); if( zErr ){ re_free(pRe); sqlite3_result_error(context, zErr, -1); @@ -853,7 +839,7 @@ static void re_bytecode_func( zPattern = (const char*)sqlite3_value_text(argv[0]); if( zPattern==0 ) return; - zErr = re_compile(&pRe, zPattern, re_maxnfa(re_maxlen(context)), + zErr = re_compile(&pRe, zPattern, 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 02d864955..07d797060 100644 --- a/ext/misc/sha1.c +++ b/ext/misc/sha1.c @@ -230,16 +230,13 @@ static void hash_finish( *****************************************************************************/ /* -** Two SQL functions: sha1(X) and sha1b(X). +** Implementation of the sha1(X) function. ** -** 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 +** 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 ** types of input, X is converted into a UTF-8 string and the string -** is hashed without the trailing 0x00 terminator. The hash of a NULL +** is hash 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, @@ -260,13 +257,11 @@ 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_text(context, zOut, 40, SQLITE_TRANSIENT); + sqlite3_result_blob(context, zOut, 40, SQLITE_TRANSIENT); } } diff --git a/ext/misc/sqlite3_stdio.c b/ext/misc/sqlite3_stdio.c index d59757526..c9bceb194 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-alikes for fprintf() and vfprintf() from the standard C library. +** Work-alike for fprintf() from the standard C library. */ int sqlite3_fprintf(FILE *out, const char *zFormat, ...){ int rc; @@ -285,24 +285,6 @@ 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 75368df9f..dd0eefad0 100644 --- a/ext/misc/sqlite3_stdio.h +++ b/ext/misc/sqlite3_stdio.h @@ -31,7 +31,6 @@ #ifdef _WIN32 /**** Definitions For Windows ****/ #include <stdio.h> -#include <stdarg.h> #include <windows.h> FILE *sqlite3_fopen(const char *zFilename, const char *zMode); @@ -39,7 +38,6 @@ 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); @@ -51,7 +49,6 @@ 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 deleted file mode 100644 index 6f1af36f7..000000000 --- a/ext/misc/tmstmpvfs.c +++ /dev/null @@ -1,1042 +0,0 @@ -/* -** 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 <string.h> -#include <assert.h> -#include <stdio.h> - -/* -** 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 <windows.h> -# define GETPID (u32)GetCurrentProcessId() -#else -# include <unistd.h> -# 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 1e28495de..2b3e30355 100644 --- a/ext/misc/vtablog.c +++ b/ext/misc/vtablog.c @@ -14,8 +14,6 @@ ** 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: ** @@ -23,7 +21,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( @@ -31,23 +29,6 @@ ** 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 @@ -68,8 +49,6 @@ 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 @@ -201,7 +180,6 @@ 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"); @@ -225,10 +203,6 @@ 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);"); @@ -247,10 +221,6 @@ 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]); } @@ -258,7 +228,6 @@ static int vtablogConnectCreate( vtablog_end_connect: sqlite3_free(zSchema); sqlite3_free(zNRow); - sqlite3_free(zConsumeOB); return rc; } static int vtablogCreate( @@ -545,27 +514,16 @@ static int vtablogBestIndex( } } printf(" nOrderBy: %d\n", p->nOrderBy); - if( p->nOrderBy ){ - for(i=0; i<p->nOrderBy; 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; - } - } + for(i=0; i<p->nOrderBy; i++){ + printf(" orderby[%d]: col=%d desc=%d\n", + i, + p->aOrderBy[i].iColumn, + p->aOrderBy[i].desc); } 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 c4862650b..086b058cc 100644 --- a/ext/misc/zipfile.c +++ b/ext/misc/zipfile.c @@ -705,12 +705,7 @@ static int zipfileScanExtra(u8 *aExtra, int nExtra, u32 *pmTime){ u8 *p = aExtra; u8 *pEnd = &aExtra[nExtra]; - /* Stop when there are less than 9 bytes left to scan in the buffer. This - ** is because the timestamp field requires exactly 9 bytes - 4 bytes of - ** header fields and 5 bytes of data. If there are less than 9 bytes - ** remaining, either it is some other field or else the extra data - ** is corrupt. Either way, do not process it. */ - while( p+(2*sizeof(u16) + 1 + sizeof(u32))<=pEnd ){ + while( p<pEnd ){ u16 id = zipfileRead16(p); u16 nByte = zipfileRead16(p); diff --git a/ext/qrf/README.md b/ext/qrf/README.md deleted file mode 100644 index 4bb1790a4..000000000 --- a/ext/qrf/README.md +++ /dev/null @@ -1,762 +0,0 @@ -# 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); -} -~~~ - -<a id="spec"></a> -## 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&nbsp;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 "&lt;", "&amp;", "&gt;", "&quot;", and "&#39;" -are displayed as "&amp;lt;", "&amp;amp;", "&amp;gt;", "&amp;quot;", -and "&amp;#39;", 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(). - -<a id="style"></a> -## 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: - -<table border=1 cellpadding=2 cellspacing=0> -<tr><th>&nbsp;<th>Quote<th>Csv -<tr><td>zColumnSep<td>","<td>"," -<tr><td>zRowSep<td>"\\n"<td>"\\r\\n" -<tr><td>zNull<td>"NULL"<td>"" -<tr><td>eText<td>QRF_TEXT_Sql<td>QRF_TEXT_Csv -<tr><td>eBlob<td>QRF_BLOB_Sql<td>QRF_BLOB_Text -</table> - -The **Html** style generates HTML table content, just without -the `<TABLE>..</TABLE>` 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` &rarr; The implementation, written in portable C99 - * `qrf.h` &rarr; A header file defining interfaces - * `README.md` &rarr; 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 deleted file mode 100644 index a46aada83..000000000 --- a/ext/qrf/dev-notes.md +++ /dev/null @@ -1,14 +0,0 @@ -# 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 deleted file mode 100644 index cacfa1526..000000000 --- a/ext/qrf/qrf.c +++ /dev/null @@ -1,2983 +0,0 @@ -/* -** 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 <string.h> -#include <assert.h> -#include <stdint.h> - -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<iLast-1 ){ - int iMid = (iFirst+iLast)/2; - int cMid = aQrfUWidth[iMid].iFirst; - if( cMid < c ){ - iFirst = iMid; - }else if( cMid > 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( z<zEnd ){ - if( z[0]<' ' ){ - int k; - if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){ - 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, "&lt;", 4); break; - case '&': sqlite3_str_append(pOut, "&amp;", 5); break; - case '<': sqlite3_str_append(pOut, "&lt;", 4); break; - case '"': sqlite3_str_append(pOut, "&quot;", 6); break; - case '\'': sqlite3_str_append(pOut, "&#39;", 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; i<n; i++) sz = (sz<<8) + aBlob[i+1]; - return sz+n+1==(sqlite3_uint64)nBlob; -} - -/* -** The current iCol-th column of p->pStmt 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<nBlob; i++, j+=2){ - unsigned char c = a[i]; - zVal[j] = "0123456789abcdef"[(c>>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<nBlob; i++, j+=szC){ - unsigned char c = a[i]; - zVal[j] = '\\'; - if( szC==4 ){ - zVal[j+1] = '0' + ((c>>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 ){ - for(k=i; 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( i<nVal ){ - unsigned char c = z[i]; - if( c<' ' ){ - int k; - sqlite3_str_append(pOut, (const char*)z, i); - nVal -= i; - z += i; - i = 0; - if( c=='\033' && (k = qrfIsVt100(z))>0 ){ - 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; i<p->n; 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; i<p->nCol; 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; i<p->nCol; 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; i<pData->nCol; i++){ - pData->a[i].e = p->spec.eDfltAlign; - if( i<p->spec.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( i<p->spec.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; i<pData->n; 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; i<nCol; i++) t += aw[i]; - t += 2*(nCol-1); - if( t>nSW ){ - 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; i<pData->n; 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( i<nRow*nCol ){ - sqlite3_int64 j = (i%nRow)*nCol + (i/nRow); - az[j] = sqlite3_mprintf(""); - if( az[j]==0 ) qrfOom(p); - aiWth[j] = 0; - abNum[j] = 0; - i++; - } - for(i=0; i<nCol; i++){ - a[i].fx = a[i].mxW = a[i].w = aw[i]; - a[i].e = pData->a[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; i<nCol; i++) w += a[i].w; - pData->nMargin = (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; i<nCol; i++) sumW += pData->a[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; i<nCol; i++){ - if( pData->a[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; i<nColumn; i++){ - const char *z = (const char*)sqlite3_column_name(p->pStmt,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; i<nColumn; i++){ - char *z; - int nNL = 0; - int n, w; - int eType = sqlite3_column_type(p->pStmt,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; i<nColumn; i++) data.a[i].e = e; - } - - for(i=0; i<nColumn; i++){ - int w = 0; - if( i<p->spec.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; i<data.n && sqlite3_str_errcode(p->pOut)==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; j<nColumn; j++){ - data.a[j].z = data.az[i+j]; - if( data.a[j].z==0 ) data.a[j].z = ""; - data.a[j].bNum = data.abNum[i+j]; - } - do{ - sqlite3_str_append(p->pOut, rowStart, szRowStart); - bMore = 0; - for(j=0; j<nColumn; j++){ - int nThis = 0; - int nWide = 0; - int iNext = 0; - int nWS; - qrfWrapLine(data.a[j].z, data.a[j].w, bWW, &nThis, &nWide, &iNext); - nWS = data.a[j].w - nWide; - qrfPrintAligned(p->pOut, &data.a[j], nThis, nWS); - data.a[j].z += iNext; - if( data.a[j].z[0]!=0 ){ - bMore = 1; - } - if( j<nColumn-1 ){ - sqlite3_str_append(p->pOut, 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; j<nColumn; j++){ - if( data.a[j].z[0]==0 ){ - sqlite3_str_appendchar(p->pOut, 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( j<nColumn-1 ){ - sqlite3_str_append(p->pOut, 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+nColumn<data.n ){ - int isTitleDataSeparator = (i==0 && p->spec.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; j<nColumn; j++){ - sqlite3_str_appendchar(p->pOut, data.a[j].w, '-'); - if( j<nColumn-1 ){ - sqlite3_str_append(p->pOut, 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; i<iOp; i++) aiIndent[i] += 2; - } - if( qrfStringInArray(zOp, azGoto) && p2op<iOp && (abYield[p2op] || p1) ){ - for(i=p2op; i<iOp; i++) aiIndent[i] += 2; - } - } - sqlite3_free(abYield); - - /* Second pass. Actually generate output */ - sqlite3_reset(p->pStmt); - 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; i<nArg; i++){ - const char *zCol = sqlite3_column_name(p->pStmt, 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; i<nArg; i++){ - sqlite3_str_appendf(p->pOut, "%.*c", aWidth[i], '-'); - if( i==nArg-1 ){ - sqlite3_str_append(p->pOut, "\n", 1); - }else{ - sqlite3_str_append(p->pOut, " ", 2); - } - } - } - - for(i=0; i<nArg; i++){ - const char *zSep = " "; - int w = aWidth[i]; - const char *zVal = (const char*)sqlite3_column_text(p->pStmt, 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 && iOp<nIndent ){ - sqlite3_str_appendchar(p->pOut, 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; i<p->nCol; 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, "<TR>", 4); - for(i=0; i<p->nCol; i++){ - const char *zCName = sqlite3_column_name(p->pStmt, i); - sqlite3_str_append(p->pOut, "\n<TH>", 5); - qrfEncodeText(p, p->pOut, zCName); - } - sqlite3_str_append(p->pOut, "\n</TR>\n", 7); - } - sqlite3_str_append(p->pOut, "<TR>", 4); - for(i=0; i<p->nCol; i++){ - sqlite3_str_append(p->pOut, "\n<TD>", 5); - qrfRenderValue(p, p->pOut, i); - } - sqlite3_str_append(p->pOut, "\n</TR>\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; i<p->nCol; 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; i<p->nCol; 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; i<p->nCol; 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; i<p->nCol; 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; i<p->nCol; 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; i<p->nCol; 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; i<p->nCol; 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 deleted file mode 100644 index c23ec772f..000000000 --- a/ext/qrf/qrf.h +++ /dev/null @@ -1,200 +0,0 @@ -/* -** 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 <stdlib.h> -#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 513ab29f6..a42163cce 100644 --- a/ext/rbu/rbu11.test +++ b/ext/rbu/rbu11.test @@ -192,32 +192,4 @@ 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 f377d5c30..e3bcd5fc7 100644 --- a/ext/rbu/sqlite3rbu.c +++ b/ext/rbu/sqlite3rbu.c @@ -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_realloc64( - pIter->aIdxCol, nIdxAlloc*sizeof(RbuSpan) + 16*sizeof(RbuSpan) + RbuSpan *aIdxCol = (RbuSpan*)sqlite3_realloc( + pIter->aIdxCol, (nIdxAlloc+16)*sizeof(RbuSpan) ); if( aIdxCol==0 ){ rc = SQLITE_NOMEM; diff --git a/ext/repair/README.md b/ext/repair/README.md new file mode 100644 index 000000000..927ceb7c4 --- /dev/null +++ b/ext/repair/README.md @@ -0,0 +1,16 @@ +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 new file mode 100644 index 000000000..d1d3d5407 --- /dev/null +++ b/ext/repair/checkfreelist.c @@ -0,0 +1,310 @@ +/* +** 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(<database-name>); +** +** 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 <string.h> +# include <stdio.h> +# include <stdlib.h> +# include <assert.h> +# 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 && i<nLeaf; i++){ + u32 iLeaf = get4byte(&aData[8 + 4*i]); + if( iLeaf==0 || iLeaf>nPage ){ + 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 new file mode 100644 index 000000000..ed30357e5 --- /dev/null +++ b/ext/repair/checkindex.c @@ -0,0 +1,929 @@ +/* +** 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 <string.h> +# include <stdio.h> +# include <stdlib.h> +# include <assert.h> +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; i<pInfo->nConstraint; 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; i<pIdx->nCol; 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<nCol; i++){ + while( *p==' ' ) p++; + + /* Check NULL values */ + if( *p=='N' ){ + if( memcmp(p, "NULL", 4) ) goto parse_error; + p += 4; + } + + /* Check strings and blob literals */ + else if( *p=='X' || *p=='\'' ){ + azAfter[i] = p; + if( *p=='X' ) p++; + if( *p!='\'' ) goto parse_error; + p++; + while( 1 ){ + if( *p=='\0' ) goto parse_error; + if( *p=='\'' ){ + p++; + if( *p!='\'' ) break; + } + p++; + } + } + + /* Check numbers */ + else{ + azAfter[i] = p; + while( (*p>='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<iGt; i++){ + zRet = cidxMprintf(pRc, "%z%s(%s) IS %s", zRet, + zSep, aCol[i].zExpr, (azAfter[i] ? azAfter[i] : "NULL") + ); + zSep = " AND "; + } + + if( bLastIsNull ){ + zRet = cidxMprintf(pRc, "%z%s(%s) IS NULL", zRet, zSep, aCol[iGt].zExpr); + } + else if( azAfter[iGt] ){ + zRet = cidxMprintf(pRc, "%z%s(%s) %s %s", zRet, + zSep, aCol[iGt].zExpr, (aCol[iGt].bDesc ? "<" : ">"), + 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; i<pIdx->nCol; 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 new file mode 100644 index 000000000..96b15f271 --- /dev/null +++ b/ext/repair/sqlite3_checker.c.in @@ -0,0 +1,85 @@ +/* +** 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 <stdio.h> + +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 new file mode 100644 index 000000000..2ae6e15b1 --- /dev/null +++ b/ext/repair/sqlite3_checker.tcl @@ -0,0 +1,264 @@ +# 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 new file mode 100644 index 000000000..8cc954adf --- /dev/null +++ b/ext/repair/test/README.md @@ -0,0 +1,13 @@ +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 new file mode 100644 index 000000000..7e2dd51c3 --- /dev/null +++ b/ext/repair/test/checkfreelist01.test @@ -0,0 +1,92 @@ +# 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 new file mode 100644 index 000000000..97973aee7 --- /dev/null +++ b/ext/repair/test/checkindex01.test @@ -0,0 +1,349 @@ +# 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 new file mode 100644 index 000000000..c073bb73c --- /dev/null +++ b/ext/repair/test/test.tcl @@ -0,0 +1,67 @@ +# 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 22166a6f9..0ae42e7b7 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); + (void)sqlite3AtoF((const char*)p->z, &r, j, SQLITE_UTF8); *pVal = r; #else *pVal = (GeoCoord)atof((const char*)p->z); diff --git a/ext/session/session4.test b/ext/session/session4.test index 5e44a8eb6..55cb76f15 100644 --- a/ext/session/session4.test +++ b/ext/session/session4.test @@ -135,7 +135,6 @@ 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 1997ba5e8..74370cb79 100644 --- a/ext/session/sessionC.test +++ b/ext/session/sessionC.test @@ -192,16 +192,6 @@ 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 5468dff28..90fedc6db 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -349,20 +349,6 @@ 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]) @@ -657,10 +643,14 @@ 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++; @@ -668,16 +658,12 @@ static unsigned int sessionChangeHash( if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ h = sessionHashAppendI64(h, sessionGetI64(a)); a += 8; - }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + }else{ 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); } @@ -3086,13 +3072,10 @@ 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 ){ @@ -3575,8 +3558,7 @@ static int sessionReadRecord( u8 *aVal = &pIn->aData[pIn->iNext]; if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ int nByte; - int nRem = pIn->nData - pIn->iNext; - pIn->iNext += sessionVarintGetSafe(aVal, nRem, &nByte); + pIn->iNext += sessionVarintGet(aVal, &nByte); rc = sessionInputBuffer(pIn, nByte); if( rc==SQLITE_OK ){ if( nByte<0 || nByte>pIn->nData-pIn->iNext ){ @@ -3629,8 +3611,7 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){ rc = sessionInputBuffer(pIn, 9); if( rc==SQLITE_OK ){ - int nBuf = pIn->nData - pIn->iNext; - nRead += sessionVarintGetSafe(&pIn->aData[pIn->iNext], nBuf, &nCol); + nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &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 @@ -3650,15 +3631,8 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){ while( (pIn->iNext + nRead)<pIn->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)<pIn->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; @@ -3790,10 +3764,10 @@ static int sessionChangesetNextOne( memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2); } - /* 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. */ + /* 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. */ p->rc = sessionInputBuffer(&p->in, 2); if( p->rc!=SQLITE_OK ) return p->rc; @@ -3823,13 +3797,11 @@ static int sessionChangesetNextOne( return (p->rc = SQLITE_CORRUPT_BKPT); } - 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( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ + return (p->rc = SQLITE_CORRUPT_BKPT); + } if( paRec ){ int nVal; /* Number of values to buffer */ diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 46a9e3a38..6ad5b3774 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -7,8 +7,6 @@ #include <string.h> #include "tclsqlite.h" -#include <stdlib.h> - #ifndef SQLITE_AMALGAMATION typedef unsigned char u8; #endif @@ -858,21 +856,6 @@ 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, @@ -937,7 +920,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( return TCL_ERROR; } db = *(sqlite3 **)info.objClientData; - pChangeset = (void *)testGetByteArrayFromObj(objv[2], &nChangeset); + pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); ctx.pConflictScript = objv[3]; ctx.pFilterScript = objc==5 ? objv[4] : 0; ctx.interp = interp; @@ -989,7 +972,6 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( } } - free(pChangeset); if( rc!=SQLITE_OK ){ return test_session_error(interp, rc, 0); }else{ @@ -1213,12 +1195,7 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach( pCS = objv[2]; pScript = objv[3]; - /* 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); - + pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset); sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); if( isInvert ){ int f = SQLITE_CHANGESETSTART_INVERT; @@ -1239,33 +1216,32 @@ 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); + } - 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); + 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; } } - free(pChangeset); + 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( 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 new file mode 100644 index 000000000..103704df1 --- /dev/null +++ b/ext/wasm/EXPORTED_FUNCTIONS.fiddle.in @@ -0,0 +1,10 @@ +_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 dc3c2d255..937e16d6e 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -1,6 +1,5 @@ -# -# This GNU makefile creates the canonical sqlite3 WASM builds. Plus some -# others. +####################################################################### +# This GNU makefile creates the canonical sqlite3 WASM builds. # # This build assumes a Linux platform and is not intended for # general-purpose client-level use, except for creating builds with @@ -11,33 +10,28 @@ # # default, all = build in dev mode # -# 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. +# 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. # # 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? target names. +# with a specific optimization level, where oX is one of the +# above-listed o? or qo? 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. -# It will build without this but the .wasm files will be huge. -# +# - wasm-strip for release builds: https://github.com/WebAssembly/wabt # - InfoZip for 'dist' zip file -# +######################################################################## default: all MAKEFILE = $(lastword $(MAKEFILE_LIST)) CLEAN_FILES = @@ -86,7 +80,6 @@ emo.strip = 💈 emo.test = 🧪 emo.tool = 🔨 emo.wasm-opt = 🧼 -emo.cleanup = 🧼 # 👷🪄🧮🧫🧽🍿⛽🚧🎱🪚🏆🧼 # @@ -206,7 +199,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 -f -p $(2) $(3) || exit + cp -p $(2) $(3) || exit # # $(call b.c-pp.shcmd,LOGTAG,src,dest,-Dx=y...) @@ -220,8 +213,9 @@ 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)[$(3)] $(4)); \ -rm -f $(3); $(bin.c-pp) -o $(3) $(4) $(2) || exit; \ +$(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; \ chmod -w $(3) endef @@ -233,38 +227,10 @@ endef # Args: as for $(b.c-pp.shcmd). define b.c-pp.target $(3): $$(MAKEFILE_LIST) $$(bin.c-pp) $(2) - @$$(call b.mkdir@) - @$$(call b.c-pp.shcmd,$(1),$(2),$(3),$(4) $$(b.c-pp.target.flags)) + @$$(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 # @@ -282,21 +248,11 @@ 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 = \ - echo '$(1) $(emo.bug) (disabled because it breaks emsdk 4.0.16+)' -endif + sed -i -e '/^.*= \(_sqlite3\|_fiddle\)[^=]*=.*createExportWrapper/d' \ + -e '/^var \(_sqlite3\|_fiddle\)[^=]*=.*makeInvalidEarlyAccess/d' $@ || exit; \ + echo '$(1) $(emo.garbage) (Probably) /createExportWrapper()/d and /makeInvalidEarlyAccess()/d' + # # Set up sqlite3.c and sqlite3.h... @@ -315,36 +271,33 @@ endif # $(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 @@ -372,7 +325,7 @@ endif # undefine barebones # relatively new gmake feature, not ubiquitous # -# It's important that sqlite3.[ch] be built to completion before any +# It's important that sqlite3.h 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 @@ -389,10 +342,8 @@ $(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 \ @@ -409,15 +360,11 @@ 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 \ @@ -474,14 +421,13 @@ 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: # @@ -490,9 +436,10 @@ 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 @@ -513,7 +460,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)) @@ -534,7 +481,7 @@ endif # WASM_CUSTOM_INSTANTIATE = 1 -# +######################################################################## # $(bin.c-pp): a minimal text file preprocessor. Like C's but much # less so. # @@ -565,7 +512,6 @@ 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) \ @@ -577,12 +523,7 @@ 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 @@ -590,14 +531,13 @@ 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. @@ -609,17 +549,14 @@ 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. # @@ -646,26 +583,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.in = $(dir.api)/EXPORTED_FUNCTIONS.c-pp -EXPORTED_FUNCTIONS.api = $(dir.tmp)/EXPORTED_FUNCTIONS.api -EXPORTED_FUNCTIONS.c-pp.flags = -ifeq (1,$(wasm-bare-bones)) - EXPORTED_FUNCTIONS.c-pp.flags += -Dbare-bones +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 -$(eval $(call b.c-pp.target,filter,\ - $(EXPORTED_FUNCTIONS.api.in),\ - $(EXPORTED_FUNCTIONS.api),\ - $(EXPORTED_FUNCTIONS.c-pp.flags))) +ifeq (0,$(wasm-bare-bones)) + EXPORTED_FUNCTIONS.api.in += $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-extras +endif +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) > $@ -# +######################################################################## # emcc flags for .c/.o/.wasm/.js. -# emcc.flags = ifeq (1,$(emcc.verbose)) emcc.flags += -v @@ -753,9 +690,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 @@ -764,8 +701,7 @@ 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. @@ -805,7 +741,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 # @@ -814,7 +750,12 @@ 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 @@ -857,6 +798,51 @@ 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. @@ -866,15 +852,17 @@ endef # sqlite3-license-version.js = $(dir.tmp)/sqlite3-license-version.js $(sqlite3-license-version.js): $(bin.version-info) \ - $(dir.api)/sqlite3-license-version-header.js $(MAKEFILE) - @$(call b.mkdir@); echo '$(logtag.@) $(emo.disk)'; { \ + $(dir.api)/sqlite3-license-version-header.js + @echo '$(logtag.@) $(emo.disk)'; { \ + $(call b.mkdir@); \ cat $(dir.api)/sqlite3-license-version-header.js || exit $$?; \ - echo '/* @preserve'; \ + echo '/*'; \ 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 '*/'; \ } > $@ @@ -893,34 +881,6 @@ $(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. @@ -958,7 +918,7 @@ $(post-js.in.js): $(MKDIR.bld) $(post-jses.js) $(MAKEFILE) done > $@ # -# speedtest1 decls needed before the $(bin.mkwb)-generated makefile +# speedtest1 decls needed before the $(bin.mkws)-generated makefile # is included. # bin.speedtest1 = ../../speedtest1 @@ -997,21 +957,12 @@ 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): $(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) +$(EXPORTED_FUNCTIONS.fiddle): $(fiddle.EXPORTED_FUNCTIONS.in) $(MAKEFILE_LIST) + @$(b.mkdir@) + @sort -u $(fiddle.EXPORTED_FUNCTIONS.in) > $@ @echo $(logtag.@) $(emo.disk) - emcc.flags.fiddle = \ $(emcc.cflags) $(emcc_opt_full) \ --minify 0 \ @@ -1036,23 +987,26 @@ 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 -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 +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) # # WASMFS build - unsupported and untested. We used WASMFS @@ -1074,14 +1028,6 @@ 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 @@ -1137,10 +1083,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))) @@ -1154,10 +1100,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))) @@ -1199,19 +1145,12 @@ $(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. # @@ -1221,7 +1160,7 @@ $(foreach B,$(b.names),$(eval post: $(post-js.$(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,worker +emcc.speedtest1 += -sENVIRONMENT=web emcc.speedtest1 += -sALLOW_MEMORY_GROWTH emcc.speedtest1 += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.32) emcc.speedtest1.common += -sINVOKE_RUN=0 @@ -1260,37 +1199,28 @@ 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) - @$(call b.mkdir@); $(call b.echo,@,$(emo.disk)); \ - { echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api); } > $@ || exit +$(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api.core) + @$(call b.echo,@,$(emo.disk)); \ + $(call b.mkdir@); \ + { echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api.core); } > $@ || 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/speedtest1\.js/speedtest1-64bit\.js/' \ - -e 's/speedtest1-worker\.js/speedtest1-worker-64bit\.js/' \ - < $$< > $$@; \ + @rm -f $$@; \ + sed -e 's/$(3)\.js/$(3)-64bit\.js/' < $$< > $$@; \ chmod -w $$@ -$(2): b-speedtest164 -speedtest1: $(1) $(2) +b-speedtest164: $(2) CLEAN_FILES += $(2) endef -speedtest1: b-speedtest164 -$(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)) +$(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)) # end speedtest1.js ######################################################################## @@ -1314,11 +1244,9 @@ $(eval $(call gen-st64,speedtest1-worker.js,speedtest1-worker-64bit.js)) # # 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 a dep on +# $1 = build name to have 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)) @@ -1327,18 +1255,20 @@ 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 @@ -1373,11 +1303,9 @@ $(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 the ?esm URL flag to switch to ESM -# mode. -# +# 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. $(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,\ @@ -1387,57 +1315,8 @@ 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 @@ -1448,7 +1327,6 @@ $(out.fiddle.wasm): $(dir.fiddle)/index.html # # 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)) @@ -1499,9 +1377,7 @@ 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 @@ -1528,9 +1404,9 @@ update-docs: exit 127 else wasm.docs.jswasm = $(wasm.docs.home)/jswasm -update-docs: $(bin.stripccomments) $(out.vanilla.js) $(out.vanilla.wasm) +update-docs: $(bin.stripccomments) $(out.sqlite3.js) $(out.sqlite3.wasm) @echo "Copying files to the /wasm docs. Be sure to use an -Oz build for this!"; - cp -p $(out.vanilla.wasm) $(wasm.docs.jswasm)/. + cp -p $(sqlite3.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)/. @@ -1586,46 +1462,13 @@ endif dist-name-prefix = sqlite-wasm$(dist-name-extra) .PHONY: dist dist: - $(bin.bash) ./mkdist.sh $(dist-name-prefix) + ./mkdist.sh $(dist-name-prefix) snapshot: - $(bin.bash) ./mkdist.sh $(dist-name-prefix) --snapshot + ./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.c-pp b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core similarity index 61% rename from ext/wasm/api/EXPORTED_FUNCTIONS.c-pp rename to ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core index 2cdddf1e7..506054510 100644 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.c-pp +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core @@ -75,7 +75,6 @@ _sqlite3_limit _sqlite3_malloc _sqlite3_malloc64 _sqlite3_msize -_sqlite3_next_stmt _sqlite3_open _sqlite3_open_v2 _sqlite3_overload_function @@ -156,92 +155,3 @@ _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 new file mode 100644 index 000000000..e8304b5f2 --- /dev/null +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras @@ -0,0 +1,68 @@ +_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 new file mode 100644 index 000000000..83f3a97db --- /dev/null +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see @@ -0,0 +1,5 @@ +_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 new file mode 100644 index 000000000..aab1d8bd3 --- /dev/null +++ b/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api @@ -0,0 +1,3 @@ +FS +wasmMemory + diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md index 279d216bf..3c9669e6b 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 bootstrapping function so that + objects. This is exposed as a function, rather than objects, 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,78 +77,79 @@ 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. -- **`<build>/sqlite3-api-build-version.js`** +- **`<build>/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.c-pp.js`** +- **`sqlite3-vfs-helper.js`**\ Installs the `sqlite3.vfs` namespace, which contain helpers for use by downstream code which creates `sqlite3_vfs` implementations. -- **`sqlite3-vtab-helper.c-pp.js`** +- **`sqlite3-vtab-helper.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 @@ -170,27 +171,23 @@ 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 &uarr; two - &darr; files. -- **`post-js-footer.js`** +- (**`sqlite3-api.js`** gets sandwiched between these &uarr; two + &darr; files.) +- **`post-js-footer.js`**\ Emscripten-specific footer for the `--post-js` input. This closes - 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`** + off the function opened by `post-js-header.js`. +- **`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 @@ -208,10 +205,9 @@ 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-lite.c`](/file/ext/wasm/c-pp-lite.c) and the complete technical -details of such preprocessing are maintained in +[`c-pp.c`](/file/ext/wasm/c-pp.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 cac6e4ab4..606e02ae2 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 sqlite3InitModule to be defined by the Emscripten build."); + throw new Error("Expecting globalThis.sqlite3InitModule to be defined by the Emscripten build."); } /** We need to add some state which our custom Module.locateFile() @@ -73,8 +73,6 @@ 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 f8050ddd3..c6a2e1517 100644 --- a/ext/wasm/api/post-js-footer.js +++ b/ext/wasm/api/post-js-footer.js @@ -1,72 +1,3 @@ -/* - 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 670051bd8..cdc6b3a38 100644 --- a/ext/wasm/api/post-js-header.js +++ b/ext/wasm/api/post-js-header.js @@ -3,13 +3,14 @@ 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 = async function( - sqlite3InitScriptInfo, +Module.runSQLite3PostLoadInit = function( + sqlite3InitScriptInfo /* populated by extern-post-js.c-pp.js */, EmscriptenModule/*the Emscripten-style module object*/, sqlite3IsUnderTest ){ @@ -34,6 +35,7 @@ Module.runSQLite3PostLoadInit = async 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 3910cb000..8a4a0f9fd 100644 --- a/ext/wasm/api/pre-js.c-pp.js +++ b/ext/wasm/api/pre-js.c-pp.js @@ -14,36 +14,12 @@ 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),{ - /* 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); + debugModule: ()=>{ + console.warn("globalThis.sqlite3InitModuleState is missing"); } }); delete globalThis.sqlite3InitModuleState; @@ -71,14 +47,6 @@ 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 @@ -104,7 +72,8 @@ //#endif target:es6-module }.bind(sIMS); -//#if Module.instantiateWasm and not wasmfs and not target:node +//#if Module.instantiateWasm +//#if not wasmfs /** Override Module.instantiateWasm(). @@ -113,15 +82,8 @@ 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, ( @@ -147,7 +109,7 @@ .then(finalThen) return loadWasm(); }.bind(sIMS); -//#endif Module.instantiateWasm and not wasmfs +//#endif not wasmfs +//#endif Module.instantiateWasm })(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 new file mode 100644 index 000000000..223566326 --- /dev/null +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -0,0 +1,83 @@ +/* + 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 d268331a3..1c42b0150 100644 --- a/ext/wasm/api/sqlite3-api-glue.c-pp.js +++ b/ext/wasm/api/sqlite3-api-glue.c-pp.js @@ -120,11 +120,7 @@ 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*", [ @@ -200,7 +196,6 @@ 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 @@ -322,9 +317,7 @@ 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"], @@ -976,8 +969,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "entry SQLITE_WASM_DEALLOC (=="+capi.SQLITE_WASM_DEALLOC+")."); } const __rcMap = Object.create(null); - for(const e of Object.entries(wasm.ctype['resultCodes'])){ - __rcMap[e[1]] = e[0]; + for(const t of ['resultCodes']){ + for(const e of Object.entries(wasm.ctype[t])){ + __rcMap[e[1]] = e[0]; + } } /** For the given integer, returns the SQLITE_xxx result code as a @@ -989,6 +984,8 @@ 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, @@ -1657,45 +1654,6 @@ 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. @@ -1782,6 +1740,105 @@ 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; @@ -1887,7 +1944,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } tgt[memKey] = fProxy; }else{ - const pFunc = wasm.installFunction(fProxy, sigN); + const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name)); 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 9338eef33..f7a4e9ebd 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -26,20 +26,6 @@ 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 @@ -102,8 +88,7 @@ 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+'['+capi.sqlite3_db_filename(c,null)+']', + console.log("SQL TRACE #"+(++this.counter)+' via sqlite3@'+c+':', wasm.cstrToJs(x)); } }.bind({counter: 0})); @@ -228,18 +213,41 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ properties: - `.filename`: the db filename. It may be a special name like ":memory:" - or "". It may also be a URI-style name. + or "". - `.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; @@ -261,6 +269,12 @@ 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; @@ -285,15 +299,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }finally{ wasm.pstack.restore(stack); } - 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; - + this.filename = fnJs; } __ptrMap.set(this, pDb); __stmtMap.set(this, Object.create(null)); @@ -384,12 +390,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 or a VFS which can resolve it must be specified. + sqlite3 VFS. - 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. + 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 second argument specifies the open/create mode for the database. It must be string containing a sequence of letters (in @@ -802,9 +808,6 @@ 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); @@ -926,15 +929,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 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. + 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. ACHTUNG: The callback MUST NOT modify the Stmt object. Calling any of the Stmt.get() variants, Stmt.getColumnName(), or @@ -967,23 +970,20 @@ 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. 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. + statement to the array would be downright unhelpful. B) An integer, indicating a zero-based column in the result - row. Only that one single value, in JS form, will be passed on. + row. Only that one single value 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. These keys are - case-sensitive so must match the case used in the + and pass that field's value to the callback. Note that 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 that - `$` is a legal identifier character in JS so need not be + the check is not performed until rows are fetched). Note also + that `$` is a legal identifier character in JS so need not be quoted. Any other `rowMode` value triggers an exception. @@ -1023,7 +1023,6 @@ 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); @@ -1047,7 +1046,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_v3() returns, pzTail + space for the SQL (pSql). When prepare_v2() returns, pzTail will point to somewhere in pSql. */ let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql); const ppStmt = wasm.scopedAlloc( @@ -1059,8 +1058,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.poke8(wasm.ptr.add(pSql, sqlByteLen), 0/*NUL terminator*/); - while(pSql && wasm.peek8(pSql) + wasm.poke(wasm.ptr.add(pSql, sqlByteLen), 0/*NUL terminator*/); + while(pSql && wasm.peek(pSql, 'i8') /* 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 @@ -1124,7 +1123,6 @@ 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*/ @@ -1141,132 +1139,6 @@ 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 @@ -2423,6 +2295,55 @@ 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 c53acee76..069f3fdb5 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -27,14 +27,13 @@ /** 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 and passed configuration details - for the current environment. + 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`. This function is not intended for client-level use. It is intended for use in creating bundles configured for specific WASM - 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. + environments. This function expects a configuration object, intended to abstract away details specific to any given WASM environment, primarily so @@ -94,10 +93,9 @@ 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. This is only - used in WASMFS-capable builds of the library (which the canonical - builds do not include). + - `wasmfsOpfsDir`[^1]: Specifies the "mount point" of the OPFS-backed + filesystem in WASMFS-capable builds. + [^1] = This property may optionally be a function, in which case this function calls that function to fetch the value, @@ -127,8 +125,7 @@ Both sqlite3ApiBootstrap.defaultConfig and globalThis.sqlite3ApiConfig get deleted by sqlite3ApiBootstrap() because any changes to them made after that point would have no - useful effect. This function also deletes itself from globalThis - when it's called. + useful effect. This function returns a Promise to the sqlite3 namespace object, which resolves after the async pieces of the library init are @@ -180,6 +177,14 @@ 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 @@ -752,11 +757,6 @@ 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,10 +796,25 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( /** wasm.X properties which are used for configuring the wasm - environment via whwashutil.js. This object gets fleshed out with - a number of WASM-specific utilities, in sqlite3-api-glue.c-pp.js. + environment via whwashutil.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. @@ -810,9 +825,8 @@ 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 so the client must pass it in via - config.memory. + the other way around. In this case, the memory is not + available via this.exports.memory. */ memory: config.memory || config.exports['memory'] @@ -820,29 +834,6 @@ 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. @@ -892,7 +883,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 (or 0n) on allocation error. + exceptions, instead returning 0 on allocation error. */ realloc: undefined/*installed later*/, @@ -958,11 +949,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( }; wasm.realloc.impl = wasm.exports[keyRealloc]; wasm.dealloc = function f(m){ - 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. */; + f.impl(wasm.ptr.coerce(m)/*tag:64bit*/); }; wasm.dealloc.impl = wasm.exports[keyDealloc]; } @@ -1033,18 +1020,18 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( }/*compileOptionUsed()*/; /** - 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. + 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. 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 pattern: + It has only a single intended usage: ``` const stackPos = pstack.pointer; @@ -1061,11 +1048,13 @@ 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 (which are - pretty common when using sqlite3.capi). + limited to usage patterns like the one shown above. - The memory lives in the WASM heap and can be used with routines - such as wasm.poke() and wasm.heap8u().slice(). + 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(). */ wasm.pstack = Object.assign(Object.create(null),{ /** @@ -1139,7 +1128,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 pointer will refer to a 64-bit value, e.g. a + When a returned pointers 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 @@ -1207,9 +1196,6 @@ 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]) @@ -1244,6 +1230,8 @@ 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 @@ -1267,7 +1255,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( WASMFS capability requires a custom build. */ capi.sqlite3_wasmfs_opfs_dir = function(){ - if(undefined !== this.dir) return this.dir; + if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir; // If we have no OPFS, there is no persistent dir const pdir = config.wasmfsOpfsDir; if(!pdir @@ -1275,21 +1263,21 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( || !globalThis.FileSystemDirectoryHandle || !globalThis.FileSystemFileHandle || !wasm.exports.sqlite3__wasm_init_wasmfs){ - return this.dir = ""; + return __wasmfsOpfsDir = ""; } try{ if(pdir && 0===wasm.xCallWrapped( 'sqlite3__wasm_init_wasmfs', 'i32', ['string'], pdir )){ - return this.dir = pdir; + return __wasmfsOpfsDir = pdir; }else{ - return this.dir = ""; + return __wasmfsOpfsDir = ""; } }catch(e){ // sqlite3__wasm_init_wasmfs() is not available - return this.dir = ""; + return __wasmfsOpfsDir = ""; } - }.bind(Object.create(null)); + }; /** Returns true if sqlite3.capi.sqlite3_wasmfs_opfs_dir() is a @@ -1345,7 +1333,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( */ capi.sqlite3_js_vfs_list = function(){ const rc = []; - let pVfs = capi.sqlite3_vfs_find(wasm.ptr.null); + let pVfs = capi.sqlite3_vfs_find(wasm.ptr.coerce(0)); while(pVfs){ const oVfs = new capi.sqlite3_vfs(pVfs); rc.push(wasm.cstrToJs(oVfs.$zName)); @@ -1413,7 +1401,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( or not provided, then "main" is assumed. */ capi.sqlite3_js_db_vfs = - (dbPointer, dbName=wasm.ptr.null)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName); + (dbPointer, dbName=0)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName); /** A thin wrapper around capi.sqlite3_aggregate_context() which @@ -1609,6 +1597,86 @@ 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(). @@ -1885,58 +1953,55 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( return (0===v) ? undefined : capi.sqlite3_value_to_js(v, throwIfCannotConvert); }; - 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)); + /** + 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'); + /** + 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'); - /** - 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'); + /** + 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 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'); + /** + 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 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*/ + 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'); + + /** + The sqlite3changeset_old() counterpart of + sqlite3changeset_new_js(), with an identical interface. + */ + capi.sqlite3changeset_old_js = + (pChangesetIter, iCol)=>__newOldValue(pChangesetIter, iCol, + 'sqlite3changeset_old'); /* The remainder of the API will be set up in later steps. */ const sqlite3 = { @@ -1948,10 +2013,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. 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. 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. This object is initially a placeholder which gets replaced by a build-generated object. @@ -1976,7 +2041,9 @@ 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. + considered non-fatal. If called more than once, the second and + subsequent calls are no-ops which return a pre-resolved + Promise. Ideally this function is called as part of the Promise chain which handles the loading and bootstrapping of the API. If not @@ -1993,14 +2060,18 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( */ asyncPostInit: async function ff(){ if(ff.isReady instanceof Promise) return ff.isReady; - let lia = this.initializersAsync; - delete this.initializersAsync; + let lia = sqlite3ApiBootstrap.initializersAsync; + delete sqlite3ApiBootstrap.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; @@ -2019,7 +2090,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 @@ -2034,9 +2105,6 @@ 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); @@ -2049,34 +2117,16 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( } delete sqlite3ApiBootstrap.initializers; sqlite3ApiBootstrap.sqlite3 = sqlite3; - 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; + 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( iw ){ /* Metadata injected by the custom Module.instantiateWasm() in pre-js.c-pp.js. */ @@ -2085,21 +2135,10 @@ 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)=>{ - if( 'undefined'!==typeof sqlite3InitScriptInfo/* from post-js-header.js */ ){ - sqlite3InitScriptInfo.debugModule( - "sqlite3.asyncPostInit() complete", s - ); - } + sqlite3InitScriptInfo.debugModule( + "sqlite3.asyncPostInit() complete", sqlite3 + ); 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 dd32f4666..482989463 100644 --- a/ext/wasm/api/sqlite3-license-version-header.js +++ b/ext/wasm/api/sqlite3-license-version-header.js @@ -1,5 +1,4 @@ -/* @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 79fc47393..e10d0dd50 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 deleted file mode 100644 index e3fb72287..000000000 --- a/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js +++ /dev/null @@ -1,2095 +0,0 @@ -/* - 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<nV ){ - toss3(capi.SQLITE_RANGE, - "xRcrdRead()",jzClass,jXKey, - "input buffer is too small: need", - nV,"but have",nBuf); - } - if( 0 ){ - debug("xRcrdRead", nBuf, zClass, wasm.cstrToJs(zClass), - wasm.cstrToJs(zKey), nV, jV, store); - } - const zV = cache.memBuffer(0); - //if( !zV ) return -3 /*OOM*/; - const heap = wasm.heap8(); - let i; - for(i = 0; i < nV; ++i){ - heap[wasm.ptr.add(zV,i)] = jV.codePointAt(i) & 0xFF; - } - heap.copyWithin( - Number(zBuf), Number(zV), wasm.ptr.addn(zV, i) - ); - heap[wasm.ptr.add(zBuf, nV)] = 0; - return nBuf; - }catch(e){ - error("kvrecordRead()",e); - cache.setError(e); - return -2; - } - }, - - xRcrdWrite: (zClass, zKey, zData)=>{ - 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 c1fee5e1a..69be338b0 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(wasm.ptr.add(pOut,nOut,-1), 0); + if(n > nOut) wasm.poke8(pOut + nOut - 1, 0); }catch(e){ return capi.SQLITE_NOMEM; }finally{ @@ -410,7 +410,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*vfsMethods*/; /** - Creates, initializes, and returns an sqlite3_vfs instance for an + Creates and initializes 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,9 +1157,8 @@ 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 for - each distinct name and ignored for all subsequent calls with that - same name. + parts but it is only acknowledged for the very first call and + ignored for all subsequent calls. The options, in alphabetical order: @@ -1225,14 +1224,7 @@ 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. (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.) + doing such tricks. - 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 ffa90ed06..2b636460d 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. + after sqlite3-api-oo1.js and before sqlite3-api-cleanup.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 this thread or the + recent serialize() operation (from 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 = !(capi.SQLITE_OPEN_CREATE & flags) + fh.readOnly = !(sqlite3.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 80f4bfac2..4c2338fc5 100644 --- a/ext/wasm/api/sqlite3-vtab-helper.c-pp.js +++ b/ext/wasm/api/sqlite3-vtab-helper.c-pp.js @@ -172,7 +172,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Works like unget() plus it calls dispose() on the StructType object. */ - dispose: (pCObj)=>__xWrap(pCObj,true)?.dispose?.() + dispose: (pCObj)=>{ + const o = __xWrap(pCObj,true); + if(o) o.dispose(); + } }); }; diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index dbfcdb704..4d5e9b296 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -93,18 +93,6 @@ #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 @@ -148,8 +136,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: see -** ./EXPORTED_FUNCTIONS.c-pp. +** any C functions from the WASM exports which are listed in +** ./EXPORT_FUNCTIONS.sqlite3-extras. */ #ifdef SQLITE_WASM_BARE_BONES # undef SQLITE_ENABLE_COLUMN_METADATA @@ -229,18 +217,15 @@ ** not by client code, so an argument can be made for reducing their ** visibility by not including them in any build-time export lists. ** -** 2025-12-01: for use in non-Emscripten builds, we need a more -** invasive macro which explicitly names the export: -** SQLITE_WASM_EXPORT2. +** 2022-09-11: it's not yet _proven_ that this approach works in +** non-Emscripten builds. If not, such builds will need to export +** those using the --export=... wasm-ld flag (or equivalent). As of +** this writing we are tied to Emscripten for various reasons +** and cannot test the library with other build environments. */ #define SQLITE_WASM_EXPORT __attribute__((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 +// See also: +//__attribute__((export_name("theExportedName"), used, visibility("default"))) /* ** Which sqlite3.c we're using needs to be configurable to enable @@ -264,6 +249,45 @@ #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 @@ -302,7 +326,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) /* 8-byte aligned */); + assert(0==((unsigned long long)p & 0x7)); if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){ PStack.pPos = p; } @@ -312,10 +336,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 call for -** doing so). +** JS code from having to do so, and most uses of the pstack will +** call for doing so). */ -SQLITE_WASM_EXPORT2(void *,sqlite3__wasm_pstack_alloc,(int n)){ +SQLITE_WASM_EXPORT 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*/ @@ -327,7 +351,7 @@ SQLITE_WASM_EXPORT2(void *,sqlite3__wasm_pstack_alloc,(int n)){ ** Return the number of bytes left which can be ** sqlite3__wasm_pstack_alloc()'d. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_pstack_remaining,(void)){ +SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_remaining(void){ assert(PStack.pPos >= PStack.pBegin); assert(PStack.pPos <= PStack.pEnd); return (int)(PStack.pPos - PStack.pBegin); @@ -338,7 +362,7 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_pstack_remaining,(void)){ ** any space which is currently allocated. This value is a ** compile-time constant. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_pstack_quota,(void)){ +SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_quota(void){ return (int)(PStack.pEnd - PStack.pBegin); } @@ -351,7 +375,8 @@ struct WasmTestStruct { void (*xFunc)(void*); }; typedef struct WasmTestStruct WasmTestStruct; -SQLITE_WASM_EXPORT2(void,sqlite3__wasm_test_struct,(WasmTestStruct * s)){ +SQLITE_WASM_EXPORT +void sqlite3__wasm_test_struct(WasmTestStruct * s){ if(s){ if( 0 ){ /* Do not be alarmed by the small (and odd) pointer values. @@ -391,7 +416,8 @@ SQLITE_WASM_EXPORT2(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_EXPORT2(const char *,sqlite3__wasm_enum_json,(void)){ +SQLITE_WASM_EXPORT +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 @@ -594,7 +620,6 @@ SQLITE_WASM_EXPORT2(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); @@ -982,15 +1007,13 @@ SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_enum_json,(void)){ /** ^^^ indirection needed to expand CurrentStruct */ #define StructBinder StructBinder_(CurrentStruct) #define _StructBinder CloseBrace(2) -#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) +#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) nStruct = 0; out(", \"structs\": ["); { @@ -1053,30 +1076,11 @@ SQLITE_WASM_EXPORT2(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(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"); + M(xRead, "i(sspi)"); + M(xWrite, "i(sss)"); + M(xDelete, "i(ss)"); + M(nKeySize, "i"); } _StructBinder; #undef CurrentStruct @@ -1133,13 +1137,7 @@ SQLITE_WASM_EXPORT2(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; @@ -1234,8 +1232,6 @@ SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_enum_json,(void)){ #undef StructBinder_ #undef StructBinder__ #undef M -#undef MRO -#undef M3 #undef _StructBinder #undef CloseBrace #undef out @@ -1253,7 +1249,8 @@ SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_enum_json,(void)){ ** method, SQLITE_MISUSE is returned, else the result of the xDelete() ** call is returned. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vfs_unlink,(sqlite3_vfs *pVfs, const char *zName)){ +SQLITE_WASM_EXPORT +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 ){ @@ -1270,7 +1267,8 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vfs_unlink,(sqlite3_vfs *pVfs, const char ** defaulting to "main" if zDbName is 0. Returns 0 if no db with the ** given name is open. */ -SQLITE_WASM_EXPORT2(sqlite3_vfs *,sqlite3__wasm_db_vfs,(sqlite3 *pDb, const char *zDbName)){ +SQLITE_WASM_EXPORT +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); @@ -1292,7 +1290,8 @@ SQLITE_WASM_EXPORT2(sqlite3_vfs *,sqlite3__wasm_db_vfs,(sqlite3 *pDb, const char ** Returns 0 on success, an SQLITE_xxx code on error. Returns ** SQLITE_MISUSE if pDb is NULL. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_reset,(sqlite3 *pDb)){ +SQLITE_WASM_EXPORT +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); @@ -1373,10 +1372,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_EXPORT2(int,sqlite3__wasm_db_serialize, - (sqlite3 *pDb, const char *zSchema, - unsigned char **pOut, - sqlite3_int64 *nOut, unsigned int mFlags)){ +SQLITE_WASM_EXPORT +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; @@ -1437,9 +1436,11 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_serialize, ** portability, so that the API can still work in builds where BigInt ** support is disabled or unavailable. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vfs_create_file, - (sqlite3_vfs *pVfs, const char *zFilename, - const unsigned char * pData, int nData)){ +SQLITE_WASM_EXPORT +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; @@ -1525,9 +1526,10 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vfs_create_file, ** zFilename, appends pData bytes to it, and returns 0 on success or ** SQLITE_IOERR on error. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_posix_create_file, - (const char *zFilename, const unsigned char * pData, - int nData)){ +SQLITE_WASM_EXPORT +int sqlite3__wasm_posix_create_file( const char *zFilename, + const unsigned char * pData, + int nData ){ int rc; FILE * pFile = 0; int fileExisted = 0; @@ -1547,30 +1549,22 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_posix_create_file, ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** -** This returns either a pointer to a static buffer or zKeyIn directly -** (if zClass is NULL or empty). +** 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. */ -SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_kvvfsMakeKey, - (const char *zClass, const char *zKeyIn)){ - static char buf[SQLITE_KVOS_SZ+1] = {0}; +SQLITE_WASM_EXPORT +char * sqlite3__wasm_kvvfsMakeKeyOnPstack(const char *zClass, + const char *zKeyIn){ assert(sqlite3KvvfsMethods.nKeySize>24); - 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 + char *zKeyOut = + (char *)sqlite3__wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize); + if(zKeyOut){ + kvstorageMakeKey(zClass, zKeyIn, zKeyOut); } + return zKeyOut; } /* @@ -1580,7 +1574,8 @@ SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_kvvfsMakeKey, ** Returns the pointer to the singleton object which holds the kvvfs ** I/O methods and associated state. */ -SQLITE_WASM_EXPORT2(sqlite3_kvvfs_methods *,sqlite3__wasm_kvvfs_methods,(void)){ +SQLITE_WASM_EXPORT +sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){ return &sqlite3KvvfsMethods; } @@ -1595,8 +1590,8 @@ SQLITE_WASM_EXPORT2(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_EXPORT2(int,sqlite3__wasm_vtab_config, - (sqlite3 *pDb, int op, int arg)){ +SQLITE_WASM_EXPORT +int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){ switch(op){ case SQLITE_VTAB_DIRECTONLY: case SQLITE_VTAB_INNOCUOUS: @@ -1616,8 +1611,8 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vtab_config, ** Wrapper for the variants of sqlite3_db_config() which take ** (int,int*) variadic args. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_ip, - (sqlite3 *pDb, int op, int arg1, int* pArg2)){ +SQLITE_WASM_EXPORT +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: @@ -1652,9 +1647,8 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_ip, ** Wrapper for the variants of sqlite3_db_config() which take ** (void*,int,int) variadic args. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_pii, - (sqlite3 *pDb, int op, void * pArg1, int arg2, - int arg3)){ +SQLITE_WASM_EXPORT +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); @@ -1669,8 +1663,8 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_pii, ** Wrapper for the variants of sqlite3_db_config() which take ** (const char *) variadic args. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_s,(sqlite3 *pDb, int op, - const char *zArg)){ +SQLITE_WASM_EXPORT +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); @@ -1686,7 +1680,8 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_s,(sqlite3 *pDb, int op, ** Binding for combinations of sqlite3_config() arguments which take ** a single integer argument. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_config_i,(int op, int arg)){ +SQLITE_WASM_EXPORT +int sqlite3__wasm_config_i(int op, int arg){ return sqlite3_config(op, arg); } @@ -1697,7 +1692,8 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_config_i,(int op, int arg)){ ** Binding for combinations of sqlite3_config() arguments which take ** two int arguments. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_config_ii,(int op, int arg1, int arg2)){ +SQLITE_WASM_EXPORT +int sqlite3__wasm_config_ii(int op, int arg1, int arg2){ return sqlite3_config(op, arg1, arg2); } @@ -1708,7 +1704,8 @@ SQLITE_WASM_EXPORT2(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_EXPORT2(int,sqlite3__wasm_config_j,(int op, sqlite3_int64 arg)){ +SQLITE_WASM_EXPORT +int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){ return sqlite3_config(op, arg); } @@ -1720,7 +1717,8 @@ SQLITE_WASM_EXPORT2(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_EXPORT2(char *,sqlite3__wasm_qfmt_token,(char *z, int addQuotes)){ +SQLITE_WASM_EXPORT +char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){ char * rc = 0; if( z ){ rc = addQuotes @@ -1730,21 +1728,6 @@ SQLITE_WASM_EXPORT2(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 <emscripten/console.h> #include <emscripten/wasmfs.h> @@ -1770,7 +1753,8 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_kvvfs_encode,(const char *a, int nA, char ** the virtual FS fails. In builds compiled without SQLITE_ENABLE_WASMFS ** defined, SQLITE_NOTFOUND is returned without side effects. */ -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_init_wasmfs,(const char *zMountPoint)){ +SQLITE_WASM_EXPORT +int sqlite3__wasm_init_wasmfs(const char *zMountPoint){ static backend_t pOpfs = 0; if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs"; if( !pOpfs ){ @@ -1789,7 +1773,8 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_init_wasmfs,(const char *zMountPoint)){ return pOpfs ? 0 : SQLITE_NOMEM; } #else -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_init_wasmfs,(const char *zUnused)){ +SQLITE_WASM_EXPORT +int sqlite3__wasm_init_wasmfs(const char *zUnused){ //emscripten_console_warn("WASMFS OPFS is not compiled in."); (void)zUnused; return SQLITE_NOTFOUND; @@ -1798,43 +1783,52 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_init_wasmfs,(const char *zUnused)){ #if SQLITE_WASM_ENABLE_C_TESTS -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_test_intptr,(int * p)){ +SQLITE_WASM_EXPORT +int sqlite3__wasm_test_intptr(int * p){ return *p = *p * 2; } -SQLITE_WASM_EXPORT2(void *,sqlite3__wasm_test_voidptr,(void * p)){ +SQLITE_WASM_EXPORT +void * sqlite3__wasm_test_voidptr(void * p){ return p; } -SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64_max,(void)){ +SQLITE_WASM_EXPORT +int64_t sqlite3__wasm_test_int64_max(void){ return (int64_t)0x7fffffffffffffff; } -SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64_min,(void)){ +SQLITE_WASM_EXPORT +int64_t sqlite3__wasm_test_int64_min(void){ return ~sqlite3__wasm_test_int64_max(); } -SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64_times2,(int64_t x)){ +SQLITE_WASM_EXPORT +int64_t sqlite3__wasm_test_int64_times2(int64_t x){ return x * 2; } -SQLITE_WASM_EXPORT2(void,sqlite3__wasm_test_int64_minmax,(int64_t * min, int64_t *max)){ +SQLITE_WASM_EXPORT +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_EXPORT2(int64_t,sqlite3__wasm_test_int64ptr,(int64_t * p)){ +SQLITE_WASM_EXPORT +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_EXPORT2(void,sqlite3__wasm_test_stack_overflow,(int recurse)){ +SQLITE_WASM_EXPORT +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_EXPORT2(char *,sqlite3__wasm_test_str_hello,(int fail)){ +SQLITE_WASM_EXPORT +char * sqlite3__wasm_test_str_hello(int fail){ char * s = fail ? 0 : (char *)sqlite3_malloc(6); if(s){ memcpy(s, "hello", 5); @@ -1948,8 +1942,8 @@ static int sqlite3__wasm_SQLTester_strnotglob(const char *zGlob, const char *z){ return *z==0; } -SQLITE_WASM_EXPORT2(int,sqlite3__wasm_SQLTester_strglob, - (const char *zGlob, const char *z)){ +SQLITE_WASM_EXPORT +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 b282c5e6e..1a09bf9a6 100644 --- a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -19,12 +19,10 @@ slightly simpler client-side interface than the slightly-lower-level Worker API does. - In non-ESM builds this file necessarily exposes one global symbol, - but clients may freely `delete` that symbol after calling it. + This script 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 @@ -111,12 +109,10 @@ 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 @@ -127,9 +123,10 @@ 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. 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. Note that the rows + arrive via worker-posted messages, with all the implications + of that. Notable shortcomings: @@ -260,9 +257,7 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = { type: 'module' }); //#elif target:es6-module - return new Worker(new URL("sqlite3-worker1.mjs", import.meta.url),{ - type: 'module' - }); + return new Worker(new URL("sqlite3-worker1.js", import.meta.url)); //#else let theJs = "sqlite3-worker1.js"; if(this.currentScript){ @@ -286,7 +281,7 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = { }) //#endif , - onerror: (...args)=>console.error('sqlite3Worker1Promiser():',...args) + onerror: (...args)=>console.error('worker1 promiser error',...args) }/*defaultConfig*/; /** @@ -348,7 +343,6 @@ 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 db27c8fc0..036c4c6ea 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 sqlite3InitModule from './sqlite3-bundler-friendly.mjs'; +import {default as sqlite3InitModule} from './sqlite3-bundler-friendly.mjs'; //#elif target:es6-module -import sqlite3InitModule from './sqlite3.mjs'; + return new Worker(new URL("sqlite3.js", import.meta.url)); //#else "use strict"; { diff --git a/ext/wasm/c-pp-lite.c b/ext/wasm/c-pp-lite.c index b8d67f6a3..2120c457d 100644 --- a/ext/wasm/c-pp-lite.c +++ b/ext/wasm/c-pp-lite.c @@ -270,10 +270,9 @@ 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. If bRaw is true then the file's contents are passed through -** verbatim, rather than being preprocessed. +** error. */ -static void cmpp_process_file(const char * zName, int bRaw); +static void cmpp_process_file(const char * zName); /* ** Operator policy for cmpp_kvp_parse(). @@ -971,6 +970,7 @@ 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,23 +2213,12 @@ 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 || t->args.argc>3){ - cmpp_kwd__err(pKw, t, "Expected args: ?-raw? filename"); - } - 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]; + else if(t->args.argc!=2){ + cmpp_kwd__err(pKw, t, "Expecting exactly 1 filename argument"); } - if(!bRaw && db_including_has(zFile)){ + zFile = (const char *)t->args.argv[1]; + if(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 @@ -2240,15 +2229,16 @@ static void cmpp_kwd_include(CmppKeyword const * pKw, CmppTokenizer *t){ } zResolved = db_include_search(zFile); if(zResolved){ - if( bRaw ) db_including_add(zFile, t->zName, t->token.lineNo); - cmpp_process_file(zResolved, bRaw); - if( bRaw ) db_include_rm(zFile); + db_including_add(zFile, t->zName, t->token.lineNo); + cmpp_process_file(zResolved); + 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) ){ @@ -2402,7 +2392,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), 1, TT_Include, cmpp_kwd_include}, + {S(Include), 0, 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}, @@ -2454,25 +2444,14 @@ void cmpp_process_string(const char * zName, g.tok = oldTok; } -void cmpp_process_file(const char * zName, int bRaw){ +void cmpp_process_file(const char * zName){ 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 ){ - 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); - } + cmpp_process_string(zName, fw.zContent, fw.nContent); } g_FileWrapper_close(&fw); } @@ -2601,21 +2580,6 @@ 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; @@ -2653,7 +2617,6 @@ 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; @@ -2711,7 +2674,7 @@ int main(int argc, char const * const * argv){ DOIT { ++nFile; g_out_open; - cmpp_process_file(zVal, 0); + cmpp_process_file(zVal); } } ISFLAG("e"){ @@ -2794,7 +2757,7 @@ int main(int argc, char const * const * argv){ ++inclCount; } FileWrapper_open(&g.out, g.out.zName, "w"); - cmpp_process_file("-", 0); + cmpp_process_file("-"); } } } diff --git a/ext/wasm/common/SqliteTestUtil.js b/ext/wasm/common/SqliteTestUtil.js index a817b79f8..2c17824c5 100644 --- a/ext/wasm/common/SqliteTestUtil.js +++ b/ext/wasm/common/SqliteTestUtil.js @@ -44,8 +44,7 @@ /** abort() if expr is false. If expr is a function, it is called and its result is evaluated. */ - assert: function f(expr, ...msg){ - msg = msg?.join?.(' '); + assert: function f(expr, msg){ 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 bca05a1ee..1c678f31f 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -16,35 +16,27 @@ More specifically: - https://fossil.wanderinghorse.net/r/jaccwabyt/dir/wasmutil + https://fossil.wanderinghorse.net/r/jaccwabyt/file/common/whwasmutil.js 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 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. + 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. 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. That said: no specific incompatibilities with, - e.g., node.js are known (whereas it is known that some folks - use this with node.js). + crutches for them. Intended usage: @@ -225,9 +217,7 @@ newly-created (or config-provided) target. The current approach seemed better at the time. */ -'use strict'; -globalThis.WhWasmUtilInstaller = -function WhWasmUtilInstaller(target){ +globalThis.WhWasmUtilInstaller = function(target){ 'use strict'; if(undefined===target.bigIntEnabled){ target.bigIntEnabled = !!globalThis['BigInt64Array']; @@ -237,14 +227,6 @@ function WhWasmUtilInstaller(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. @@ -677,14 +659,12 @@ function WhWasmUtilInstaller(target){ const ft = target.functionTable(); const oldLen = __asPtrType(ft.length); let ptr; - while( (ptr = cache.freeFuncIndexes.pop()) ){ - if(ft.get(ptr)){ - /* freeFuncIndexes's entry is stale. Table was modified via a - different API */ + while(cache.freeFuncIndexes.length){ + ptr = cache.freeFuncIndexes.pop(); + if(ft.get(ptr)){ /* Table was modified via a different API */ ptr = null; continue; }else{ - /* This index is free. We'll re-use it. */ break; } } @@ -775,10 +755,10 @@ function WhWasmUtilInstaller(target){ has no side effects and returns undefined. */ target.uninstallFunction = function(ptr){ - if(!ptr && __NullPtr!==ptr) return undefined; - + if(!ptr && 0!==ptr) return undefined; + const fi = cache.freeFuncIndexes; const ft = target.functionTable(); - cache.freeFuncIndexes.push(ptr); + fi.push(ptr); const rc = ft.get(ptr); ft.set(ptr, null); return rc; @@ -1016,12 +996,12 @@ function WhWasmUtilInstaller(target){ target.heap8u(). */ target.cstrlen = function(ptr){ - if(!ptr || !target.isPtr/*64*/(ptr)) return null; + if(!ptr || !target.isPtr(ptr)) return null; ptr = Number(ptr) /*tag:64bit*/; const h = heapWrappers().HEAP8U; let pos = ptr; for( ; h[pos] !== 0; ++pos ){} - return pos - ptr; + return Number(pos - ptr); }; /** Internal helper to use in operations which need to distinguish @@ -2472,8 +2452,7 @@ function WhWasmUtilInstaller(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 - if exports.malloc exists. + wrappers for the exports `malloc` and `free` functions. It returns a function which, when called, initiates loading of the module and returns a Promise. When that Promise resolves, it calls @@ -2496,9 +2475,7 @@ function WhWasmUtilInstaller(target){ Error handling is up to the caller, who may attach a `catch()` call to the promise. */ -globalThis.WhWasmUtilInstaller -.yawl = function yawl(config){ - 'use strict'; +globalThis.WhWasmUtilInstaller.yawl = function(config){ const wfetch = ()=>fetch(config.uri, {credentials: 'same-origin'}); const wui = this; const finalThen = function(arg){ @@ -2523,7 +2500,7 @@ globalThis.WhWasmUtilInstaller tgt.alloc = function(n){ return exports.malloc(n) || toss("Allocation of",n,"bytes failed."); }; - tgt.dealloc = function(m){m && exports.free(m)}; + tgt.dealloc = function(m){exports.free(m)}; } wui(tgt); } @@ -2542,6 +2519,4 @@ globalThis.WhWasmUtilInstaller .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 e3ab5a9e5..587aa9cc5 100644 --- a/ext/wasm/demo-jsstorage.js +++ b/ext/wasm/demo-jsstorage.js @@ -16,7 +16,7 @@ */ 'use strict'; (function(){ - const T = globalThis.SqliteTestUtil; + const T = self.SqliteTestUtil; const toss = function(...args){throw new Error(args.join(' '))}; const debug = console.debug.bind(console); const eOutput = document.querySelector('#test-output'); @@ -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(globalThis.sqlite3TestModule).then((sqlite3)=>{ + sqlite3InitModule(self.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 a1005beb9..e0b487bdf 100644 --- a/ext/wasm/demo-worker1-promiser.c-pp.html +++ b/ext/wasm/demo-worker1-promiser.c-pp.html @@ -6,10 +6,10 @@ <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"> <link rel="stylesheet" href="common/emscripten.css"/> <link rel="stylesheet" href="common/testing.css"/> -//#if target:es6-module - <title>Worker1-promiser (ESM) tests</title> +//#if target=es6-module + <title>worker-promise (via ESM) tests</title> //#else - <title>Worker1-promiser tests</title> + <title>worker-promise tests</title> //#endif </head> <body> @@ -32,7 +32,7 @@ <hr> <div id='test-output'></div> <script src="common/SqliteTestUtil.js"></script> -//#if target:es6-module +//#if target=es6-module <script src="demo-worker1-promiser.mjs" type="module"></script> //#else <script src="jswasm/sqlite3-worker1-promiser.js"></script> diff --git a/ext/wasm/demo-worker1.js b/ext/wasm/demo-worker1.js index 348741bf8..1a05cc7ac 100644 --- a/ext/wasm/demo-worker1.js +++ b/ext/wasm/demo-worker1.js @@ -18,7 +18,7 @@ */ 'use strict'; (function(){ - const T = globalThis.SqliteTestUtil; + const T = self.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); - globalThis.sqlite3TestModule.setStatus(null); + self.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."); - globalThis.SW = SW; + self.SW = SW; })(); diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js index 4b1ea2c53..a5f3e25b7 100644 --- a/ext/wasm/fiddle/fiddle-worker.js +++ b/ext/wasm/fiddle/fiddle-worker.js @@ -175,6 +175,10 @@ "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.c-pp.html b/ext/wasm/fiddle/index.html similarity index 95% rename from ext/wasm/fiddle/index.c-pp.html rename to ext/wasm/fiddle/index.html index 1f818286b..378cb3902 100644 --- a/ext/wasm/fiddle/index.c-pp.html +++ b/ext/wasm/fiddle/index.html @@ -5,29 +5,20 @@ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>SQLite3 Fiddle</title> <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"> -//#if jqterm <!-- To add a terminal-style view using jquery.terminal[^1], uncomment the following two HTML lines and ensure that these files are on the web server. - jquery.terminal.bundle.min.js is a concatenation of jquery.min.js from + jquery-bundle.min.js is a concatenation of jquery.min.js from [^2] and jquery.terminal.min.js from [^1]. - jquery.terminal.min.css is from [^1]. Alterntely, jquery-VERSION.min.js - can be found in jquery.terminal's source tree. - - Fiddle automatically enables support for this if it's installed. - - In the canonical build process, if the JQTERM env var is set - to a dir containing a clone of [^1] then this block gets - enabled and a copy of [^1] is integrated automatically. + jquery.terminal.min.css is from [^1]. [^1]: https://github.com/jcubic/jquery.terminal [^2]: https://jquery.com --> - <script src="jqterm/jquery.terminal.bundle.min.js"></script> - <link rel="stylesheet" href="jqterm/jquery.terminal.min.css"> -//#endif + <!--script src="jqterm/jqterm-bundle.min.js"></script> + <link rel="stylesheet" href="jqterm/jquery.terminal.min.css"--> <style> /* The following styles are for app-level use. */ :root { diff --git a/ext/wasm/index.html b/ext/wasm/index.html index fd65dbc33..55e4cdb75 100644 --- a/ext/wasm/index.html +++ b/ext/wasm/index.html @@ -105,11 +105,6 @@ (<a href='speedtest1-worker.html?size=15'>32-bit</a>, <a href='speedtest1-worker-64bit.html?size=15'>64-bit</a>): an interactive Worker-thread variant of speedtest1.</li> - <li>speedtest1-worker?vfs=kvvfs - (<a href='speedtest1-worker.html?vfs=kvvfs&size=10'>32-bit</a>, - <a href='speedtest1-worker-64bit.html?vfs=kvvfs&size=10'>64-bit</a>): - speedtest1-worker with the - kvvfs VFS preselected and configured for a moderate workload.</li> <li>speedtest1-worker?vfs=opfs (<a href='speedtest1-worker.html?vfs=opfs&size=10'>32-bit</a>, <a href='speedtest1-worker-64bit.html?vfs=opfs&size=10'>64-bit</a>): diff --git a/ext/wasm/jaccwabyt/jaccwabyt.js b/ext/wasm/jaccwabyt/jaccwabyt.js index 6fa86c341..8ea08e213 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.js +++ b/ext/wasm/jaccwabyt/jaccwabyt.js @@ -16,17 +16,9 @@ Project homes: - https://fossil.wanderinghorse.net/r/jaccwabyt - https://sqlite.org/src/dir/ext/wasm/jaccwabyt - - This build was generated using: - - ./c-pp -o js/jaccwabyt.js -@policy=error jaccwabyt/jaccwabyt.c-pp.js - - by libcmpp 2.x 2fc4afc31f6505c27b9c34988973a2bd9b157d559247cdd26868ae75632c3a5e @ 2025-11-16 23:03:27.352 UTC */ 'use strict'; -globalThis.Jaccwabyt = -function StructBinderFactory(config){ - 'use strict'; +globalThis.Jaccwabyt = function StructBinderFactory(config){ /* ^^^^ it is recommended that clients move that object into wherever they'd like to have it and delete the globalThis-held copy. This API does not require the global reference - it is simply installed @@ -38,55 +30,35 @@ function StructBinderFactory(config){ all args with a space between each. */ const toss = (...args)=>{throw new Error(args.join(' '))}; - { - let h = config.heap; - if( h instanceof WebAssembly.Memory ){ - h = function(){return new Uint8Array(this.buffer)}.bind(h); - }else if( !(h instanceof Function) ){ - //console.warn("The bothersome StructBinderFactory config:",config); - toss("config.heap must be WebAssembly.Memory instance or", - "a function which returns one."); - } - config.heap = h; + if(!(config.heap instanceof WebAssembly.Memory) + && !(config.heap instanceof Function)){ + toss("config.heap must be WebAssembly.Memory instance or a function."); } ['alloc','dealloc'].forEach(function(k){ (config[k] instanceof Function) || toss("Config option '"+k+"' must be a function."); }); + const __heap = config.heap; const SBF = StructBinderFactory; - const heap = config.heap, + const heap = __heap ? __heap : ()=>new Uint8Array(__heap.buffer), alloc = config.alloc, dealloc = config.dealloc, - realloc = (config.realloc || function(){ - toss("This StructBinderFactory was configured without realloc()"); - /* We can't know the original memory's size from here unless - we internally proxy alloc()/dealloc() to track all - pointers (not going to happen), so we can't fall back to - doing alloc()/copy/dealloc(). */ - }), log = config.log || console.debug.bind(console), memberPrefix = (config.memberPrefix || ""), memberSuffix = (config.memberSuffix || ""), BigInt = globalThis['BigInt'], BigInt64Array = globalThis['BigInt64Array'], - bigIntEnabled = config.bigIntEnabled ?? !!BigInt64Array; - - //console.warn("config",config); - let ptr; - const ptrSize = config.pointerSize - || config.ptrSize/*deprecated*/ - || ('bigint'===typeof (ptr = alloc(1)) ? 8 : 4); - const ptrIR = config.pointerIR/*deprecated*/ + bigIntEnabled = config.bigIntEnabled ?? !!BigInt64Array, + ptrIR = config.pointerIR || config.ptrIR/*deprecated*/ - || (4===ptrSize ? 'i32' : 'i64'); - if( ptr ){ - dealloc(ptr); - ptr = undefined; - } - //console.warn("ptrIR =",ptrIR,"ptrSize =",ptrSize); + || 'i32', + /* Undocumented (on purpose) config options: */ + ptrSize = config.ptrSize/*deprecated*/ + || ('i32'===ptrIR ? 4 : 8) + ; - if(ptrSize!==4 && ptrSize!==8) toss("Invalid pointer size:",ptrSize); if(ptrIR!=='i32' && ptrIR!=='i64') toss("Invalid pointer representation:",ptrIR); + if(ptrSize!==4 && ptrSize!==8) toss("Invalid pointer size:",ptrSize); /** Either BigInt or, if !bigIntEnabled, a function which throws complaining that BigInt is not enabled. */ @@ -114,10 +86,6 @@ function StructBinderFactory(config){ return rc; }; - const __ptrAddSelf = function(...args){ - return __ptrAdd(this.pointer,...args); - }; - if(!SBF.debugFlags){ SBF.__makeDebugFlags = function(deriveFrom=null){ /* This is disgustingly overengineered. :/ */ @@ -147,12 +115,12 @@ function StructBinderFactory(config){ SBF.debugFlags = SBF.__makeDebugFlags(); }/*static init*/ - const isLittleEndian = true || (function() { + const isLittleEndian = (function() { const buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 256, true /* littleEndian */); // Int16Array uses the platform's endianness. return new Int16Array(buffer)[0] === 256; - })() /* WASM is, by definition, Little Endian */; + })(); /** Some terms used in the internal docs: @@ -168,14 +136,13 @@ function StructBinderFactory(config){ /** True if SIG s looks like a function signature, else false. */ const isFuncSig = (s)=>'('===s[1]; - /** True if SIG s is-a pointer-type signature. */ - const isPtrSig = (s)=>'p'===s || 'P'===s || 's'===s; + /** True if SIG s is-a pointer signature. */ + const isPtrSig = (s)=>'p'===s || 'P'===s; const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/; /** Returns p if SIG s is a function SIG, else returns s[0]. */ - const sigLetter = (s)=>s ? (isFuncSig(s) ? 'p' : s[0]) : undefined; - - /** Returns the WASM IR form of the letter at SIG s[0]. Throws for - an unknown SIG. */ + const sigLetter = (s)=>isFuncSig(s) ? 'p' : s[0]; + /** Returns the WASM IR form of the Emscripten-conventional letter + at SIG s[0]. Throws for an unknown SIG. */ const sigIR = function(s){ switch(sigLetter(s)){ case 'c': case 'C': return 'i8'; @@ -188,23 +155,8 @@ function StructBinderFactory(config){ toss("Unhandled signature IR:",s); }; - /** Returns the WASM sizeof of the letter at SIG s[0]. Throws for an - unknown SIG. */ - const sigSize = function(s){ - switch(sigLetter(s)){ - case 'c': case 'C': return 1; - case 'i': return 4; - case 'p': case 'P': case 's': return ptrSize; - case 'j': return 8; - case 'f': return 4; - case 'd': return 8; - } - toss("Unhandled signature sizeof:",s); - }; - const affirmBigIntArray = BigInt64Array ? ()=>true : ()=>toss('BigInt64Array is not available.'); - /** Returns the name of a DataView getter method corresponding to the given SIG. */ const sigDVGetter = function(s){ @@ -225,7 +177,6 @@ function StructBinderFactory(config){ } toss("Unhandled DataView getter for signature:",s); }; - /** Returns the name of a DataView setter method corresponding to the given SIG. */ const sigDVSetter = function(s){ @@ -277,44 +228,15 @@ function StructBinderFactory(config){ /** In order to completely hide StructBinder-bound struct pointers from JS code, we store them in a scope-local WeakMap which maps - the struct-bound objects to an object with their metadata: - - { - .p = the native pointer, - .o = self (for an eventual reverse-mapping), - .xb = extra bytes allocated for p, - .zod = zeroOnDispose, - .ownsPointer = true if this object owns p - } - - The .p data are accessible via obj.pointer, which is gated behind - a property interceptor, but are not exposed anywhere else in the - public API. + the struct-bound objects to their WASM pointers. The pointers are + accessible via boundObject.pointer, which is gated behind a + property interceptor, but are not exposed anywhere else in the + object. */ - const getInstanceHandle = function f(obj, create=true){ - let ii = f.map.get(obj); - if( !ii && create ){ - f.map.set(obj, (ii=f.create(obj))); - } - return ii; - }; - getInstanceHandle.map = new WeakMap; - getInstanceHandle.create = (forObj)=>{ - return Object.assign(Object.create(null),{ - o: forObj, - p: undefined/*native ptr*/, - ownsPointer: false, - zod: false/*zeroOnDispose*/, - xb: 0/*extraBytes*/ - }); - }; + const __instancePointerMap = new WeakMap(); - /** - Remove the getInstanceHandle() mapping for obj. - */ - const rmInstanceHandle = (obj)=>getInstanceHandle.map.delete(obj) - /* If/when we have a reverse map of ptr-to-objects, we need to - clean that here. */; + /** Property name for the pointer-is-external marker. */ + const xPtrPropName = '(pointer-is-external)'; const __isPtr32 = (ptr)=>('number'===typeof ptr && (ptr===(ptr|0)) && ptr>=0); const __isPtr64 = (ptr)=>( @@ -327,199 +249,83 @@ function StructBinderFactory(config){ */ const __isPtr = (4===ptrSize) ? __isPtr32 : __isPtr64; - const __isNonNullPtr = (v)=>__isPtr(v) && (v>0); - - /** Frees the obj.pointer memory (a.k.a. m), handles obj.ondispose, - and unmaps obj from its native resources. */ + /** Frees the obj.pointer memory and clears the pointer + property. */ const __freeStruct = function(ctor, obj, m){ - const ii = getInstanceHandle(obj, false); - if( !ii ) return; - rmInstanceHandle(obj); - if( !m && !(m = ii.p) ){ - console.warn("Cannot(?) happen: __freeStruct() found no instanceInfo"); - return; - } - if(Array.isArray(obj.ondispose)){ - let x; - while((x = obj.ondispose.pop())){ - try{ - if(x instanceof Function) x.call(obj); - else if(x instanceof StructType) x.dispose(); - else if(__isPtr(x)) dealloc(x); - // else ignore. Strings are permitted to annotate entries - // to assist in debugging. - }catch(e){ + if(!m) m = __instancePointerMap.get(obj); + if(m) { + __instancePointerMap.delete(obj); + if(Array.isArray(obj.ondispose)){ + let x; + while((x = obj.ondispose.shift())){ + try{ + if(x instanceof Function) x.call(obj); + else if(x instanceof StructType) x.dispose(); + else if(__isPtr(x)) dealloc(x); + // else ignore. Strings are permitted to annotate entries + // to assist in debugging. + }catch(e){ + console.warn("ondispose() for",ctor.structName,'@', + m,'threw. NOT propagating it.',e); + } + } + }else if(obj.ondispose instanceof Function){ + try{obj.ondispose()} + catch(e){ + /*do not rethrow: destructors must not throw*/ console.warn("ondispose() for",ctor.structName,'@', m,'threw. NOT propagating it.',e); } } - }else if(obj.ondispose instanceof Function){ - try{obj.ondispose()} - catch(e){ - /*do not rethrow: destructors must not throw*/ - console.warn("ondispose() for",ctor.structName,'@', - m,'threw. NOT propagating it.',e); - } - } - delete obj.ondispose; - if(ctor.debugFlags.__flags.dealloc){ - log("debug.dealloc:",(ii.ownsPointer?"":"EXTERNAL"), - ctor.structName,"instance:", - ctor.structInfo.sizeof,"bytes @"+m); - } - if(ii.ownsPointer){ - if( ii.zod || ctor.structInfo.zeroOnDispose ){ - heap().fill(0, Number(m), - Number(m) + ctor.structInfo.sizeof + ii.xb); + delete obj.ondispose; + if(ctor.debugFlags.__flags.dealloc){ + log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""), + ctor.structName,"instance:", + ctor.structInfo.sizeof,"bytes @"+m); } - dealloc(m); + if(!obj[xPtrPropName]) dealloc(m); } }; - /** Returns a skeleton for a read-only, non-iterable property - * descriptor. */ - const rop0 = ()=>{return {configurable: false, writable: false, - iterable: false}}; - /** Returns a skeleton for a read-only property accessor wrapping value v. */ - const rop = (v)=>{return {...rop0(), value: v}}; + const rop = (v)=>{return {configurable: false, writable: false, + iterable: false, value: v}}; /** Allocates obj's memory buffer based on the size defined in ctor.structInfo.sizeof. */ - const __allocStruct = function f(ctor, obj, xm){ - let opt; - const checkPtr = (ptr)=>{ - __isNonNullPtr(ptr) || - toss("Invalid pointer value",arguments[0],"for",ctor.structName,"constructor."); - }; - if( arguments.length>=3 ){ - if( xm && ('object'===typeof xm) ){ - opt = xm; - xm = opt?.wrap; - }else{ - checkPtr(xm); - opt = {wrap: xm}; - } - }else{ - opt = {} - } - - const fill = !xm /* true if we need to zero the memory */; - let nAlloc = 0; - let ownsPointer = false; - if(xm){ - /* Externally-allocated memory. */ - checkPtr(xm); - ownsPointer = !!opt?.takeOwnership; - }else{ - const nX = opt?.extraBytes ?? 0; - if( nX<0 || (nX!==(nX|0)) ){ - toss("Invalid extraBytes value:",opt?.extraBytes); - } - nAlloc = ctor.structInfo.sizeof + nX; - xm = alloc(nAlloc) - || toss("Allocation of",ctor.structName,"structure failed."); - ownsPointer = true; + const __allocStruct = function(ctor, obj, m){ + let fill = !m; + if(m) Object.defineProperty(obj, xPtrPropName, rop(m)); + else{ + m = alloc(ctor.structInfo.sizeof); + if(!m) toss("Allocation of",ctor.structName,"structure failed."); } try { - if( opt?.debugFlags ){ - /* specifically undocumented */ - obj.debugFlags(opt.debugFlags); - } - if(ctor./*prototype.???*/debugFlags.__flags.alloc){ + if(ctor.debugFlags.__flags.alloc){ log("debug.alloc:",(fill?"":"EXTERNAL"), ctor.structName,"instance:", - ctor.structInfo.sizeof,"bytes @"+xm); + ctor.structInfo.sizeof,"bytes @"+m); } if(fill){ - heap().fill(0, Number(xm), Number(xm) + nAlloc); - } - const ii = getInstanceHandle(obj); - ii.p = xm; - ii.ownsPointer = ownsPointer; - ii.xb = nAlloc ? (nAlloc-ctor.structInfo.sizeof) : 0; - ii.zod = !!opt?.zeroOnDispose; - if( opt?.ondispose && opt.ondispose!==xm ){ - obj.addOnDispose( opt.ondispose ); + heap().fill(0, Number(m), Number(m) + ctor.structInfo.sizeof); } + __instancePointerMap.set(obj, m); }catch(e){ - __freeStruct(ctor, obj, xm); + __freeStruct(ctor, obj, m); throw e; } }; - - /** True if sig looks like an emscripten/jaccwabyt - type signature, else false. */ - const looksLikeASig = function f(sig){ - f.rxSig1 ??= /^[ipPsjfdcC]$/; - f.rxSig2 ??= /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/; - return f.rxSig1.test(sig) || f.rxSig2.test(sig); - }; - - /** Returns a pair of adaptor maps (objects) in a length-3 - array specific to the given object. */ - const __adaptorsFor = function(who){ - let x = this.get(who); - if( !x ){ - x = [ Object.create(null), Object.create(null), Object.create(null) ]; - this.set(who, x); - } - return x; - }.bind(new WeakMap); - - /** Code de-duplifier for __adaptGet(), __adaptSet(), and - __adaptStruct(). */ - const __adaptor = function(who, which, key, proxy){ - const a = __adaptorsFor(who)[which]; - if(3===arguments.length) return a[key]; - if( proxy ) return a[key] = proxy; - return delete a[key]; - }; - - const noopAdapter = (x)=>x; - - // StructBinder::adaptGet() - const __adaptGet = function(key, ...args){ - return __adaptor(this, 0, key, ...args); - }; - - const __affirmNotASig = function(ctx,key){ - looksLikeASig(key) && - toss(ctx,"(",key,") collides with a data type signature."); - }; - - // StructBinder::adaptSet() - const __adaptSet = function(key, ...args){ - __affirmNotASig('Setter adaptor',key); - return __adaptor(this, 1, key, ...args); - }; - - // StructBinder::adaptStruct() - const __adaptStruct = function(key, ...args){ - __affirmNotASig('Struct adaptor',key); - return __adaptor(this, 2, key, ...args); - }; - - /** - An internal counterpart of __adaptStruct(). If key is-a string, - uses __adaptor(who) to fetch the struct-adaptor entry for key, - else key is assumed to be a struct description object. If it - resolves to an object, that's returned, else an exception is - thrown. - */ - const __adaptStruct2 = function(who,key){ - const si = ('string'===typeof key) - ? __adaptor(who, 2, key) : key; - if( 'object'!==typeof si ){ - toss("Invalid struct mapping object. Arg =",key,JSON.stringify(si)); - } - return si; + /** Gets installed as the memoryDump() method of all structs. */ + const __memoryDump = function(){ + const p = this.pointer; + return p + ? new Uint8Array(heap().slice(Number(p), Number(p) + this.structInfo.sizeof)) + : null; }; const __memberKey = (k)=>memberPrefix + k + memberSuffix; const __memberKeyProp = rop(__memberKey); - //const __adaptGetProp = rop(__adaptGet); /** Looks up a struct member in structInfo.members. Throws if found @@ -536,8 +342,7 @@ function StructBinderFactory(config){ if(v.key===memberName){ m = v; break; } } if(!m && tossIfNotFound){ - toss(sPropName(structInfo.name || structInfo.structName, memberName), - 'is not a mapped struct member.'); + toss(sPropName(structInfo.name,memberName),'is not a mapped struct member.'); } } return m; @@ -554,6 +359,15 @@ function StructBinderFactory(config){ return emscriptenFormat ? f._(m.signature) : m.signature; }; + const __ptrPropDescriptor = { + configurable: false, enumerable: false, + get: function(){return __instancePointerMap.get(this)}, + set: ()=>toss("Cannot assign the 'pointer' property of a struct.") + // Reminder: leaving `set` undefined makes assignments + // to the property _silently_ do nothing. Current unit tests + // rely on it throwing, though. + }; + /** Impl of X.memberKeys() for StructType and struct ctors. */ const __structMemberKeys = rop(function(){ const a = []; @@ -687,13 +501,13 @@ function StructBinderFactory(config){ Prototype for all StructFactory instances (the constructors returned from StructBinder). */ - const StructType = function StructType(structName, structInfo){ - if(arguments[2]!==rop/*internal sentinel value*/){ + const StructType = function ctor(structName, structInfo){ + if(arguments[2]!==rop){ toss("Do not call the StructType constructor", "from client-level code."); } Object.defineProperties(this,{ - //isA: rop((v)=>v instanceof StructType), + //isA: rop((v)=>v instanceof ctor), structName: rop(structName), structInfo: rop(structInfo) }); @@ -719,31 +533,8 @@ function StructBinderFactory(config){ memberSignature: rop(function(memberName, emscriptenFormat=false){ return __memberSignature(this, memberName, emscriptenFormat); }), - memoryDump: rop(function(){ - const p = this.pointer; - return p - ? new Uint8Array(heap().slice(Number(p), Number(p) + this.structInfo.sizeof)) - : null; - }), - extraBytes: { - configurable: false, enumerable: false, - get: function(){return getInstanceHandle(this, false)?.xb ?? 0;} - }, - zeroOnDispose: { - configurable: false, enumerable: false, - get: function(){ - return getInstanceHandle(this, false)?.zod - ?? !!this.structInfo.zeroOnDispose; - } - }, - pointer: { - configurable: false, enumerable: false, - get: function(){return getInstanceHandle(this, false)?.p}, - set: ()=>toss("Cannot assign the 'pointer' property of a struct.") - // Reminder: leaving `set` undefined makes assignments - // to the property _silently_ do nothing. Current unit tests - // rely on it throwing, though. - }, + memoryDump: rop(__memoryDump), + pointer: __ptrPropDescriptor, setMemberCString: rop(function(memberName, str){ return __setMemberCString(this, memberName, str); }) @@ -762,435 +553,183 @@ function StructBinderFactory(config){ Object.defineProperties(StructType, { allocCString: rop(__allocCString), isA: rop((v)=>v instanceof StructType), - hasExternalPointer: rop((v)=>{ - const ii = getInstanceHandle(v, false); - return !!(ii?.p && !ii?.ownsPointer); - }), + hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]), memberKey: __memberKeyProp - //ptrAdd = rop(__ptrAdd) no b/c one might think that it adds based on this.pointer. }); /** - If struct description object si has a getter proxy, return it (a - function), else return undefined. - */ - const memberGetterProxy = function(si){ - return si.get || (si.adaptGet - ? StructBinder.adaptGet(si.adaptGet) - : undefined); - }; - - /** - If struct description object si has a setter proxy, return it (a - function), else return undefined. - */ - const memberSetterProxy = function(si){ - return si.set || (si.adaptSet - ? StructBinder.adaptSet(si.adaptSet) - : undefined); - }; - - /** - To be called by makeMemberWrapper() when si has a 'members' - member, i.e. is an embedded struct. This function sets up that - struct like any other and also sets up property accessor for - ctor.memberKey(name) which returns an instance of that new - StructType when the member is accessed. That instance wraps the - memory of the member's part of the containing C struct instance. - - That is, if struct Foo has member bar which is an inner struct - then: - - const f = new Foo; - const b = f.bar; - assert( b is-a StructType object ); - assert( b.pointer === f.b.pointer ); - - b will be disposed of when f() is. Calling b.dispose() will not - do any permanent harm, as the wrapper object will be recreated - when accessing f.bar, pointing to the same memory in f. - - The si.zeroOnDispose flag has no effect on embedded structs because - they wrap "external" memory, so do not own it, and are thus never - freed, as such. + Pass this a StructBinder-generated prototype, and the struct + member description object. It will define property accessors for + proto[memberKey] which read from/write to memory in + this.pointer. It modifies descr to make certain downstream + operations much simpler. */ - const makeMemberStructWrapper = function callee(ctor, name, si){ - /** - Where we store inner-struct member proxies. Keys are a - combination of the parent object's pointer address and the - property's name. The values are StructType instances. - */ - const __innerStructs = (callee.innerStructs ??= new Map()); - const key = ctor.memberKey(name); - if( undefined!==si.signature ){ - toss("'signature' cannot be used on an embedded struct (", - ctor.structName,".",key,")."); - } - if( memberSetterProxy(si) ){ - toss("'set' and 'adaptSet' are not permitted for nested struct members."); - } - //console.warn("si =",ctor.structName, name, JSON.stringify(si,' ')); - si.structName ??= ctor.structName+'::'+name; - si.key = key; - si.name = name; - si.constructor = this.call(this, si.structName, si); - //console.warn("si.constructor =",si.constructor); - //console.warn("si =",si,"ctor=",ctor); - const getterProxy = memberGetterProxy(si); - const prop = Object.assign(Object.create(null),{ - configurable: false, - enumerable: false, - set: __propThrowOnSet(ctor/*not si.constructor*/.structName, key), - get: function(){ - const dbg = this.debugFlags.__flags; - const p = this.pointer; - const k = p+'.'+key; - let s = __innerStructs.get(k); - if(dbg.getter){ log("debug.getter: k =",k); } - if( !s ){ - s = new si.constructor(__ptrAdd(p, si.offset)); - __innerStructs.set(k, s); - this.addOnDispose(()=>s.dispose()); - s.addOnDispose(()=>__innerStructs.delete(k)); - //console.warn("Created",k,"proxy"); - } - if(getterProxy) s = getterProxy.apply(this,[s,key]); - if(dbg.getter) log("debug.getter: result =",s); - return s; - } - }); - Object.defineProperty(ctor.prototype, key, prop); - }/*makeMemberStructWrapper()*/; - - /** - This is where most of the magic happens. - - Pass this a StructBinderImpl-generated constructor, a member - property name, and the struct member description object. It will - define property accessors for proto[memberKey] which read - from/write to memory in this.pointer. It modifies si to make - certain downstream operations simpler. - */ - const makeMemberWrapper = function f(ctor, name, si){ - si = __adaptStruct2(this, si); - if( si.members ){ - return makeMemberStructWrapper.call(this, ctor, name, si); - } - - if(!f.cache){ - /* Cache all available getters/setters/set-wrappers for - direct reuse in each accessor function. */ - f.cache = {getters: {}, setters: {}, sw:{}}; + const makeMemberWrapper = function f(ctor,name, descr){ + if(!f._){ + /*cache all available getters/setters/set-wrappers for + direct reuse in each accessor function. */ + f._ = {getters: {}, setters: {}, sw:{}}; const a = ['i','c','C','p','P','s','f','d','v()']; if(bigIntEnabled) a.push('j'); a.forEach(function(v){ - f.cache.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */; - f.cache.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */; - f.cache.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values + //const ir = sigIR(v); + f._.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */; + f._.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */; + f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values for conversion */; }); + const rxSig1 = /^[ipPsjfdcC]$/, + rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/; f.sigCheck = function(obj, name, key,sig){ if(Object.prototype.hasOwnProperty.call(obj, key)){ toss(obj.structName,'already has a property named',key+'.'); } - looksLikeASig(sig) + rxSig1.test(sig) || rxSig2.test(sig) || toss("Malformed signature for", sPropName(obj.structName,name)+":",sig); }; } const key = ctor.memberKey(name); - f.sigCheck(ctor.prototype, name, key, si.signature); - si.key = key; - si.name = name; - const sigGlyph = sigLetter(si.signature); - const xPropName = sPropName(ctor.structName,key); - const dbg = ctor.debugFlags.__flags; + f.sigCheck(ctor.prototype, name, key, descr.signature); + descr.key = key; + descr.name = name; + const sigGlyph = sigLetter(descr.signature); + const xPropName = sPropName(ctor.prototype.structName,key); + const dbg = ctor.prototype.debugFlags.__flags; /* - TODO?: set prototype of si to an object which can set/fetch + TODO?: set prototype of descr to an object which can set/fetch its preferred representation, e.g. conversion to string or mapped function. Advantage: we can avoid doing that via if/else if/else in the get/set methods. */ - const getterProxy = memberGetterProxy(si); const prop = Object.create(null); prop.configurable = false; prop.enumerable = false; prop.get = function(){ - /** - This getter proxy reads its value from the appropriate pointer - address in the heap. It knows where and how much to read based on - this.pointer, si.offset, and si.sizeof. - */ if(dbg.getter){ - log("debug.getter:",f.cache.getters[sigGlyph],"for", sigIR(sigGlyph), - xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof); + log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), + xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof); } let rc = ( - new DataView(heap().buffer, Number(this.pointer) + si.offset, si.sizeof) - )[f.cache.getters[sigGlyph]](0, isLittleEndian); - - if(getterProxy) rc = getterProxy.apply(this,[key,rc]); + new DataView(heap().buffer, Number(this.pointer) + descr.offset, descr.sizeof) + )[f._.getters[sigGlyph]](0, isLittleEndian); if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); return rc; }; - if(si.readOnly){ + if(descr.readOnly){ prop.set = __propThrowOnSet(ctor.prototype.structName,key); }else{ - const setterProxy = memberSetterProxy(si); prop.set = function(v){ - /** - The converse of prop.get(), this encodes v into the appropriate - spot in the WASM heap. - */ if(dbg.setter){ - log("debug.setter:",f.cache.setters[sigGlyph],"for", sigIR(sigGlyph), - xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof, v); + log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), + xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v); } if(!this.pointer){ - toss("Cannot set native property on a disposed", - this.structName,"instance."); + toss("Cannot set struct property on disposed instance."); } - if( setterProxy ) v = setterProxy.apply(this,[key,v]); - if( null===v || undefined===v ) v = __NullPtr; - else if( isPtrSig(si.signature) && !__isPtr(v) ){ - if(isAutoPtrSig(si.signature) && (v instanceof StructType)){ - // It's a struct instance: store its pointer value + if(null===v) v = __NullPtr; + else while(!__isPtr(v)){ + if(isAutoPtrSig(descr.signature) && (v instanceof StructType)){ + // It's a struct instance: let's store its pointer value! v = v.pointer || __NullPtr; if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v); - }else{ - toss("Invalid value for pointer-type",xPropName+'.'); + break; } + toss("Invalid value for pointer-type",xPropName+'.'); } ( - new DataView(heap().buffer, Number(this.pointer) + si.offset, - si.sizeof) - )[f.cache.setters[sigGlyph]](0, f.cache.sw[sigGlyph](v), isLittleEndian); + new DataView(heap().buffer, Number(this.pointer) + descr.offset, + descr.sizeof) + )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); }; } Object.defineProperty(ctor.prototype, key, prop); - }/*makeMemberWrapper()*/; + }/*makeMemberWrapper*/; /** The main factory function which will be returned to the - caller. The third argument is structly for internal use. - - This level of indirection is to avoid that clients can pass a - third argument to this, as that's only for internal use. - - internalOpt options: - - - None right now. This is for potential use in recursion. - - Usages: - - StructBinder(string, obj [,optObj]); - StructBinder(obj); + caller. */ - const StructBinderImpl = function StructBinderImpl( - structName, si, opt = Object.create(null) - ){ - /** - StructCtor is the eventual return value of this function. We - need to populate this early on so that we can do some trickery - in feeding it through recursion. - - Uses: - - // heap-allocated: - const x = new StructCtor; - // externally-managed memory: - const y = new StructCtor( aPtrToACompatibleCStruct ); - - or, more recently: - - const z = new StructCtor({ - extraBytes: [int=0] extra bytes to allocate after the struct - - wrap: [aPtrToACompatibleCStruct=undefined]. If provided, this - instance waps, but does not (by default) own the memory, else - a new instance is allocated from the WASM heap. - - ownsPointer: true if this object takes over ownership of - wrap. - - zeroOnDispose: [bool=StructCtor.structInfo.zeroOnDispose] - - autoCalcSizeOffset: [bool=false] Automatically calculate - sizeof an offset. This is fine for pure-JS structs (which - probably aren't useful beyond testing of Jaccwabyt) but it's - dangerous to use with actual WASM objects because we cannot - be guaranteed to have the same memory layout as an ostensibly - matching C struct. This applies recursively to all children - of the struct description. - - // TODO? Per-instance overrides of the struct-level flags? - - get: (k,v)=>v, - set: (k,v)=>v, - adaptGet: string, - adaptSet: string - - // That wouldn't fit really well right now, apparently. - }); - - */ - const StructCtor = function StructCtor(arg){ - //console.warn("opt",opt,arguments[0]); - if(!(this instanceof StructCtor)){ - toss("The",structName,"constructor may only be called via 'new'."); - } - __allocStruct(StructCtor, this, ...arguments); - }; - const self = this; - /** - "Convert" struct description x to a struct description, if - needed. This expands adaptStruct() mappings and transforms - {memberName:signatureString} signature syntax to object form. - */ - const ads = (x)=>{ - //console.warn("looksLikeASig(",x,") =",looksLikeASig(x)); - return (('string'===typeof x) && looksLikeASig(x)) - ? {signature: x} : __adaptStruct2(self,x); - }; + const StructBinder = function StructBinder(structName, structInfo){ if(1===arguments.length){ - si = ads(structName); - structName = si.structName || si.name; - }else if(2===arguments.length){ - si = ads(si); - si.name ??= structName; - }else{ - si = ads(si); + structInfo = structName; + structName = structInfo.name; + }else if(!structInfo.name){ + structInfo.name = structName; } - structName ??= si.structName; - //console.warn("arguments =",JSON.stringify(arguments)); - structName ??= opt.structName; - if( !structName ) toss("One of 'name' or 'structName' are required."); - if( si.adapt ){ - /* Install adaptGet(), adaptSet(), and adaptStruct() proxies. */ - Object.keys(si.adapt.struct||{}).forEach((k)=>{ - __adaptStruct.call(StructBinderImpl, k, si.adapt.struct[k]); - }); - Object.keys(si.adapt.set||{}).forEach((k)=>{ - __adaptSet.call(StructBinderImpl, k, si.adapt.set[k]); - }); - Object.keys(si.adapt.get||{}).forEach((k)=>{ - __adaptGet.call(StructBinderImpl, k, si.adapt.get[k]); - }); - } - if(!si.members && !si.sizeof){ - si.sizeof = sigSize(si.signature); + if(!structName) toss("Struct name is required."); + let lastMember = false; + Object.keys(structInfo.members).forEach((k)=>{ + // Sanity checks of sizeof/offset info... + const m = structInfo.members[k]; + if(!m.sizeof) toss(structName,"member",k,"is missing sizeof."); + else if(m.sizeof===1){ + (m.signature === 'c' || m.signature === 'C') || + toss("Unexpected sizeof==1 member", + sPropName(structInfo.name,k), + "with signature",m.signature); + }else{ + // sizes and offsets of size-1 members may be odd values, but + // others may not. + if(0!==(m.sizeof%4)){ + console.warn("Invalid struct member description =",m,"from",structInfo); + toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof); + } + if(0!==(m.offset%4)){ + console.warn("Invalid struct member description =",m,"from",structInfo); + toss(structName,"member",k,"offset is not aligned. offset="+m.offset); + } + } + if(!lastMember || lastMember.offset < m.offset) lastMember = m; + }); + if(!lastMember) toss("No member property descriptions found."); + else if(structInfo.sizeof < lastMember.offset+lastMember.sizeof){ + toss("Invalid struct config:",structName, + "max member offset ("+lastMember.offset+") ", + "extends past end of struct (sizeof="+structInfo.sizeof+")."); } - const debugFlags = rop(SBF.__makeDebugFlags(StructBinder.debugFlags)); + /** Constructor for the StructCtor. */ + const zeroAsPtr = __asPtrType(0); + const StructCtor = function StructCtor(externalMemory){ + externalMemory = __asPtrType(externalMemory); + //console.warn("externalMemory",externalMemory,arguments[0]); + if(!(this instanceof StructCtor)){ + toss("The",structName,"constructor may only be called via 'new'."); + }else if(arguments.length){ + if(Number.isNaN(externalMemory) || externalMemory<=zeroAsPtr){ + toss("Invalid pointer value",arguments[0],"for",structName,"constructor."); + } + __allocStruct(StructCtor, this, externalMemory); + }else{ + __allocStruct(StructCtor, this); + } + }; Object.defineProperties(StructCtor,{ debugFlags: debugFlags, isA: rop((v)=>v instanceof StructCtor), memberKey: __memberKeyProp, memberKeys: __structMemberKeys, - //methodInfoForKey: rop(function(mKey){/*???*/}), - structInfo: rop(si), - structName: rop(structName), - ptrAdd: rop(__ptrAdd) + methodInfoForKey: rop(function(mKey){ + }), + structInfo: rop(structInfo), + structName: rop(structName) }); - StructCtor.prototype = new StructType(structName, si, rop); + StructCtor.prototype = new StructType(structName, structInfo, rop); Object.defineProperties(StructCtor.prototype,{ debugFlags: debugFlags, constructor: rop(StructCtor) /*if we assign StructCtor.prototype and don't do - this then StructCtor!==instance.constructor*/, - ptrAdd: rop(__ptrAddSelf) + this then StructCtor!==instance.constructor!*/ }); - let lastMember = false; - let offset = 0; - const autoCalc = !!si.autoCalcSizeOffset; - //console.warn(structName,"si =",si); - if( !autoCalc ){ - if( !si.sizeof ){ - toss(structName,"description is missing its sizeof property."); - } - /*if( undefined===si.offset ){ - toss(structName,"description is missing its offset property."); - }*/ - si.offset ??= 0; - }else{ - si.offset ??= 0; - } - Object.keys(si.members || {}).forEach((k)=>{ - // Sanity checks of sizeof/offset info... - let m = ads(si.members[k]); - if(!m.members && !m.sizeof){ - /* ^^^^ fixme: we need to recursively collect all sizeofs - before updating that. */ - m.sizeof = sigSize(m.signature); - if(!m.sizeof){ - toss(sPropName(structName,k), "is missing a sizeof property.",m); - } - } - if( undefined===m.offset ){ - if( autoCalc ) m.offset = offset; - else{ - toss(sPropName(structName,k),"is missing its offset.", - JSON.stringify(m)); - } - /* A missing offset on the initial child is okay (it's always - zero), but we don't know for sure that the members are - their natural order, so we don't know, at this point, which - one is "first". */ - } - si.members[k] = m /* in case ads() resolved it to something else */; - if(!lastMember || lastMember.offset < m.offset) lastMember = m; - const oldAutoCalc = !!m.autoCalc; - if( autoCalc ) m.autoCalcSizeOffset = true; - makeMemberWrapper.call(self, StructCtor, k, m); - if( oldAutoCalc ) m.autoCalcSizeOffset = true; - else delete m.autoCalcSizeOffset; - offset += m.sizeof; - //console.warn("offset",sPropName(structName,k),offset); - }); - - if( !lastMember ) toss("No member property descriptions found."); - if( !si.sizeof ) si.sizeof = offset; - if(si.sizeof===1){ - (si.signature === 'c' || si.signature === 'C') || - toss("Unexpected sizeof==1 member", - sPropName(structName,k), - "with signature",si.signature); - }else{ - // sizes and offsets of size-1 members may be odd values, but - // others may not. - if(0!==(si.sizeof%4)){ - console.warn("Invalid struct member description",si); - toss(structName,"sizeof is not aligned. sizeof="+si.sizeof); - } - if(0!==(si.offset%4)){ - console.warn("Invalid struct member description",si); - toss(structName,"offset is not aligned. offset="+si.offset); - } - } - if( si.sizeof < offset ){ - console.warn("Suspect struct description:",si,"offset =",offset); - toss("Mismatch in the calculated vs. the provided sizeof/offset info.", - "Expected sizeof",offset,"but got",si.sizeof,"for",si); - /* It is legal for the native struct to be larger, so long as - we're pointing to all the right offsets for the members - exposed here. */ - } - delete si.autoCalcSizeOffset; + Object.keys(structInfo.members).forEach( + (name)=>makeMemberWrapper(StructCtor, name, structInfo.members[name]) + ); return StructCtor; - }/*StructBinderImpl*/; - - const StructBinder = function StructBinder(structName, structInfo){ - return (1==arguments.length) - ? StructBinderImpl.call(StructBinder, structName) - : StructBinderImpl.call(StructBinder, structName, structInfo); }; StructBinder.StructType = StructType; StructBinder.config = config; StructBinder.allocCString = __allocCString; - StructBinder.adaptGet = __adaptGet; - StructBinder.adaptSet = __adaptSet; - StructBinder.adaptStruct = __adaptStruct; - StructBinder.ptrAdd = __ptrAdd; if(!StructBinder.debugFlags){ StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags); } diff --git a/ext/wasm/jaccwabyt/jaccwabyt.md b/ext/wasm/jaccwabyt/jaccwabyt.md index 5c30268e8..5ec3151d5 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.md +++ b/ext/wasm/jaccwabyt/jaccwabyt.md @@ -12,35 +12,6 @@ friction. (If that means nothing to you, neither will the rest of this page!) -To the best of its creator's fallible knowledge, Jaccwabyt is the only -library of its kind (as of 2025-11). Aside from wrapping existing -structs, e.g. to integrate "legacy" C code into JS/WASM, it can also -model C structs without requiring a native C counterpart, a feature -which probably has only exceedingly obscure uses in JS-side -implementations for native callbacks. - -How it works: - -- The client provides a JSON-friendly description of a C struct, - describing the names, sizes, and offsets of each member. -- Pass that description to a factory function to create - a JS constructor for that C struct. -- That constructor allocates a block of heap memory of the C struct's - size and maps it to the new JS-side struct instance. Each instance - inherits property interceptors for each struct member, such that - fetching the C struct's members reads directly from the struct's - memory and setting them writes to that memory. Similarly, these - objects can be provided with memory constructed elsewhere, e.g. - a struct pointer returned from a WASM function, and can proxy - that memory via the struct's interface. -- Clients eventually call the `dispose()` method to free the - instance's heap memory and disassociate the JS instance with its - WASM-side resources. - -Easy peasy! - -**Build instructions**: [see Appendix B](#appendix-b). - **Browser compatibility**: this library requires a _recent_ browser and makes no attempt whatsoever to accommodate "older" or lesser-capable ones, where "recent," _very roughly_, means released in @@ -54,10 +25,7 @@ are based solely on feature compatibility tables provided at **Non-browser compatibility**: this code does not target non-browser JS engines and is completely untested on them. That said, it "might -work". These JS APIs do not use the DOM API in any way, so are not -specifically tied to a browser, but they _are_ fully untested in such -environments. This code is known to work with both [Emscripten][] builds -and [WASI-SDK][] SDK builds (at of this writing, 2025-11-08). +work". **64-bit WASM:** as of 2025-09-21 this API supports 64-bit WASM builds but it has to be configured for it (see [](#api-binderfactory) for @@ -89,7 +57,7 @@ project was spawned: ----- -<a id='overview'></a> +<a name='overview'></a> Table of Contents ============================================================ @@ -104,18 +72,16 @@ Table of Contents - APIs - [Struct Binder Factory](#api-binderfactory) - [Struct Binder](#api-structbinder) - - [Struct Description Objects](#struct-descr) -- [Struct Type](#api-structtype) + - [Struct Type](#api-structtype) - [Struct Constructors](#api-structctor) - [Struct Protypes](#api-structprototype) - [Struct Instances](#api-structinstance) - Appendices - [Appendix A: Limitations, TODOs, etc.](#appendix-a) - - [Appendix B: Build](#appendix-b) - [Appendix D: Debug Info](#appendix-d) - [Appendix G: Generating Struct Descriptions](#appendix-g) -<a id='overview'></a> +<a name='overview'></a> Overview ============================================================ @@ -148,16 +114,15 @@ Portability notes: because it is the most widespread WASM toolchain, but this code is specifically designed to be usable in arbitrary WASM environments. It abstracts away a few Emscripten-specific features into - configurable options. The build tree supports both [Emscripten][] - and [WASI-SDK][] to demonstrate that it has no dependencies on - either. + configurable options. Similarly, the build tree requires Emscripten + but Jaccwabyt does not have any hard Emscripten dependencies. - This code is encapsulated into a single JavaScript function. It should be trivial to copy/paste into arbitrary WASM/JS-using projects. - The source tree includes C code, but only for testing and - demonstration purposes. It is not a core distributable. + demonstration purposes. It is not part of the core distributable. -<a id='architecture'></a> +<a name='architecture'></a> Architecture ------------------------------------------------------------ @@ -191,10 +156,9 @@ Its major classes and functions are: an appropriate configuration, to generate a single... - **[StructBinder][]** is a factory function which converts an arbitrary number struct descriptions into... -- **[StructType][]** are [constructors][StructCtor], one per struct +- **[StructTypes][StructCtors]** are constructors, one per struct description, which inherit from - **[`StructBinder.StructType`][StructType]** and are used to - instantiate... + **[`StructBinder.StructType`][StructType]** and are used to instantiate... - **[Struct instances][StructInstance]** are objects representing individual instances of generated struct types. @@ -203,7 +167,7 @@ need only one. Each StructBinder is effectively a separate namespace for struct creation. -<a id='creating-binding'></a> +<a name='creating-binding'></a> Creating and Binding Structs ============================================================ @@ -226,7 +190,7 @@ essentially boils down to: Detailed instructions for each of those steps follows... -<a id='step-1'></a> +<a name='step-1'></a> Step 1: Configure Jaccwabyt for the Environment ------------------------------------------------------------ @@ -244,7 +208,7 @@ const MyBinder = StructBinderFactory({ a Uint8Array or Int8Array view of the WASM memory, alloc: function(howMuchMemory){...}, dealloc: function(pointerToFree){...}, - pointerSize: 4 or 8 // WASM pointer size + pointerIR: 'i32' or 'i64' // WASM pointer type - default = 'i32' }); ``` @@ -270,7 +234,7 @@ a conventional Emscripten setup, that config might simply look like: The StructBinder factory function returns a function which can then be used to create bindings for our structs. -<a id='step-2'></a> +<a name='step-2'></a> Step 2: Create a Struct Description ------------------------------------------------------------ @@ -303,9 +267,100 @@ Its JSON description looks like: } ``` -This is described in more detail in [][StructBinder]. +These data _must_ match up with the C-side definition of the struct +(if any). See [Appendix G][appendix-g] for one way to easily generate +these from C code. + +Each entry in the `members` object maps the member's name to +its low-level layout: + +- `offset`: the byte offset from the start of the struct, as reported + by C's `offsetof()` feature. +- `sizeof`: as reported by C's `sizeof()`. +- `signature`: described below. +- `readOnly`: optional. If set to true, the binding layer will + throw if JS code tries to set that property. + +The order of the `members` entries is not important: their memory +layout is determined by their `offset` and `sizeof` members. The +`name` property is technically optional, but one of the steps in the +binding process requires that either it be passed an explicit name or +there be one in the struct description. The names of the `members` +entries need not match their C counterparts. Project conventions may +call for giving them different names in the JS side and the +[StructBinderFactory][] can be configured to automatically add a +prefix and/or suffix to their names. + +Nested structs are as-yet unsupported by this tool. -<a id='step-2-pvsp'></a> +Struct member "signatures" describe the data types of the members and +are an extended variant of the format used by Emscripten's +`addFunction()`. A signature for a non-function-pointer member, or +function pointer member which is to be modelled as an opaque pointer, +is a single letter. A signature for a function pointer may also be +modelled as a series of letters describing the call signature. The +supported letters are: + +- **`v`** = `void` (only used as return type for function pointer members) +- **`i`** = `int32` (4 bytes) +- **`j`** = `int64` (8 bytes) is only really usable if this code is built + with BigInt support (e.g. using the Emscripten `-sWASM_BIGINT` build + flag). Without that, this API may throw when encountering the `j` + signature entry. +- **`f`** = `float` (4 bytes) +- **`d`** = `double` (8 bytes) +- **`c`** = `int8` (1 byte) char - see notes below! +- **`C`** = `uint8` (1 byte) unsigned char - see notes below! +- **`p`** = `int32` (see notes below!) +- **`P`** = Like `p` but with extra handling. Described below. +- **`s`** = like `int32` but is a _hint_ that it's a pointer to a + string so that _some_ (very limited) contexts may treat it as such, + noting that such algorithms must, for lack of information to the + contrary, assume both that the encoding is UTF-8 and that the + pointer's member is NUL-terminated. If that is _not_ the case for a + given string member, do not use `s`: use `i` or `p` instead and do + any string handling yourself. + +Noting that: + +- **All of these types are numeric**. Attempting to set any + struct-bound property to a non-numeric value will trigger an + exception except in cases explicitly noted otherwise. +- **"Char" types**: WASM does not define an `int8` type, nor does its + JS representation distinguish between signed and unsigned. This API + treats `c` as `int8` and `C` as `uint8` for purposes of getting and + setting values when using the `DataView` class. It is _not_ + recommended that client code use these types in new WASM-capable + code, but they were added for the sake of binding some immutable + legacy code to WASM. + +> Sidebar: Emscripten's public docs do not mention `p`, but their +generated code includes `p` as an alias for `i`, presumably to mean +"pointer". Though `i` is legal for pointer types in the signature, `p` +is more descriptive, so this framework encourages the use of `p` for +pointer-type members. Using `p` for pointers also helps future-proof +the signatures against the eventuality that WASM eventually supports +64-bit pointers. Note that sometimes `p` _really_ means a +pointer-to-pointer. We simply have to be aware of when we need to deal +with pointers and pointers-to-pointers in JS code. + +> Trivia: this API treates `p` as distinctly different from `i` in +some contexts, so its use is encouraged for pointer types. + +Signatures in the form `x(...)` denote function-pointer members and +`x` denotes non-function members. Functions with no arguments use the +form `x()`. For function-type signatures, the strings are formulated +such that they can be passed to Emscripten's `addFunction()` after +stripping out the `(` and `)` characters. For good measure, to match +the public Emscripten docs, `p`, `c`, and `C`, should also be replaced +with `i`. In JavaScript that might look like: + +> +``` +signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i'); +``` + +<a name='step-2-pvsp'></a> ### `P` vs `p` in Method Signatures *This support is experimental and subject to change.* @@ -324,7 +379,7 @@ stored in `myStruct.x`. If `y` is neither a pointer nor a or `P` is used). -<a id='step-3'></a> +<a name='step-3'></a> Step 3: Binding the Struct ------------------------------------------------------------ @@ -347,7 +402,7 @@ simplify certain later operations. If that is not desired, then feed it a copy of the original, e.g. by passing it `JSON.parse(JSON.stringify(structDefinition))`. -<a id='step-4'></a> +<a name='step-4'></a> Step 4: Creating, Using, and Destroying Struct Instances ------------------------------------------------------------ @@ -406,11 +461,11 @@ Now that we have struct instances, there are a number of things we can do with them, as covered in the rest of this document. -<a id='api'></a> +<a name='api'></a> API Reference ============================================================ -<a id='api-binderfactory'></a> +<a name='api-binderfactory'></a> API: Binder Factory ------------------------------------------------------------ @@ -425,14 +480,14 @@ Function StructBinderFactory(object configOptions); It returns a function which these docs refer to as a [StructBinder][] (covered in the next section). It throws on error. -The binder factory supports the following options in its configuration -object argument: +The binder factory supports the following options in its +configuration object argument: -- `pointerSize` (Added 2025-11-15 to replace `pointerIR`) - Optionally specify the WASM pointer size of 4 (32-bit) or 8 - (64-bit). Any other truthy value triggers an exception. If - `pointerSize` is not set then it will guess the size by `alloc()`ing - one byte, checking the result type, and `dealloc()`ing it. +- `pointerIR` (Added 2025-09-21) + Optionally specify the WASM pointer size with the string `'i32'` or + `'i64'`, defaulting to the former. When using with 64-bit WASM + builds, this must be set to `'i64'` by the client. Any other value + triggers an exception. - `heap` Must be either a `WebAssembly.Memory` instance representing the WASM @@ -443,24 +498,21 @@ object argument: for the WASM heap to grow at runtime. - `alloc` - Must be a function semantically compatible with C's - `malloc(3)`. That is, it is passed the number of bytes to allocate - and it returns a pointer. On allocation failure it may either return - 0 or throw an exception. This API will throw an exception if - allocation fails or will propagate whatever exception the allocator - throws. The allocator _must_ use the same heap as the `heap` config - option. + Must be a function semantically compatible with Emscripten's + `Module._malloc()`. That is, it is passed the number of bytes to + allocate and it returns a pointer. On allocation failure it may + either return 0 or throw an exception. This API will throw an + exception if allocation fails or will propagate whatever exception + the allocator throws. The allocator _must_ use the same heap as the + `heap` config option. - `dealloc` - Must be a function semantically compatible with C's `free(3)`. That - is, it takes a pointer returned from `alloc()` and releases that - memory. It must never throw and must accept a value of 0/null to - mean "do nothing". - -- `realloc` - Optional but required for (eventual (and optional) realloc support - of structs. If set, it must be a function semantically compatible - with C's `realloc()`. See `alloc`, above, for other requirements. + Must be a function semantically compatible with Emscripten's + `Module._free()`. That is, it takes a pointer returned from + `alloc()` and releases that memory. It must never throw and must + accept a value of 0/null to mean "do nothing" (noting that 0 is + _technically_ a legal memory address in WASM, but that seems like a + design flaw). - `bigIntEnabled` (bool=true if BigInt64Array is available, else false) If true, the WASM bits this code is used with must have been @@ -490,13 +542,13 @@ object argument: (like `console.debug` does). See [Appendix D](#appendix-d) for info about enabling debugging output. -<a id='api-structbinder'></a> +<a name='api-structbinder'></a> API: Struct Binder ------------------------------------------------------------ Struct Binders are factories which are created by the [StructBinderFactory][]. A given Struct Binder can process any number -of distinct structs. In a typical setup, an app will have only one +of distinct structs. In a typical setup, an app will have ony one shared Binder Factory and one Struct Binder. Struct Binders which are created via different [StructBinderFactory][] calls are unrelated to each other, sharing no state except, perhaps, indirectly via @@ -516,7 +568,7 @@ The returned object is a constructor for instances of the struct described by its argument(s), each of which derives from a separate [StructType][] instance. -StructBinder has the following members: +The Struct Binder has the following members: - `allocCString(str)` Allocates a new UTF-8-encoded, NUL-terminated copy of the given JS @@ -531,221 +583,7 @@ StructBinder has the following members: any of its "significant" configuration values may have undefined results. -- `adaptGet(key [,func])` - Gets or sets a "get adaptor" by name - an arbitrary client-defined - string, e.g. `"to-js-string"`. Struct description objects may have - their `adaptGet` property set to the name of a mapped getter to - behave exactly as if that struct description had set the given - function as its `get` property. This offers a JSON-friendly way of - storing adaptor mappings, with the caveat that the adaptors need to - be defined _somewhere_ outside of JSON (typically it should be done - immediately after creating the StructBinder). - -- `adaptSet(key [,func])` - The "set" counterpart of `adaptGet`. - -- `ptrAdd(...)` - Coerces all of its arguments to the WASM pointer type, adds them - together, and returns a result of that same type. This is a - workaround for mixed-BigInt/Number pointer math being illegal in JS. - -The `structDescription` argument is described in detail in the -following section. - -<a id='struct-descr'></a> -### Struct Description Object - -C structs are described in a JSON-friendly format: - -> -```json -{ - "name": "MyStruct", - "sizeof": 16, - "members": { - "member1": {"offset": 0,"sizeof": 4,"signature": "i"}, - "member2": {"offset": 4,"sizeof": 4,"signature": "p"}, - "member3": {"offset": 8,"sizeof": 8,"signature": "j"} - } -} -``` - -Forewarning: these data _must_ match up with the C-side definition of -the struct (if any). See [Appendix G][appendix-g] for one way to -easily generate these from C code. - -Every struct must have a `sizeof`. (Though we _could_ calculate it -based on the list of members, we don't. Actually, we do, then we throw -if the values don't match up.) The `name` is required as well but it -may optionally be passed as the first argument to -`StructBinder(structName,structDescription)`. the `name` property -represents the member's symbolic name, typically its native name. - -Abstractly speaking, a struct description in an object with the -properties `sizeof`, `offset`, and either `signature` _or_ -`member`. `offset` is optional only in the top-most object of a struct -description. Every sub-object (a.k.a. member description object) -requires the `offset` property. - -Member description objects are those in the `members` property: - -`"members": {"memberName": {...member description...}, ...}` - -A struct description which has its own `members` object represents a -nested struct, with an identical description syntax to that of a -top-level struct except that nested structs require an `offset` -property. - -Each entry in a struct/member description object maps the member's -name to its low-level layout and other metadata: - -- `offset` - The byte offset from the start of the struct, as reported by C's - `offsetof()` feature. For nested structs's members, this value is - relative to the nested struct, not the parent struct. -- `sizeof` - As reported by C's `sizeof()`. -- `signature` - A type-id signature for this member. Described below. -- `readOnly [=false]` - Optional boolean. If set to true, the binding layer will throw if JS - code tries to set that property. -- `zeroOnDispose [=false]` - If true, then the library will zero out the memory of instances of - this struct when their `dispose()` method is called. Because - `StructType.dispose()` does not free instances which wrap - externally-provided memory, those instances are not wiped when - disposed (doing so would likely interfere with other users of that - memory). (There is no need for a `zeroOnAlloc` counterpart because - newly-allocated instances are always zero-filled for sanity's - sake.) -- `members` - This object describes the individual struct members, mapping their - names to a member description object. `members` gets processed - recursively. Any member with its own `members` property is a - nested-struct, and the property accessor for such members will - return an instance of the distinct StructType which wraps that - member's native memory. - Nested-struct members cannot be assigned over. `signature` is - illegal if `members` is set. -- `get` - Optional function. When fetching this member, this getter is passed - `(K,V)`, where `K` is the struct member's key and `V` is the - _native_ value. `get()`'s return value becomes the value of the - property access operation. This enables custom "native-to-JS" - conversions. If the member is a nested struct, the value passed to - the getter is a StructType proxy object which provides access to its - own members, a subset of the parent object's memory. In the context - of the getter call, "this" is the object upon which the get is being - performed. -- `set` - Optional function. When setting this member, this setter is passed - `(K,V)`, where `K` is the struct member's key and `V` is the _JS_ - value the property is being assigned to. The `set()` return value is - assigned to the _native_ struct member. Thus `set()` _must_ return - an appropriate numeric value and can perform "JS-to-native" - conversions. `set` is not currently legal for nested struct values, - but it is on their own non-nested-struct members. In the context of - the setter call, "this" is the object upon which the set is being - performed. -- `adaptGet` and `adaptSet` - JSON-friendly variants of `get` and `set`. Each may be assigned a - string value, and each such string must be mapped with - `StructBinder.adaptGet(key,func)` - resp. `StructBinder.adaptSet(key,func)`. With that in place, these - behave like `get` resp. `set`. -- `structName` - Optional descriptive name, possibly distinct from the `name`, - primarily used for nested structs. The intent is that this be some - form of the struct type's name, optionally with leading parts of - this object is a nested struct. -- `name` - Is usually optional, and is always optional in `members` entries - because their name is conveniently derived from their containing - object. `name` must be provided only for the top-most struct. The - intent is that `name` maps to the member's property name and that - `structName` optionally be set for nested structs (it will be - derived from the name if it's not set). - -The order of the `members` entries is not important: their memory -layout is determined by their `offset` and `sizeof` members. The -`name` property is technically optional, but one of the steps in the -binding process requires that either it be passed an explicit name or -there be one in the struct description. The names of the `members` -entries need not match their C counterparts. Project conventions may -call for giving them different names in the JS side and the -[StructBinderFactory][] can be configured to automatically add a -prefix and/or suffix to their names. - -Struct member "signatures" describe the data types of the members and -are an extended variant of the format used by Emscripten's -`addFunction()`. A signature for a non-function-pointer member, or -function pointer member which is to be modelled as an opaque pointer, -is a single letter. A signature for a function pointer may also be -modelled as a series of letters describing the call signature. The -supported letters are: - -- **`v`** = `void` (only used as return type for function pointer members) -- **`i`** = `int32` (4 bytes) -- **`j`** = `int64` (8 bytes) is only really usable if this code is built - with BigInt support (e.g. using the Emscripten `-sWASM_BIGINT` build - flag). Without that, this API may throw when encountering the `j` - signature entry. -- **`f`** = `float` (4 bytes) -- **`d`** = `double` (8 bytes) -- **`c`** = `int8` (1 byte) char - see notes below! -- **`C`** = `uint8` (1 byte) unsigned char - see notes below! -- **`p`** = `int32` (see notes below!) -- **`P`** = Like `p` but with extra handling. Described below. -- **`s`** = like `int32` but is a _hint_ that it's a pointer to a - string so that _some_ (very limited) contexts may treat it as such, - noting that such algorithms must, for lack of information to the - contrary, assume both that the encoding is UTF-8 and that the - pointer's member is NUL-terminated. If that is _not_ the case for a - given string member, do not use `s`: use `i` or `p` instead and do - any string handling yourself. - -Noting that: - -- **All of these types are numeric**. Attempting to set any - struct-bound property to a non-numeric value will trigger an - exception except in cases explicitly noted otherwise. -- **"Char" types**: WASM does not define an `int8` type, nor does its - JS representation distinguish between signed and unsigned. This API - treats `c` as `int8` and `C` as `uint8` for purposes of getting and - setting values when using the `DataView` class. It is _not_ - recommended that client code use these types in new WASM-capable - code, but they were added for the sake of binding some immutable - legacy code to WASM. - -> Sidebar: Emscripten's public docs do not mention `p`, but their -generated code includes `p` as an alias for `i`, presumably to mean -"pointer". Though `i` is legal for pointer types in the signature, `p` -is more descriptive, so this framework encourages the use of `p` for -pointer-type members. Using `p` for pointers also helps future-proof -the signatures against the eventuality that WASM eventually supports -64-bit pointers. Note that sometimes `p` _really_ means a -pointer-to-pointer. We simply have to be aware of when we need to deal -with pointers and pointers-to-pointers in JS code. - -> Trivia: this API treates `p` as distinctly different from `i` in -some contexts, so its use is encouraged for pointer types. - -Signatures in the form `x(...)` denote function-pointer members and -`x` denotes non-function members. Functions with no arguments use the -form `x()`. For function-type signatures, the strings are formulated -such that they can be passed to Emscripten's `addFunction()` after -stripping out the `(` and `)` characters. For good measure, to match -the public Emscripten docs, `p`, `c`, and `C`, should also be replaced -with `i`. In JavaScript that might look like: - -> -``` -signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i'); -``` - - -<a id='api-structtype'></a> +<a name='api-structtype'></a> API: Struct Type ------------------------------------------------------------ @@ -760,7 +598,7 @@ config options. The StructType constructor cannot be called from client code. It is only called by the [StructBinder][]-generated -[constructors][StructCtor]. The `StructBinder.StructType` object +[constructors][StructCtors]. The `StructBinder.StructType` object has the following "static" properties (^Which are accessible from individual instances via `theInstance.constructor`.): @@ -770,8 +608,7 @@ individual instances via `theInstance.constructor`.): a function-typed `ondispose` property, this call replaces it with an array and moves that function into the array. In all other cases, `ondispose` is assumed to be an array and the argument(s) is/are - appended to it. Returns `this`. See `dispose()`, below, for where - this applies. + appended to it. Returns `this`. - `allocCString(str)` Identical to the [StructBinder][] method of the same name. @@ -779,7 +616,7 @@ individual instances via `theInstance.constructor`.): - `hasExternalPointer(object)` Returns true if the given object's `pointer` member refers to an "external" object. That is the case when a pointer is passed to a - [struct's constructor][StructCtor]. If true, the memory is owned by + [struct's constructor][StructCtors]. If true, the memory is owned by someone other than the object and must outlive the object. - `isA(value)` @@ -796,7 +633,7 @@ individual instances via `theInstance.constructor`.): The base StructType prototype has the following members, all of which are inherited by [struct instances](#api-structinstance) and may only -legally be used with concrete struct instances unless noted otherwise: +legally be called on concrete struct instances unless noted otherwise: - `dispose()` Frees, if appropriate, the WASM-allocated memory which is allocated @@ -804,11 +641,11 @@ legally be used with concrete struct instances unless noted otherwise: cleans up the object, a leak in the WASM heap memory pool will result. When `dispose()` is called, if the object has a property named `ondispose` then it is treated as follows: - - If it is a function, it is called with the struct object as its - `this`. That method must not throw - if it does, the exception - will be ignored. + - If it is a function, it is called with the struct object as its `this`. + That method must not throw - if it does, the exception will be + ignored. - If it is an array, it may contain functions, pointers, other - [StructType][] instances, and/or JS strings. If an entry is a + [StructType] instances, and/or JS strings. If an entry is a function, it is called as described above. If it's a number, it's assumed to be a pointer and is passed to the `dealloc()` function configured for the parent [StructBinder][]. If it's a @@ -818,12 +655,7 @@ legally be used with concrete struct instances unless noted otherwise: supported primarily for use as debugging information. - Some struct APIs will manipulate the `ondispose` member, creating it as an array or converting it from a function to array as - needed. Most simply, `addOnDispose()` is used to manipulate the - on-dispose data. - -- `extraBytes` (integer, read-only) - If this instance was allocated with the `extraBytes` option, this is - that value, else it is 0. + needed. - `lookupMember(memberName,throwIfNotFound=true)` Given the name of a mapped struct member, it returns the member @@ -876,19 +708,6 @@ legally be used with concrete struct instances unless noted otherwise: the struct will invalidate older serialized data and (B) serializing member pointers is useless. -- `pointer` (number, read-only) - A read-only numeric property which is the "pointer" returned by the - configured allocator when this object is constructed. After - `dispose()` (inherited from [StructType][]) is called, this property - has the `undefined` value. When calling C-side code which takes a - pointer to a struct of this type, simply pass it `myStruct.pointer`. - Whether this member is of type Number or BigInt depends on whether - the WASM environment is 32-bit (Number) or 64-bit (BigInt). - -- `ptrAdd(args...)` - Equivalent to [StructBinder][]`.ptrAdd(this.pointer, args...)` - or [StructCtor][]`.ptrAdd(this.pointer, args...)`. - - `setMemberCString(memberName,str)` Uses `StructType.allocCString()` to allocate a new C-style string, assign it to the given member, and add the new string to this @@ -906,13 +725,9 @@ legally be used with concrete struct instances unless noted otherwise: from JS be kept to a minimum or that the relationship be one-way: let C manage the strings and only fetch them from JS using, e.g., `memberToJsString()`. + -- `zeroOnDispose` (bool, read-only) - True if this instance or its prototype were configured with - the `zeroOnDispose` flag. - - -<a id='api-structctor'></a> +<a name='api-structctor'></a> API: Struct Constructors ------------------------------------------------------------ @@ -926,90 +741,29 @@ const x = new MyStruct; ``` Normally they should be passed no arguments, but they optionally -accept a single argument: a WASM heap pointer address of memory which -the object will use for storage. It does _not_ take over ownership of -that memory and that memory must remain valid for at least as long as -this struct instance. This is used, for example, to proxy -static/shared C-side instances or those which we simply want to -get access to via this higher-level API for a while: +accept a single argument: a WASM heap pointer address of memory +which the object will use for storage. It does _not_ take over +ownership of that memory and that memory must be valid at +for least as long as this struct instance. This is used, for example, +to proxy static/shared C-side instances: > ``` const x = new MyStruct( someCFuncWhichReturnsAMyStructPointer() ); ... -x.dispose(); // does NOT free or zero the memory +x.dispose(); // does NOT free the memory ``` The JS-side construct does not own the memory in that case and has no way of knowing when the C-side struct is destroyed. Results are specifically undefined if the JS-side struct is used after the C-side -struct's member is freed. However, if the client has allocated that -memory themselves and wants to transfer ownership of it to the -instance, that can be done with: - -> -``` -x.addOnDispose( thePtr ); -``` - -Which will free `thePtr` when `x.dispose()` is called. - -As of 2025-11-10, a third option is available: +struct's member is freed. -> -``` -const x = new MyStruct({ - wrap: ptr, // as per MyStruct(ptr) - takeOwnership: bool, // If true, take ownership of the wrap ptr. - zeroOnDispose: bool , // if true, overrides MyStruct.structInfo.zeroOnDispose - extraBytes: int // bytes to alloc after the end of the struct -}); -``` - -- If `wrap` is set then (A) it must be at least - `MyStruct.structInfo.sizeof` of memory owned by the caller, (B) it - _must_ have been allocated using the same allocator as Jaccwabyt is - configured for, and (C) ownership of it is transfered to the new - object. `zeroOnDispose` and `extraBytes` are disregarded if `wrap` - is set. A falsy `wrap` value is equivalent to not providing one and - a non-integer value, or a number less than 0, triggers an error. - -- If `takeOwnership` is truthy then this object takes over ownership - of the `wrap` pointer (if any). This flag is otherwise ignored. - -- If `zeroOnDispose` or `extraBytes` are are used without `wrap`, each - which is used is set as a read-only propperty on the resulting - `MyStruct` object, except that `zeroOnDispose` is only recorded if - it is truthy and `extraBytes` is only recorded if it is not 0 (a - negative value, or non-integer, triggers an exception). - -- `ondispose`: if set it is passed to the new object's - `addOnDispose()` before the constructor returns, so may have any - value legal for that method. Results are undefined if `ondispose` - and `wrap` have the same pointer value (because `wrap` will already - be cleaned up via `dispose()`, as if it had been passed to - `addOnDispose()`). - -In the case of `extraBytes`, a pointer to the tail of the memory can -be obtained by adding `theInstance.pointer` to -`theInstance.structInfo.sizeof`, with the caveat that `pointer` may be -a BigInt value (on 64-bit WASM), `sizeof` will be a Number and, -spoiler alert, `(BigInt(1) + Number(1))` is not legal. Thus this API -adds a small convenience method to work around that portability issue: - -> -``` -const ptrTail = MyStruct.ptrAdd(theInstance.pointer, theInstance.extraBytes); -``` - -`typeof ptrTail` is `'bigint'` in 64-bit builds and `'number'` in -32-bit. i.e. it's the same type as `theInstance.pointer`. -Equivalently, the inherited `MyStruct` instance method with the same -name adds an instance's own `pointer` value to its arguments: - -``` -const ptrTail = theInstance.ptrAdd(theInstance.extraBytes); -``` +> Potential TODO: add a way of passing ownership of the C-side struct +to the JS-side object. e.g. maybe simply pass `true` as the second +argument to tell the constructor to take over ownership. Currently the +pointer can be taken over using something like +`myStruct.ondispose=[myStruct.pointer]` immediately after creation. These constructors have the following "static" members: @@ -1022,10 +776,6 @@ These constructors have the following "static" members: - `memberKeys(string)` Works exactly as documented for [StructType][]. -- `ptrAdd(args...)` - Equivalent to [StructBinder][]`.ptrAdd(args...)`. The [_inherited_ - method with the same name][StructType] behaves differently. - - `structInfo` The structure description passed to [StructBinder][] when this constructor was generated. @@ -1033,14 +783,14 @@ These constructors have the following "static" members: - `structName` The structure name passed to [StructBinder][] when this constructor was generated. + - -<a id='api-structprototype'></a> +<a name='api-structprototype'></a> API: Struct Prototypes ------------------------------------------------------------ The prototypes of structs created via [the constructors described in -the previous section][StructCtor] are each a struct-type-specific +the previous section][StructCtors] are each a struct-type-specific instance of [StructType][] and add the following struct-type-specific properties to the mix: @@ -1052,57 +802,78 @@ properties to the mix: The name of the struct, as it was given to the [StructBinder][] which created this class. -<a id='api-structinstance'></a> +<a name='api-structinstance'></a> API: Struct Instances ------------------------------------------------------------------------ Instances of structs created via [the constructors described -above][StructCtor]. Each inherits all of the methods and properties -from their constructor's prototype. +above][StructCtors] each have the following instance-specific state in +common: + +- `pointer` + A read-only numeric property which is the "pointer" returned by the + configured allocator when this object is constructed. After + `dispose()` (inherited from [StructType][]) is called, this property + has the `undefined` value. When calling C-side code which takes a + pointer to a struct of this type, simply pass it `myStruct.pointer`. -<a id='appendices'></a> +<a name='appendices'></a> Appendices ============================================================ -<a id='appendix-a'></a> +<a name='appendix-a'></a> Appendix A: Limitations, TODOs, and Non-TODOs ------------------------------------------------------------ - This library only supports the basic set of member types supported - by WASM: numbers (which includes pointers). - -- Binding JS functions to struct instances, such that C can see and - call JS-defined functions, is not as transparent as it really could - be. [The WhWasmUtil API][whwasmutil.js], and - standalone subproject co-developed with Jaccwabyt, provides such a - binding mechanism. There is some overlap between the two APIs and - they arguably belong bundled together, but doing so would more than - triple Jaccwabyt's size. (That said, the only known Jaccwabyt - deployment uses both APIs, so maybe it's time to merge them (he says - on 2025-11-10). As of this writing, jaccwabyt.js is 38k, half of - which is comments/docs, whereas whwasmutil.js is 100kb and 75% - docs). - -<a id='appendix-b'></a> -Appendix B: Build ------------------------------------------------------------------------- - -In order to support both vanilla JS and ESM (ES6 module) builds from a -single source, this project uses [a preprocessor][c-pp], which requires -only a C compiler: - -> $ make - -The makefile requires GNU make, not BSD or POSIX make. - -The resulting files are in the `js/` subdirectory, in both "vanilla" -JS and ESM formats. - -This tree [includes all of the requisite sources](/dir/tool) and -requires no out-of-tree dependencies beyond the system's libc. - - -<a id='appendix-d'></a> + by WASM: numbers (which includes pointers). Nested structs are not + handled except that a member may be a _pointer_ to such a + struct. Whether or not it ever will depends entirely on whether its + developer ever needs that support. Conversion of strings between + JS and C requires infrastructure specific to each WASM environment + and is not directly supported by this library. + +- Binding functions to struct instances, such that C can see and call + JS-defined functions, is not as transparent as it really could be, + due to [shortcomings in the Emscripten + `addFunction()`/`removeFunction()` + interfaces](https://github.com/emscripten-core/emscripten/issues/17323). Until + a replacement for that API can be written, this support will be + quite limited. It _is_ possible to bind a JS-defined function to a + C-side function pointer and call that function from C. What's + missing is easier-to-use/more transparent support for doing so. + - In the meantime, a [standalone + subproject](/file/common/whwasmutil.js) of Jaccwabyt provides such a + binding mechanism, but integrating it directly with Jaccwabyt would + not only more than double its size but somehow feels inappropriate, so + experimentation is in order for how to offer that capability via + completely optional [StructBinderFactory][] config options. + +- It "might be interesting" to move access of the C-bound members into + a sub-object. e.g., from JS they might be accessed via + `myStructInstance.s.structMember`. The main advantage is that it would + eliminate any potential confusion about which members are part of + the C struct and which exist purely in JS. "The problem" with that + is that it requires internally mapping the `s` member back to the + object which contains it, which makes the whole thing more costly + and adds one more moving part which can break. Even so, it's + something to try out one rainy day. Maybe even make it optional and + make the `s` name configurable via the [StructBinderFactory][] + options. (Over-engineering is an arguably bad habit of mine.) + +- It "might be interesting" to offer (de)serialization support. It + would be very limited, e.g. we can't serialize arbitrary pointers in + any meaningful way, but "might" be useful for structs which contain + only numeric or C-string state. As it is, it's easy enough for + client code to write wrappers for that and handle the members in + ways appropriate to their apps. Any impl provided in this library + would have the shortcoming that it may inadvertently serialize + pointers (since they're just integers), resulting in potential chaos + after deserialization. Perhaps the struct description can be + extended to tag specific members as serializable and how to + serialize them. + +<a name='appendix-d'></a> Appendix D: Debug Info ------------------------------------------------------------ @@ -1123,7 +894,7 @@ client code: [StructType][]. -<a id='appendix-g'></a> +<a name='appendix-g'></a> Appendix G: Generating Struct Descriptions From C ------------------------------------------------------------ @@ -1293,23 +1064,18 @@ div.content h3::before { div.content h3 {border-left-width: 2.5em} </style> -[appendix-g]: #appendix-g -[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 +[emscripten]: https://emscripten.org +[sgb]: https://wanderinghorse.net/home/stephan/ +[appendix-g]: #appendix-g [StructBinderFactory]: #api-binderfactory -[StructCtor]: #api-structctor -[StructInstance]: #api-structinstance +[StructCtors]: #api-structctor [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 -[WASI-SDK]: https://github.com/WebAssembly/wasi-sdk -[whwasmutil.js]: /file/wasmutil/whwasmutil.c-pp.js - +[StructBinder]: #api-structbinder +[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 +[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 diff --git a/ext/wasm/mkdist.sh b/ext/wasm/mkdist.sh index 78b93e5e0..84780668b 100755 --- a/ext/wasm/mkdist.sh +++ b/ext/wasm/mkdist.sh @@ -52,7 +52,7 @@ for arg in $@; do ;; --snapshot) - snapshotSuffix=-snapshot-$(date +%Y%m%d) + snapshotSuffix=$(date +%Y%m%d) ;; -?|--help) diff --git a/ext/wasm/mkwasmbuilds.c b/ext/wasm/mkwasmbuilds.c index 37f2967d8..2730d9d76 100644 --- a/ext/wasm/mkwasmbuilds.c +++ b/ext/wasm/mkwasmbuilds.c @@ -40,22 +40,14 @@ /* ** Flags for use with BuildDef::flags. ** -** 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. +** 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. */ enum BuildDefFlags { /* Indicates an ESM module build. */ F_ESM = 0x01, - /* 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. - */ + /* Indicates a "bundler-friendly" build mode. */ 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 @@ -65,11 +57,8 @@ 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. 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. */ + /* Indicates a node.js-for-node.js build (untested and + ** unsupported). */ F_NODEJS = 1<<5, /* Indicates a wasmfs build (untested and unsupported). */ F_WASMFS = 1<<6, @@ -82,13 +71,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 distinct from + ** by Emscripten. For a given set of C flags (as opposed to ** 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, /* X.js or X.mjs, depending on F_ESM */ - CP_WASM = 1 << 31, /* X.wasm */ + CP_JS = 1 << 30, + CP_WASM = 1 << 31, CP_ALL = CP_JS | CP_WASM }; @@ -105,10 +94,11 @@ 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.$(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. +** 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. ** ** The final result of each build is a pair of JS/WASM files, but ** getting there requires generation of several files, primarily as @@ -126,9 +116,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 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. +** 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. ** ** Then there's sqlite3-api.BuildName.js, which is the entire SQLite3 ** JS API (generated from the list defined in $(sqlite3-api.jses)). It @@ -136,7 +126,9 @@ 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-lite.c). +** in or out (using ./c-pp.c). +** +** [^1]: The legal BuildNames are in this file's BuildDef_map macro. */ struct BuildDef { /* @@ -151,8 +143,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 with a "64" suffix, except that that - ** throws off the output alignment in parallel builds ;). + ** Alternately, the same emoji a "64" suffix, excep that that throws + ** off the output alignment in parallel builds ;). */ const char *zEmo; /* @@ -169,13 +161,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 make target deps */ + const char *zDeps; /* Extra deps */ const char *zEnv; /* emcc -sENVIRONMENT=... value */ /* ** Makefile code "ifeq (...)". If set, this build is enclosed in a ** $zIfCond/endif block. */ - const char *zIfCond; + const char *zIfCond; /* makefile "ifeq (...)" or similar */ int flags; /* Flags from BuildDefFlags */ }; typedef struct BuildDef BuildDef; @@ -191,7 +183,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) \ @@ -204,8 +196,9 @@ 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 vanilla, esm, and -** bundler-friendly builds should be defined first (in that order). +** up by prior builds. Because of that, the (sqlite3, vanilla), +** (sqlite3, esm), and (sqlite3, bundler-friendly) builds should be +** defined first (in that order). */ struct BuildDefs { #define E(N) BuildDef N; @@ -291,7 +284,7 @@ const BuildDefs oBuildDefs = { " -DSQLITE_SPEEDTEST1_WASM" " $(SQLITE_OPT)" " -USQLITE_WASM_BARE_BONES" - " -USQLITE_C -DSQLITE_C=$(sqlite3.c)" + " -USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c)" " $(speedtest1.exit-runtime0)" " $(speedtest1.c.in)" " -lm", @@ -319,7 +312,7 @@ const BuildDefs oBuildDefs = { " -DSQLITE_SPEEDTEST1_WASM" " $(SQLITE_OPT)" " -USQLITE_WASM_BARE_BONES" - " -USQLITE_C -DSQLITE_C=$(sqlite3.c)" + " -USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c)" " $(speedtest1.exit-runtime0)" " $(speedtest1.c.in)" " -lm", @@ -352,7 +345,8 @@ 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. */ @@ -377,7 +371,7 @@ const BuildDefs oBuildDefs = { .node = { .zEmo = "🍟", .zBaseName = "sqlite3-node", - .zDotWasm = "sqlite3", + .zDotWasm = 0, .zCmppD = "-Dtarget:node $(c-pp.D.bundler)", .zEmcc = 0, .zEmccExtra = 0, @@ -388,21 +382,21 @@ const BuildDefs oBuildDefs = { ** node. */, .zDeps = 0, .zIfCond = 0, - .flags = CP_JS | F_UNSUPPORTED | F_ESM | F_NODEJS + .flags = CP_ALL | F_UNSUPPORTED | F_NODEJS }, /* 64-bit node. */ .node64 = { .zEmo = "🍔", .zBaseName = "sqlite3-node-64bit", - .zDotWasm = "sqlite3-64bit", + .zDotWasm = 0, .zCmppD = "-Dtarget:node $(c-pp.D.bundler)", .zEmcc = 0, .zEmccExtra = 0, .zEnv = "node", .zDeps = 0, .zIfCond = 0, - .flags = CP_JS | F_UNSUPPORTED | F_ESM | F_NODEJS | F_64BIT + .flags = CP_ALL | F_UNSUPPORTED | F_NODEJS | F_64BIT }, /* Entirely unsupported. */ @@ -493,8 +487,8 @@ static void mk_prologue(void){ ** configuration). Comments like "saves nothing" may not be ** technically correct: "nothing" means "some neglible amount." ** - ** Performance gains or losses are _not_ taken into account - ** here, only wasm file size. + ** Note that performance gains/losses are _not_ taken into + ** account here: only wasm file size. */ "--enable-bulk-memory-opt " /* required */ "--all-features " /* required */ @@ -644,7 +638,7 @@ static void mk_pre_post(char const *zBuildName, BuildDef const * pB){ pB->zDotWasm); } ps(""); - pf("\t@$(call b.mkdir@); $(call b.c-pp.shcmd," + pf("\t@$(call b.c-pp.shcmd," "%s," "$(pre-js.in.js)," "$(pre-js.%s.js)," @@ -783,14 +777,13 @@ 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); @@ -921,9 +914,10 @@ 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.wasm)\n", + "b-%s: $(out.%s.js) $(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", @@ -994,18 +988,11 @@ static void mk_fiddle(void){ pf("$(out.%s.js): $(MAKEFILE_LIST) " "$(EXPORTED_FUNCTIONS.fiddle) " "$(fiddle.c.in) " - "$(pre-post.%s.deps)", + "$(pre-post.%s.deps)\n", 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@$(call b.mkdir@)\n" - "\t$(b.cmd@)$(bin.emcc) -o $@" - " $(emcc.flags.%s)" /* set in GNUmakefile */ + pf("\t$(b.cmd@)$(bin.emcc) -o $@" + " $(emcc.flags.%s)" /* set in fiddle.make */ " $(pre-post.%s.flags)" " $(fiddle.c.in)" "\n", @@ -1070,7 +1057,7 @@ int main(int argc, char const ** argv){ BuildDefs_map(E) if( 0==strcmp("prologue",zArg) ){ mk_prologue(); }else { - fprintf(stderr,"Unknown build name: %s\n", zArg); + fprintf(stderr,"Unkown build name: %s\n", zArg); rc = 1; break; } diff --git a/ext/wasm/speedtest1-worker.js b/ext/wasm/speedtest1-worker.js index 9355ba93f..ba11fd163 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(globalThis.location.href).searchParams; + const urlParams = new URL(self.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( !globalThis.FileSystemHandle - || !globalThis.FileSystemDirectoryHandle - || !globalThis.FileSystemFileHandle){ + if( !self.FileSystemHandle + || !self.FileSystemDirectoryHandle + || !self.FileSystemFileHandle){ return f._ = ""; } try{ @@ -92,7 +92,7 @@ } }; - globalThis.onmessage = function(msg){ + self.onmessage = function(msg){ msg = msg.data; switch(msg.type){ case 'run': @@ -111,8 +111,7 @@ setStatus: (text)=>mPost('load-status',text) }; log("Initializing speedtest1 module..."); - globalThis.sqlite3InitModule.__isUnderTest = true; - globalThis.sqlite3InitModule(EmscriptenModule).then(async (sqlite3)=>{ + self.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 d41f20a4e..cce617185 100644 --- a/ext/wasm/speedtest1.html +++ b/ext/wasm/speedtest1.html @@ -44,137 +44,132 @@ 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._ = ""; + 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._ = ""; } - }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){ - 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; + const capi = sqlite3.capi, wasm = sqlite3.wasm; + //console.debug('sqlite3 =',sqlite3); + const pDir = wasmfsDir(wasm); + if(pDir){ + console.warn("Persistent storage:",pDir); } - 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); + 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(','))); } - } - 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)); + + 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); + } + } + 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( + "--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"); - }, 50); + 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); }/*runTests()*/; self.sqlite3TestModule.print = log; self.sqlite3TestModule.printErr = logErr; - sqlite3InitModule.__isUnderTest = true; sqlite3InitModule(self.sqlite3TestModule).then(runTests); })();</script> </body> diff --git a/ext/wasm/tester1-worker.c-pp.html b/ext/wasm/tester1-worker.c-pp.html index 71d827a35..e461b6cbf 100644 --- a/ext/wasm/tester1-worker.c-pp.html +++ b/ext/wasm/tester1-worker.c-pp.html @@ -13,18 +13,10 @@ <body> <h1 id='color-target'>sqlite3 tester #1: Worker thread (@bitness@-bit WASM)</h1> <div>Variants: - conventional UI thread: - (<a href='tester1.html' target='tester1.html'>32-bit</a>, - <a href='tester1-64bit.html' target='tester1-64bit.html'>64-bit</a>), - conventional worker: - (<a href='tester1-worker.html' target='tester1-worker.html'>32-bit</a>, - <a href='tester1-worker-64bit.html' target='tester1-worker-64bit.html'>64-bit</a>), - ESM in UI thread: - (<a href='tester1-esm.html' target='tester1-esm.html'>32-bit</a>, - <a href='tester1-esm-64bit.html' target='tester1-esm-64bit.html'>64-bit</a>), - ESM worker:</a> - (<a href='tester1-worker.html?esm' target='tester1-worker.html?esm'>32-bit</a> - <a href='tester1-worker-64bit.html?esm' target='tester1-worker-64bit.html?esm'>64-bit</a>) + <a href='tester1.html' target='tester1.html'>conventional UI thread</a>, + <a href='tester1-worker.html' target='tester1-worker.html'>conventional worker</a>, + <a href='tester1-esm.html' target='tester1-esm.html'>ESM in UI thread</a>, + <a href='tester1-worker.html?esm' target='tester1-worker.html?esm'>ESM worker</a> </div> <div class='input-wrapper'> <input type='checkbox' id='cb-log-reverse'> diff --git a/ext/wasm/tester1.c-pp.html b/ext/wasm/tester1.c-pp.html index 4bb53ee56..95fe52219 100644 --- a/ext/wasm/tester1.c-pp.html +++ b/ext/wasm/tester1.c-pp.html @@ -11,19 +11,11 @@ <style></style> </head> <body><h1 id='color-target'></h1> - <div>Variants (32-bit): - conventional UI thread: - (<a href='tester1.html' target='tester1.html'>32-bit</a>, - <a href='tester1-64bit.html' target='tester1-64bit.html'>64-bit</a>), - conventional worker: - (<a href='tester1-worker.html' target='tester1-worker.html'>32-bit</a>, - <a href='tester1-worker-64bit.html' target='tester1-worker-64bit.html'>64-bit</a>), - ESM in UI thread: - (<a href='tester1-esm.html' target='tester1-esm.html'>32-bit</a>, - <a href='tester1-esm-64bit.html' target='tester1-esm-64bit.html'>64-bit</a>), - ESM worker:</a> - (<a href='tester1-worker.html?esm' target='tester1-worker.html?esm'>32-bit</a> - <a href='tester1-worker-64bit.html?esm' target='tester1-worker-64bit.html?esm'>64-bit</a>) + <div>Variants: + <a href='tester1.html' target='tester1.html'>conventional UI thread</a>, + <a href='tester1-worker.html' target='tester1-worker.html'>conventional worker</a>, + <a href='tester1-esm.html' target='tester1-esm.html'>ESM in UI thread</a>, + <a href='tester1-worker.html?esm' target='tester1-worker.html?esm'>ESM worker</a> </div> <div class='input-wrapper'> <input type='checkbox' id='cb-log-reverse'> diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index 083b5eca4..f72e0803f 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -153,10 +153,6 @@ globalThis.sqlite3InitModule = sqlite3InitModule; logClass('error',...args); }; - const debug = (...args)=>{ - console.debug('tester1',...args); - }; - const toss = (...args)=>{ error(...args); throw new Error(args.join(' ')); @@ -214,9 +210,8 @@ globalThis.sqlite3InitModule = sqlite3InitModule; filter.test(error.message) passes. If it's a function, the test passes if filter(error) returns truthy. If it's a string, the test passes if the filter matches the exception message - precisely. If filter is a number then it is compared against - the resultCode property of the exception. In all other cases - the test fails, throwing an Error. + precisely. In all other cases the test fails, throwing an + Error. If it throws, msg is used as the error report unless it's falsy, in which case a default is used. @@ -230,9 +225,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; if(filter instanceof RegExp) pass = filter.test(err.message); else if(filter instanceof Function) pass = filter(err); else if('string' === typeof filter) pass = (err.message === filter); - else if('number' === typeof filter) pass = (err.resultCode === filter); if(!pass){ - console.error("Filter",filter,"rejected exception",err); throw new Error(msg || ("Filter rejected this exception: <<"+err.message+">>")); } return this; @@ -376,88 +369,6 @@ globalThis.sqlite3InitModule = sqlite3InitModule; clearOnInit: true, initialCapacity: 6 }; - -//#if enable-see - /** - Code consolidator for SEE sanity checks for various VFSes. ctor - is the VFS's oo1.DB-type constructor. ctorOptFunc(bool) is a - function which must return a constructor args object for ctor. It - is passed true if the db needs to be cleaned up/unlinked before - opening it (OPFS) and false if not (how that is done is - VFS-dependent). dbUnlink is a function which is expected to - unlink() the db file if the ctorOpfFunc does not do so when - passed true (kvvfs). - - This function initializes the db described by ctorOptFunc(...), - writes some secret info into it, and re-opens it twice to - confirmi that it can be read with an SEE key and cannot be read - without one. - */ - T.seeBaseCheck = function(ctor, ctorOptFunc, dbUnlink){ - let initDb = true; - const tryKey = function(keyKey, key, expectCount){ - let db; - //console.debug('tryKey()',arguments); - try { - if (initDb) { - const ctoropt = ctorOptFunc(initDb); - initDb = false; - db = new ctor({ - ...ctoropt, - [keyKey]: key - }); - db.exec([ - "drop table if exists t;", - "create table t(a);" - ]); - db.close(); - db = null; - // Ensure that it's actually encrypted... - let err; - try { - db = new ctor(ctorOptFunc(false)); - T.assert(db, 'db opened') /* opening is fine, but... */; - db.exec("select 1 from sqlite_schema"); - console.warn("(should not be reached) sessionStorage =", sessionStorage); - } catch (e) { - err = e; - } finally { - db.close() - db = null; - } - T.assert(err, "Expecting an exception") - .assert(capi.SQLITE_NOTADB == err.resultCode, - "Expecting NOTADB"); - }/*initDb*/ - db = new ctor({ - ...ctorOptFunc(false), - [keyKey]: key - }); - db.exec("insert into t(a) values (1),(2)"); - T.assert(expectCount === db.selectValue('select sum(a) from t')); - } finally { - if (db) db.close(); - } - }; - const hexFoo = new Uint8Array([0x66,0x6f,0x6f]/*=="foo"*/); - dbUnlink(); - tryKey('textkey', 'foo', 3); - T.assert( !initDb ); - tryKey('textkey', 'foo', 6); - dbUnlink(); - initDb = true; - tryKey('key', 'foo', 3); - T.assert( !initDb ); - tryKey('key', hexFoo, 6); - dbUnlink(); - initDb = true; - tryKey('hexkey', hexFoo, 3); - T.assert( !initDb ); - tryKey('hexkey', hexFoo, 6); - dbUnlink(); - }, -//#endif enable-see - //////////////////////////////////////////////////////////////////////// // End of infrastructure setup. Now define the tests... //////////////////////////////////////////////////////////////////////// @@ -520,7 +431,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; assert(wasmCtypes.structs[1/*sqlite3_io_methods*/ ].members.xFileSize.offset>0); [ /* Spot-check a handful of constants to make sure they got installed... */ - 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8','SQLITE_UTF8_ZT', + 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8', 'SQLITE_STATIC', 'SQLITE_DIRECTONLY', 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE' ].forEach((k)=>T.assert('number' === typeof capi[k])); @@ -1034,7 +945,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let ptr = k1.pointer; k1.dispose(); T.assert(undefined === k1.pointer). - mustThrowMatching(()=>{k1.$pP=1}, /disposed/); + mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/); }finally{ k1.dispose(); k2.dispose(); @@ -1638,9 +1549,6 @@ globalThis.sqlite3InitModule = sqlite3InitModule; T.assert(blob instanceof Uint8Array). assert(0x68===blob[0] && 0x69===blob[1]); blob = null; - blob = db.selectValue("select ?1", new Uint8Array([97,0,98,0,99]), - sqlite3.capi.SQLITE_TEXT); - T.assert("a\0b\0c"===blob, "Something is amiss with embedded NULs"); let counter = 0, colNames = []; list.length = 0; db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{ @@ -2635,7 +2543,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .assert(wasm.isPtr(tmplMod.$xRowid)) .assert(wasm.isPtr(tmplMod.$xCreate)) .assert(tmplMod.$xCreate === tmplMod.$xConnect, - "setupModule() must make these equivalent and "+ + "setup() must make these equivalent and "+ "installMethods() must avoid re-compiling identical functions"); tmplMod.$xCreate = wasm.ptr.null /* make tmplMod eponymous-only */; let rc = capi.sqlite3_create_module( @@ -2886,69 +2794,25 @@ globalThis.sqlite3InitModule = sqlite3InitModule; //////////////////////////////////////////////////////////////////////// T.g('kvvfs') .t({ - name: 'kvvfs v1 API availability', + name: 'kvvfs is disabled in worker', + predicate: ()=>(isWorker() || "test is only valid in a Worker"), test: function(sqlite3){ - const capi = sqlite3.capi; - if( isUIThread() ){ - T.assert( !!capi.sqlite3_js_kvvfs_size ) - .assert( !!capi.sqlite3_js_kvvfs_clear ); - }else{ - /* Historical behaviour retained not for compatibility but - to help avoid some confusion between the v1 and v2 kvvfs - APIs (namely in how the v1 variants handle empty - strings). */ - T.assert( !capi.sqlite3_js_kvvfs_clear ) - .assert( !capi.sqlite3_js_kvvfs_size ); - } - const k = sqlite3.kvvfs; - T.assert( k && 'object'===typeof k ); - for(const n of ['reserve', 'import', 'export', - 'unlink', 'listen', 'unlisten', - 'exists', - 'estimateSize', 'clear'] ){ - T.assert( k[n] instanceof Function ); - } - - if( 0 ){ - const scope = wasm.scopedAllocPush(); - try{ - const pg = [ - "53514C69746520666F726D61742033b20b0101b402020d02d02l01d0", - "4l01jb02b2E91E00Dd011FD3b1FD3dxl2B010617171701377461626C", - "656B767666736B7676667302435245415445205441424C45206B76766673286129" - ].join(''); - const n = pg.length; - const pI = wasm.scopedAlloc( n+1 ); - const nO = 8192 * 2; - const pO = wasm.scopedAlloc( nO ); - const heap = wasm.heap8u(); - let i; - for( i=0; i<n; ++i ){ - heap[wasm.ptr.add(pI, i)] = pg.codePointAt(i) & 0xff; - } - heap[wasm.ptr.add(pI, i)] = 0; - const rc = wasm.exports.sqlite3__wasm_kvvfs_decode(pI, pO, nO); - const u = heap.slice(pO, wasm.ptr.add(pO,rc)); - debug("decode rc=", rc, u); - }finally{ - wasm.scopedAllocPop(scope); - } - } + T.assert( + !capi.sqlite3_vfs_find('kvvfs'), + "Expecting kvvfs to be unregistered." + ); } - }/*kvvfs API availability*/) + }) .t({ - name: 'kvvfs sessionStorage', - predicate: ()=>(globalThis.sessionStorage || "sessionStorage is unavailable"), + name: 'kvvfs in main thread', + predicate: ()=>(isUIThread() + || "local/sessionStorage are unavailable in a Worker"), test: function(sqlite3){ - const JDb = sqlite3.oo1.JsStorageDb; + const filename = this.kvvfsDbFile = 'session'; const pVfs = capi.sqlite3_vfs_find('kvvfs'); T.assert(looksLikePtr(pVfs)); - let x = sqlite3.kvvfs.internal.storageForZClass('session'); - T.assert( 0 === x.files.length ) - .assert( globalThis.sessionStorage===x.storage ) - .assert( 'kvvfs-session-' === x.keyPrefix ); - const filename = this.kvvfsDbFile = 'session'; - const unlink = this.kvvfsUnlink = ()=>sqlite3.kvvfs.clear(filename); + const JDb = this.JDb = sqlite3.oo1.JsStorageDb; + const unlink = this.kvvfsUnlink = ()=>JDb.clearStorage(this.kvvfsDbFile); unlink(); let db = new JDb(filename); try { @@ -2956,533 +2820,87 @@ globalThis.sqlite3InitModule = sqlite3InitModule; 'create table kvvfs(a);', 'insert into kvvfs(a) values(1),(2),(3)' ]); - T.assert(3 === db.selectValue('select count(*) from kvvfs')) - .assert( db.storageSize() > 0, "Db size counting is broken" ); - db.close(); - db = undefined; - db = new JDb(filename); - db.exec('insert into kvvfs(a) values(4),(5),(6)'); - T.assert(6 === db.selectValue('select count(*) from kvvfs')); - }finally{ - if( db ) db.close(); - } - //console.debug("sessionStorage",globalThis.sessionStorage); - } - }/*kvvfs sanity checks*/) - .t({ - name: 'transient kvvfs', - //predicate: ()=>false, - test: function(sqlite3){ - const filename = '.' /* preinstalled instance */; - const JDb = sqlite3.oo1.JsStorageDb; - const DB = sqlite3.oo1.DB; - T.mustThrowMatching(()=>new JDb(""), capi.SQLITE_MISUSE); - T.mustThrowMatching(()=>{ - new JDb("this\ns an illegal - contains control characters"); - /* We don't have a way to get error strings from xOpen() - to this point? xOpen() does not have a handle to the - db and SQLite is not calling xGetLastError() to fetch - the error string. */ - }, capi.SQLITE_RANGE); - T.mustThrowMatching(()=>{new JDb("foo-journal");}, - capi.SQLITE_MISUSE); - T.mustThrowMatching(()=>{new JDb("foo-wal");}, - capi.SQLITE_MISUSE); - T.mustThrowMatching(()=>{new JDb("foo-shm");}, - capi.SQLITE_MISUSE); - T.mustThrowMatching(()=>{ - new JDb("01234567890123456789"+ - "01234567890123456789"+ - "01234567890123456789"+ - "01234567890123456789"+ - "01234567890123456789"+ - "01234567890123456789"+ - "0"/*too long*/); - }, capi.SQLITE_RANGE); - { - const name = "01234567890123456789012" /* max name length */; - (new JDb(name)).close(); - T.assert( sqlite3.kvvfs.unlink(name) ); - } - - sqlite3.kvvfs.clear(filename); - let db = new JDb(filename); - const sqlSetup = [ - 'create table kvvfs(a);', - 'insert into kvvfs(a) values(1),(2),(3)' - ]; - try { - T.assert( 0===db.storageSize(), "expecting 0 storage size" ); - T.mustThrowMatching(()=>db.clearStorage(), /in-use/); - //db.clearStorage(); - T.assert( 0===db.storageSize(), "expecting 0 storage size" ); - db.exec(sqlSetup); - T.assert( 0<db.storageSize(), "expecting non-0 db size" ); - T.mustThrowMatching(()=>db.clearStorage(), /in-use/); - //db.clearStorage(/*wiping everything out from under it*/); - T.assert( 0<db.storageSize(), "expecting non-0 storage size" ); - //db.exec(sqlSetup/*that actually worked*/); - /* Clearing the storage out from under the db does actually - work so long as we re-initialize it before reading. - It is tentatively disallowed for sanity's sake rather - than it not working. - */ - T.assert( 0<db.storageSize(), "expecting non-0 db size" ); - const close = ()=>{ - db.close(); - db = undefined; - }; T.assert(3 === db.selectValue('select count(*) from kvvfs')); - close(); - - const exportDb = sqlite3.kvvfs.export; + db.close(); db = new JDb(filename); db.exec('insert into kvvfs(a) values(4),(5),(6)'); T.assert(6 === db.selectValue('select count(*) from kvvfs')); - const exp = exportDb({name:filename,includeJournal:true}); - T.assert( filename===exp.name, "Broken export filename" ) - .assert( exp?.size > 0, "Missing db size" ) - .assert( exp?.pages?.length > 0, "Missing db pages" ); - console.debug("kvvfs to Object:",exp); - close(); - - const dbFileRaw = 'file:new-storage?vfs=kvvfs&delete-on-close=1'; - db = new DB({ - filename: dbFileRaw, - //flags: 'ct' - }); - db.exec(sqlSetup); - const dbFilename = db.dbFilename(); - //console.warn("db.dbFilename() =",dbFilename); - T.assert(3 === db.selectValue('select count(*) from kvvfs')); - debug("kvvfs to Object:",exportDb(dbFilename)); - const n = sqlite3.kvvfs.estimateSize( dbFilename ); - T.assert( n>0, "Db size count failed" ); - - if( 1 ){ - // Concurrent open of that same name uses the same storage - const x = new JDb(dbFilename); - T.assert(3 === db.selectValue('select count(*) from kvvfs')); - x.close(); - } - close(); - // When the final instance of a name is closed, the storage - // disappears... - T.mustThrowMatching(function(){ - /* Ensure that 'new-storage' was deleted when its refcount - went to 0, because of its 'transient' flag. By default - the objects are retained, like a filesystem would. */ - let ddb = new JDb(dbFilename); - try{ddb.selectValue('select a from kvvfs')} - finally{ddb.close()} - }, /no such table: kvvfs/); }finally{ - if( db ) db.close(); - } - } - }/*transient kvvfs*/) - .t({ - name: 'concurrent transient kvvfs', - //predicate: ()=>false, - test: function(sqlite3){ - const filename = 'myStorage'; - const kvvfs = sqlite3.kvvfs; - const DB = sqlite3.oo1.DB; - const JDb = sqlite3.oo1.JsStorageDb; - let db; - let duo; - let q1, q2; - const sqlSetup = [ - 'create table kvvfs(a);', - 'insert into kvvfs(a) values(1),(2),(3)' - ]; - const sqlCount = 'select count(*) from kvvfs'; - - try { - const exportDb = sqlite3.kvvfs.export; - const dbFileRaw = 'file:'+filename+'?vfs=kvvfs&delete-on-close=1'; - sqlite3.kvvfs.clear(filename); - db = new DB(dbFileRaw); - db.exec(sqlSetup); - T.assert(3 === db.selectValue(sqlCount)); - - duo = new JDb(filename); - duo.exec('insert into kvvfs(a) values(4),(5),(6)'); - T.assert(6 === db.selectValue(sqlCount)); - const expOpt = { - name: filename, - decodePages: true - }; - let exp = exportDb(expOpt); - let expectRows = 6; - debug("exported db",exp); db.close(); - T.assert(expectRows === duo.selectValue(sqlCount)); - duo.close(); - T.mustThrowMatching(function(){ - let ddb = new JDb(filename); - try{ddb.selectValue('select a from kvvfs')} - finally{ddb.close()} - }, /.*no such table: kvvfs.*/); - - T.assert( kvvfs.unlink(filename) ) - .assert( !kvvfs.exists(filename) ); - - const importDb = sqlite3.kvvfs.import; - duo = new JDb(dbFileRaw); - T.mustThrowMatching(()=>importDb(exp,true), /.*in use.*/); - duo.close(); - importDb(exp, true); - duo = new JDb(dbFileRaw); - T.assert(expectRows === duo.selectValue(sqlCount)); - let newCount; - try{ - duo.transaction(()=>{ - duo.exec("insert into kvvfs(a) values(7)"); - newCount = duo.selectValue(sqlCount); - T.assert(false, "rolling back"); - }); - }catch(e){/*ignored*/} - T.assert(7===newCount, "Unexpected row count before rollback") - .assert(expectRows === duo.selectValue(sqlCount), - "Unexpected row count after rollback"); - duo.close(); - - T.assert( kvvfs.unlink(filename) ) - .assert( !kvvfs.exists(filename) ); - - importDb(exp, true); - db = new JDb({ - filename, - flags: 'c' - /* BUG: without the 'c' flag, the db works until we try to - vacuum, at which point it fails with "read only db". */ - }); - duo = new JDb(filename); - T.assert(expectRows === duo.selectValue(sqlCount)); - const sqlIns1 = "insert into kvvfs(a) values(?)"; - q1 = db.prepare(sqlIns1); - q2 = duo.prepare(sqlIns1); - if( 0 ){ - q1.bind('from q1').stepFinalize(); - ++expectRows; - T.assert(expectRows === duo.selectValue(sqlCount), - "Unexpected record count."); - q2.bind('from q1').stepFinalize(); - ++expectRows; - }else{ - q1.bind('from q1'); - T.assert(capi.SQLITE_DONE===capi.sqlite3_step(q1), - "Unexpected step result"); - ++expectRows; - T.assert(expectRows === duo.selectValue(sqlCount), - "Unexpected record count."); - q2.bind('from q1').step(); - ++expectRows; - } - T.assert(expectRows === db.selectValue(sqlCount), - "Unexpected record count."); - q1.finalize(); - q2.finalize(); - - if( 1 ){ - debug("Begin vacuum/page size test..."); - const defaultPageSize = 1024 * 8 /* build-time default */; - const pageSize = 0 - ? defaultPageSize - : 1024 * 16 /* any valid value other than the default */; - if( 0 ){ - debug("Export before vacuum", exportDb(expOpt)); - debug("page size before vacuum", - db.selectArray( - "select page_size from pragma_page_size()" - )); - } - //kvvfs.log.xFileControl = true; - //kvvfs.log.xAccess = true; - db.exec([ - "BEGIN;", - "insert into kvvfs(a) values(randomblob(16000/*>pg size*/));", - "COMMIT;", - "delete from kvvfs where octet_length(a)>100;", - "pragma page_size="+pageSize+";", - "vacuum;", - "select 1;" - ]); - const expectPageSize = kvvfs.internal.disablePageSizeChange - ? defaultPageSize - : pageSize; - const gotPageSize = db.selectValue( - "select page_size from pragma_page_size()" - ); - T.assert(+gotPageSize === expectPageSize, - "Expecting page size",expectPageSize, - "got",gotPageSize); - T.assert(expectRows === duo.selectValue(sqlCount), - "Unexpected record count."); - kvvfs.log.xAccess = kvvfs.log.xFileControl = false; - T.assert(expectRows === duo.selectValue(sqlCount), - "Unexpected record count."); - exp = exportDb(expOpt); - debug("Exported page-expanded db",exp); - if( 0 ){ - debug("vacuumed export",exp); - } - debug("End vacuum/page size test."); - }else{ - expectRows = 6; - } - - db.close(); - duo.close(); - T.assert( kvvfs.unlink(exp.name) ) - .assert( !kvvfs.exists(exp.name) ); - importDb(exp); - T.assert( kvvfs.exists(exp.name) ); - db = new JDb(exp.name); - //debug("column count after export",db.selectValue(sqlCount)); - T.assert(expectRows === db.selectValue(sqlCount), - "Unexpected record count."); - - /* - TODO: more advanced concurrent use tests, e.g. looping - over a query in one connection while writing from - another. Currently that will probably corrupt the db, and - kvvfs's journaling does not support multiple journals per - storage unit. We need to test the locking and fix it as - appropriate. - */ - }finally{ - q1?.finalize?.(); - q2?.finalize?.(); - db?.close?.(); - duo?.close?.(); } } - }/*concurrent transient kvvfs*/) - + }/*kvvfs sanity checks*/) +//#if enable-see .t({ - name: 'kvvfs listeners (experiment)', + name: 'kvvfs with SEE encryption', + predicate: ()=>(isUIThread() + || "Only available in main thread."), test: function(sqlite3){ - const kvvfs = sqlite3.kvvfs; - const filename = 'listen'; - let db; - try { - const DB = sqlite3.oo1.DB; - const sqlSetup = [ - 'create table kvvfs(a);', - 'insert into kvvfs(a) values(1),(2),(3)' - ]; - const sqlCount = "select count(*) from kvvfs"; - const sqlSelectSchema = "select * from sqlite_schema"; - const counts = Object.create(null); - const incr = (key)=>counts[key] = 1 + (counts[key] ?? 0); - const pglog = Object.assign(Object.create(null),{ - pages: [], - jrnl: undefined, - size: undefined, - includeJournal: false, - decodePages: true, - exception: new Error("Testing that exceptions from listeners do not interfere") - }); - const toss = ()=>{ - if( pglog.exception ){ - const e = pglog.exception; - delete pglog.exception; - throw e; - } + this.kvvfsUnlink(); + let initDb = true; + const tryKey = function(keyKey, key, expectCount){ + let db; + //console.debug('tryKey()',arguments); + const ctoropt = { + filename: this.kvvfsDbFile + //vfs: 'kvvfs' + //,flags: 'ct' }; - - const listener = { - storage: filename, - reserve: true, - includeJournal: pglog.includeJournal, - decodePages: pglog.decodePages, - events: { - /** - These may be async but must not be in this case - because we can't test their result without a lot of - hoop-jumping if they are. Kvvfs calls these - asynchronously, though. - */ - 'open': (ev)=>{ - //console.warn('open',ev); - incr(ev.type); - T.assert(filename===ev.storageName) - .assert('number'===typeof ev.data); - }, - 'close': (ev)=>{ - //console.warn('close',ev); - incr(ev.type); - T.assert('number'===typeof ev.data); - toss(); - }, - 'delete': (ev)=>{ - //console.warn('delete',ev); - incr(ev.type); - T.assert('string'===typeof ev.data); - switch(ev.data){ - case 'jrnl': - T.assert(pglog.includeJournal); - pglog.jrnl = null; - break; - default:{ - const n = +ev.data; - T.assert( n>0, "Expecting positive db page number" ); - if( n < pglog.pages.length ){ - pglog.size = undefined; - } - pglog.pages[n] = undefined; - break; - } - } - }, - 'sync': (ev)=>{ - incr(ev.data ? 'xSync' : 'xFileControlSync'); - }, - 'write': (ev)=>{ - //console.warn('write',ev); - incr(ev.type); - T.assert(Array.isArray(ev.data)); - const key = ev.data[0], val = ev.data[1]; - T.assert('string'===typeof key); - switch( key ){ - case 'jrnl': - T.assert(pglog.includeJournal); - pglog.jrnl = val; - break; - case 'sz':{ - const sz = +val; - T.assert( sz>0, "Expecting a db page number" ); - if( sz < pglog.sz ){ - pglog.pages.length = sz / pglog.pages.length; - } - pglog.size = sz; - break; - } - default: - T.assert( +key>0, "Expecting a positive db page number" ); - pglog.pages[+key] = val; - if( pglog.decodePages ){ - T.assert( val instanceof Uint8Array ); - }else{ - T.assert( 'string'===typeof val ); - } - break; - } + try { + if (initDb) { + initDb = false; + db = new this.JDb({ + ...ctoropt, + [keyKey]: key + }); + db.exec([ + "drop table if exists t;", + "create table t(a);" + ]); + db.close(); + // Ensure that it's actually encrypted... + let err; + try { + db = new this.JDb(ctoropt); + T.assert(db, 'db opened') /* opening is fine, but... */; + db.exec("select 1 from sqlite_schema"); + console.warn("(should not be reached) sessionStorage =", sessionStorage); + } catch (e) { + err = e; + } finally { + db.close() } - } - }; - - kvvfs.listen(listener); - const dbFileRaw = 'file:'+filename+'?vfs=kvvfs&delete-on-close=1'; - const expOpt = { - name: filename, - //decodePages: true - }; - db = new DB(dbFileRaw); - db.exec(sqlSetup); - T.assert(db.selectObjects(sqlSelectSchema)?.length>0, - "Unexpected empty schema"); - db.close(); - debug("kvvfs listener counts:",counts); - T.assert( counts.open ); - T.assert( counts.close ); - T.assert( listener.includeJournal ? counts.delete : !counts.delete ); - T.assert( counts.write ); - T.assert( counts.xSync ); - T.assert( counts.xFileControlSync>=counts.xSync ); - T.assert( counts.open===counts.close ); - T.assert( pglog.includeJournal - ? (null===pglog.jrnl) - : (undefined===pglog.jrnl), - "Unexpected pglog.jrnl value: "+pglog.jrnl ); - if( 1 ){ - T.assert(undefined===pglog.pages[0], "Expecting empty slot 0"); - pglog.pages.shift(); - //debug("kvvfs listener pageLog", pglog); - } - const before = JSON.stringify(counts); - T.assert( kvvfs.unlisten(listener) ); - T.assert( !kvvfs.unlisten(listener) ); - db = new DB(dbFileRaw); - T.assert( db.selectObjects(sqlSelectSchema)?.length>0 ); - const exp = kvvfs.export(expOpt); - const expectRows = db.selectValue(sqlCount); - db.exec("delete from kvvfs"); - db.close(); - const after = JSON.stringify(counts); - T.assert( before===after, "Expecting no events after unlistening." ); - if( 0 ){ - exp = kvvfs.export(expOpt); - debug("Post-delete export:",exp); - } - if( 1 ){ - // Replace the storage with the pglog state... - const bogoExp = { - name: filename, - size: pglog.size, - timestamp: Date.now(), - pages: pglog.pages - }; - //debug("exp",exp); - //debug("bogoExp",bogoExp); - kvvfs.import(bogoExp, true); - //debug("Re-exported", kvvfs.export(expOpt)); - db = new DB(dbFileRaw); - // Failing on the next line despite exports looking good - T.assert(db.selectObjects(sqlSelectSchema)?.length>0, - "Empty schema on imported db"); - T.assert(expectRows===db.selectValue(sqlCount)); - db.close(); + T.assert(err, "Expecting an exception") + .assert(sqlite3.capi.SQLITE_NOTADB == err.resultCode, + "Expecting NOTADB"); + }/*initDb*/ + //console.debug('tryKey()',arguments); + db = new sqlite3.oo1.DB({ + ...ctoropt, + vfs: 'kvvfs', + [keyKey]: key + }); + db.exec("insert into t(a) values (1),(2)"); + T.assert(expectCount === db.selectValue('select sum(a) from t')); + } finally { + if (db) db.close(); } - }finally{ - db?.close?.(); - kvvfs.unlink(filename); - } - } - })/*kvvfs listeners */ - - .t({ - name: 'kvvfs vtab', - predicate: (sqlite3)=>!!sqlite3.kvvfs.create_module, - test: function(sqlite3){ - const kvvfs = sqlite3.kvvfs; - const db = new sqlite3.oo1.DB(); - const db2 = new sqlite3.oo1.DB('file:foo?vfs=kvvfs&delete-on-close=1'); - try{ - kvvfs.create_module(db); - let rc = db.selectObjects("select * from sqlite_kvvfs order by name"); - debug("sqlite_kvvfs vtab:", rc); - const nDb = rc.length; - rc = db.selectObject("select * from sqlite_kvvfs where name='foo'"); - T.assert(rc, "Expecting foo storage record") - .assert('foo'===rc.name, "Unexpected name") - .assert(1===rc.nRef, "Unexpected refcount"); - db2.close(); - rc = db.selectObjects("select * from sqlite_kvvfs"); - T.assert( !rc.filter((o)=>o.name==='foo').length, - "Expecting foo storage to be gone"); - debug("sqlite_kvvfs vtab:", rc); - T.assert( rc.length+1 === nDb, - "Unexpected storage count: got",rc.length,"expected",nDb); - }finally{ - db.close(); - db2.close(); - } - } - })/* kvvfs vtab */ - -//#if enable-see - .t({ - name: 'kvvfs SEE encryption in sessionStorage', - predicate: ()=>(!!globalThis.sessionStorage - || "sessionStorage is not available"), - test: function(sqlite3){ - const JDb = sqlite3.oo1.JsStorageDb; - T.seeBaseCheck(JDb, - (isInit)=>return {filename: "session"}, - ()=>JDb.clearStorage('session')); + }.bind(this); + const hexFoo = new Uint8Array([0x66,0x6f,0x6f]/*=="foo"*/); + tryKey('textkey', 'foo', 3); + T.assert( !initDb ); + tryKey('textkey', 'foo', 6); + this.kvvfsUnlink(); + initDb = true; + tryKey('key', 'foo', 3); + T.assert( !initDb ); + tryKey('key', hexFoo, 6); + this.kvvfsUnlink(); + initDb = true; + tryKey('hexkey', hexFoo, 3); + T.assert( !initDb ); + tryKey('hexkey', hexFoo, 6); + this.kvvfsUnlink(); } })/*kvvfs with SEE*/ //#endif enable-see @@ -3497,7 +2915,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let countCommit = 0, countRollback = 0;; const db = new sqlite3.oo1.DB(':memory:',1 ? 'c' : 'ct'); let rc = capi.sqlite3_commit_hook(db, (p)=>{ - //debug("commit hook",arguments); + //console.debug("commit hook",arguments); ++countCommit; return (17 == p) ? 0 : capi.SQLITE_ERROR; }, 17); @@ -3897,14 +3315,71 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .t({ name: 'OPFS with SEE encryption', test: function(sqlite3){ - T.seeBaseCheck( - sqlite3.oo1.OpfsDb, - function(isInit){ - const opt = {filename: 'file:///sqlite3-see.edb'}; - if( isInit ) opt.filename += '?delete-before-open=1'; - return opt; - }, - ()=>{}); + const dbFile = 'file:///sqlite3-see.edb'; + const dbCtor = sqlite3.oo1.OpfsDb; + const hexFoo = new Uint8Array([0x66,0x6f,0x6f]/*=="foo"*/); + let initDb = true; + const tryKey = function(keyKey, key, expectCount){ + let db; + //console.debug('tryKey()',arguments); + const ctoropt = { + filename: dbFile, + flags: 'c' + }; + try { + if (initDb) { + initDb = false; + const opt = { + ...ctoropt, + [keyKey]: key + }; + opt.filename += '?delete-before-open=1'; + db = new dbCtor(opt); + db.exec([ + "drop table if exists t;", + "create table t(a);" + ]); + db.close(); + // Ensure that it's actually encrypted... + let err; + try { + db = new dbCtor(ctoropt); + T.assert(db, 'db opened') /* opening is fine, but... */; + const rv = db.exec({ + sql:"select count(*) from sqlite_schema", + returnValue: 'resultRows' + }); + console.warn("(should not be reached) rv =",rv); + } catch (e) { + err = e; + } finally { + db.close() + } + T.assert(err, "Expecting an exception") + .assert(sqlite3.capi.SQLITE_NOTADB == err.resultCode, + "Expecting NOTADB"); + }/*initDb*/ + db = new dbCtor({ + ...ctoropt, + [keyKey]: key + }); + db.exec("insert into t(a) values (1),(2)"); + T.assert(expectCount === db.selectValue('select sum(a) from t')); + } finally { + if (db) db.close(); + } + }; + tryKey('textkey', 'foo', 3); + T.assert( !initDb ); + tryKey('textkey', 'foo', 6); + initDb = true; + tryKey('key', 'foo', 3); + T.assert( !initDb ); + tryKey('key', hexFoo, 6); + initDb = true; + tryKey('hexkey', hexFoo, 3); + T.assert( !initDb ); + tryKey('hexkey', hexFoo, 6); } })/*OPFS with SEE*/ //#endif enable-see @@ -4109,11 +3584,69 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let poolUtil; const P1 = await inst(poolConfig).then(u=>poolUtil = u).catch(catcher); const dbFile = '/sqlite3-see.edb'; - T.seeBaseCheck( - poolUtil.OpfsSAHPoolDb, - (isInit)=>{return {filename: dbFile}}, - ()=>poolUtil.unlink(dbFile) - ); + const dbCtor = poolUtil.OpfsSAHPoolDb; + const hexFoo = new Uint8Array([0x66,0x6f,0x6f]/*=="foo"*/); + let initDb = true; + const tryKey = function(keyKey, key, expectCount){ + let db; + //console.debug('tryKey()',arguments); + const ctoropt = { + filename: dbFile, + flags: 'c' + }; + try { + if (initDb) { + initDb = false; + poolUtil.unlink(dbFile); + db = new dbCtor({ + ...ctoropt, + [keyKey]: key + }); + db.exec([ + "drop table if exists t;", + "create table t(a);" + ]); + db.close(); + // Ensure that it's actually encrypted... + let err; + try { + db = new dbCtor(ctoropt); + T.assert(db, 'db opened') /* opening is fine, but... */; + const rv = db.exec({ + sql:"select count(*) from sqlite_schema", + returnValue: 'resultRows' + }); + console.warn("(should not be reached) rv =",rv); + } catch (e) { + err = e; + } finally { + db.close() + } + T.assert(err, "Expecting an exception") + .assert(sqlite3.capi.SQLITE_NOTADB == err.resultCode, + "Expecting NOTADB"); + }/*initDb*/ + db = new dbCtor({ + ...ctoropt, + [keyKey]: key + }); + db.exec("insert into t(a) values (1),(2)"); + T.assert(expectCount === db.selectValue('select sum(a) from t')); + } finally { + if (db) db.close(); + } + }; + tryKey('textkey', 'foo', 3); + T.assert( !initDb ); + tryKey('textkey', 'foo', 6); + initDb = true; + tryKey('key', 'foo', 3); + T.assert( !initDb ); + tryKey('key', hexFoo, 6); + initDb = true; + tryKey('hexkey', hexFoo, 3); + T.assert( !initDb ); + tryKey('hexkey', hexFoo, 6); poolUtil.removeVfs(); } })/*opfs-sahpool with SEE*/ @@ -4182,54 +3715,44 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .t("Misc. stmt_...", function(sqlite3){ const db = new sqlite3.oo1.DB(); db.exec("create table t(a doggiebiscuits); insert into t(a) values(123)"); - let stmt; - try{ - stmt = db.prepare("select a, a+1 from t"); - T.assert( stmt.isReadOnly() ) - .assert( 0===capi.sqlite3_stmt_isexplain(stmt) ) - .assert( 0===capi.sqlite3_stmt_explain(stmt, 1) ) - .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) ) - .assert( 0===capi.sqlite3_stmt_explain(stmt, 2) ) - .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) ) - .assert( 0===capi.sqlite3_stmt_explain(stmt, 0) ) - .assert( 0===capi.sqlite3_stmt_isexplain(stmt) ); - let n = 0; - while( capi.SQLITE_ROW === capi.sqlite3_step(stmt) ){ - ++n; - T.assert( 0!==capi.sqlite3_stmt_explain(stmt, 1), - "Because stmt is busy" ) - .assert( capi.sqlite3_stmt_busy(stmt) ) - .assert( stmt.isBusy() ) - .assert( 0!==capi.sqlite3_stmt_readonly(stmt) ) - .assert( true===stmt.isReadOnly() ); - const sv = capi.sqlite3_column_value(stmt, 0); - T.assert( 123===capi.sqlite3_value_int(sv) ) - .assert( "doggiebiscuits"===capi.sqlite3_column_decltype(stmt,0) ) - .assert( null===capi.sqlite3_column_decltype(stmt,1) ); - } - T.assert( 1===n ) - .assert( 0===capi.sqlite3_stmt_busy(stmt) ) - .assert( !stmt.isBusy() ); - - if( wasm.exports.sqlite3_column_origin_name ){ - log("Column metadata APIs enabled"); - T.assert( "t" === capi.sqlite3_column_table_name(stmt, 0)) - .assert("a" === capi.sqlite3_column_origin_name(stmt, 0)) - .assert("main" === capi.sqlite3_column_database_name(stmt, 0)) - }else{ - log("Column metadata APIs not enabled"); - } // column metadata APIs - stmt.finalize(); - stmt = null; - stmt = db.prepare("select ?1").bind(new Uint8Array([97,0,98,0,99])); - stmt.step(); - const sv = capi.sqlite3_column_value(stmt,0); - T.assert("a\0b\0c"===capi.sqlite3_value_text(sv), - "Expecting NULs to have survived."); - }finally{ - if(stmt) stmt.finalize(); - db.close(); - } + const stmt = db.prepare("select a, a+1 from t"); + T.assert( stmt.isReadOnly() ) + .assert( 0===capi.sqlite3_stmt_isexplain(stmt) ) + .assert( 0===capi.sqlite3_stmt_explain(stmt, 1) ) + .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) ) + .assert( 0===capi.sqlite3_stmt_explain(stmt, 2) ) + .assert( 0!==capi.sqlite3_stmt_isexplain(stmt) ) + .assert( 0===capi.sqlite3_stmt_explain(stmt, 0) ) + .assert( 0===capi.sqlite3_stmt_isexplain(stmt) ); + let n = 0; + while( capi.SQLITE_ROW === capi.sqlite3_step(stmt) ){ + ++n; + T.assert( 0!==capi.sqlite3_stmt_explain(stmt, 1), + "Because stmt is busy" ) + .assert( capi.sqlite3_stmt_busy(stmt) ) + .assert( stmt.isBusy() ) + .assert( 0!==capi.sqlite3_stmt_readonly(stmt) ) + .assert( true===stmt.isReadOnly() ); + const sv = capi.sqlite3_column_value(stmt, 0); + T.assert( 123===capi.sqlite3_value_int(sv) ) + .assert( "doggiebiscuits"===capi.sqlite3_column_decltype(stmt,0) ) + .assert( null===capi.sqlite3_column_decltype(stmt,1) ); + } + T.assert( 1===n ) + .assert( 0===capi.sqlite3_stmt_busy(stmt) ) + .assert( !stmt.isBusy() ); + + if( wasm.exports.sqlite3_column_origin_name ){ + log("Column metadata APIs enabled"); + T.assert( "t" === capi.sqlite3_column_table_name(stmt, 0)) + .assert("a" === capi.sqlite3_column_origin_name(stmt, 0)) + .assert("main" === capi.sqlite3_column_database_name(stmt, 0)) + }else{ + log("Column metadata APIs not enabled"); + } // column metadata APIs + + stmt.finalize(); + db.close(); }) //////////////////////////////////////////////////////////////////// diff --git a/main.mk b/main.mk index 46d646d03..ac0fdb513 100644 --- a/main.mk +++ b/main.mk @@ -652,7 +652,7 @@ SRC = \ $(TOP)/src/sqliteInt.h \ $(TOP)/src/sqliteLimit.h \ $(TOP)/src/table.c \ - tclsqlite-ex.c \ + $(TOP)/src/tclsqlite.c \ $(TOP)/src/threads.c \ $(TOP)/src/tokenize.c \ $(TOP)/src/treeview.c \ @@ -952,6 +952,25 @@ FUZZDATA = \ # TESTOPTS = --verbose=file --output=test-out.txt +# +# Extra compiler options for various shell tools +# +# Note that some of these will only apply when embedding sqlite3.c +# into the shell, as these flags are not otherwise passed on to the +# library. +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 FUZZERSHELL_OPT = FUZZCHECK_OPT += -I$(TOP)/test FUZZCHECK_OPT += -I$(TOP)/ext/recover @@ -1409,15 +1428,15 @@ whereexpr.o: $(TOP)/src/whereexpr.c $(DEPS_OBJ_COMMON) window.o: $(TOP)/src/window.c $(DEPS_OBJ_COMMON) $(T.cc.sqlite) -c $(TOP)/src/window.c -tclsqlite.o: $(T.tcl.env.sh) tclsqlite-ex.c $(DEPS_OBJ_COMMON) +tclsqlite.o: $(T.tcl.env.sh) $(TOP)/src/tclsqlite.c $(DEPS_OBJ_COMMON) $(T.compile.tcl) -DUSE_TCL_STUBS=1 $$TCL_INCLUDE_SPEC \ - -c tclsqlite-ex.c -o tclsqlite.o + -c $(TOP)/src/tclsqlite.c -tclsqlite-shell.o: $(T.tcl.env.sh) tclsqlite-ex.c $(DEPS_OBJ_COMMON) - $(T.compile.tcl) -DTCLSH -o $@ -c tclsqlite-ex.c $$TCL_INCLUDE_SPEC +tclsqlite-shell.o: $(T.tcl.env.sh) $(TOP)/src/tclsqlite.c $(DEPS_OBJ_COMMON) + $(T.compile.tcl) -DTCLSH -o $@ -c $(TOP)/src/tclsqlite.c $$TCL_INCLUDE_SPEC -tclsqlite-stubs.o: $(T.tcl.env.sh) tclsqlite-ex.c $(DEPS_OBJ_COMMON) - $(T.compile.tcl) -DUSE_TCL_STUBS=1 -o $@ -c tclsqlite-ex.c $$TCL_INCLUDE_SPEC +tclsqlite-stubs.o: $(T.tcl.env.sh) $(TOP)/src/tclsqlite.c $(DEPS_OBJ_COMMON) + $(T.compile.tcl) -DUSE_TCL_STUBS=1 -o $@ -c $(TOP)/src/tclsqlite.c $$TCL_INCLUDE_SPEC # # STATIC_TCLSQLITE3 = 1 to statically link tclsqlite3, else @@ -1661,19 +1680,11 @@ install-tcl-0 install-tcl-: install-tcl: install-tcl-$(HAVE_TCL) install: install-tcl -TCLSQLITEEX = \ - $(TOP)/ext/qrf/qrf.h \ - $(TOP)/ext/qrf/qrf.c \ - $(TOP)/src/tclsqlite.c - -tclsqlite-ex.c: $(TCLSQLITEEX) $(TOP)/tool/mkcombo.tcl $(B.tclsh) - $(B.tclsh) $(TOP)/tool/mkcombo.tcl $(TCLSQLITEEX) -o $@ - -tclsqlite3.c: sqlite3.c tclsqlite-ex.c +tclsqlite3.c: sqlite3.c echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c cat sqlite3.c >>tclsqlite3.c echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c - cat tclsqlite-ex.c >>tclsqlite3.c + cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c # # $(CFLAGS.tclextension) = CFLAGS for the tclextension* targets. @@ -1790,13 +1801,13 @@ TESTFIXTURE_FLAGS += -DSQLITE_STRICT_SUBTYPE=1 TESTFIXTURE_SRC0 = $(TESTSRC2) $(libsqlite3.LIB) TESTFIXTURE_SRC1 = sqlite3.c -TESTFIXTURE_SRC = $(TESTSRC) tclsqlite-ex.c +TESTFIXTURE_SRC = $(TESTSRC) $(TOP)/src/tclsqlite.c TESTFIXTURE_SRC += $(TESTFIXTURE_SRC$(USE_AMALGAMATION)) testfixture$(T.exe): $(T.tcl.env.sh) has_tclsh85 $(TESTFIXTURE_SRC) $(T.link.tcl) -DSQLITE_NO_SYNC=1 $(TESTFIXTURE_FLAGS) \ -o $@ $(TESTFIXTURE_SRC) \ - $$TCL_LIB_SPEC $$TCL_INCLUDE_SPEC $$TCL_LIBS \ + $$TCL_LIB_SPEC $$TCL_INCLUDE_SPEC \ $(LDFLAGS.libsqlite3) coretestprogs: testfixture$(B.exe) sqlite3$(B.exe) @@ -1863,15 +1874,6 @@ mdevtest: srctree-check has_tclsh85 sdevtest: has_tclsh85 $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest $(TSTRNNR_OPTS) -retest: has_tclsh85 - $(TCLSH_CMD) $(TOP)/test/testrunner.tcl retest - -errors: - $(TCLSH_CMD) $(TOP)/test/testrunner.tcl errors - -status: - $(TCLSH_CMD) $(TOP)/test/testrunner.tcl status -d 2 - # Like releasetest, except it omits srctree-check and verify-source so # that it can be used on a modified source tree. # @@ -1929,7 +1931,7 @@ shelltest: # sqlite3_analyzer.c.flags.0 = -DINCLUDE_SQLITE3_C=1 sqlite3_analyzer.c.flags.1 = -sqlite3_analyzer.c: sqlite3.c tclsqlite-ex.c $(TOP)/tool/spaceanal.tcl \ +sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl \ $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in $(B.tclsh) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in \ $(sqlite3_analyzer.c.flags.$(LINK_TOOLS_DYNAMICALLY)) \ @@ -1953,7 +1955,7 @@ sqlite3_analyzer$(T.exe): $(T.tcl.env.sh) sqlite3_analyzer.c \ # can cause the $@ to link to an out-of-tree libsqlite3.so, which may # or may not fail or otherwise cause confusion. -sqltclsh.c: sqlite3.c tclsqlite-ex.c $(TOP)/tool/sqltclsh.tcl \ +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 $(B.tclsh) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c @@ -1971,6 +1973,24 @@ sqlite3_expert$(T.exe): $(TOP)/ext/expert/sqlite3expert.h $(TOP)/ext/expert/sqli $(TOP)/ext/expert/expert.c sqlite3.c -o sqlite3_expert $(LDFLAGS.libsqlite3) xbin: sqlite3_expert$(T.exe) +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) + $(B.tclsh) $(TOP)/tool/mkccode.tcl $(TOP)/ext/repair/sqlite3_checker.c.in >$@ + +sqlite3_checker$(T.exe): $(T.tcl.env.sh) sqlite3_checker.c + $(T.link.tcl) sqlite3_checker.c -o $@ $$TCL_INCLUDE_SPEC \ + $$TCL_LIB_SPEC $(LDFLAGS.libsqlite3) +xbin: sqlite3_checker$(T.exe) + dbdump$(T.exe): $(TOP)/ext/misc/dbdump.c sqlite3.o $(T.link) -DDBDUMP_STANDALONE -o $@ \ $(TOP)/ext/misc/dbdump.c sqlite3.o $(LDFLAGS.libsqlite3) @@ -2000,10 +2020,6 @@ showshm$(T.exe): $(TOP)/tool/showshm.c $(T.link) -o $@ $(TOP)/tool/showshm.c $(LDFLAGS.configure) xbin: showshm$(T.exe) -showtmlog$(T.exe): $(TOP)/tool/showtmlog.c - $(T.link) -o $@ $(TOP)/tool/showtmlog.c $(LDFLAGS.configure) -xbin: showshm$(T.exe) - index_usage$(T.exe): $(TOP)/tool/index_usage.c sqlite3.o $(T.link) $(SHELL_OPT) -o $@ $(TOP)/tool/index_usage.c sqlite3.o \ $(LDFLAGS.libsqlite3) @@ -2102,19 +2118,16 @@ src-archives: sqlite-amalgamation.zip amalgamation-tarball sqlite-src.zip # Build a ZIP archive containing various command-line tools. # -tool-zip: sqlite3$(T.exe) sqldiff$(T.exe) \ +tool-zip: testfixture$(T.exe) sqlite3$(T.exe) sqldiff$(T.exe) \ sqlite3_analyzer$(T.exe) sqlite3_rsync$(T.exe) $(TOP)/tool/mktoolzip.tcl strip sqlite3$(T.exe) sqldiff$(T.exe) sqlite3_analyzer$(T.exe) sqlite3_rsync$(T.exe) - $(TCLSH_CMD) $(TOP)/tool/mktoolzip.tcl - + ./testfixture$(T.exe) $(TOP)/tool/mktoolzip.tcl snapshot-zip: testfixture$(T.exe) sqlite3$(T.exe) sqldiff$(T.exe) \ sqlite3_analyzer$(T.exe) sqlite3_rsync$(T.exe) $(TOP)/tool/mktoolzip.tcl strip sqlite3$(T.exe) sqldiff$(T.exe) sqlite3_analyzer$(T.exe) sqlite3_rsync$(T.exe) - $(TCLSH_CMD) $(TOP)/tool/mktoolzip.tcl --snapshot - + ./testfixture$(T.exe) $(TOP)/tool/mktoolzip.tcl --snapshot clean-tool-zip: rm -f sqlite-tools-*.zip - clean: clean-tool-zip # @@ -2339,8 +2352,6 @@ mptest: mptester$(T.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 \ diff --git a/make.bat b/make.bat deleted file mode 100644 index 2dc9b61c0..000000000 --- a/make.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -nmake /f Makefile.msc %* diff --git a/manifest b/manifest index 079aacfd7..648b118a6 100644 --- a/manifest +++ b/manifest @@ -1,14 +1,14 @@ -C Version\s3.52.0 -D 2026-03-06T16:01:44.367 +C Version\s3.51.3 +D 2026-03-13T10:38:09.694 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md 6bc480fc673fb4acbc4094e77edb326267dd460162d7723c7f30bee2d3d9e97d F Makefile.in 3ce07126d7e87c7464301482e161fdae6a51d0a2aa06b200b8f0000ef4d6163b F Makefile.linux-generic bd3e3cacd369821a6241d4ea1967395c962dfe3057e38cb0a435cee0e8b789d0 -F Makefile.msc 174764cb7e80c80f9003c46b3e388d74c68c8c40230208904b3af8fcabee5f4e -F README.md 3fa51fc7ababc32edd175ae8b2986c86d5ea120c1cb1e57c7f7849492d1405ec -F VERSION 74672bfd4c7826c0fc6f84762488a707c52e7d2d94af42ccb0edcc6c74311c41 +F Makefile.msc d4459fad28b388063698cbb7a73bfce8684da998a844a04b21d4b9b10291196a +F README.md dae499194b75deed76a13a4a83c82493f2530331882d7dfe5754d63287d3f8f7 +F VERSION 04eea51aa150c240c9a4dbd740393c575e73b04428482dc2b14ca6b98967b53c F art/icon-243x273.gif 9750b734f82fdb3dc43127753d5e6fbf3b62c9f4e136c2fbf573b2f57ea87af5 F art/icon-80x90.gif 65509ce3e5f86a9cd64fe7fca2d23954199f31fe44c1e09e208c80fb83d87031 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 @@ -17,8 +17,8 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F art/sqlite370.svg 40b7e2fe8aac3add5d56dd86ab8d427a4eca5bcb3fe4f8946cb3794e1821d531 F auto.def 44a0d1bf09d78355fc88251ccbf8e64e6341fd89c11de68a01c3645e53a2bade F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.in efab1f56c7961d301efc030baa4391b9faae38733d94385a3d56b54720a74aba -F autoconf/Makefile.msc e6c596e6e63ea17aa7a97eb8ded18cd984c8dd4203766644fbd57b8fcf720225 +F autoconf/Makefile.in 118aa2c4d49173672d065fdda19eb8a28642e2c684212d7a626d6db5e6762521 +F autoconf/Makefile.msc 9c1ca648062fd5a4a83ba7590c4422090cccd6399002af0346b7572f086c7483 F autoconf/README.first f1d3876e9a7852c22f275a6f06814e64934cecbc0b5b9617d64849094c1fd136 F autoconf/README.txt b749816b8452b3af994dc6d607394bef3df1736d7e09359f1087de8439a52807 F autoconf/auto.def 3d994f3a9cc9b712dbce92a5708570ddcf3b988141b6eb738f2ed16127a9f0ac @@ -33,7 +33,7 @@ F autoconf/tea/teaish.tcl 81feb417e718ed75cdd7e2fdf6771f3da80dae97377a90c4d5b62b F autoconf/tea/teaish.test.tcl cfe94e1fb79dd078f650295be59843d470125e0cc3a17a1414c1fb8d77f4aea6 F autosetup/LICENSE 41a26aebdd2cd185d1e2b210f71b7ce234496979f6b35aef2cbf6b80cbed4ce4 F autosetup/README.autosetup a78ff8c4a3d2636a4268736672a74bf14a82f42687fcf0631a70c516075c031e -F autosetup/README.md e8503618f6cb31b692383ca662f6eb9ef4345463f5e3c1fc84a5d6863c7058be +F autosetup/README.md ce0f95980a687bb861bd830b76bc4b48513567be5cf5ee7004f4f3439ffe3841 F autosetup/autosetup b16e44924c197783df67366762dda985b45d49ebc4af15f4054e3ee0e3b65169 x F autosetup/autosetup-config.guess dfa101c5e8220e864d5e9c72a85e87110df60260d36cb951ad0a85d6d9eaa463 x F autosetup/autosetup-config.sub a38fb074d0dece01cf919e9fb534a26011608aa8fa606490864295328526cd73 x @@ -44,10 +44,10 @@ F autosetup/cc-lib.tcl 493c5935b5dd3bf9bd4eca89b07c8b1b1a9356d61783035144e21795f F autosetup/cc-shared.tcl 163eda58c14cd662fd8a504bd2ad8a716ef4db7015dc1de0095d5de8dd601a4b F autosetup/cc.tcl c0fcc50ca91deff8741e449ddad05bcd08268bc31177e613a6343bbd1fd3e45f F autosetup/find_tclconfig.tcl e64886ffe3b982d4df42cd28ed91fe0b5940c2c5785e126c1821baf61bc86a7e -F autosetup/jimsh0.c 916bbdf8023fbda9937afae57d81a853d8c2ea00f2320aa27becbc33574f963d +F autosetup/jimsh0.c a57c16e65dcffc9c76e496757cb3f7fb47e01ecbd1631a0a5e01751fc856f049 F autosetup/pkg-config.tcl 4e635bf39022ff65e0d5434339dd41503ea48fc53822c9c5bde88b02d3d952ba -F autosetup/proj.tcl ce301197f364f7ce2acabbbd84b43d19e917ec73653157ca134a06f32d322712 -F autosetup/sqlite-config.tcl 7463d59c9c5e86ca286ea16fdab943058beb9346110049eca435154795890f71 +F autosetup/proj.tcl 6fc14ef82b19b77a95788ffbcfad7989b4e3cb4ce96a21dcb5cf7312f362fba9 +F autosetup/sqlite-config.tcl 5d779fce20c11fde3fe99d157dcd5b5569d729b301141b8dfb8d5aacf9d48cba F autosetup/system.tcl 51d4be76cd9a9074704b584e5c9cbba616202c8468cf9ba8a4f8294a7ab1dba9 F autosetup/teaish/README.txt b40071e6f8506500a2f7f71d5fc69e0bf87b9d7678dd9da1e5b4d0acbf40b1ca F autosetup/teaish/core.tcl e014dd95900c7f9a34e8e0f460f47e94841059827bce8b4c49668b0c7ae3f1a0 @@ -56,14 +56,14 @@ F autosetup/teaish/tester.tcl 1799514c2652db49561b3386c5242b94534d1663f2cfac861a F configure 9a00b21dfd13757bbfb8d89b30660a89ec1f8f3a79402b8f9f9b6fc475c3303a x F contrib/sqlitecon.tcl eb4c6578e08dd353263958da0dc620f8400b869a50d06e271ab0be85a51a08d3 F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd -F doc/compile-for-unix.md c8f05bf9ff8c588c501515eb11642540572203e53d0b5eb5bf60983acdd47643 -F doc/compile-for-windows.md 36601c95fa4070eebfe757684271d17a7c4a586912ba706d0b5e7817e1df54ad +F doc/compile-for-unix.md c9dce1ddd4bf0d25efccc5c63eb047e78c01ce06a6ff29c73e0a8af4a0f4adbc +F doc/compile-for-windows.md f9e74d74da88f384edd5809f825035e071608f00f7f39c0e448df7b3982f979c F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f F doc/jsonb.md acd77fc3a709f51242655ad7803510c886aa8304202fa9cf2abc5f5c4e9d7ae5 -F doc/lemon.html 2085fda0a90a94fe92159a79dccc5c30d5a2313524887a31659cd66162f17233 +F doc/lemon.html 89ea833a6f71773ab1a9063fbb7fb9b32147bc0b1057b53ecab94a3b30c0aef5 F doc/pager-invariants.txt 83aa3a4724b2d7970cc3f3461f0295c46d4fc19a835a5781cbb35cb52feb0577 F doc/tcl-extension-testing.md b88861804fc1eaf83249f8e206334189b61e150c360e1b80d0dcf91af82354f5 -F doc/testrunner.md daffa0ebbbe397a73537ae1b19b3124d489ce5f89dfe570781d1f1ef1809597c +F doc/testrunner.md 5ee928637e03f136a25fef852c5ed975932e31927bd9b05a574424ae18c31019 F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56 F doc/vfs-shm.txt 1a55f3f0e7b6745931b117ba5c9df3640d7a0536f532ef0052563100f4416f86 @@ -71,7 +71,7 @@ F doc/wal-lock.md 7db0cd61e2000b545b78ce89b0c2a9a8dd8d64c097839258ac10d7c5c4156e F ext/README.md 6eb1ac267d917767952ed0ef63f55de003b6a5da433ce1fa389e1a9532e73132 F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 -F ext/expert/expert1.test d9dfbf7fb527cfd43049e30a6238ef02c94484041fa4461ed41acbc6435425d6 +F ext/expert/expert1.test 1d2da6606623b57bb47064e02140823ce1daecd4cacbf402c73ad3473d7f000c F ext/expert/sqlite3expert.c 546010043fbec93544f762de5161b3d553165859e6bd853c4b85c05f93484260 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c c395134bd6d4efa594a7d26578a1cb624c4027b79b4b5fcd44736c5ef1f5f725 @@ -79,9 +79,9 @@ F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c F ext/fts3/README.syntax b72477722e9b4fe43f8403227d790a1c94221bfad15c27863a4b36d1052e892b F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 6cc7bbc307f27e7b6ee2e1d5ff63ffff4df3b42529dfe00eb34ddded417961b3 +F ext/fts3/fts3.c 4f02858ab845a97bedf06e6cc1fba49a81fe5e00a26df68d0ad0f00a5814fa70 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h fd6051f7aa4db93e05fdc703ef35faf79f78170419e809139109d7aef28f4170 +F ext/fts3/fts3Int.h ed9b8bc5ed5be402069651e49d4855cb849af706cf3fe68548f58a2c21eefc7f F ext/fts3/fts3_aux.c 7eab82a9cf0830f6551ba3abfdbe73ed39e322a4d3940ee82fbf723674ecd9f3 F ext/fts3/fts3_expr.c 5c13796638d8192c388777166075cdc8bc4b6712024cd5b72c31acdbefce5984 F ext/fts3/fts3_hash.c d9dba473741445789330c7513d4f65737c92df23c3212784312931641814672a @@ -97,7 +97,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c c1de4ae28356ad98ccb8b2e3388a7fdcce7607b5523738c9afb6275dab765154 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c d218b687fb55bce8c9340c6dbb368a10d94647cbe39801d85492d576a4e7da75 +F ext/fts3/fts3_write.c 2bee1c5828f6401adffd07ca64260aeb79d64138958273a56de8fa5e8759a0c1 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674 @@ -108,21 +108,21 @@ F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a0 F ext/fts5/extract_api_docs.tcl 009cf59c77afa86d137b0cca3e3b1a5efbe2264faa2df233f9a7aa8563926d15 F ext/fts5/fts5.h ff5d3cc88b29e41612bfb29eb723e29e38973de62ca75ba3e8f94ccb67f5b5f2 F ext/fts5/fts5Int.h 8d98f8e180fe28d6067e240ed45b9011735d29d5cfb5bac194e1e376baa7c708 -F ext/fts5/fts5_aux.c 042da27e97d38071312c111cf18f3cb7983b75ba5e724aa1c3164e61e90f428a -F ext/fts5/fts5_buffer.c dcc3f0352339fe79c9d8abbc1c2009bc3469206467880bf43558447ef4f846fb -F ext/fts5/fts5_config.c bfba970fe1e4eed18ee57c8d51458e226db9a960ddf775c5e50e3d76603a667e -F ext/fts5/fts5_expr.c 71d48e8cf0358deace4949276647d317ff7665db6db09f40b81e2e7fe6664c7c -F ext/fts5/fts5_hash.c d5871df92ce3fa210a650cf419ee916b87c29977e86084d06612edf772bff6f5 -F ext/fts5/fts5_index.c f8cfa37bb7397e5ede20242e4c9cb030bc8b4584ce3f23a5e2495038c0ae64bd -F ext/fts5/fts5_main.c 6889f1373c469d515e792fb3d783c2218e63c560433ebd66edc0f740ab086c1b +F ext/fts5/fts5_aux.c da4a7a9a11ec15c6df0699d908915a209bcde48f0b04101461316b59f71abffb +F ext/fts5/fts5_buffer.c f1e6d0324d7c55329d340673befc26681a372a4d36086caa8d1ec7d7c53066c7 +F ext/fts5/fts5_config.c e7d8dd062b44a66cd77e5a0f74f23a2354cd1f3f8575afb967b2773c3384f7f8 +F ext/fts5/fts5_expr.c b8c32da1127bafaf10d6b4768b0dcb92285798524bed2d87a8686f99a8e8d259 +F ext/fts5/fts5_hash.c a6266cedd801ab7964fa9e74ebcdda6d30ec6a96107fa24148ec6b7b5b80f6e0 +F ext/fts5/fts5_index.c f0562b4adb9dc2d56addcb8833edab50817725032b1cbd46335c0b32d7f1525d +F ext/fts5/fts5_main.c 42025174a556257287071e90516d3ab8115daf1dd525a301883544469a260014 F ext/fts5/fts5_storage.c 19bc7c4cbe1e6a2dd9849ef7d84b5ca1fcbf194cefc3e386b901e00e08bf05c2 -F ext/fts5/fts5_tcl.c 2be6cc14f9448f720fd4418339cd202961a0801ea9424cb3d9de946f8f5a051c +F ext/fts5/fts5_tcl.c 7fb5a3d3404099075aaa2457307cb459bbc257c0de3dbd52b1e80a5b503e0329 F ext/fts5/fts5_test_mi.c 4308d5658cb1f5eee5998dcbaac7d5bdf7a2ef43c8192ca6e0c843f856ccee26 -F ext/fts5/fts5_test_tok.c 6021033bd4f4feffe8579efb6e1f58156ed462256bf99a2acdbd629246529204 -F ext/fts5/fts5_tokenize.c cfc16dde905552fe238c0403670852e75c0330ba508a9fb4836c1f596618561d +F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b +F ext/fts5/fts5_tokenize.c 49aea8cc400a690a6c4f83c4cedc67f4f8830c6789c4ee343404f62bcaebca7b F ext/fts5/fts5_unicode2.c 536a6dae41d16edadd6a6b58c56e2ebbb133f0dfe757562a2edbcdc9b8362e50 F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80 -F ext/fts5/fts5_vocab.c bebee4aabcd056a44b3731166433cfdecf17ece750c08cb58733216222bd39e2 +F ext/fts5/fts5_vocab.c 23e263ad94ac357cfffd19bd7e001c3f15c4420fb10fa35b5993142127e780e6 F ext/fts5/fts5parse.y eb526940f892ade5693f22ffd6c4f2702543a9059942772526eac1fde256bb05 F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba F ext/fts5/test/fts5_common.tcl c5aa7cf7148b6dcffb5b61520ae18212baf169936af734ab265143f59db328fe @@ -168,7 +168,7 @@ F ext/fts5/test/fts5corrupt5.test 73985d4fe6d8f0d5d5c7bcf79ae7c6522c376cd6ad710a F ext/fts5/test/fts5corrupt6.test 2d72db743db7b5d9c9a6d0cfef24d799ed1aa5e8192b66c40e871a37ed9eed06 F ext/fts5/test/fts5corrupt7.test 814aab492d7a09abb5bfdd81cc66fc206d7f3868f9a3bae91876e02efc466fb3 F ext/fts5/test/fts5corrupt8.test 0b10750caf8aa23fa1c379ca4caf6130d41454505e4d5315590f4061eedcbe44 -F ext/fts5/test/fts5corrupt9.test 4253b9b59f33effac8b67da72ec34309c738aca2d5e8e2656bfbbd6a489a1dfe +F ext/fts5/test/fts5corrupt9.test 65a3a64f0e930fcc37e0389ae26224a4faba3a2c938d2f8eb4811a45b5b495fe F ext/fts5/test/fts5corruptbig.test 9f95b40fa36e292feceab02b2ef06e21878bfa1ac7afefa138aae05518b51774 F ext/fts5/test/fts5delete.test 2a5008f8b1174ef41d1974e606928c20e4f9da77d9f8347aed818994d89cced4 F ext/fts5/test/fts5detail.test 54015e9c43ec4ba542cfb93268abdf280e0300f350efd08ee411284b03595cc4 @@ -199,9 +199,9 @@ F ext/fts5/test/fts5first.test bfd685b96905bf541d99d8644e0a7219d1d833455a08ab64e F ext/fts5/test/fts5full.test 97d263c1072f4a560929cca31e70f65d2ae232610e17e6affcf7e979df59547b F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e F ext/fts5/test/fts5hash.test fd3e0367fbf0b0944d6936fdb22696350f57b9871069c6766251578a103e8a14 -F ext/fts5/test/fts5integrity.test 613efcebe16b2d7a4096f03bcfb164f79a000b3354420ceda4a6f3e035090789 +F ext/fts5/test/fts5integrity.test c423ce16fd1ccadcac7fc22f794226b2bb00f5a187c0ab1d9f8502521b1bae05 F ext/fts5/test/fts5integrity2.test 4c3636615c0201232c44a8105d5cb14fd5499fd0ee3014d7ffd7e83aac76ece8 -F ext/fts5/test/fts5interrupt.test af7834ac6c2e71c05aea42d92f272eef3655e89b7a14a5620a2cd9de35e2e8ea +F ext/fts5/test/fts5interrupt.test 20d04204d3e341b104c0c24a41596b6393a3a81eba1044c168db0e106f9ac92c F ext/fts5/test/fts5join.test 48b7ed36956948c5b8456c8bcaa5b087808d99000675918a43c4f51a925f1514 F ext/fts5/test/fts5lastrowid.test f36298a1fb9f988bde060a274a7ce638faa9c38a31400f8d2d27ea9373e0c4a1 F ext/fts5/test/fts5leftjoin.test 1c14b51f4d1344a89e488160882f05a2246dd7e70c5cf077c8fb473e03c66338 @@ -285,14 +285,14 @@ F ext/intck/intck_common.tcl a61fd2697ae55b0a3d89847ca0b590c6e0d8ff64bebb70920d9 F ext/intck/intckbusy.test d5ed4ef85a4b1dc1dee2484bd14a4bb68529659cca743327df0c775f005fa387 F ext/intck/intckcorrupt.test f6c302792326fb3db9dcfc70b554c55369bc4b52882eaaf039cfe0b74c821029 F ext/intck/intckfault.test cff3f75dff74abb3edfcb13f6aa53f6436746ab64b09fe5e2028f051e985efab -F ext/intck/sqlite3intck.c 3c4a166645a1624731f63acd342e24e81e4ffd497116d94a427d72e6cc6caa69 +F ext/intck/sqlite3intck.c b1c8a86f90fc00741d13314db9c58f7e2f92d1d19c5ad1c6904ec83a6bbd5c96 F ext/intck/sqlite3intck.h 2b40c38e7063ab822c974c0bd4aed97dabb579ccfe2e180a4639bb3bbef0f1c9 F ext/intck/test_intck.c 4f9eaadaedccb9df1d26ba41116a0a8e5b0c5556dc3098c8ff68633adcccdea8 F ext/jni/GNUmakefile 8a94e3a1953b88cf117fb2a5380480feada8b4f5316f02572cab425030a720b4 -F ext/jni/README.md 1479c83dbe26125264a060ee6873531795a7082dbc0d43c4067683371331559f +F ext/jni/README.md e3fbd47c774683539b7fdc95a667eb9cd6e64d8510f3ee327e7fa0c61c8aa787 F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa -F ext/jni/src/c/sqlite3-jni.c 6ccc50b0e98b8ef8fd6a8b837223e1c8ebb92bfe6b976128cc757c26257d994a -F ext/jni/src/c/sqlite3-jni.h df43024cced914c49485633d0f90168689e70577b3b17b0ecbdaf16e4a417bff +F ext/jni/src/c/sqlite3-jni.c 3d84a0176af779737ae977ba1c90d2ffe2537b8299c5d9f6552620493f12ac4b +F ext/jni/src/c/sqlite3-jni.h ac180ba9b21978727006c790d3006a95a2402a4c3ec7a0def92707ed89b79945 F ext/jni/src/org/sqlite/jni/annotation/Experimental.java 8603498634e41d0f7c70f661f64e05df64376562ea8f126829fd1e0cdd47e82b F ext/jni/src/org/sqlite/jni/annotation/NotNull.java be6cc3e8e114485822331630097cc0f816377e8503af2fc02f9305ff2b353917 F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 56e3dee1f3f703a545dfdeddc1c3d64d1581172b1ad01ffcae95c18547fafd90 @@ -302,7 +302,7 @@ F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0e28a0df51368c7127e505f F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759 F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca -F ext/jni/src/org/sqlite/jni/capi/CApi.java a9701cbe736b8bef5bc72ae465be0250e137f67bdb5a3ab62497972b6f51572d +F ext/jni/src/org/sqlite/jni/capi/CApi.java 3d275f5f4fbdbe4fff15f4d42cf5ff559f9a4897e7373fa99f3b1dc9cf7f849c F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 1b3baf5b772f266e8baf8f35f0ddc5bd87fc8c4927ec69115c46fd6fba6701c3 F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab @@ -320,7 +320,7 @@ F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385 F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 3c0babc067d8560627a9ed1b07979f9d4393464e2282c2fca4832052e982c7bc F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615 F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java 9133bb7685901d2edf07801191284975e33b5583ce09dce1c05202ff91e7bb99 -F ext/jni/src/org/sqlite/jni/capi/Tester1.java 378d142435d220b20b7ce7343c62a03e853bb8c51e80447ee0f8ac5c37362d9a +F ext/jni/src/org/sqlite/jni/capi/Tester1.java 4c3d16fdf6e979f839b2ecdb14d0a0c04bd3d0e41500fc9e8110b588883b140b F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723 F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java c8bdf7848e6599115d601bcc9427ff902cb33129b9be32870ac6808e04b6ae56 F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 2ce069f3e007fdbbe1f4e507a5a407fc9679da31a0aa40985e6317ed4d5ec7b5 @@ -347,7 +347,7 @@ F ext/jni/src/org/sqlite/jni/test-script-interpreter.md d7987b432870d23f7c72a780 F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483 F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 326ffba29aab836a6ea189703c3d7fb573305fd93da2d14b0f9e9dcf314c8290 F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java e920f7a031e04975579240d4a07ac5e4a9d0f8de31b0aa7a4be753c98ae596c9 -F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java ce8aef867e06a21685658eb0173768c355e9e1e2dfdc75f382643fa62c7ee779 +F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java c82bc00c1988f86246a89f721d3c41f0d952f33f934aa6677ec87f7ca42519a0 F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 982538ddb4c0719ef87dfa664cd137b09890b546029a7477810bd64d4c47ee35 F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 08f92d52be2cec28a7b4555479cc54b7ebeeb94985256144eeb727154ec3f85b F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java a84e90c43724a69c2ecebd601bc8e5139f869b7d08cb705c77ef757dacdd0593 @@ -363,20 +363,20 @@ F ext/misc/base64.c 8dc0a08cee11722822858a62625f1b63e5d5f1adac1cf4492d5732b571e3 F ext/misc/base85.c ff54cc676c6ec86231f75ecc86ea45416fcb69751dfb79690d5f5da5f7d39867 F ext/misc/basexx.c 89ad6b76558efbceb627afd5e2ef1d84b2e96d9aaf9b7ecb20e3d00b51be6fcf F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a -F ext/misc/btreeinfo.c 13bc9e9f1c13cde370d0e4a6a2683e9f1926a4cead7fb72c71871b11a06d78a1 +F ext/misc/btreeinfo.c 8f5e6da2c82ec2f06ee0216e922370a436dafdbb06ffa7a552203515ff9e7ddf F ext/misc/cksumvfs.c 9d7d0cf1a8893ac5d48922bfe9f3f217b4a61a6265f559263a02bb2001259913 F ext/misc/closure.c 5559daf1daf742228431db929d1aa86dd535a4224cc634a81d2fd0d1e6ad7839 F ext/misc/completion.c c27b64fdd0943c1b7f152376599814cee2641f7d67a7bb9bd2b957c2a64a5591 F ext/misc/compress.c 8191118b9b73e7796c961790db62d35d9b0fb724b045e005a5713dc9e0795565 -F ext/misc/csv.c e82124eabee0e692d7b90ab8b2c34fadbf7b375279f102567fa06e4da4b771bf +F ext/misc/csv.c d9dab032420d81cdf0abc4b8523711d20a355704f82d958f963c86be48387dd9 F ext/misc/dbdump.c 678f1b9ae2317b4473f65d03132a2482c3f4b08920799ed80feedd2941a06680 -F ext/misc/decimal.c e1da22eee70d7e3eaa99a6b761bc03c4d01d7ffa554bf3178b1f1f184932806c +F ext/misc/decimal.c d4883de142f6dcd36eda23da40b55e2b51374e7b01eb54a7173940191389fc5e F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 606100185fb90d6a1eade1ed0414d53503c86820d8956a06e3b0a56291894f2b -F ext/misc/fileio.c 33165b3cd99f83dcd333a338eb51491f6b01c8d96cb6ae81f96a6a096834e030 +F ext/misc/fileio.c d80268a5328fe839062a9d3103ea0fc7cacc6d42605959275675cb37867c84f7 F ext/misc/fossildelta.c 86dfa83f85f7ccd640591d8a5c6865346d0c2ee6a949d78591eceb892f1cbfec -F ext/misc/fuzzer.c 684a4996b523ea89f495b38fd8a14a2ae00695089a88031366a4df6adc2c873b -F ext/misc/ieee754.c 2901d08a586d00a1d3c0fd89e03c57ee9e2b5f013b0daab9e49c7a48a9d5946b +F ext/misc/fuzzer.c 6b231352815304ba60d8e9ec2ee73d4918e74d9b76bda8940ba2b64e8777515e +F ext/misc/ieee754.c 176c061c94857b543313959289cb60cf777c999fd002f82b53d194b95e9f347a F ext/misc/memstat.c 43705d795090efb78c85c736b89251e743c291e23daaa8382fe7a0df2c6a283d F ext/misc/memtrace.c 7c0d115d2ef716ad0ba632c91e05bd119cb16c1aedf3bec9f06196ead2d5537b F ext/misc/mmapwarm.c a81af4aaec00f24f308e2f4c19bf1d88f3ac3ce848c36daa7a4cd38145c4080d @@ -388,22 +388,21 @@ F ext/misc/percentile.c 72e05a21db20a2fa85264b99515941f00ae698824c9db82d7edfbb16 F ext/misc/prefixes.c 82645f79229877afab08c8b08ca1e7fa31921280906b90a61c294e4f540cd2a6 F ext/misc/qpvtab.c fc189e127f68f791af90a487f4460ec91539a716daf45a0c357e963fd47cc06c F ext/misc/randomjson.c ef835fc64289e76ac4873b85fe12f9463a036168d7683cf2b773e36e6262c4ed -F ext/misc/regexp.c 69bd45f6931bdc6801c1059b65a3e8b15ba88255e6abe387a34b653ce17e8908 +F ext/misc/regexp.c f1f7cfe90fc027b33d2b5ae7d6235eecce69c3aca71c9afce56fec62342c8b44 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 F ext/misc/series.c 22c6d8f00cc1b5089b1b37392e9097e9df9a5db53be86daf9a7669d95bb179f4 -F ext/misc/sha1.c 3030b5926b34bb09459200e100fae34e48c04077cf175381a7560f72bbf3d9cf +F ext/misc/sha1.c cb5002148c2661b5946f34561701e9105e9d339b713ec8ac057fd888b196dcb9 F ext/misc/shathree.c fd22d70620f86a0467acfdd3acd8435d5cb54eb1e2d9ff36ae44e389826993df F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 F ext/misc/spellfix.c 693c8fd3293087fa821322967a97e59dfa24051e5d2ca7fa85790a4034db6fa4 F ext/misc/sqlar.c a6175790482328171da47095f87608b48a476d4fac78d8a9ff18b03a2454f634 -F ext/misc/sqlite3_stdio.c e49c07050bf7bdc87866da7583beda236f2f8c462018a34b61785d99cbddedfd -F ext/misc/sqlite3_stdio.h 27a4ecea47e61bc9574ccdf2806f468afe23af2f95028c9b689bfa08ab1ce99f +F ext/misc/sqlite3_stdio.c 0fe5a45bd332b30aef2b68c64edbe69e31e9c42365b0fa79ce95a034bca6fbb0 +F ext/misc/sqlite3_stdio.h f05eaf5e0258f0573910324a789a9586fc360a57678c57a6d63cfaa2245b6176 F ext/misc/stmt.c b090086cd6bd6281c21271d38d576eeffe662f0e6b67536352ce32bbaa438321 F ext/misc/stmtrand.c 59cffa5d8e158943ff1ce078956d8e208e8c04e67307e8f249dece2436dcb7fc F ext/misc/templatevtab.c 10f15b165b95423ddef593bc5dcb915ec4eb5e0f1066d585e5435a368b8bc22b -F ext/misc/tmstmpvfs.c 240caad4441328dc52bd2871f48811db46dff858d5598030e389176837a2f4df F ext/misc/totype.c ba11aac3c0b52c685bd25aa4e0f80c41c624fb1cc5ab763250e09ddc762bc3a8 F ext/misc/uint.c 327afc166058acf566f33a15bf47c869d2d3564612644d9ff81a23efc8b36039 F ext/misc/unionvtab.c 716d385256d5fb4beea31b0efede640807e423e85c9784d21d22f0cce010a785 @@ -412,20 +411,16 @@ F ext/misc/uuid.c 5bb2264c1b64d163efa46509544fd7500cb8769cb7c16dd52052da8d961505 F ext/misc/vfslog.c 3932ab932eeb2601dbc4447cb14d445aaa9fbe43b863ef5f014401c3420afd20 F ext/misc/vfsstat.c 0b23c0a69a2b63dc0ef0af44f9c1fc977300c480a1f7a9814500369d8211f56e F ext/misc/vfstrace.c 0e4b8b17ac0675ea90f6d168d8214687e06ca3efbc0060aad4814994d82b41fb -F ext/misc/vtablog.c 402496fb38add7dd2c50f2a0ad20f83a9916ceab48dcd31e62ad621e663c83ac +F ext/misc/vtablog.c 2d04386c2f5a3bb93bc9ae978f0b7dcd5a264e126abd640dd6d82aa9067cbd48 F ext/misc/vtshim.c e5bce24ab8c532f4fdc600148718fe1802cb6ed57417f1c1032d8961f72b0e8f F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f65b4fc0668 F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c -F ext/misc/zipfile.c c8ee04e1b349270b5df401ad732f5d7c387146e69b33c02fa90322760cc6fee0 +F ext/misc/zipfile.c 837591f0505d21f7f7937ea046c9b0fc594f7fa3ca00c2bd54ffa1c94bfccd63 F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee -F ext/qrf/README.md e6e0ce2700acf6fd06312b42726a8f08ca240f30e1b122bff87c71c602046352 -F ext/qrf/dev-notes.md e68a6d91ce4c7eb296ef2daadc2bb79c95c317ad15b9fafe40850c67b29c2430 -F ext/qrf/qrf.c cd48c23500c3b129be5e0627ce9d41b5df3c2d715525b00a6ccbd1f30689fb17 -F ext/qrf/qrf.h 2ac14b0aaacf44636d8c81051bfeab4afae50a98fbb2e10ff5aed0c28a87b2b2 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363 -F ext/rbu/rbu11.test 3b71377c018b05dd39c30ea2feb272a087eb0faeff1b7b4511144f87219c478e +F ext/rbu/rbu11.test 8584f80ef4be00e6beec4154f638847ffc40b5f2832ffadfbaf558ae40e50cb5 F ext/rbu/rbu12.test ec63aa7bfc3c65c1d774bf4357ed731723827d211d9d7cb0efa171bbaeeebaf4 F ext/rbu/rbu13.test 658edbc3325d79252a98b761fde95460e439f80e820ff29e10261e25f870b3b6 F ext/rbu/rbu14.test 05dac607a62f62102f4db92135979a8a4501143638060019aca08c753822cf39 @@ -466,7 +461,7 @@ F ext/rbu/rbuvacuum.test 542561741ff2b262e3694bc6012b44694ee62c545845319a06f3237 F ext/rbu/rbuvacuum2.test 1a9bd41f127be2826de2a65204df9118525a8af8d16e61e6bc63ba3ac0010a23 F ext/rbu/rbuvacuum3.test 3ce42695fdf21aaa3499e857d7d4253bc499ad759bcd6c9362042c13cd37d8de F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69eefaebb205 -F ext/rbu/sqlite3rbu.c e99400d29d029936075e27495b269a2dcdceae3cf8c86b1d0869b4af487be3ab +F ext/rbu/sqlite3rbu.c 3fb2390575b261c365d3f6fea61ff15e74d5d89e373f2a2bfa4d80c24321e793 F ext/rbu/sqlite3rbu.h e3a5bf21e09ca93ce4e8740e00d6a853e90a697968ec0ea98f40826938bdb68e F ext/rbu/test_rbu.c 8b6e64e486c28c41ef29f6f4ea6be7b3091958987812784904f5e903f6b56418 F ext/recover/dbdata.c 10d3c56968a9af6853722a47280805ad1564714d79ea45ac6f7da14bb57fd137 @@ -488,8 +483,17 @@ F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e F ext/recover/sqlite3recover.c 56c216332ea91233d6d820d429f3384adbec9ecedda67aa98186b691d427cc57 F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959 F ext/recover/test_recover.c 3d0fb1df7823f5bc22a0b93955034d16a2dfa2eb1e443e9a0123a77f120599a3 +F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 +F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996 +F ext/repair/checkindex.c 7639b4f8928f82c10b950169e60cc45a7f6798df0b299771d17bef025736f657 +F ext/repair/sqlite3_checker.c.in 445118c5f7fea958b36fba1b2c464283e60ed4842039ddee3265f1698115ebf7 +F ext/repair/sqlite3_checker.tcl a9a2caa9660567257c177a91124d8c0dccdfa341e25c51e6da7f1fd9e601eafa +F ext/repair/test/README.md 34b2f542cf5be7bffe479242b33ee3492cea30711e447cc4a1a86cb5915f419e +F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc78249442da72ff3f8297398a69 +F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c335096108c12c01bddbadcec +F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 734aa36238bcd2dee91db5dba107d5fcbdb02396612811377a8ad50f1272b1c1 -F ext/rtree/geopoly.c bd1971479184d559499ff3087c37f2823977d7b0ec80916141ae66f70345c88d +F ext/rtree/geopoly.c f0573d5109fdc658a180db0db6eec86ab2a1cf5ce58ec66cbf3356167ea757eb F ext/rtree/rtree.c 9331997a76b88a9bc04e156bdfd6e2fe35c0aa93bc338ebc6aa0ae470fe4a852 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 F ext/rtree/rtree1.test e0608db762b2aadca0ecb6f97396cf66244490adc3ba88f2a292b27be3e1da3e @@ -532,14 +536,14 @@ F ext/session/changesetfuzz1.test 15b629004e58d5ffcc852e6842a603775bb64b1ce51254 F ext/session/session1.test 5d2502922d38a1579076863827342379a1609ca6bae78c40691a2be1ed1be2aa x F ext/session/session2.test ee83bb973b9ce17ccce4db931cdcdae65eb40bbb22089b2fe6aa4f6be3b9303f F ext/session/session3.test 2cc1629cfb880243aec1a7251145e07b78411d851b39b2aa1390704550db8e6a -F ext/session/session4.test ad0ddaaddb9a99dac433d83fc6674aae2af072b8f57e63a6b3f2d76f1a66e98c +F ext/session/session4.test 823f6f018fcbb8dacf61e2960f8b3b848d492b094f8b495eae1d9407d9ab7219 F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169 F ext/session/session6.test 35279f2ec45448cd2e24a61688219dc6cf7871757716063acf4a8b5455e1e926 F ext/session/session8.test 326f3273abf9d5d2d7d559eee8f5994c4ea74a5d935562454605e6607ee29904 F ext/session/session9.test 0c4a8fbe7a5031f50855f020f3408e1f07fd7859f1daa1629eadcec3422072d6 F ext/session/sessionA.test 1feeab0b8e03527f08f2f1defb442da25480138f F ext/session/sessionB.test c4fb7f8a688787111606e123a555f18ee04f65bb9f2a4bb2aa71d55ce4e6d02c -F ext/session/sessionC.test c3fade0a460d898fa42e9077b88e45c0d24ead3150268e145c8e19aeafc24ba1 +F ext/session/sessionC.test f8a5508bc059ae646e5ec9bdbca66ad24bc92fe99fda5790ac57e1f59fce2fdf F ext/session/sessionD.test 470ff917dc849e2eb78142ade63aaabd729d773833cff0ff01bca0eda68a21ce F ext/session/sessionE.test b2010949c9d7415306f64e3c2072ddabc4b8250c98478d3c0c4d064bce83111d F ext/session/sessionF.test d37ed800881e742c208df443537bf29aa49fd56eac520d0f0c6df3e6320f3401 @@ -569,10 +573,11 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795 F ext/session/sessionstat1.test 5e718d5888c0c49bbb33a7a4f816366db85f59f6a4f97544a806421b85dc2dec F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c 6ebd02be470f36d41c4bd78927f39d507b62051ba025eacaed9936c769902a07 +F ext/session/sqlite3session.c b3de195ce668cace9b324599bf6255a70290cbfb5451e826e946f3aee6e64c54 F ext/session/sqlite3session.h 7404723606074fcb2afdc6b72c206072cdb2b7d8ba097ca1559174a80bc26f7a -F ext/session/test_session.c 190110e3bd9463717248dec1272b44fe9943e57b7646d0b4200dcf11e4dccee6 -F ext/wasm/GNUmakefile 79236447d750609aa6beda30feec1314180c5462a493ad94214122887232bfd4 +F ext/session/test_session.c 8766b5973a6323934cb51248f621c3dc87ad2a98f023c3cc280d79e7d78d36fb +F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c +F ext/wasm/GNUmakefile 3dc01e673c456d3b752674c9407276e8fef35dec1d304b3cc1de362f019b2a09 F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a F ext/wasm/README.md 2e87804e12c98f1d194b7a06162a88441d33bb443efcfe00dc6565a780d2f259 F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff @@ -580,68 +585,71 @@ F ext/wasm/SQLTester/SQLTester.mjs 6b3c52ed36a5573ca4883176f326332a8d4c0cecf5efd F ext/wasm/SQLTester/SQLTester.run.mjs 57f2adb33f43f2784abbf8026c1bfd049d8013af1998e7dcb8b50c89ffc332e0 F ext/wasm/SQLTester/index.html 64f3435084c7d6139b08d1f2a713828a73f68de2ae6a3112cbb5980d991ba06f F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536 -F ext/wasm/api/EXPORTED_FUNCTIONS.c-pp 7ba933e8f1290cc65459dd371c0c9a031d96bdf14d7a2244fa761d9775117b90 -F ext/wasm/api/README.md a905d5c6bfc3e2df875bd391d6d6b7b48d41b43bdee02ad115b47244781a7e81 -F ext/wasm/api/extern-post-js.c-pp.js d9f42ecbedc784c0d086bc37800e52946a14f7a21600b291daa3f963c314f930 +F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core ef34398a903d0a2425fbbfbd4ed2cd596daea55b8515e2617c8dc7ad7c0767dd +F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras 9eae68943ce91ab145892b31370819c2103525240eb72e0fce53c498b8d8275a +F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b +F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 +F ext/wasm/api/README.md f4c0d67caaee21a77b8938c30b5f79667bfc9d0c95d01b51df77ea35ee773884 +F ext/wasm/api/extern-post-js.c-pp.js 205f55aacfc62c580985db5c790300779de3876a76a5c7e1bfb13e71c8b4506b F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 -F ext/wasm/api/post-js-footer.js a50c1a2c4d008aede7b2aa1f18891a7ee71437c2f415b8aeb3db237ddce2935b -F ext/wasm/api/post-js-header.js d24bd0d065f3489c8b78ddf3ead6321e5d047187a162cd503c41700e03dd1f06 -F ext/wasm/api/pre-js.c-pp.js 9234ea680a2f6a2a177e8dcd934bdc5811a9f8409165433a252b87f4c07bba6f -F ext/wasm/api/sqlite3-api-glue.c-pp.js 9b33e3ee467791dec4fd1b444b12a8545dfbb6c8b28ac651c7bdc7661a3b5a5c -F ext/wasm/api/sqlite3-api-oo1.c-pp.js 45454631265d9ce82685f1a64e1650ee19c8e121c41db98a22b534c15e543cfa -F ext/wasm/api/sqlite3-api-prologue.js 1fefd40ab21e3dbf46f43b6fafb07f13eb13cc157a884f7c1134caf631ddb3f2 +F ext/wasm/api/post-js-footer.js 5bd7170b5e8ce7b62102702bbcf47ef7b3b49cd56ed40c043fd990aa715b74ee +F ext/wasm/api/post-js-header.js 79d078aec33d93b640a19c574b504d88bb2446432f38e2fbb3bb8e36da436e70 +F ext/wasm/api/pre-js.c-pp.js a876c6399dff29b6fe9e434036beb89889164cc872334e184291723ecc7cb072 +F ext/wasm/api/sqlite3-api-cleanup.js a3d6b9e449aefbb8bba283c2ba9477e2333a0eeb94a7a26b5bf952736f65a6dd +F ext/wasm/api/sqlite3-api-glue.c-pp.js d2b8263b3ce0cefc6c5a68d0a4d448a9770eda4bf9d9ded9d7eb0198e4ce4da1 +F ext/wasm/api/sqlite3-api-oo1.c-pp.js c4260f3fdc553c56ee530c20cc1119029067b503f0d6d7b472705536cb45aa1d +F ext/wasm/api/sqlite3-api-prologue.js 307583ff39a978c897c4ef4ce53fe231dce5c73dc84785969c81c1ab5960a293 F ext/wasm/api/sqlite3-api-worker1.c-pp.js 1041dd645e8e821c082b628cd8d9acf70c667430f9d45167569633ffc7567938 -F ext/wasm/api/sqlite3-license-version-header.js 98d90255a12d02214db634e041c8e7f2f133d9361a8ebf000ba9c9af4c6761cc -F ext/wasm/api/sqlite3-opfs-async-proxy.js 92d6d327a862f1627ff3e88e60fdfea9def06ad539d98929ba46490e64372736 +F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 +F ext/wasm/api/sqlite3-opfs-async-proxy.js 9654b565b346dc609b75d15337f20acfa7af7d9d558da1afeb9b6d8eaa404966 F ext/wasm/api/sqlite3-vfs-helper.c-pp.js 3f828cc66758acb40e9c5b4dcfd87fd478a14c8fb7f0630264e6c7fa0e57515d -F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js 2ccf4322f42063aefc150972943e750c77f7926b866f1639d40eec05df075b6e -F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 1575ea6bbcf2da1e6df6892c17521a0c1c1c199a672e9090176ea0b88de48bd9 -F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 88ce2078267a2d1af57525a32d896295f4a8db7664de0e17e82dc9ff006ed8d3 -F ext/wasm/api/sqlite3-vtab-helper.c-pp.js 366596d8ff73d4cefb938bbe95bc839d503c3fab6c8335ce4bf52f0d8a7dee81 -F ext/wasm/api/sqlite3-wasm.c 45bb20e19b245136711f9b78584371233975811b6560c29ed9b650e225417e29 -F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js aa9715f661fb700459a5a6cb1c32a4d6a770723b47aa9ac0e16c2cf87d622a66 -F ext/wasm/api/sqlite3-worker1.c-pp.js bd0655687090e3b1657268a6a9cacde1ea2a734079d194e16dbbed9083e51b38 -F ext/wasm/c-pp-lite.c f38254fba42561728c2e4764a7ba8d68700091e7c2f4418112868c0daba16783 -F ext/wasm/common/SqliteTestUtil.js dae753b95e72248c4395d8de8359e0d055cd9928488e8dd84aef89e46d23b32e +F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 26cb41d5a62f46a106b6371eb00fef02de3cdbfaa51338ba087a45f53028e0d0 +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 418c33fe284739564daab3c7a7a88882fdd3c99137497900f98eddec1e409af5 +F ext/wasm/api/sqlite3-vtab-helper.c-pp.js 9097074724172e31e56ce20ccd7482259cf72a76124213cbc9469d757676da86 +F ext/wasm/api/sqlite3-wasm.c dd7fc1d535281f0d5d2732bb1b662d1d403a762f07b63c2ea5663053377b2804 +F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bda1c75bd674a92a0e27cc2f3d46dbbf21e422413f8046814515a0bd7409328a +F ext/wasm/api/sqlite3-worker1.c-pp.js 802d69ead8c38dc1be52c83afbfc77e757da8a91a2e159e7ed3ecda8b8dba2e7 +F ext/wasm/c-pp-lite.c 8fa0148e73782a86274db688c4730e2962cd675af329490493adddaf3322f16f +F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 F ext/wasm/common/testing.css e97549bab24126c24e0daabfe2de9bb478fb0a69fdb2ddd0a73a992c091aad6f -F ext/wasm/common/whwasmutil.js 831f07a0d9bb61713164871370811432e96d0f813806a4d2c783d3c77c2373a0 +F ext/wasm/common/whwasmutil.js 0d539324097fc83b953e9844267359ba0fd02286caa784ea2f597ced279ea640 F ext/wasm/config.make.in c424ae1cc3c89274520ad312509d36c4daa34a3fce5d0c688e5f8f4365e1049a F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 F ext/wasm/demo-123.js c7b3cca50c55841c381a9ca4f9396e5bbdc6114273d0b10a43e378e32e7be5bf F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e -F ext/wasm/demo-jsstorage.js 467cb4126ff679ebcdb112d100d073af26b9808d0a0b52d66a40e28f59c5099b -F ext/wasm/demo-worker1-promiser.c-pp.html f73b0b98457e7fdad40d8353cb9b2919391da180f49549a86f3d58b4e5a010eb +F ext/wasm/demo-jsstorage.js 42131ddfa18e817d0e39ac63745e9ea31553980a5ebd2222e04d4fac60c19837 +F ext/wasm/demo-worker1-promiser.c-pp.html 635cf90685805e21772a5f7a35d1ace80f98a9ef7c42ff04d7a125ddca7e5db8 F ext/wasm/demo-worker1-promiser.c-pp.js f40ec65810048e368896be71461028bd10de01e24277208c59266edf23bb9f52 F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d -F ext/wasm/demo-worker1.js fdfa90aa9d6b402bfed802cf1595fe4da6cc834ac38c8ff854bf1ee01f5ff9bb +F ext/wasm/demo-worker1.js 08720227e98fa5b44761cf6e219269cee3e9dd0421d8d91459535da776950314 F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c95839afd8b05862f -F ext/wasm/fiddle/fiddle-worker.js 6c72acac2d381480bc9f5eb538e3f2faf2c1f72dd4fcbd05d3b409818a9a8fd5 +F ext/wasm/fiddle/fiddle-worker.js 7798af02e672e088ff192716f80626c8895e19301a65b8af6d5d12b2d13d2451 F ext/wasm/fiddle/fiddle.js 84fd75967e0af8b69d3dd849818342227d0f81d13db92e0dcbc63649b31a4893 -F ext/wasm/fiddle/index.c-pp.html 72c7e5517217960b3809648429ea396a7cbad0ffb2c92f6a2f5703abecb27317 +F ext/wasm/fiddle/index.html a27b8127ef9ecf19612da93b2a6a73bdb3777b5c56b5450bb7200a94bc108ff9 F ext/wasm/index-dist.html db23748044e286773f2768eec287669501703b5d5f72755e8db73607dc54d290 -F ext/wasm/index.html 475bc283338749db4e3fbf24cf3f5aa020cc85a1fffb780d400a915fcb5f1756 -F ext/wasm/jaccwabyt/jaccwabyt.js 4e2b797dc170851c9c530c3567679f4aa509eec0fab73b466d945b00b356574b -F ext/wasm/jaccwabyt/jaccwabyt.md 6aa90fa1a973d0ad10d077088bea163b241d8470c75eafdef87620a1de1dea41 -F ext/wasm/mkdist.sh f8883b077a2ca47cf92e6f0ce305fbf72ca648c3501810125056c4b09c2d5554 x -F ext/wasm/mkwasmbuilds.c 0e9198eb90acae4bcf57cf62d7186f6af5aaac02efdb075a1aded33614b3805a +F ext/wasm/index.html 54e27db740695ab2cb296e02d42c4c66b3f11b65797340d19fa6590f5b287da1 +F ext/wasm/jaccwabyt/jaccwabyt.js bbac67bc7a79dca34afe6215fd16b27768d84e22273507206f888c117e2ede7d +F ext/wasm/jaccwabyt/jaccwabyt.md 167fc0b624c9bc2c477846e336de9403842d81b1a24fc4d3b24317cb9eba734f +F ext/wasm/mkdist.sh 64d53f469c823ed311f6696f69cec9093f745e467334b34f5ceabdf9de3c5b28 x +F ext/wasm/mkwasmbuilds.c 1b53c4d2a1350c19a96a8cdfbda6a39baea9d2142bfe0cbef0ccb0e898787f47 F ext/wasm/module-symbols.html e54f42112e0aac2a31f850ab33e7f2630a2ea4f63496f484a12469a2501e07e2 F ext/wasm/scratchpad-wasmfs.html a3d7388f3c4b263676b58b526846e9d02dfcb4014ff29d3a5040935286af5b96 F ext/wasm/scratchpad-wasmfs.mjs 66034b9256b218de59248aad796760a1584c1dd842231505895eff00dbd57c63 F ext/wasm/speedtest1-wasmfs.html 0e9d335a9b5b5fafe6e1bc8dc0f0ca7e22e6eb916682a2d7c36218bb7d67379d F ext/wasm/speedtest1-wasmfs.mjs 60dd5842f6d2a70a6d0bef12633a11491bde6984aff75a37c2040980d8cbf36a F ext/wasm/speedtest1-worker.html 068d4190f304fa1c34e6501a1b3a4c32fe8d8dac93c2d0f53d667a1cb386eedc -F ext/wasm/speedtest1-worker.js 8acad67bfd6aeeb799bd5ae007ea32af85a082a287d8877c5a10adf4bd7efd89 -F ext/wasm/speedtest1.html f32c66997eb0b036c4546e6302cd0673157912661df0b290ab65816f713feac6 +F ext/wasm/speedtest1-worker.js 958a2d3c710bf8e82567277f656193a0248216db99a3c2c86966124b84309efb +F ext/wasm/speedtest1.html c90d63dfa795f0cb1ad188de587be9024b1ff73b4adc5fdf7efc0d781be94d03 F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c -F ext/wasm/tester1-worker.c-pp.html d0032241d0b24d996cf1c4dd0dde364189693af9b5c986e48af7d3d720fcd244 -F ext/wasm/tester1.c-pp.html 52d88fe2c6f21a046030a36410b4839b632f4424028197a45a3d5669ea724ddb -F ext/wasm/tester1.c-pp.js 6b946cd6d4da130dbae4a401057716d27117ca02cad2ea8c29ae9c46c675d618 +F ext/wasm/tester1-worker.c-pp.html 883881eeac14eeeecc8ff22acf9fe0f18a97cacb48be08ebb0bae891ceded584 +F ext/wasm/tester1.c-pp.html 949920126dcf477925d8d540093d9cc374d3ab4c4ddee920c1dcadcf37917306 +F ext/wasm/tester1.c-pp.js 2b014884dadf28928fabcb688746ca87145673eef75e154486505a266203fc15 F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e F ext/wasm/tests/opfs/concurrency/test.js d08889a5bb6e61937d0b8cbb78c9efbefbf65ad09f510589c779b7cc6a803a88 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -651,8 +659,7 @@ F ext/wasm/tests/opfs/sahpool/index.html be736567fd92d3ecb9754c145755037cbbd2bca F ext/wasm/tests/opfs/sahpool/sahpool-pausing.js f264925cfc82155de38cecb3d204c36e0f6991460fff0cb7c15079454679a4e2 F ext/wasm/tests/opfs/sahpool/sahpool-worker.js bd25a43fc2ab2d1bafd8f2854ad3943ef673f7c3be03e95ecf1612ff6e8e2a61 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk e1a03e9206f6a042a9147035915cb944e9242d570779bc3ccd7ed6a39df10cae -F make.bat a136fd0b1c93e89854a86d5f4edcf0386d211e5d5ec2434480f6eea436c7420c +F main.mk 00dd631c66c1f7922b2d691631163899eff1c3d1da780ff37a62f8d997b368e1 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421 @@ -662,38 +669,38 @@ F mptest/multiwrite01.test dab5c5f8f9534971efce679152c5146da265222d F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 1b9c24374a85dfc7eb8fa7c4266ee0db4f9609cceecfc5481cd8307e5af04366 F sqlite3.pc.in e6dee284fba59ef500092fdc1843df3be8433323a3733c91da96690a50a5b398 -F src/alter.c fc36b19273ffe364aeb4d00ba04bda8798ad7a67fec7a035ee8ee56272e1bdbe +F src/alter.c fc7bbbeb9e89c7124bf5772ce474b333b7bdc18d6e080763211a40fde69fb1da F src/analyze.c 03bcfc083fc0cccaa9ded93604e1d4244ea245c17285d463ef6a60425fcb247d -F src/attach.c 7cf07d4fa42b9fc8662237c60c40b730326c30aa90ae5fffc0b18b2d726ebf61 -F src/auth.c ebec42df26b34a62b6750d30d9c2c03554a1c522020182476f7729a439fef04f +F src/attach.c 9af61b63b10ee702b1594ecd24fb8cea0839cfdb6addee52fba26fa879f5db9d +F src/auth.c 54ab9c6c5803b47c0d45b76ce27eff22a03b4b1f767c5945a3a4eb13aa4c78dc F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c e242d4496774dfc88fa278177dd23b607dce369ccafb3f61b41638eea2c9b399 F src/btmutex.c 30dada73a819a1ef5b7583786370dce1842e12e1ad941e4d05ac29695528daea -F src/btree.c b744bf69d520534751c742cababe7ad28c3892f1e3a75242e75a20bca15a834a +F src/btree.c 240fa5b4ced4733ac5882b43448f433a9742a76d9d7e28aa9ce48d13a38ceb5d F src/btree.h e823c46d87f63d904d735a24b76146d19f51f04445ea561f71cc3382fd1307f0 F src/btreeInt.h 9c0f9ea5c9b5f4dcaea18111d43efe95f2ac276cd86d770dce10fd99ccc93886 -F src/build.c b993e4adef4c4cdfd7abf62e2676c467bb1923f25f40c3c7ab2a7bfbace3de7f +F src/build.c 611e07299d72ff04bbcb9e7109183467e30925d203c3e121ef9bb3cf6876289b F src/callback.c 3605bbf02bd7ed46c79cd48346db4a32fc51d67624400539c0532f4eead804ad -F src/carray.c 3efe3982d5fb323334c29328a4e189ccaef6b95612a6084ad5fa124fd5db1179 +F src/carray.c ff6081a31878fc34df8fa1052a9cbf17ddc22652544dcb3e2326886ed1053b55 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/date.c 61e92f1f7e2e88e1cd91e91dc69eb2b2854e7877254470f9fabd776bfac922b8 +F src/date.c e19e0cfff9a41bfdd884c655755f6f00bca4c1a22272b56e0dd6667b7ea893a2 F src/dbpage.c c9ea81c11727f27e02874611e92773e68e2a90a875ef2404b084564c235fd91f F src/dbstat.c 73362c0df0f40ad5523a6f5501224959d0976757b511299bf892313e79d14f5c -F src/delete.c 901499bed747c3b4b2be45be1abe912ba50a3f6a40ba88cc006ccf279f2d0e97 -F src/expr.c 8c3b23cb35f43c2d0570c1058b9a269e561e769e09c81ba192992c95022c1939 +F src/delete.c 03a77ba20e54f0f42ebd8eddf15411ed6bdb06a2c472ac4b6b336521bf7cea42 +F src/expr.c 28b1cc3d2f147cc888703d5482f9581f17656d02abfa331c34370cb3350776be F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c fb0f74c57d19a2d3f113f3476826919d68feda7ff334abfdb479a9a6353b9fcd -F src/func.c 6e7de3551ae0f8205006e5109f025223246edd20186d54d90746dee7c1c5c093 +F src/fkey.c 928ed2517e8732113d2b9821aa37af639688d752f4ea9ac6e0e393d713eeb76f +F src/func.c 0b802107498048d3dcac0b757720bcb8506507ce02159e213ab8161458eb293b F src/global.c a19e4b1ca1335f560e9560e590fc13081e21f670643367f99cb9e8f9dc7d615b F src/hash.c 03c8c0f4be9e8bcb6de65aa26d34a61d48a9430747084a69f9469fbb00ea52ca F src/hash.h 46b92795a95bfefb210f52f0c316e9d7cdbcdd7e7fcfb0d8be796d3a5767cddf -F src/hwtime.h 21c2cf1f736e7b97502c3674d0c386db3f06870d6f10d0cf8174e2a4b8cb726e +F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c dfd311b0ac2d4f6359e62013db67799757f4d2cc56cca5c10f4888acfbbfa3fd -F src/json.c 8b6341a419150b28530cc21e3951b2238c35cdc312f11b2ca29017fe4b1dedc0 +F src/json.c fb031340edee159c07ad37dbe668ffe945ed86f525b0eb3822e4a67cbc498a72 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c 56a542244fbefc739a2ef57fac007c16b2aefdb4377f584e9547db2ce3e071f9 -F src/main.c 31a13302193fbd51279c7e69cdfa0320d0de7629f9151e0964c1d320e8bdd7a4 +F src/loadext.c a3bc9a2522dc3b960e38b7582d1818f6245a49289387c2c7b19f27bfeabf1e81 +F src/main.c 65d11c17890966d271c925c6cc55e3ba50fa08374633cb99c0dee4719a20915a F src/malloc.c 422f7e0498e1c9ef967f06283b6f2c0b16db6b905d8e06f6dbc8baaa3e4e6c5a F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 @@ -703,44 +710,44 @@ F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff F src/memdb.c a3feb427cdd4036ea2db0ba56d152f14c8212ca760ccb05fb7aa49ff6b897df3 F src/memjournal.c c283c6c95d940eb9dc70f1863eef3ee40382dbd35e5a1108026e7817c206e8a0 F src/msvc.h 80b35f95d93bf996ccb3e498535255f2ef1118c78764719a7cd15ab4106ccac9 -F src/mutex.c 00b8cee206a67fd764d001f3a148494331d8d0b3b9c3974ecd69ff29bb444462 +F src/mutex.c 06bcd9c3dbf2d9b21fcd182606c00fafb9bfe0287983c8e17acd13d2c81a2fa9 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 F src/mutex_unix.c f7ee5a2061a4c11815a2bf4fc0e2bfa6fb8d9dc89390eb613ca0cec32fc9a3d1 -F src/mutex_w32.c e1d317d29cb623667d43de94714264d1e1871cc4bb39fa67dd17048e8138c739 +F src/mutex_w32.c 28f8d480387db5b2ef5248705dd4e19db0cfc12c3ba426695a7d2c45c48e6885 F src/notify.c 57c2d1a2805d6dee32acd5d250d928ab94e02d76369ae057dee7d445fd64e878 F src/os.c 509452169d5ea739723e213b8e2481cf0e587f0e88579a912d200db5269f5f6d F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 -F src/os_kv.c e7d96727db5b67e39d590a68cc61c86daf4c093c36c011a09ebfb521182ec28d +F src/os_kv.c fb7ba8d6204197357f1eb7e1c7450d09c10043bf7e99aba602f4aa46b8fb11a3 F src/os_setup.h 8efc64eda6a6c2f221387eefc2e7e45fd5a3d5c8337a7a83519ba4fbd2957ae2 -F src/os_unix.c fa5e09b4df35ad845440cad67b86908cfe1fd4c28c51915f82e23633d1992bf4 -F src/os_win.c 0d553b6e8b92c8eb85e7f1b4a8036fe8638c8b32c9ad8d9d72a861c10f81b4c5 -F src/os_win.h 5e168adf482484327195d10f9c3bce3520f598e04e07ffe62c9c5a8067c1037b -F src/pager.c fe34fd22ec251436985d7b6ebdd05bf238a17901c2cb23d3d28974dd2361a912 +F src/os_unix.c dcf7988ddbdd68619b821c9a722f9377abb46f1d26c9279eb5a50402fd43d749 +F src/os_win.c a89b501fc195085c7d6c9eec7f5bd782625e94bb2a96b000f4d009703df1083f +F src/os_win.h 4c247cdb6d407c75186c94a1e84d5a22cbae4adcec93fcae8d2bc1f956fd1f19 +F src/pager.c cd562b878ea1b44d021ba199abc9d3b54f6b3347500a9fed03f66d6000620945 F src/pager.h 6137149346e6c8a3ddc1eeb40aee46381e9bc8b0fcc6dda8a1efde993c2275b8 -F src/parse.y 3b784d6083380a950e3b1b32ce5ddd303e8c7c209d8ab788df2c62aaf9ee8eb3 +F src/parse.y 619c3e92a54686c5e47923688c4b9bf7ec534a4690db5677acc28b299c403250 F src/pcache.c 588cc3c5ccaaadde689ed35ce5c5c891a1f7b1f4d1f56f6cf0143b74d8ee6484 -F src/pcache.h 092b758d2c5e4dabb30eae46d8dfad77c0f70b16bf3ff1943f7a232b0fe0d4ba +F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c 131ca0daf4e66b4608d2945ae76d6ed90de3f60539afbd5ef9ec65667a5f2fcd F src/pragma.c ecec75795c1821520266e4f93fa8840cce48979af532db06f085e36a7813860f -F src/prepare.c f6a6e28a281bd1d1da12f47d370a81af46159b40f73bf7fa0b276b664f9c8b7d -F src/printf.c 9cff219dba73b1aa9a8113e83e962f03f7bea8b6eb51cefb25bc468d5a69fb2d +F src/prepare.c 2af0b5c1ec787c8eebd21baa9d79caf4a4dc3a18e76ce2edbf2027d706bca37a +F src/printf.c 7297c2aeed4d90d80c5ba82920d9e57b7bfad04b3466be1d7e042db382fe296e F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd +F src/resolve.c 243888da9f31c48d0890f8c9df30ed4be6fd098d295b0fe1c297a7f3c453ca53 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c ffe199f025a0dd74670d2a77232bdea364a4d7b36f32c64a6572d39ba6a11576 -F src/shell.c.in d4e9ce266ca8f7364da6e86df011f8655beeb5f0d074d624215a2d8ce220a0ad -F src/sqlite.h.in 1f853f1d836af3e5a0b451521041d05658988a45f6978aaae08286e483fee5ac +F src/select.c 5abbd22cae7469dae0a600d78636be77a5682cc372cf8da744d68bbe23298df2 +F src/shell.c.in 2c7e751795f38bb1855c35b556419cab5b8ba22e0f6758f5a629338065d6b79f +F src/sqlite.h.in c0979f9ac1f5be887397dd2a0bb485636893a81b34d64df85123aae9650c42f2 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 -F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca -F src/sqliteInt.h 1c7f23ab9d6efdf3dc434880b6320f158937284f6e2cebd2a024def0c749cb04 -F src/sqliteLimit.h 904a3f520362c7065c18165aaabd504fb13cc1b76cb411f38bd41ac219e4af1e +F src/sqlite3ext.h 7f236ca1b175ffe03316d974ef57df79b3938466c28d2f95caef5e08c57f3a52 +F src/sqliteInt.h 601512a60e48f88e5148fc0e15f1efd03372df2a18bdee312d1590d14da6b5c1 +F src/sqliteLimit.h 4bc72c1519a27c538b7575c240a4472c829d78d27d69a00ddd5a046a0dbfd73a F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c 85b5a20df96016e5d1d8fdc68c8a4c279c5b93e2049b77cd806c2cc50b9d8c56 +F src/tclsqlite.c 3c604c49e6cf4211960a9ddb9505280fd22cde32175f40884c641c0f5a286036 F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a -F src/test1.c 3e3b013f59ffcb57dce00c90d55907072d71d4e970cb0a590cb261efe11bae9c +F src/test1.c 5d061afe479c7364842e0170be7220dea13389575fa6030d30b3e20bec4e1f75 F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff F src/test3.c 432646f581d8af1bb495e58fc98234380250954f5d5535e507fc785eccc3987a F src/test4.c 0ac87fc13cdb334ab3a71823f99b6c32a6bebe5d603cd6a71d84c823d43a25a0 @@ -750,10 +757,10 @@ F src/test8.c 206d8f3cc73950d252906656e2646b5de0d580b07187b635fcb3edd8c2c5fbc0 F src/test9.c df9ddc7db6ef1b8cf745866ee229090779728bcbe660c7f297d3127ab21d92af F src/test_autoext.c 14d4bbd3d0bd1eec0f6d16b29e28cf1e2d0b020d454835f0721a5f68121ac10f F src/test_backup.c a2bfd90d2ff2511b8635507bdb30fa9b605ade19c16b533066cae3077f5bdb72 -F src/test_bestindex.c d75fad21369d80910238032bcf8d9ca1f2bffda13c1ceec63bfbb7f704448b15 +F src/test_bestindex.c a9428931bec06de830b2630f57a7b1f2711761269f04df62b7aa1affcbce15bb F src/test_blob.c 77b994e17f2c87055f44fd96c9a206c5a7155bae2cda2769af60c2f3582f962c F src/test_btree.c 28283787d32b8fa953eb77412ad0de2c9895260e4e5bd5a94b3c7411664f90d5 -F src/test_config.c e02566c2c4ee2916324ce17123a798b47663cead2de546cfbd71d8cddb46bb26 +F src/test_config.c 18aa596d37de1d5968c439fd58ebf38bc4d9c9d1db63621504e241fde375cecd F src/test_delete.c d0e8f6dc55cfc98a7c27c057fb88d512260564bf0b611482656c68b8f7f401ed F src/test_demovfs.c 3efa2adf4f21e10d95521721687d5ca047aea91fa62dd8cc22ac9e5a9c942383 F src/test_devsym.c 649434ed34d0b03fbd5a6b42df80f0f9a7e53f94dd1710aad5dd8831e91c4e86 @@ -773,7 +780,7 @@ F src/test_mutex.c dacae6790956c0d4e705aaed2090227792e291b0496cccd688e9994c1e21f F src/test_onefile.c f31e52e891c5fef6709b9fcef54ce660648a34172423a9cbdf4cbce3ba0049f4 F src/test_osinst.c 269039d9c0820a02ee928014c30860d57ee757ecda54df42e463d0ca1377b835 F src/test_pcache.c 496da3f7e2ca66aefbc36bbf22138b1eff43ba0dff175c228b760fa020a37bd0 -F src/test_quota.c 5bb44452b9c6c248bb3c82d2466a20915aa6d12801f6c1784b6499aaa04d9811 +F src/test_quota.c 180e87437250bed7e17e4e61c106730939e39fec9be73d28961f27f579a92078 F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d F src/test_rtree.c d844d746a3cc027247318b970025a927f14772339c991f40e7911583ea5ed0d9 F src/test_schema.c b06d3ddc3edc173c143878f3edb869dd200d57d918ae2f38820534f9a5e3d7d9 @@ -788,21 +795,21 @@ F src/test_vfs.c b4135c1308516adf0dfd494e6d6c33114e03732be899eace0502919b674586b F src/test_window.c 6d80e11fba89a1796525e6f0048ff0c7789aa2c6b0b11c80827dc1437bd8ea72 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c f297bbf02037639e7a93b37d9c6e4415b3de1273395ee8fa8183e741e1e7fb72 -F src/treeview.c feaa59f14db4f7b5aacca9c5ad5aeb562c1f98262c1ffd74371f4186ade91fc5 -F src/trigger.c 4bf3bfb3851d165e4404a9f9e69357345f3f7103378c07e07139fdd8aeb7bd20 +F src/tokenize.c cb3294cf23c11106b50d9af6998a6c1bf389b52e15b17698c9fab97bbaa9b37f +F src/treeview.c 3ce7ac9835d2d70cc1c868b01b747ae8a062322e155701e58e3d62ca79aada7a +F src/trigger.c d5cf2541ff048f30b6a0507eb3d1ec4e695c53584e3b2298a5bf248714fe185e F src/update.c 3e5e7ff66fa19ebe4d1b113d480639a24cc1175adbefabbd1a948a07f28e37cf F src/upsert.c 215328c3f91623c520ec8672c44323553f12caeb4f01b1090ebdca99fdf7b4f1 F src/utf.c 7267c3fb9e2467020507601af3354c2446c61f444387e094c779dccd5ca62165 -F src/util.c eccfa8b3b414bb64c6543421c9fd10e5f07e103baae36427a273a9131527694c -F src/vacuum.c d3d35d8ae893d419ade5fa196d761a83bddcbb62137a1a157ae751ef38b26e82 -F src/vdbe.c 5328c99dd256ee8132383565a86e253543a85daccfd7477c52f20bac6b385a7f -F src/vdbe.h 966d0677a540b7ea6549b7c4e1312fc0d830fce3a235a58c801f2cc31cf5ecf9 -F src/vdbeInt.h 42488247a80cd9d300627833c6c85ace067ae5011a99e7614e2358130d62feea -F src/vdbeapi.c 6cdcbe5c7afa754c998e73d2d5d2805556268362914b952811bdfb9c78a37cf1 -F src/vdbeaux.c 396d38a62a357b807eabae0cae441fc89d2767a57ab08026b7072bf7aa2dd00c +F src/util.c 36fb1150062957280777655976f3f9a75db236cb8207a0770ceae8d5ec17fcd3 +F src/vacuum.c 1bacdd0a81d2b5dc1c508fbf0d938c89fa78dd8d5b46ec92686d44030d4f4789 +F src/vdbe.c b44c366e83412d3b8c190feb1f029b7d02e1bd69252a57b32f195107f0d03964 +F src/vdbe.h be33bd7b17f2ec92939642416030491508c51071f6c14e27cd195983fec56b63 +F src/vdbeInt.h 2aaeb6df2938b181b4700a9328688a3986f2bba71e8b96f6a80671316618fa49 +F src/vdbeapi.c 790f199ec0d9c423f5efef58d7538ac9c6b34248d84180eb0dca3d635f1c9c9b +F src/vdbeaux.c 908d8a191aed444b2e4c920159249127f3ff67b94c56a16fad1dfdf9c7488f20 F src/vdbeblob.c b3f0640db9642fbdc88bd6ebcc83d6009514cafc98f062f675f2c8d505d82692 -F src/vdbemem.c 317ec5e870ddb16951b606c9fe8be22baef22ecbe46f58fdefc259662238afb7 +F src/vdbemem.c fdd023e357ad3129e1dcae46df47fccceeb8bd1ffa6c5d43a1e3f04460bb59b7 F src/vdbesort.c b69220f4ea9ffea5fdef34d968c60305444eea909252a81933b54c296d9cca70 F src/vdbetrace.c 49e689f751505839742f4a243a1a566e57d5c9eaf0d33bbaa26e2de3febf7b41 F src/vdbevtab.c fc46b9cbd759dc013f0b3724549cc0d71379183c667df3a5988f7e2f1bd485f3 @@ -811,11 +818,11 @@ F src/vxworks.h 9d18819c5235b49c2340a8a4d48195ec5d5afb637b152406de95a9436beeaeab F src/wal.c 88d94fd15a75f6eda831fa32d1148a267ea37bf0a4b69829a73dfde06244b08f F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014 -F src/where.c 9f09ee7b260010138d5f9fb5f195b98051119eae3096a99d72ff16c83230f4af +F src/where.c 287324fe73a0ae8e55b3be89bb2fe4148e3a8394e1e2f10ed2113713a037d8a3 F src/whereInt.h 8d94cb116c9e06205c3d5ac87af065fc044f8cf08bfdccd94b6ea1c1308e65da -F src/wherecode.c 783ecd30061c875c919a5163e4b55f9a0eccdaf7c9b17ad2908a1668a8766bc4 -F src/whereexpr.c e9f7185fba366d9365aa7a97329609e4cf00b3dd0400d069fbaa5187350c17c6 -F src/window.c c0a38cd32473e8e8e7bc435039f914a36ca42465506dc491c65870c01ddac9fb +F src/wherecode.c fe08356c7f20f4e2290204b9147bded3bbfe5453e2c590be0f9b0b5f1c959e76 +F src/whereexpr.c 403a44eeec1a0f0914fccc6a59376b6924bc00ef6728fe6ffce4cf3051b320fc +F src/window.c 538195bbc75bb924e18e368fbd4ed731a3fe3f901351b44f6466ec486f53affe F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test 4d7a34d328e58ca2a2d78fd76c27614a41ca7ddf4312ded9c68c04f430b3b47d F test/affinity3.test 9b7d1133e11d5edd7805573c4ab6f3ba73b0b74a1f280d5b130d4bf3506a93ff @@ -830,14 +837,12 @@ F test/alter2.test 7e3d26ab409df52df887b366a63902c3429b935c41cb962fd58ffc25784f2 F test/alter3.test dcdd5f850f30656a45a0f05e41abfb52b74bbf6ccba165d0f7adf6b0116e4fd6 F test/alter4.test 37cafe164067a6590a0ee4cec780bddbbaa33dc50b11542dcfbe0e65626494fd F test/alterauth.test 63442ba61ceb0c1eeb63aac1f4f5cebfa509d352276059d27106ae256bafc959 -F test/alterauth2.test 4b74fa8f184f4736497317feb587b65759eb87d87acfe3a8ef433d4d18bb002b -F test/altercol.test 3661c432aacb42bc2198dd4611bbb9c3b09fc73251b59edda046109103b8ac00 -F test/altercons.test ea18def4a0f26b9066da56095c9c480df705df4d02e4ae151708fae76f7e3884 -F test/altercons2.test 3c1f58312817df43aeada3b1827fdc3ce3fc50c6f49a95ef62cf4cbbae8583a0 +F test/alterauth2.test 48967abae0494d9a300d1c92473d99fcb66edfcc23579c89322f033f49410adc +F test/altercol.test b43fb5725332f4cf8cff0280605202c1672e808281accea60a066d2ccc5129e5 F test/altercorrupt.test 2e1d705342cf9d7de884518ddbb053fd52d7e60d2b8869b7b63b2fda68435c12 F test/alterdropcol.test a653a3945f964d26845ec0cd0a8e74189f46de3119a984c5bc45457da392612e F test/alterdropcol2.test 527fce683b200d620f560f666c44ae33e22728e990a10a48a543280dfd4b4d41 -F test/alterfault.test 2bb3103954ea60f2e2777b1ae12e79ec3e1fd278f2b1398ad316c68835a62898 +F test/alterfault.test 289067108947bedca27534edd4ff251bcd298cf84402d7b24eaa3749305418c6 F test/alterlegacy.test f38c6d06cda39e1f7b955bbce57f2e3ef5b7cb566d3d1234502093e228c15811 F test/altermalloc.test 167a47de41b5c638f5f5c6efb59784002b196fff70f98d9b4ed3cd74a3fb80c9 F test/altermalloc2.test 17fb3724c4b004c469c27dc4ef181608aa644555fbd3f3236767584f73747c81 @@ -845,8 +850,8 @@ F test/altermalloc3.test 8040e486368403f2fdd6fc3998258b499bd4cc2f3ddbb5f8f874cd4 F test/alterqf.test 8ec03d776de9c391daa0078ea8f838903bdcfb11dfae4ba3576b48436834ccba F test/altertab.test 8a2712f9076da5012a002d0b5cc0a421398a5bf61c25bab41b77c427586a7a27 F test/altertab2.test 0889ba0700cc1cdb7bc7d25975aa61fece34f621de963d0886e2395716b38576 -F test/altertab3.test 575e771e2f02b13eb98798dc92eabacd187d6dbcf596e70f11d699b0b6b5d0b2 -F test/altertrig.test b1590647076add5a47aea0f2236c609ca0bc8a7a2462463edd3e5882c7894802 +F test/altertab3.test 471b8898d10bbc6488db9c23dc76811f405de6707d2d342b1b8b6fd1f13cd3c8 +F test/altertrig.test aacc980b657354fe2d3d4d3a004f07d04ccc1a93e5ef82d68a79088c274ddc6b F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f F test/analyze.test 2fb21d7d64748636384e6cb8998dbf83968caf644c07fcb4f76c18f2e7ede94b F test/analyze3.test c5156cef33f04b90a6b9e9d5d0bbc273a0fb44147d4508407bf1080811e2c6c8 @@ -863,7 +868,6 @@ F test/analyzeF.test 40b5cc3ad7b10e81020d7ca86f1417647ecfae7477cfd88acc5aa7ae106 F test/analyzeG.test 623be33038c49648872746c8dd8b23b5792c08fef173c55e82f1b12fca259852 F test/analyzer1.test b6a624ec0af92eec209e1328465b66937c8fdf2fb442a3fa45321ddb3700f4aa F test/atof1.test bd21c4a0e718ab1470de07a2a79f2544d7903be34feebcc80de04beee4807b00 -F test/atof2.test 12912add57230495450e2fc94cb8ad1c9f3277f8843a3bc27079cae45c9782a1 F test/atomic.test 065a453dde33c77ff586d91ccaa6ed419829d492dbb1a5694b8a09f3f9d7d061 F test/atomic2.test b6863b4aa552543874f80b42fb3063f1c8c2e3d8e56b6562f00a3cc347b5c1da F test/atrc.c c388fac43dbba05c804432a7135ae688b32e8f25818e9994ffba4b64cf60c27c @@ -877,7 +881,7 @@ F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 F test/auth3.test 76d20a7fa136d63bcfcf8bcb65c0b1455ed71078d81f22bcd0550d3eb18594ab F test/autoanalyze1.test b9cc3f32a990fa56669b668d237c6d53e983554ae80c0604992e18869a0b2dec F test/autoinc.test 9df9930966dbe92c55ef37a4d89112cfd537be0d0596d397177c12db9e581be0 -F test/autoindex1.test 2523a76f30734742c3f4d948d0cbf3b6627f775e7833814f425a2e289ba58b22 +F test/autoindex1.test 65931519206bbec71948b11e125af0656435a0937973fe5fed70d776a712911f F test/autoindex2.test 12ef578928102baaa0dc23ad397601a2f4ecb0df F test/autoindex3.test ca502c8050166ac6107a7b4fe4e951f4d3270a23a958af02b14f1b962b83c4b6 F test/autoindex4.test 3c2105e9172920e26f950ba3c5823e4972190e022c1e6f260ba476b0af24c593 @@ -885,7 +889,7 @@ F test/autoindex5.test 3fb938cbf4e7f3896563ce04e2a24b0bc653fc6245b4bf3268cd7b20f F test/autovacuum.test 00671369bbf96c6a49989a9425f5b78b94075d6a4b031e5e00000c2c32f365df F test/autovacuum2.test 76f7eb4fe6a6bf6d33a196a7141dba98886d2fb53a268d7feca285d5da4759d7 F test/autovacuum_ioerr2.test 8a367b224183ad801e0e24dcb7d1501f45f244b4 -F test/avfs.test 95bb8d04f8edad6dc9e600221d103f7e2cc3da398af84df215a3a819e560c45c +F test/avfs.test 76f59743dc1f5fa533840d1818b420fe1ee45e21c0fd6bbac7942ba677903128 F test/avtrans.test 7a6eae44763293024b137b53ff824d8500d754dbae060a8d940afbacfc1d4a15 F test/backcompat.test f2431465ed668f09fc3f6998e56e893a1506ccea6e8b6f409f085f759f431b48 F test/backup.test 3b08fd4af69f0fa786931103a31f4542b184aba16e239e5f22b18c3c2476697f @@ -905,14 +909,13 @@ F test/bestindex4.test 3039894f2dad50f3a68443dffad1b44c9b067ac03870102df1ce3d9a4 F test/bestindex5.test a0c90b2dad7836e80a01379e200e5f8ec9476d49b349af02c0dbff2fb75dc98d F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a6f99dd4 F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e -F test/bestindex8.test 4d8b1e8f30a7f405988ce4dbcc2b95c0775f0bed9ec08e0291a07e2f35f7e653 +F test/bestindex8.test b63a4f171a2c83d481bb14c431a8b72e85d27b2ffdaa0435a95d58ca941678f9 F test/bestindex9.test 1a4b93db117fd8abe74ae9be982f86aa72f01e60cd4ac541e6ede39673a451a0 F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572eef44c7f -F test/bestindexB.test 14db2f66ec9cc5064a74996033b74e5eec0fd2f3a327fbe34ff18de67e9d2671 +F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce F test/bestindexC.test 95b4a527b1a5d07951d731604a6d4cf7e5a806b39cea0e7819d4c9667e11c3fc F test/bestindexD.test 6a8f6f84990bcf17dfa59652a1f935beddb7afd96f8302830fbc86b0a13df3c3 F test/bestindexE.test 297f3ea8500a8f3c17d6f78e55bdfee089064c6144ee84a110bd005a03338f49 -F test/bestindexF.test 4e53d606cbde40a2254aa016d500c5b71766a4065b8541202d195a3d9fe11b1c F test/between.test e7587149796101cbe8d5f8abae8d2a7b87f04d8226610aa1091615005dcf4d54 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -947,7 +950,7 @@ F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4 F test/capi3c.test 31d3a6778f2d06f2d9222bd7660c41a516d1518a059b069e96ebbeadb5a490f7 F test/capi3d.test 8b778794af891b0dca3d900bd345fbc8ebd2aa2aae425a9dccdd10d5233dfbde F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe -F test/carray01.test 17c1cf8287862b15dda949dba626fd5fee5c58471dcc1cae0341471c2ae7da01 +F test/carray01.test 49e2aedfdf2c715bc002d2773cdc1217166679639542c79c8aa4115f06421407 F test/carray02.test 9d070b54f24a34d1f3b3c552ba34db0375a9d1c4219067416fb07d1595987c9d F test/carrayfault.test 108a7d83904fc267c448e27c13b2a857c700bd6ddaa2f1e2518be718b159cb6b F test/cast.test a2a3b32df86e3c0601ffa2e9f028a18796305d251801efea807092dbf374a040 @@ -965,7 +968,7 @@ F test/collate1.test 0890fa372753b59eba53832d37328af815f6b8e4b16761823180eeb62c8 F test/collate2.test 471c6f74573382b89b0f8b88a05256faa52f7964f9e4799e76708a3b1ece6ba4 F test/collate3.test 89defc49983ddfbf0a0555aca8c0521a676f56a5 F test/collate4.test c953715fb498b87163e3e73dd94356bff1f317bd -F test/collate5.test 42daaf7799b04221206a219fb3c0f9efeede03e760f9562b8c0114b8df183fe3 +F test/collate5.test b1dfeff239ea69ee9225832553f423d37a6184eb730cee06f6846ab4e3c6dbef F test/collate6.test 8be65a182abaac8011a622131486dafb8076e907 F test/collate7.test 8ec29d98f3ee4ccebce6e16ce3863fb6b8c7b868 F test/collate8.test cd9b3d3f999b8520ffaa7cc1647061fc5bab1334 @@ -1002,7 +1005,7 @@ F test/corruptK.test ac13504593d89d69690d45479547616ed12644d42b5cb7eeb2e759a76fc F test/corruptL.test f15de2b4729c0851ea89916a26766b094d74bac79f9f9f2b0191935aa3b344c9 F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 F test/corruptN.test a034bb217bebd8d007625dfb078e76ec3d53515052dbceb68bd47b2c27674d5c -F test/cost.test 3786cd1cc6d1ab416004a1e39387fb0db0c8e259f46b0bce62cbdc328f2c55a0 +F test/cost.test cc434a026b1e9d0d98137a147e24e5daf1b1ad09e9ff7da63b34c83ddd136d92 F test/count.test cd4bd531066e8d77ef8fe1e3fc8253d042072e117ccab214b290cf83f1602249 F test/countofview.test 4088e461a10ee33e69803c177a69aa1d7bba81a9ffc2df66d76465a22ca7fdfc F test/coveridxscan.test f35c7208dedc4f98e471c569df64c0f95a49f6e072d8dc7c8f99bdee2697de1b @@ -1033,7 +1036,7 @@ F test/dbfuzz.c fc566102f72c8af84ae8077b4faf7f056c571e6fa7a32e98b66e42b7505f47b6 F test/dbfuzz001.test 6c9a4622029d69dc38926f115864b055cb2f39badd25ec22cbfb130c8ba8e9c3 F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee F test/dbfuzz2.c 4b3c12de4d98b1b2d908ab03d217d4619e47c8b23d5e67f8a6f2b1bdee7cae23 -F test/dblwidth-a.sql 59dd59aa78ce8fd8ab631a3816516831f4e947b143039257e6fe132c3cea4171 +F test/dblwidth-a.sql eb4141518610e52f931a55a984310075e98dc31eee5a28ae806b1e35377be85a F test/dbpage.test 63fab1eb026bada121107e53436fa749bbf83281dc9dea17af422f7a5c0f289f F test/dbpagefault.test ea39de2ca86041a9c6df1135645180a76d0a8da93ac159e2fafe38e39636530b F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 @@ -1050,9 +1053,8 @@ F test/descidx2.test a0ba347037ff3b811f4c6ceca5fd0f9d5d72e74e59f2d9de346a9d2f6ad F test/descidx3.test 953c831df7ea219c73826dfbf2f6ee02d95040725aa88ccb4fa43d1a1999b926 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e F test/distinct.test 691c9e850b0d0b56b66e7e235453198cb4cf0760e324b7403d3c5abbeab0a014 -F test/distinct2.test a6af6a90b2c1eea64c3cc87ea7f8feb832053f7276fe3c212abacf646de4762a +F test/distinct2.test 4d6316b6487a0aa5a90bee111575c957e2a5ba5a9be9156febe9533ce78876e8 F test/distinctagg.test 40d7169ae5846caaf62c6e307d2ca3c333daf9b6f7cde888956a339a97afe85f -F test/dotcmd01.sql 0388a778912ed08436ae5c80e03389d8bd347fa724611193257a18c69692019d F test/e_blobbytes.test 4c01dfe4f12087b92b20705a3fdfded45dc4ed16d5a211fed4e1d2786ba68a52 F test/e_blobclose.test 692fc02a058476c2222a63d97e3f3b2b809c1842e5525ded7f854d540ac2e075 F test/e_blobopen.test 29f6055ee453b8e679fe9570c4d3acfedbef821622c5dad16875148c5952ef50 @@ -1062,7 +1064,7 @@ F test/e_createtable.test 31b9bcb6ac8876bc7ec342d86d9c231a84c62b442093a6651dfd0f F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e F test/e_droptrigger.test 235c610f8bf8ec44513e222b9085c7e49fad65ad0c1975ac2577109dd06fd8fa F test/e_dropview.test 74e405df7fa0f762e0c9445b166fe03955856532e2bb234c372f7c51228d75e7 -F test/e_expr.test 9bdb347b78b9f4eff9153ea97797facc179a821898588471a70808b4471a69b0 +F test/e_expr.test 0a1e175caddc78b27306647cb4ce2362c55790190f8cdd178b75fd6262eb8f76 F test/e_fkey.test feeba6238aeff9d809fb6236b351da8df4ae9bda89e088e54526b31a0cbfeec5 F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07 F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e @@ -1071,19 +1073,19 @@ F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8 F test/e_select.test 327a15f14068bbd6f647cedc67210f8680fcb2f05e481a0a855fccd2abfa1292 F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f F test/e_totalchanges.test c927f7499dc3aa28b9b556b7d6d115a2f0fe41f012b128d16bf1f3b30e9b41e4 -F test/e_update.test 9f8bb82b8760ac66f2c9c2aadb78a418bd639d22f041d712570c9db56f20afda +F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528 F test/e_uri.test 86564382132d9c453845eeb5293c7e375487b625900ab56c181a0464908417d8 F test/e_vacuum.test 89fc48e8beee2f9dfd6de1fbb2edea6542dae9121dc0fbe6313764169e742104 F test/e_wal.test db7c33642711cf3c7959714b5f012aca08cacfa78da0382f95e849eb3ba66aa4 F test/e_walauto.test 248af31e73c98df23476a22bdb815524c9dc3ba8 -F test/e_walckpt.test 16e7d006e8687654ee59e7ad5a6d285ba23f0fe0eeb87f790afd6bc9cf1d1924 +F test/e_walckpt.test 28c371a6bb5e5fe7f31679c1df1763a19d19e8a0 F test/e_walhook.test 01b494287ba9e60b70f6ebf3c6c62e0ffe01788e344a4846b08e5de0b344cb66 F test/emptytable.test a38110becbdfa6325cd65cb588dca658cd885f62 F test/enc.test b5503a87b31cea8a5084c6e447383f9ca08933bd2f29d97b6b6201081b2343eb F test/enc2.test 872afe58db772e7dfa1ad8e0759f8cc820e9efc8172d460fae83023101c2e435 F test/enc3.test 55ef64416d72975c66167310a51dc9fc544ba3ae4858b8d5ab22f4cb6500b087 F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 -F test/eqp.test 1d653fe8d2612cd6764e5ea2f16dcf5f13a9f50448b9233bb1573804bccd7579 +F test/eqp.test 746db9fe11629a0d00328e1721cc2a2e4726d574b677ab14de35fd914f54cc82 F test/eqp2.test 6e8996148de88f0e7670491e92e712a2920a369b4406f21a27c3c9b6a46b68dd F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9 F test/errofst1.test 6da78363739ba8991f498396ab331b5d64e7ab5c4172c12b5884683ef523ac53 @@ -1103,7 +1105,7 @@ F test/extension01.test 5de412c66276105901c370770175003381fdcb0c4da7054fa43cf4a3 F test/external_reader.test 6fdec43eeca23eb32faad1e95a4d1abc402bc8b3db70df12d6fc08a637f4a2b5 F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 F test/fallocate.test 37a62e396a68eeede8f8d2ecf23573a80faceb630788d314d0a073d862616717 -F test/filectrl.test 3344987f143f3cb9914895dc63d9e9518a535ef1b1438d22caf3ba2fa76cc6da +F test/filectrl.test 4b720117388cf6766d0b798e2dddd785953f8f371633b0c0084d2f34cf72336a F test/filefmt.test f393e80c4b8d493b7a7f8f3809a8425bbf4292af1f5140f01cb1427798a2bbd4 F test/filter1.test 590f8ba9a0cd0823b80d89ac75c5ce72276189cef9225d2436adaf1ee87f3727 F test/filter2.tcl 44e525497ce07382915f01bd29ffd0fa49dab3adb87253b5e5103ba8f93393e8 @@ -1122,8 +1124,7 @@ F test/fordelete.test ba98f14446b310f9c9d935b97ec748753d0144a28b356ba30d1f4f6958 F test/fork-test.c 9ac2e6423a1d38df3d6be0e8ac15608b545de21e2b19d9d876254c5931b63edb F test/format4.test eeae341953db8b6bda7f549044797c3278a6cc345d11ada81471671b654f8ef4 F test/fp-speed-1.c b37de94eba034e1703668816225f54510ec60fb0685406608cc707afe6b8234d -F test/fpconv1.test 63f352682fa65601a326563ad633086df6ab194e6ed5e7366786f38a525a7fd7 -F test/fptest01.sql 210562ad8d5a7895f26273dd3be56561a41bcb51d78a28a337af0f1ceaa3bb8d +F test/fpconv1.test d5d8aa0c427533006c112fb1957cdd1ea68c1d0709470dabb9ca02c2e4c06ad8 F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c F test/fts3.test 672a040ea57036fb4b6fdc09027c18d7d24ab654 F test/fts3_common.tcl dffad248f9ce090800e272017d2898005c28ee6314fc1dd5550643a02666907a @@ -1149,7 +1150,7 @@ F test/fts3aux1.test 1880eaa75c586cd10f53080479a2b819b3915ae7ce55c4e0ba8f1fe05ac F test/fts3aux2.test 2459e7fa3e22734aed237d1e2ae192f5541c4d8b218956ad2d90754977bf907f F test/fts3b.test c15c4a9d04e210d0be67e54ce6a87b927168fbf9c1e3faec8c1a732c366fd491 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 -F test/fts3comp1.test 73a53ada3d25bf242c4b2a24cfe9d39e658be56cfa74754279b9e6db776ed7ce +F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c F test/fts3conf.test c9cd45433b6787d48a43e84949aa2eb8b3b3d242bac7276731c1476290d31f29 F test/fts3corrupt.test 6732477c5ace050c5758a40a8b5706c8c0cccd416b9c558e0e15224805a40e57 F test/fts3corrupt2.test e318f0676e5e78d5a4b702637e2bb25265954c08a1b1e4aaf93c7880bb0c67d0 @@ -1199,7 +1200,7 @@ F test/fts3tok_err.test 52273cd193b9036282f7bacb43da78c6be87418d F test/fts3varint.test 0b84a3fd4eba8a39f3687523804d18f3b322e6d4539a55bf342079c3614f2ada F test/fts4aa.test 0e6bfd6a81695a39b23e448dda25d864e63dda75bde6949c45ddc95426c6c3f5 F test/fts4check.test f0ea5e5581951d8ef7a341eea14486daf6c5f516a2f3273b0d5e8cb8a6cd3bd2 -F test/fts4content.test 7f441866207f5b1e76e0f18bde5d9925d1ee8f60388054613dd14a29a79f0bc4 +F test/fts4content.test 73bbb123420d2c46ef2fb3b24761e9acdb78b0877179d3a5d7d57aada08066f6 F test/fts4docid.test e33c383cfbdff0284685604d256f347a18fdbf01 F test/fts4growth.test 289833c34ad45a5e6e6133b53b6a71647231fb89d36ddcb8d9c87211b6721d7f F test/fts4growth2.test 13ad4e76451af6e6906c95cdc725d01b00044269 @@ -1211,7 +1212,7 @@ F test/fts4merge.test 57d093660a5093ae6e9fbd2d17592a88b45bbd66db2703c4b640b28828 F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891 F test/fts4merge3.test 8d9ccb4a3d41c4c617a149d6c4b13ad02de797d0 F test/fts4merge4.test 66fce89934cd9508cbdc67de486558c34912ffb2e8ffe5c9a1bbb9b8a4408ba7 -F test/fts4merge5.test 987af90c930e8555f74ab994f597431caec7f8defc52de7718655c32da07af9e +F test/fts4merge5.test 69932d85cda8a1c4dcfb742865900ed8fbda51724b8cf9a45bbe226dfd06c596 F test/fts4min.test 1c11e4bde16674a0c795953509cbc3731a7d9cbd1ddc7f35467bf39d632d749f F test/fts4noti.test d5d933705b1b1516b67a5e3f8e514ecb19c6522fb3357bb744776d48427c2292 F test/fts4onepass.test d69ddc4ee3415e40b0c5d1d0408488a87614d4f63ba9c44f3e52db541d6b7cc7 @@ -1238,7 +1239,7 @@ F test/fuzz3.test 70ba57260364b83e964707b9d4b5625284239768ab907dd387c740c0370ce3 F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 9096506277f33cc242eb59743c409c81306492b6ebb84571198f864e536ebe22 +F test/fuzzcheck.c 34a025386f84d818cd3343e69e9d9083091af83153e226d71d4e1c126b5f1dd0 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1250,7 +1251,7 @@ F test/fuzzdata8.db 8f34ae00d8d5d4747dd80983cf46161065e4f78324dcff3c893506ff8db3 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc -F test/fuzzinvariants.c 6768bcd03290776cd982624729d2abee2e89e6aba62b4a2b839a98332725a167 +F test/fuzzinvariants.c 3ddfec7f5b970b018f1a982532de905cf180e0c1e48cd653be9365d3e6177625 F test/gcfault.test 4ea410ac161e685f17b19e1f606f58514a2850e806c65b846d05f60d436c5b0d F test/gencol1.test ceb3163b59cb77f4ad57ae4f01a143ce36b06fdd6a8dab1149235db89979ffd8 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 @@ -1261,8 +1262,6 @@ F test/hook.test 2d89bf9480646feb8093be3a58ea502d6521906779ed960de31dd9c4502c054 F test/hook2.test b9ff3b8c6519fb67f33192f1afe86e7782ee4ac8 F test/icu.test 8da7d52cd9722c82f33b0466ed915460cb03c23a38f18a9a2d3ff97da9a4a8c0 F test/ieee754.test 0d3ab84ab2069c9994c833a7cd820ee6037f0cf888e206a4a7fc05f735d5790a -F test/import01.sql 11a5f8325b8b04c66fe489d30558d462411432adf750cef1c0f7b06564691a8c -F test/imposter1.sql fc5ad0945bb19622688c7a1cd7dfd1cefa4b013bac9e2628c22b03c7309f021f F test/imposter1.test 5a20b2cdeb53e65fc57cdb10a33750bd4ef6259909eaf1972253b9e79f7a3fb2 F test/in.test edf979bff3244b9e47849e2b43886631354c8213791f42da92216f08012141af F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 @@ -1302,12 +1301,11 @@ F test/insert.test 97cfb30b83ca1622b9422a1e4c4831b4cb767cf5d654660945036d1e72067 F test/insert2.test 4d14b8f1b810a41995f6286b64a6943215d52208 F test/insert3.test 1b7db95a03ad9c5013fdf7d6722b6cd66ee55e30 F test/insert4.test 2bf81535a990c969665d66db51fcf76c23499b39893b5109f413d1de4ad34cd3 -F test/insert5.test 79f6b6efd0d3db5f4e3ff442300b7d9e7185adb345b29aacc3ea5a9c58ab9beb +F test/insert5.test 394f96728d1258f406fe5f5aeb0aaf29487c39a6 F test/insertfault.test ac63d14ea3b49c573673a572f4014b9117383a03e497c58f308b5c776e4a7f74 F test/instr.test 67ba309e9697c24a304e98a7c8f372456177dd4e32237d2a305e1e05f7bb79c2 F test/instrfault.test 95e28efade652e6d51ae11b377088fe523a581a07ec428009e152a4dd0e0f44c F test/intarray.test bb976b0b3df0ebb6a2eddfb61768280440e672beba5460ed49679ea984ccf440 -F test/intck01.sql f2d88bf41cdd64f2ed8c3d4f357cf520f017aa2986999ab9a62eb6506ef18106 F test/interrupt.test ac1ef50ec9ab8e4f0e17c47629f82539d4b22558904e321ed5abea2e6187da7a F test/interrupt2.test e4408ca770a6feafbadb0801e54a0dcd1a8d108d F test/intpkey.test 7d54711acf553cdd641a40e9c6cfc2bf1a76070074940c1b126442517054320f @@ -1320,7 +1318,7 @@ F test/ioerr4.test f130fe9e71008577b342b8874d52984bd04ede2c F test/ioerr5.test 5984da7bf74b6540aa356f2ab0c6ae68a6d12039a3d798a9ac6a100abc17d520 F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b F test/istrue.test e7f285bb70282625c258e866ce6337d4c762922f5a300e1b50f958aef6e7d9c9 -F test/join.test c706b382ed09ddc89eee7ad0ffd08d862655b0abc292a690d41d995c18c17b3f +F test/join.test 2fcfd84640cfd9ff48f31b4b0d370c4d5498c355ae4384544668ca54d37ae186 F test/join2.test f59d63264fb24784ae9c3bc9d867eb569cd6d442da5660f8852effe5c1938c27 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 @@ -1336,7 +1334,7 @@ F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 F test/joinH.test 1d2fc3190be68525fd9ce749b9468c40ba2930181e52fb5ee6f836051b38effb -F test/joinI.test 249802b168ce96d8d57943ef9abafc1e36e28d91829f68bc2b6e87f2b4d33241 +F test/joinI.test a4d37143fcc39e915d9feb08e614a13f88dfe332d77152a3c526a2370ddb9a70 F test/journal1.test bc61a4228db11bffca118bd358ba4b868524bf080f3532749de6c539656e20fa F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test e5aeff93a7776cf644dbc48dec277655cff80a1cd24689036abc87869b120ea6 @@ -1349,14 +1347,13 @@ F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb9 F test/json/json-speed-check.sh 7d5898808ce7542762318306ae6075a30f5e7ee115c4a409f487e123afe91d88 x F test/json/jsonb-q1.txt 1e180fe6491efab307e318b22879e3a736ac9a96539bbde7911a13ee5b33abc7 F test/json101.test cf53254f0f0c1399a01b21fc58fee0e63a12a556be91b9ee9faccdb8b82c083c -F test/json102.test ea5c9811e408e115c8fc539548deef431fda4924c23cacd79dd4b783f4449f07 -F test/json103.test e626d109cd0bdb8282ec9bf755af3befa50e3e03a255362fc53433d31e1d66d4 +F test/json102.test 9b2e5ada10845ff84853b3feaae2ce51ce7145ae458f74c6a6cecc6ef6ee3ae1 +F test/json103.test 355746a6b66aa438f214b4fae454b13068fad2444b5f693e0d538ad1c059b264 F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 -F test/json105.test 9900caa21888289873bc6c14f5ee41213d28ac9b7ca3395f8afb73d540e80f66 +F test/json105.test 043838b56e68f3252a0dcf5be1689016f6f3f05056f8dcfcdc9d074f4d932988 F test/json106.test 4aed3afd16549045d198a8d9cea00deea96e1f2ecf55864dce96cac558b8abef F test/json107.test 59054e815c8f6b67d634d44ace421cf975828fb5651c4460aa66015c8e19d562 F test/json108.test 0a5f1e2d4b35a1bc33052563d2a5ede03052e2099e58cb424547656c898e0f49 -F test/json109.test 441cea5d73c24a1a34d284101740dfae5a082237c048c8a66b03aeebe5e3643e F test/json501.test b95e2d14988b682a5cadf079dd6162f0f85fb74cd59c6b1f1624110104a974eb F test/json502.test 4ef68e4f272dfb083d4cbceb4e9e51d67ec1186a185e0c13637c50a4dc2f9796 F test/jsonb01.test f4cdfb4cf5a0c940091b17675ed9583f45add0c938f07d65b0de0e19d3a9a101 @@ -1429,7 +1426,7 @@ F test/misc1.test e3e36262aff1bd9b8b9bf1eeb3af04adb3fc1e23f0a92dbff708bba9e939ac F test/misc2.test a1a3573cc02662becd967766021d6f16c54684d56df5f227481c7ef0d9df0bd0 F test/misc3.test 651b88bca19b8ff6a7b6af73dae00c3fd5b3ea5bee0c0d1d91abd4c4b4748718 F test/misc4.test 10cd6addb2fa9093df4751a1b92b50440175dd5468a6ec84d0386e78f087db0e -F test/misc5.test 0a5d7604e197f10ee471280bfcaaf8229f9d8e2eebfef2c8853222cbc1ea9cd5 +F test/misc5.test 02fcaf4d42405be02ec975e946270a50b0282dac98c78303ade0d1392839d2b8 F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 F test/misc7.test d595599972ec0b436985f0f02f243b68500ffc977b9b3194ec66c0866cfddcab F test/misc8.test 08d2380bc435486b12161521f225043ac2be26f02471c2c1ea4cac0b1548edbd @@ -1442,12 +1439,11 @@ F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93 F test/mmapcorrupt.test 470fb44fe92e99c1d23701d156f8c17865f5b027063c9119dcfdb842791f4465 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3 -F test/modeA.sql 3f2b5a7ce7074a52b2b7ec07b07dc1a08edba19e40bce9b4d65d3965413bbea3 F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7 F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4 -F test/mutex1.test 2cdc320a3320521d73b8090a04a2245c1e625e5f90672882517bf5fedcec8f13 +F test/mutex1.test 42cb5e244c3a77bb0ef2b967e06fa5e7ba7d32d90a9b20bed98f6f5ede185a25 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660 F test/nan.test 73ea63ab43668313e2f8cc9ef9e9a966672c7934f3ce76926fbe991235d07d91 F test/nockpt.test 3db354270fc63b6871eebd40285d4c55324fb27be629c958adbff6d7fcaa8e14 @@ -1457,13 +1453,13 @@ F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 F test/notify3.test 796c7b7157f55c93b4e672b724e9c923a6fc6aa72ac419379a623e2350472e22 F test/notnull.test a37b663d5bb728d66fc182016613fb8e4a0a4bbf3d75b8876a7527f7d4ed3f18 -F test/notnull2.test c2c7b670fb8fa6ffe5f9cc08af88864fbb8237e28b56ad528e8dee921019c5fe +F test/notnull2.test 5b7dd6e82c409b2d011ad6acf19ae4bf0816a9c69ccf600b529d7405d7c49874 F test/notnullfault.test fc4bb7845582a2b3db376001ef49118393b1b11abe0d24adb03db057ee2b73d5 F test/null.test b7ff206a1c60fe01aa2abd33ef9ea83c93727d993ca8a613de86e925c9f2bc6f F test/nulls1.test 7a5e4346ee4285034100b4cd20e6784f16a9d6c927e44ecdf10034086bbee9c9 F test/numcast.test 5d126f7f581432e86a90d1e35cac625164aec4a1 F test/numindex1.test 20a5450d4b056e48cd5db30e659f13347a099823 -F test/offset1.test c21e67d2d5ae8ed310243fbe84fc2f0dca49e9ffed3ea89110c0d5914c0de620 +F test/offset1.test 72cca52482cbd5bc687cfa67aa2566c859081b5a353fd2f9da9bbd3914dea1ef F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394 F test/optfuzz-db01.c 9f2fa80b8f84ebbf1f2e8b13421a4e0477fe300f6686fbd76cac1d2db66e0fdc F test/optfuzz-db01.txt 21f6bdeadc701cf11528276e2a55c70bfcb846ba42df327f979bd9e7b6ce7041 @@ -1512,12 +1508,6 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd -F test/qrf01.test abc3e558a75ae2678a3172051b39960dc6fd4b298b6d594afa50939759f4037f -F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92 -F test/qrf03.test e7efe46d204671726b4707585126cd78d107368de4a7d0c7b8d5157cdd8624ed -F test/qrf04.test 0894692c998d2401dcc33449c02051b503ecce0c94217be54fb007c82d2d1379 -F test/qrf05.test 8ade5bfa7ef0b448e531687203fa8ae9ef41f1d7e4c11d5ba0c4846af75b13d5 -F test/qrf06.test cd7d0f0e2904904ab88141630a8fff5718ef7e3cc23e5a9c519cf29bb0919d89 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a @@ -1530,9 +1520,8 @@ F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 F test/readonly.test 0d307c335b3421898cfe64a783a376138aa003849b6bff61ee2d21e805bc0051 -F test/recover.test 643139b911ac880a1e881d7621f02cfb546b608b8f2494d7d26fd5ed103b1ceb -F test/regexp1.sql de2b5b33b16b664d655b41e780f2efca38de3e5559fc254b4c9783ff0bea96b0 -F test/regexp1.test 0023eae4073265641b826a70d81ba34d4dd66ad71871a5b4a1b7cf500d5c0c51 +F test/recover.test c76d05f33f0271fba0f0752170e03b0ab5952dc61dcea7ab3ba40df03c4c42de +F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 F test/regexp2.test 64f9726b2ddc71aea06725fcad53231833d038d58b936d49083ace658b370a13 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d F test/reservebytes.test 6163640b5a5120c0dee6591481e673a0fa0bf0d12d4da7513bad692c1a49a162 @@ -1550,13 +1539,13 @@ F test/rowid.test d27191b5ce794c05bf61081e8b2c546a1844c1641321dcaf7fb785234256cc F test/rowvalue.test 93474d8e1c496e970bdcc3a7f54ac835adda667d2fd971957b4bce0c0b11707b F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b F test/rowvalue3.test 103e9a224ca0548dd0d67e439f39c5dd16de4200221a333927372408c025324c -F test/rowvalue4.test 6e160977d44ee715e142f63ec0e339586c61f12bbbffacee369b1cdc0b7390f0 +F test/rowvalue4.test bac9326d1e886656650f67c0ec484eb5f452244a8209c6af508e9a862ace08ed F test/rowvalue5.test 00740304ea6a53a8704640c7405690f0045d5d2a6b4b04dde7bccc14c3068ea7 F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087 F test/rowvalue7.test 06ec0aca725bf683313d03793aa2943bc7f45a901848c7056a9665b769c8fc38 F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0 F test/rowvalue9.test 7499a8fd7ca3a3f0e19d94e135355439aa2b596f86b775ca8de79672da2ca378 -F test/rowvalueA.test 1c5ed13f3b0641452ae35e6488d6ecc16cefce99f2adf7c07c513530e2aac6b7 +F test/rowvalueA.test be8d6ad8b476eb24c151bb20bfd487e0d50c5e99618b7b0e656035069d2fc2cf F test/rowvaluefault.test 963ae9cdaed30a85a29668dd514e639f3556cae903ee9f172ea972d511c54fff F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 @@ -1570,7 +1559,7 @@ F test/savepoint7.test 24c69af86d750c80d51cf6500fde9270717f2b6e5658f055b5e75af75 F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 F test/scanstatus.test b249328caf4d317e71058006872b8012598a5fa045b30bf24a81eeff650ab49e F test/scanstatus2.test d85d17f2b0b4c013dde95232f7beab749f11f0ef847f5ecffb9486d2f5ecf9f9 -F test/schema.test e615575f2d756df4629596523f11d9322384ecf9f980e58c774cff80ff041c33 +F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5c9ce F test/schema4.test 3b26c9fa916abb6dadf894137adcf41b7796f7b9 @@ -1588,7 +1577,7 @@ F test/select5.test 8afc5e5dcdebc2be54472e73ebd9cd1adef1225fd15d37a1c62f969159f3 F test/select6.test da91e61d26b8dea4b61e4a862088dd6ab19998f7be22a16a5b0cfe806e597639 F test/select7.test b825420da8a0b5722fdb77f3369f6396a3d198c46e8787eb26ff9425d4ac9d27 F test/select8.test 8c8f5ae43894c891efc5755ed905467d1d67ad5d -F test/select9.test 108ceff733f31698fef41eb9a0c332f150c54e98be534ee38019a19943f3f5ae +F test/select9.test f7586b207ce2304ab80dc93d3146469a28fd4403621dd3a82d06644563d3c812 F test/selectA.test 1da8ce3884c326e11d2855baffb76436b0d7e044404af8a2a70d1399a4ff7e29 F test/selectB.test 954e4e49cf1f896d61794e440669e03a27ceea25 F test/selectC.test 38c530b0cc5728b793c3c11f52b52c70290d39822224acd39011c89c1853bd31 @@ -1612,17 +1601,16 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 2d658ceee13d9e4361d04d0ea16340ad17784ddf378fb6e9ca6d49c682cb4bae -F test/shell2.test dc541d2681503e55466a24d35a4cbf8ca5b90b8fcdef37fc4db07373a67d31d3 +F test/shell1.test ebe953d64c937ad42a0f33170ac0d2d2568faae26813fc7a95203756446d54aa +F test/shell2.test ab23f01ea2347e4b72bb2399af7ee82aa00f9c059141749f7c4064abca5ad728 F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e -F test/shell4.test e25580a792b7b54560c3a76b6968bd8189261f38979fe28e6bc6312c5db280db -F test/shell5.test a9cd2c8b62e125049ef500937674f47dd6787f0157ac0515aa554044a4dc3ea9 +F test/shell4.test 03593fa7908a55f255916ffeda707cdf55680c777736e3da62b1d78cde0d684d +F test/shell5.test d17e7927ab8b7f720efbdd9b5d05fceb6c3c56c25917901b315400214bf24ef4 F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8 F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3 -F test/shell8.test 38c9e4d7e85d2a3ecfacaa9f6cda4f7a81bf4fffb5f3f37f9cd76827c6883192 +F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871 F test/shell9.test 8742a5b390cdcef6369f5aa223e415aa4255a4129ef249b177887dc635a87209 -F test/shellA.test 05cdaafa1f79913654487ce3aefa038d4106245d58f52e02faf506140a76d480 -F test/shellB.test 7123d231158588401f332bf278754687b83ba5fc5b352ec8679fb19edfb4cc0a +F test/shellA.test 4ecff8b7b2c0122ba8174abfbcc4b0f59e44d80f2a911068f8cd4cfc6661032d F test/shmlock.test 9f1f729a7fe2c46c88b156af819ac9b72c0714ac6f7246638a73c5752b5fd13c F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@ -1656,8 +1644,8 @@ F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c -F test/speedtest.md ea0c85ebe0ecff8b45ba6cdb26e694871f469009a5a29dcfe634b055f05ab241 -F test/speedtest.tcl b06f6321ef90bb68f18f7b0e430e25203d9da79b80f8926986a0d5f21ac485fb x +F test/speedtest.md ee958457ae1b729d9715ae33c0320600000bf1d9ddea1a88dcf79f56729d6fad +F test/speedtest.tcl 6b66974d833d35a63d0e9ec344e0ffa92fbbfac83e173556f700a61cb3be96fc x F test/speedtest1.c 6c01252e66f46de0b6b8d5316e03521e2151782104f3608c10262aa5dce85721 F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 @@ -1692,24 +1680,23 @@ F test/sync.test a619e407ede58a7b6e3e44375328628559fc9695a9c24c47cb5690a866b0031 F test/sync2.test 06152269ed73128782c450c355988fe8dd794d305833af75e1a5e79edd4dae47 F test/syscall.test a067468b43b8cb2305e9f9fe414e5f40c875bb5d2cba5f00b8154396e95fcf37 F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04 -F test/tabfunc01.test cfa96a9a235c39fb0cae69928b989b28bfec108f62d2533486f76e32dcedfdfb +F test/tabfunc01.test 56eeae736217204bb1d9f9ef38340d48058f809b64249217cf77ff4ba600cc21 F test/table.test e87294bf1c80bfd7792142b84ab32ea5beb4f3f71e535d7fb263a6b2068377bf F test/tableapi.test e37c33e6be2276e3a96bb54b00eea7f321277115d10e5b30fdb52a112b432750 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 -F test/tclsqlite.test 5d6c73bfe7006c85e2f7fb7db8638b521eb2043d5451aaacdac4851eab895443 +F test/tclsqlite.test 3f697424cfc1cdc9c076ec0cadb0e700f059400a3e3ce134b7d856fc9f880e1c F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cbd08 F test/tempdb2.test 353864e96fd3ae2f70773d0ffbf8b1fe48589b02c2ec05013b540879410c3440 F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900 F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 -F test/temptrigfault.tes fc5918e64f3867156fefe7cfca9d8e1f495134a5229b2b511b0dc11c07f2eab4 -F test/temptrigger.test a00f258ed8d21a0e8fd4f322f15e8cfb5cef2e43655670e07a753e3fb4769d61 -F test/tester.tcl 2d943f60200e0a36bcd3f1f0baf181a751cd3604ef6b6bd4c8dc39b4e8a53116 +F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc +F test/tester.tcl 463ae33b8bf75ac77451df19bd65e7c415c2e9891227c7c9e657d0a2d8e1074a F test/testloadext.c 862b848783eaed9985fbce46c65cd214664376b549fae252b364d5d1ef350a27 -F test/testrunner.tcl 78d67079fc39caf2af3fd9d4c30bdac78dae7ec50b9fc802835e7a5189581e07 x -F test/testrunner_data.tcl 078e251983c8fc573567125147655f68132210f226c92922daf21fb913779717 -F test/testrunner_estwork.tcl 81e2ae10238f50540f42fbf2d94913052a99bfb494b69e546506323f195dcff9 +F test/testrunner.tcl 60d7efa1816c5dfc37df3e3454b94b9042c0c8c50b27ae296d4a797cd309ace6 x +F test/testrunner_data.tcl c507a9afa911c03446ed90442ffd4a98aca02882c3d51bd1177c24795674def8 +F test/testrunner_estwork.tcl 7927a84327259a32854926f68a75292e33a61e7e052fdbfcb01f18696c99c724 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1758,7 +1745,7 @@ F test/tkt-8454a207b9.test ead80b7a01438ca1436cee029694a96c821346cf1e24f06de12f8 F test/tkt-868145d012.test a5f941107ece6a64410ca4755c6329b7eb57a356 F test/tkt-8c63ff0ec.test 258b7fc8d7e4e1cb5362c7d65c143528b9c4cbed F test/tkt-91e2e8ba6f.test 08c4f94ae07696b05c9b822da0b4e5337a2f54c5 -F test/tkt-99378177930f87bd.test 1ee631d155f0d51a4547e9405ef35a3a9a32977352a37a10bcbbacc5e38356ad +F test/tkt-99378177930f87bd.test 9d6cff39b50d062c813ae1cb0ebbd1b7acf81ecc23ae5d5215e5bb05667dc137 F test/tkt-9a8b09f8e6.test b2ef151d0984b2ebf237760dbeaa50724e5a0667 F test/tkt-9d68c883.test 16f7cb96781ba579bc2e19bb14b4ad609d9774b6 F test/tkt-9f2eb3abac.test cb6123ac695a08b4454c3792fbe85108f67fabf8 @@ -1806,7 +1793,7 @@ F test/tkt2213.test a9702175601a57b61aba095a233b001d6f362474 F test/tkt2251.test 5aab8c7898cd2df2a68fe19289cc29e8f5cf8c82 F test/tkt2285.test cca17be61cf600b397188e77e7143844d2b977e9 F test/tkt2332.test fc955609b958ca86dfa102832243370a0cc84070 -F test/tkt2339.test bad48bd064594aa7b4de23f6d59a72b0b0c4175fd917f4b66907584732d41652 +F test/tkt2339.test 73bd17818924cd2ac442e5fd9916b58565739450 F test/tkt2391.test ab7a11be7402da8b51a5be603425367aa0684567 F test/tkt2409.test be0d60e7d283f639dccea4b0b5e1cd3a4851fb5b F test/tkt2450.test 77ed94863f2049c1420288ddfea2d41e5e0971d6 @@ -1940,15 +1927,15 @@ F test/vacuum4.test 7ea76b769fffeb41f925303b04cbcf5a5bbeabe55e4c60ae754ff24eeeb7 F test/vacuum5.test 263b144d537e92ad8e9ca8a73cc6e1583f41cfd0dda9432b87f7806174a2f48c F test/vacuum6.test b137b04bf3392d3f5c3b8fda0ce85a6775a70ca112f6559f74ff52dc9ce042fd F test/vacuummem.test 4b30f5b95a9ff86e9d5c20741e50a898b2dc10b0962a3211571eb165357003fb -F test/values.test 0e037c50789ac2a308746567d07b53b2f6026c1bb3a435d1b099424600e64caf +F test/values.test 0eda08a6ce6545f1ab012dff4cc72a7dd0fee2510f42444136bb2b2b5ed84bc0 F test/valuesfault.test 2ef23ed965e3bd08e268cdc38a0d11653390ddbbe1e8e2e98d16f55edd30f6e8 F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 F test/view.test 3c23d7a068e9e4a0c4e6907498042772adea725f0630c3d9638ffd4e5a08b92b F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456 -F test/vt02.c c2faf56d74470d569cd00741acb3f1719ee95d668f84ef58acc3872635789680 -F test/vt100-a.sql a3e188a118ca78c08b41681a4db6d0f353e554ceb33f1573b1872d16e2d30596 +F test/vt02.c 5b44ac67b1a283fedecf2d6e2ceda61e7a157f01d44dcb4490dcb1e87d057060 +F test/vt100-a.sql 631eeab18c5adb531bab79aecf64eee3934b42c75a309ee395c814717a6a7651 F test/vtab1.test 09a72330d0f31eda2ffaa828b06a6b917fb86250ee72de0301570af725774c07 F test/vtab2.test 14d4ab26cee13ba6cf5c5601b158e4f57552d3b055cdd9406cf7f711e9c84082 F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e @@ -1989,7 +1976,7 @@ F test/wal_common.tcl 204d1721ac13c5e0c7fae6380315b5ab7f4e8423f580d826c5e9df1995 F test/walbak.test 018d4e5a3d45c6298d11b99f09a8ef6876527946 F test/walbig.test f437473a16cfb314867c6b5d1dbcd519e73e3434 F test/walblock.test 6bb472e82730e7e4e81395e907a01d8cfc2bd9e1f01f8a9184ca572e2955a4bf -F test/walckptnoop.test 5f6123750f40cb86633a7e014f9fb805d0eb494b811840086dc72e554e68c7c1 +F test/walckptnoop.test b13a2c3140f2c913cfd422d9a224544757d04b8b14ab4c267ab9910467c0b9be F test/walcksum.test 50e204500eed9c691b6045e467bb2923f49aa93d8adf315e2be135fdb202c1c2 F test/walcrash.test 21038858cc552077b0522f50b0fa87e38139306a F test/walcrash2.test a0edab4e5390f03b99a790de89aad15d6ec70b36 @@ -2004,7 +1991,7 @@ F test/waloverwrite.test dad2f26567f1b45174e54fbf9a8dc1cb876a7f03 F test/walpersist.test 8d78a1ec91299163451417b451a2bac3481f8eb9f455b1ca507a6625c927ca6e F test/walprotocol.test 1b3f922125e341703f6e946d77fdc564d38fb3e07a9385cfdc6c99cac1ecf878 F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db868eebc131 -F test/walrestart.test 5168c0c2414d1971d8dec949c1070a0144cf15402361ba0d0e6a8054f5598a64 +F test/walrestart.test 3b0a9198ad2eb9f716d8f3846b133ba9f4619fb56decb1e67bba27743c766289 F test/walro.test 78a84bc0fdae1385c06b017215c426b6845734d6a5a3ac75c918dd9b801b1b9d F test/walro2.test 33955a6fd874dd9724005e17f77fef89d334b3171454a1256fe4941a96766cdc F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68 @@ -2019,7 +2006,7 @@ F test/walslow.test 0c51843836c9dcf40a5ac05aa781bfb977b396ee2c872d92bd48b79d5dd9 F test/walthread.test d562f51a61191ccfab64940df7aa1cef87c902fa5ab742590ef7f859dfe6a44b F test/walvfs.test e1a6ad0f3c78e98b55c3d5f0889cf366cc0d0a1cb2bccb44ac9ec67384adc4a1 F test/where.test 5087c72d26fd075a1644c8512be9fe18de9bf2d2b0754f7fd9b74a1c6540c4fc -F test/where2.test 1dbff4ab847068a52b927a0c1a7cf7faed4c8cae081fbcdac9b052a3a209aefa +F test/where2.test 52237a8cb27ebbf6583469429bb5733d1b94ac37d09c3dbd0f487952ed0ab3f8 F test/where3.test 4ccb156ae33de86414a52775a6f590a9d60ba2cbc7a93a24fa331b7bcf5b6030 F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8 F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2 @@ -2037,7 +2024,7 @@ F test/whereG.test 875d020ac0a47828b31e36c54f1bf0cf81c9ea43b257bc21286eca1fe9a48 F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 F test/whereI.test c4bb7e2ca56d49bd8ab5c7bd085b8b83e353922b46904d68aefb3c7468643581 F test/whereJ.test fc05e374cc9f2dc204148d6c06822c380ad388895fe97a6d335b94a26a08aecf -F test/whereK.test 4fb96b078f2ecedc467fa53177787378ff659539e415a4256cae7ae4e2a804b2 +F test/whereK.test 0270ab7f04ba5436fb9156d31d642a1c82727f4c4bfe5ba90d435c78cf44684a F test/whereL.test cb115604cc9bd61acbc99a1f1df0eb1ea7a7875a77fef25ba9282f01d10283e1 F test/whereM.test 0dbc9998783458ddcf3cc078ca7c2951d8b2677d472ecf0028f449ed327c0250 F test/whereN.test 63a3584b71acfb6963416de82f26c6b1644abc5ca6080c76546b9246734c8803 @@ -2049,7 +2036,7 @@ F test/wherelimit3.test 22d73e046870cf8bbe15573eda6b432b07ebe64a88711f9f849c6b36 F test/widetab1.test c296a98e123762de79917350e45fa33fdf88577a2571eb3a64c8bf7e44ef74d1 F test/win32heap.test 1ec2ce646aee491ec23bfcdfd005b33c79f13bf91467966f374a76ffe7c7e85f F test/win32lock.test e56d7a9b6cf9d5f3867c2dd19ff36c5326881e4038c6867610ecb3a9868ea4eb -F test/win32longpath.test 2641e3a5dbb59f49456f6caf78c0acd6ec7dbba27cb56363bab8fbfe93995caa +F test/win32longpath.test 0f9837039b306735c13521c5f25b6ed42937b600dace58e28a3d2f8baf429b6a F test/win32nolock.test 95854dc0206b8a95e4aee15a76acc082767b38f079b2e24676aed6cbb0f32798 F test/window1.test b46d28b9698559e66aa4adafd8074b940faee498bf0c4fbdb62548bfcccc67e7 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 @@ -2074,7 +2061,7 @@ F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387 F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c31660a7c F test/windowpushd.test c420e2265f0e09a0e798d0513a660d71b51602088d81b3dbd038918ee1339dcc -F test/with1.test 31db84788e0429885b63995149fab57d32e26196b752a3a926249ae74c0adddd +F test/with1.test 1ee171d7c306ab8b0771f3511d870f56c735607729836585bbceb1fc2f47e0b1 F test/with2.test 181674a6cc86a601ca2ac052741cdfad5b529e07e870435d2f6cdb92d589ff17 F test/with3.test e30369ea27aa27eb1bda4c5e510c8a9f782c8afd2ab99d1a02b8a7f25a5d3e65 F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205 @@ -2094,12 +2081,12 @@ F test/zeroblob.test 7b74cefc7b281dfa2b07cd237987fbe94b4a2037a7771e9e83f2d5f608b F test/zeroblobfault.test 861d8191a0d944dfebb3cb4d2c5b4e46a5a119eaec5a63dd996c2389f8063441 F test/zerodamage.test 9c41628db7e8d9e8a0181e59ea5f189df311a9f6ce99cc376dc461f66db6f8dc F test/zipfile.test a3fcfc43115e4226fdddadd43bdf31c8ca805ad08dad435634f1633d8f5840d9 -F test/zipfile2.test 21afaffcf4f7769df38bf16e4a9c4dfa6ba1b0f5b695f844ec61fafb92db0db7 +F test/zipfile2.test a577e0775e32ef8972e7d5e9a45bc071a5ae061b5b965a08c9c4b709ad036a25 F test/zipfilefault.test 44d4d7a7f7cca7521d569d7f71026b241d65a6b1757aa409c1a168827edbbc2c F tool/GetFile.cs 47852aa0d806fe47ed1ac5138bdce7f000fe87aaa7f28107d0cb1e26682aeb44 F tool/GetTclKit.bat d84033c6a93dfe735d247f48ba00292a1cc284dcf69963e5e672444e04534bbf F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91 -F tool/build-all-msvc.bat 1ee9dbadcc07fc23268025854e97b392bcbad72376b47aee7b22f3797a4f2c87 x +F tool/build-all-msvc.bat 1960a7a3e5d8176c4329e31476f6e3dfa9543675355fa9020a569f4452628458 x F tool/build-shell.sh 369c4b171cc877ad974fef691e4da782b4c1e99fe8f4361316c735f64d49280f F tool/buildtclext.tcl d09b753d7858314104eeaf5f4def85d35784470279809e47a633f142226f2b3f F tool/cg_anno.tcl c1f875f5a4c9caca3d59937b16aff716f8b1883935f1b4c9ae23124705bc8099 x @@ -2108,7 +2095,7 @@ F tool/cktclsh.sh 6075eef9c6b9ba4b38fef2ca2a66d25f2311bd3c610498d18a9b01f861629c F tool/cp.tcl 9a0d663ad45828de13763ee7ca0200f31f56c6d742cf104a56ae80e027c242d8 F tool/custom.txt 24ed55e71c5edae0067ba159bbf09240d58b160331f7716e95816cd3aa0ba5c4 F tool/dbhash.c 5da0c61032d23d74f2ab84ffc5740f0e8abec94f2c45c0b4306be7eb3ae96df0 -F tool/dbtotxt.c cfeb957571735af345f253ba8417256031fa0dddf79468eefad184262d17211e +F tool/dbtotxt.c ca48d34eaca6d6b6e4bd6a7be2b72caf34475869054240244c60fa7e69a518d6 F tool/dbtotxt.md c9a57af8739957ef36d2cfad5c4b1443ff3688ed33e4901ee200c8b651f43f3c F tool/emcc.sh.in 41a049468c8155433e37e656ba5bae063a000768b1d627025f277732c4e7c4a4 F tool/enlargedb.c 3e8b2612b985cfa7e3e8800031ee191b43ae80de96abb5abbd5eada62651ee21 @@ -2121,25 +2108,24 @@ F tool/genfkey.README e550911fa984c8255ebed2ef97824125d83806eb5232582700de949edf F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a5a4f F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/index_usage.c f62a0c701b2c7ff2f3e21d206f093c123f222dbf07136a10ffd1ca15a5c706c5 -F tool/lemon.c 3fdc16b23f1ea0c91c049b518fc3f75c71843dbfe2b447fcb3cd92d9e4f219f8 -F tool/lempar.c b57e1780bf8098dd4a9a5bba537f994276ea825a420f6165153e5894dc2dfb07 +F tool/lemon.c 8f6c122e5727cb0e5f302b8efc91489b1947a8d98206d7a1b1cfc0ed685b6e7c +F tool/lempar.c bdffd3b233a4e4e78056c9c01fadd2bb3fe902435abde3bce3d769fdf0d5cca2 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c 63412f9790e5e8538fbde0b4f6db154aaaf80f7a10a01e3c94d14b773a8dd5a6 F tool/logest.c c34e5944318415de513d29a6098df247a9618c96d83c38d4abd88641fe46e669 F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkamalzip.tcl 8aa5ebe7973c8b8774062d34e15fea9815c4cc2ceea3a9b184695f005910876a -F tool/mkautoconfamal.sh 06fbe090b81c24e592c1f22b404334f805ba74d482a9260f2ac81e6f3d3386d8 +F tool/mkautoconfamal.sh 647dada5e34c466bef62a4408e1c99a7e5e1922805479dd57944f33f9803f2f8 F tool/mkccode.tcl c42a8f8cf78f92e83795d5447460dbce7aaf78a3bbf9082f1507dc71a3665f3c x -F tool/mkcombo.tcl 2a5189b219c4a495e1ff7fc980bd568d3cfb82ae9d50c84e77f7a161e96fc132 F tool/mkctimec.tcl 3fb5cad05922f5da61262cb6bcd5868a34e94a49ca8833ae2d7796e7df075576 x -F tool/mkkeywordhash.c 82d5af1d0e677900739fba59155cddac172d8c712c2d91ab73d6e6bcb30060f0 +F tool/mkkeywordhash.c 6b0be901c47f9ad42215fc995eb2f4384ac49213b1fba395102ec3e999acf559 F tool/mkmsvcmin.tcl d76c45efda1cce2d4005bcea7b8a22bb752e3256009f331120fb4fecb14ebb7a F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef F tool/mkopcodeh.tcl 2b4e6967a670ef21bf53a164964c35c6163277d002a4c6f56fa231d68c88d023 F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl 3801ce32f8c55fe63a3b279f231fb26c2c1a2ea9a09d2dd599239d87a609acec -F tool/mkshellc.tcl da6918b128e928a8f0d663519e14829153e59465bd5eb596442e99fa10a411b7 +F tool/mkshellc.tcl bab0a72a68384181a5706712dfdf6815f6526446d4e8aacace2de5e80cda91b2 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mksqlite3c-noext.tcl 351c55256213154cabb051a3c870ef9f4487de905015141ae50dc7578a901b84 F tool/mksqlite3c.tcl 7a268139158e5deef27a370bc2f8db6ccf100c1ad7ac5e5b23743c0fd354f609 @@ -2151,27 +2137,26 @@ F tool/mkvsix.tcl 67b40996a50f985a573278eea32fc5a5eb6110bdf14d33f1d8086e48c69e54 F tool/mkwinarm64ec.tcl 171f79234fa53552a129b360356df5599fdab15239caffb3d29c571292728033 F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c6115285845 F tool/omittest-msvc.tcl d6b8f501ac1d7798c4126065030f89812379012cad98a1735d6d7221492abc08 -F tool/omittest.tcl 436b7072e00e25e9b77145a9f67aa8e0eeabd186168827435fd03f8f981aac32 +F tool/omittest.tcl bec70ef0e16255c8d9eb06ecd7edf823c07a60a836186cdbce3528fb34b67995 F tool/opcodesum.tcl 740ed206ba8c5040018988129abbf3089a0ccf4a F tool/pagesig.c f98909b4168d9cac11a2de7f031adea0e2f3131faa7515a72807c03ec58eafeb F tool/replace.tcl 511c61acfe563dfb58675efb4628bb158a13d48ff8322123ac447e9d25a82d9a F tool/restore_jrnl.tcl 1079ecba47cc82fa82115b81c1f68097ab1f956f357ee8da5fc4b2589af6bd98 F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5 -F tool/showdb.c 1faa3661d2d634f206c76794cb21d89d3ea9082d07d5e983be0f025e40f21320 +F tool/showdb.c 3956d71e5193162609a60e8c9edfcf09274c00cfea2b1d221261427adb2b5cca F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818 F tool/showlocks.c 9cc5e66d4ebbf2d194f39db2527ece92077e86ae627ddd233ee48e16e8142564 F tool/showshm.c a0ab6ec32dd1f11218ca2a4018f8fb875b59414801ab8ceed8b2e69b7b45a809 F tool/showstat4.c b706fcbc4cd1a6e4a73ac32549afc4b460479d650402d64b23e8d813516e8de4 -F tool/showtmlog.c 2e9da6c4b4767113a0ad5ddabd4337ea100d38ff9c7fee260f9ccdefb2ffdc23 F tool/showwal.c 11eca547980a066b081f512636151233350ac679f29ecf4ebfce7f4530230b3d F tool/soak1.tcl a3892082ed1079671565c044e93b55c3c7f38829aedf53cc597c65d23ffdaddf F tool/spaceanal.tcl 1f83962090a6b60e1d7bf92495d643e622bef9fe82ea3f2d22350dcbce9a12d0 F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d40618 x F tool/split-sqlite3c.tcl 4969fd642dad0ea483e4e104163021d92baf98f6a8eac981fe48525f9b873430 -F tool/sqldiff.c 847edc1e0d1e1feb652d3d6128e504456deaf254ab9ad3e7cebd4317d2037182 +F tool/sqldiff.c 134be7866be19f8beb32043d5aea5657f01aaeae2df8d33d758ff722c78666b9 F tool/sqlite3_analyzer.c.in 14f02cb5ec3c264cd6107d1f1dad77092b1cf440fc196c30b69ae87b56a1a43b -F tool/sqlite3_rsync.c f510a8b230e1c5b0f62842acd0e94ff15d2f77a00ae782f7d20f9e39919fa19b -F tool/sqltclsh.c.in c103c6fc7d42bce611f9d4596774d60b7ef3d0b291a1f58c9e6184e458b89296 +F tool/sqlite3_rsync.c d0e58a1e49fe2192c3ee0b697aed182d502bebfe5b4b406ba6b2baa52a04ecbe +F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 F tool/src-verify.c 6c655d9a8d6b30f3648fc78a79bf3838ed68f8543869d380c43ea9f17b3b8501 F tool/srcck1.c 559e703c6cca1d70398bdba1d7f91036c1a71adf718a1aaa6401a562ccaed154 @@ -2188,12 +2173,10 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 5fa49c4d592778fb82c4e25c77cf0442d3dc23cc7f8d91d25952c722af866930 -R b4a00b0f2cf0b157145ad751c36ae742 -T +sym-major-release * +P 6ca269b677c6cdc03628fc4f3634ee1b9b956170f9bc41ba8d37b15cc6571a88 +R 6723709c10ad0e4cbb0f48842d99491e T +sym-release * -T +sym-version-3.52.0 * +T +sym-version-3.51.3 * U drh -Z ee8b8689818d3dfbd1a3f7d6f78a36ce +Z 8a6b03453203488391a91516890c1c7d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index e0b8607c1..cb78ee568 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,5 +1,4 @@ -branch trunk -tag trunk +branch branch-3.51 tag release -tag major-release -tag version-3.52.0 +tag branch-3.51 +tag version-3.51.3 diff --git a/manifest.uuid b/manifest.uuid index 55fe12eaa..bae34afb9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -557aeb43869d3585137b17690cb3b64f7de6921774daae9e56403c3717dceab6 +737ae4a34738ffa0c3ff7f9bb18df914dd1cad163f28fd6b6e114a344fe6d618 diff --git a/src/alter.c b/src/alter.c index fb5a37935..a7255e75e 100644 --- a/src/alter.c +++ b/src/alter.c @@ -491,7 +491,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ /* Look up the table being altered. */ assert( pParse->pNewTable==0 ); assert( sqlite3BtreeHoldsAllMutexes(db) ); - if( NEVER(db->mallocFailed) ) goto exit_begin_add_column; + if( db->mallocFailed ) goto exit_begin_add_column; pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); if( !pTab ) goto exit_begin_add_column; @@ -563,7 +563,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ ** Or, if pTab is not a view or virtual table, zero is returned. */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) -static int isRealTable(Parse *pParse, Table *pTab, int iOp){ +static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ const char *zType = 0; #ifndef SQLITE_OMIT_VIEW if( IsView(pTab) ){ @@ -576,12 +576,9 @@ static int isRealTable(Parse *pParse, Table *pTab, int iOp){ } #endif if( zType ){ - const char *azMsg[] = { - "rename columns of", "drop column from", "edit constraints of" - }; - assert( iOp>=0 && iOp<ArraySize(azMsg) ); sqlite3ErrorMsg(pParse, "cannot %s %s \"%s\"", - azMsg[iOp], zType, pTab->zName + (bDrop ? "drop column from" : "rename columns of"), + zType, pTab->zName ); return 1; } @@ -1052,25 +1049,6 @@ static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){ return pBest; } -/* -** Set the error message of the context passed as the first argument to -** the result of formatting zFmt using printf() style formatting. -*/ -static void errorMPrintf(sqlite3_context *pCtx, const char *zFmt, ...){ - sqlite3 *db = sqlite3_context_db_handle(pCtx); - char *zErr = 0; - va_list ap; - va_start(ap, zFmt); - zErr = sqlite3VMPrintf(db, zFmt, ap); - va_end(ap); - if( zErr ){ - sqlite3_result_error(pCtx, zErr, -1); - sqlite3DbFree(db, zErr); - }else{ - sqlite3_result_error_nomem(pCtx); - } -} - /* ** An error occurred while parsing or otherwise processing a database ** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an @@ -1368,8 +1346,8 @@ static int renameResolveTrigger(Parse *pParse){ sqlite3SelectPrep(pParse, pStep->pSelect, &sNC); if( pParse->nErr ) rc = pParse->rc; } - if( rc==SQLITE_OK && pStep->pSrc ){ - SrcList *pSrc = sqlite3SrcListDup(db, pStep->pSrc, 0); + if( rc==SQLITE_OK && pStep->zTarget ){ + SrcList *pSrc = sqlite3TriggerStepSrc(pParse, pStep); if( pSrc ){ Select *pSel = sqlite3SelectNew( pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0 @@ -1397,10 +1375,10 @@ static int renameResolveTrigger(Parse *pParse){ pSel->pSrc = 0; sqlite3SelectDelete(db, pSel); } - if( ALWAYS(pStep->pSrc) ){ + if( pStep->pFrom ){ int i; - for(i=0; i<pStep->pSrc->nSrc && rc==SQLITE_OK; i++){ - SrcItem *p = &pStep->pSrc->a[i]; + for(i=0; i<pStep->pFrom->nSrc && rc==SQLITE_OK; i++){ + SrcItem *p = &pStep->pFrom->a[i]; if( p->fg.isSubquery ){ assert( p->u4.pSubq!=0 ); sqlite3SelectPrep(pParse, p->u4.pSubq->pSelect, 0); @@ -1469,13 +1447,13 @@ static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){ sqlite3WalkExpr(pWalker, pUpsert->pUpsertWhere); sqlite3WalkExpr(pWalker, pUpsert->pUpsertTargetWhere); } - if( pStep->pSrc ){ + if( pStep->pFrom ){ int i; - SrcList *pSrc = pStep->pSrc; - for(i=0; i<pSrc->nSrc; i++){ - if( pSrc->a[i].fg.isSubquery ){ - assert( pSrc->a[i].u4.pSubq!=0 ); - sqlite3WalkSelect(pWalker, pSrc->a[i].u4.pSubq->pSelect); + SrcList *pFrom = pStep->pFrom; + for(i=0; i<pFrom->nSrc; i++){ + if( pFrom->a[i].fg.isSubquery ){ + assert( pFrom->a[i].u4.pSubq!=0 ); + sqlite3WalkSelect(pWalker, pFrom->a[i].u4.pSubq->pSelect); } } } @@ -1646,8 +1624,8 @@ static void renameColumnFunc( if( rc!=SQLITE_OK ) goto renameColumnFunc_done; for(pStep=sParse.pNewTrigger->step_list; pStep; pStep=pStep->pNext){ - if( pStep->pSrc ){ - Table *pTarget = sqlite3LocateTableItem(&sParse, 0, &pStep->pSrc->a[0]); + if( pStep->zTarget ){ + Table *pTarget = sqlite3LocateTable(&sParse, 0, pStep->zTarget, zDb); if( pTarget==pTab ){ if( pStep->pUpsert ){ ExprList *pUpsertSet = pStep->pUpsert->pUpsertSet; @@ -1659,6 +1637,7 @@ static void renameColumnFunc( } } + /* Find tokens to edit in UPDATE OF clause */ if( sParse.pTriggerTab==pTab ){ renameColumnIdlistNames(&sParse, &sCtx,sParse.pNewTrigger->pColumns,zOld); @@ -1860,10 +1839,13 @@ static void renameTableFunc( if( rc==SQLITE_OK ){ renameWalkTrigger(&sWalker, pTrigger); for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){ - if( pStep->pSrc ){ + if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){ + renameTokenFind(&sParse, &sCtx, pStep->zTarget); + } + if( pStep->pFrom ){ int i; - for(i=0; i<pStep->pSrc->nSrc; i++){ - SrcItem *pItem = &pStep->pSrc->a[i]; + for(i=0; i<pStep->pFrom->nSrc; i++){ + SrcItem *pItem = &pStep->pFrom->a[i]; if( 0==sqlite3_stricmp(pItem->zName, zOld) ){ renameTokenFind(&sParse, &sCtx, pItem->zName); } @@ -2110,57 +2092,6 @@ static void renameTableTest( #endif } - -/* -** Return the number of bytes until the end of the next non-whitespace and -** non-comment token. For the purpose of this function, a "(" token includes -** all of the bytes through and including the matching ")", or until the -** first illegal token, whichever comes first. -** -** Write the token type into *piToken. -** -** The value returned is the number of bytes in the token itself plus -** the number of bytes of leading whitespace and comments skipped plus -** all bytes through the next matching ")" if the token is TK_LP. -** -** Example: (Note: '.' used in place of '*' in the example z[] text) -** -** ,--------- *piToken := TK_RP -** v -** z[] = " /.comment./ --comment\n (two three four) five" -** | | -** |<-------------------------------------->| -** | -** `--- return value -*/ -static int getConstraintToken(const u8 *z, int *piToken){ - int iOff = 0; - int t = 0; - do { - iOff += sqlite3GetToken(&z[iOff], &t); - }while( t==TK_SPACE || t==TK_COMMENT ); - - *piToken = t; - - if( t==TK_LP ){ - int nNest = 1; - while( nNest>0 ){ - iOff += sqlite3GetToken(&z[iOff], &t); - if( t==TK_LP ){ - nNest++; - }else if( t==TK_RP ){ - t = TK_LP; - nNest--; - }else if( t==TK_ILLEGAL ){ - break; - } - } - } - - *piToken = t; - return iOff; -} - /* ** The implementation of internal UDF sqlite_drop_column(). ** @@ -2205,24 +2136,15 @@ static void dropColumnFunc( goto drop_column_done; } + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); if( iCol<pTab->nCol-1 ){ RenameToken *pEnd; - pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zCnName); zEnd = (const char*)pEnd->t.z; }else{ - int eTok; assert( IsOrdinaryTable(pTab) ); - assert( iCol!=0 ); - /* Point pCol->t.z at the "," immediately preceding the definition of - ** the column being dropped. To do this, start at the name of the - ** previous column, and tokenize until the next ",". */ - pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol-1].zCnName); - do { - pCol->t.z += getConstraintToken((const u8*)pCol->t.z, &eTok); - }while( eTok!=TK_COMMA ); - pCol->t.z--; zEnd = (const char*)&zSql[pTab->u.tab.addColOffset]; + while( ALWAYS(pCol->t.z[0]!=0) && pCol->t.z[0]!=',' ) pCol->t.z--; } zNew = sqlite3MPrintf(db, "%.*s%s", pCol->t.z-zSql, zSql, zEnd); @@ -2391,651 +2313,6 @@ void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ sqlite3SrcListDelete(db, pSrc); } -/* -** Return the number of bytes of leading whitespace/comments in string z[]. -*/ -static int getWhitespace(const u8 *z){ - int nRet = 0; - while( 1 ){ - int t = 0; - int n = sqlite3GetToken(&z[nRet], &t); - if( t!=TK_SPACE && t!=TK_COMMENT ) break; - nRet += n; - } - return nRet; -} - - -/* -** Argument z points into the body of a constraint - specifically the -** second token of the constraint definition. For a named constraint, -** z points to the first token past the CONSTRAINT keyword. For an -** unnamed NOT NULL constraint, z points to the first byte past the NOT -** keyword. -** -** Return the number of bytes until the end of the constraint. -*/ -static int getConstraint(const u8 *z){ - int iOff = 0; - int t = 0; - - /* Now, the current constraint proceeds until the next occurence of one - ** of the following tokens: - ** - ** CONSTRAINT, PRIMARY, NOT, UNIQUE, CHECK, DEFAULT, - ** COLLATE, REFERENCES, FOREIGN, GENERATED, AS, RP, or COMMA - ** - ** Also exit the loop if ILLEGAL turns up. - */ - while( 1 ){ - int n = getConstraintToken(&z[iOff], &t); - if( t==TK_CONSTRAINT || t==TK_PRIMARY || t==TK_NOT || t==TK_UNIQUE - || t==TK_CHECK || t==TK_DEFAULT || t==TK_COLLATE || t==TK_REFERENCES - || t==TK_FOREIGN || t==TK_RP || t==TK_COMMA || t==TK_ILLEGAL - || t==TK_AS || t==TK_GENERATED - ){ - break; - } - iOff += n; - } - - return iOff; -} - -/* -** Compare two constraint names. -** -** Summary: *pRes := zQuote != zCmp -** -** Details: -** Compare the (possibly quoted) constraint name zQuote[0..nQuote-1] -** against zCmp[]. Write zero into *pRes if they are the same and -** non-zero if they differ. Normally return SQLITE_OK, except if there -** is an OOM, set the OOM error condition on ctx and return SQLITE_NOMEM. -*/ -static int quotedCompare( - sqlite3_context *ctx, /* Function context on which to report errors */ - int t, /* Token type */ - const u8 *zQuote, /* Possibly quoted text. Not zero-terminated. */ - int nQuote, /* Length of zQuote in bytes */ - const u8 *zCmp, /* Zero-terminated, unquoted name to compare against */ - int *pRes /* OUT: Set to 0 if equal, non-zero if unequal */ -){ - char *zCopy = 0; /* De-quoted, zero-terminated copy of zQuote[] */ - - if( t==TK_ILLEGAL ){ - *pRes = 1; - return SQLITE_OK; - } - zCopy = sqlite3MallocZero(nQuote+1); - if( zCopy==0 ){ - sqlite3_result_error_nomem(ctx); - return SQLITE_NOMEM_BKPT; - } - memcpy(zCopy, zQuote, nQuote); - sqlite3Dequote(zCopy); - *pRes = sqlite3_stricmp((const char*)zCopy, (const char*)zCmp); - sqlite3_free(zCopy); - return SQLITE_OK; -} - -/* -** zSql[] is a CREATE TABLE statement, supposedly. Find the offset -** into zSql[] of the first character past the first "(" and write -** that offset into *piOff and return SQLITE_OK. Or, if not found, -** set the SQLITE_CORRUPT error code and return SQLITE_ERROR. -*/ -static int skipCreateTable(sqlite3_context *ctx, const u8 *zSql, int *piOff){ - int iOff = 0; - - if( zSql==0 ) return SQLITE_ERROR; - - /* Jump past the "CREATE TABLE" bit. */ - while( 1 ){ - int t = 0; - iOff += sqlite3GetToken(&zSql[iOff], &t); - if( t==TK_LP ) break; - if( t==TK_ILLEGAL ){ - sqlite3_result_error_code(ctx, SQLITE_CORRUPT_BKPT); - return SQLITE_ERROR; - } - } - - *piOff = iOff; - return SQLITE_OK; -} - -/* -** Internal SQL function sqlite3_drop_constraint(): Given an input -** CREATE TABLE statement, return a revised CREATE TABLE statement -** with a constraint removed. Two forms, depending on the datatype -** of argv[2]: -** -** sqlite_drop_constraint(SQL, INT) -- Omit NOT NULL from the INT-th column -** sqlite_drop_constraint(SQL, TEXT) -- OMIT constraint with name TEXT -** -** In the first case, the left-most column is 0. -*/ -static void dropConstraintFunc( - sqlite3_context *ctx, - int NotUsed, - sqlite3_value **argv -){ - const u8 *zSql = sqlite3_value_text(argv[0]); - const u8 *zCons = 0; - int iNotNull = -1; - int ii; - int iOff = 0; - int iStart = 0; - int iEnd = 0; - char *zNew = 0; - int t = 0; - sqlite3 *db; - UNUSED_PARAMETER(NotUsed); - - if( zSql==0 ) return; - - /* Jump past the "CREATE TABLE" bit. */ - if( skipCreateTable(ctx, zSql, &iOff) ) return; - - if( sqlite3_value_type(argv[1])==SQLITE_INTEGER ){ - iNotNull = sqlite3_value_int(argv[1]); - }else{ - zCons = sqlite3_value_text(argv[1]); - } - - /* Search for the named constraint within column definitions. */ - for(ii=0; iEnd==0; ii++){ - - /* Now parse the column or table constraint definition. Search - ** for the token CONSTRAINT if this is a DROP CONSTRAINT command, or - ** NOT in the right column if this is a DROP NOT NULL. */ - while( 1 ){ - iStart = iOff; - iOff += getConstraintToken(&zSql[iOff], &t); - if( t==TK_CONSTRAINT && (zCons || iNotNull==ii) ){ - /* Check if this is the constraint we are searching for. */ - int nTok = 0; - int cmp = 1; - - /* Skip past any whitespace. */ - iOff += getWhitespace(&zSql[iOff]); - - /* Compare the next token - which may be quoted - with the name of - ** the constraint being dropped. */ - nTok = getConstraintToken(&zSql[iOff], &t); - if( zCons ){ - if( quotedCompare(ctx, t, &zSql[iOff], nTok, zCons, &cmp) ) return; - } - iOff += nTok; - - /* The next token is usually the first token of the constraint - ** definition. This is enough to tell the type of the constraint - - ** TK_NOT means it is a NOT NULL, TK_CHECK a CHECK constraint etc. - ** - ** There is also the chance that the next token is TK_CONSTRAINT - ** (or TK_DEFAULT or TK_COLLATE), for example if a table has been - ** created as follows: - ** - ** CREATE TABLE t1(cols, CONSTRAINT one CONSTRAINT two NOT NULL); - ** - ** In this case, allow the "CONSTRAINT one" bit to be dropped by - ** this command if that is what is requested, or to advance to - ** the next iteration of the loop with &zSql[iOff] still pointing - ** to the CONSTRAINT keyword. */ - nTok = getConstraintToken(&zSql[iOff], &t); - if( t==TK_CONSTRAINT || t==TK_DEFAULT || t==TK_COLLATE - || t==TK_COMMA || t==TK_RP || t==TK_GENERATED || t==TK_AS - ){ - t = TK_CHECK; - }else{ - iOff += nTok; - iOff += getConstraint(&zSql[iOff]); - } - - if( cmp==0 || (iNotNull>=0 && t==TK_NOT) ){ - if( t!=TK_NOT && t!=TK_CHECK ){ - errorMPrintf(ctx, "constraint may not be dropped: %s", zCons); - return; - } - iEnd = iOff; - break; - } - - }else if( t==TK_NOT && iNotNull==ii ){ - iEnd = iOff + getConstraint(&zSql[iOff]); - break; - }else if( t==TK_RP || t==TK_ILLEGAL ){ - iEnd = -1; - break; - }else if( t==TK_COMMA ){ - break; - } - } - } - - /* If the constraint has not been found it is an error. */ - if( iEnd<=0 ){ - if( zCons ){ - errorMPrintf(ctx, "no such constraint: %s", zCons); - }else{ - /* SQLite follows postgres in that a DROP NOT NULL on a column that is - ** not NOT NULL is not an error. So just return the original SQL here. */ - sqlite3_result_text(ctx, (const char*)zSql, -1, SQLITE_TRANSIENT); - } - }else{ - - /* Figure out if an extra space should be inserted after the constraint - ** is removed. And if an additional comma preceding the constraint - ** should be removed. */ - const char *zSpace = " "; - iEnd += getWhitespace(&zSql[iEnd]); - sqlite3GetToken(&zSql[iEnd], &t); - if( t==TK_RP || t==TK_COMMA ){ - zSpace = ""; - if( zSql[iStart-1]==',' ) iStart--; - } - - db = sqlite3_context_db_handle(ctx); - zNew = sqlite3MPrintf(db, "%.*s%s%s", iStart, zSql, zSpace, &zSql[iEnd]); - sqlite3_result_text(ctx, zNew, -1, SQLITE_DYNAMIC); - } -} - -/* -** Internal SQL function: -** -** sqlite_add_constraint(SQL, CONSTRAINT-TEXT, ICOL) -** -** SQL is a CREATE TABLE statement. Return a modified version of -** SQL that adds CONSTRAINT-TEXT at the end of the ICOL-th column -** definition. (The left-most column defintion is 0.) -*/ -static void addConstraintFunc( - sqlite3_context *ctx, - int NotUsed, - sqlite3_value **argv -){ - const u8 *zSql = sqlite3_value_text(argv[0]); - const char *zCons = (const char*)sqlite3_value_text(argv[1]); - int iCol = sqlite3_value_int(argv[2]); - int iOff = 0; - int ii; - char *zNew = 0; - int t = 0; - sqlite3 *db; - UNUSED_PARAMETER(NotUsed); - - if( skipCreateTable(ctx, zSql, &iOff) ) return; - - for(ii=0; ii<=iCol || (iCol<0 && t!=TK_RP); ii++){ - iOff += getConstraintToken(&zSql[iOff], &t); - while( 1 ){ - int nTok = getConstraintToken(&zSql[iOff], &t); - if( t==TK_COMMA || t==TK_RP ) break; - if( t==TK_ILLEGAL ){ - sqlite3_result_error_code(ctx, SQLITE_CORRUPT_BKPT); - return; - } - iOff += nTok; - } - } - - iOff += getWhitespace(&zSql[iOff]); - - db = sqlite3_context_db_handle(ctx); - if( iCol<0 ){ - zNew = sqlite3MPrintf(db, "%.*s, %s%s", iOff, zSql, zCons, &zSql[iOff]); - }else{ - zNew = sqlite3MPrintf(db, "%.*s %s%s", iOff, zSql, zCons, &zSql[iOff]); - } - sqlite3_result_text(ctx, zNew, -1, SQLITE_DYNAMIC); -} - -/* -** Find a column named pCol in table pTab. If successful, set output -** parameter *piCol to the index of the column in the table and return -** SQLITE_OK. Otherwise, set *piCol to -1 and return an SQLite error -** code. -*/ -static int alterFindCol(Parse *pParse, Table *pTab, Token *pCol, int *piCol){ - sqlite3 *db = pParse->db; - char *zName = sqlite3NameFromToken(db, pCol); - int rc = SQLITE_NOMEM; - int iCol = -1; - - if( zName ){ - iCol = sqlite3ColumnIndex(pTab, zName); - if( iCol<0 ){ - sqlite3ErrorMsg(pParse, "no such column: %s", zName); - rc = SQLITE_ERROR; - }else{ - rc = SQLITE_OK; - } - } - -#ifndef SQLITE_OMIT_AUTHORIZATION - if( rc==SQLITE_OK ){ - const char *zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; - const char *zCol = pTab->aCol[iCol].zCnName; - if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, zCol) ){ - pTab = 0; - } - } -#endif - - sqlite3DbFree(db, zName); - *piCol = iCol; - return rc; -} - - -/* -** Find the table named by the first entry in source list pSrc. If successful, -** return a pointer to the Table structure and set output variable (*pzDb) -** to point to the name of the database containin the table (i.e. "main", -** "temp" or the name of an attached database). -** -** If the table cannot be located, return NULL. The value of the two output -** parameters is undefined in this case. -*/ -static Table *alterFindTable( - Parse *pParse, /* Parsing context */ - SrcList *pSrc, /* Name of the table to look for */ - int *piDb, /* OUT: write the iDb here */ - const char **pzDb, /* OUT: write name of schema here */ - int bAuth /* Do ALTER TABLE authorization checks if true */ -){ - sqlite3 *db = pParse->db; - Table *pTab = 0; - assert( sqlite3BtreeHoldsAllMutexes(db) ); - pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); - if( pTab ){ - int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - *pzDb = db->aDb[iDb].zDbSName; - *piDb = iDb; - - if( SQLITE_OK!=isRealTable(pParse, pTab, 2) - || SQLITE_OK!=isAlterableTable(pParse, pTab) - ){ - pTab = 0; - } - } -#ifndef SQLITE_OMIT_AUTHORIZATION - if( pTab && bAuth ){ - if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, *pzDb, pTab->zName, 0) ){ - pTab = 0; - } - } -#endif - sqlite3SrcListDelete(db, pSrc); - return pTab; -} - -/* -** Generate bytecode for one of: -** -** (1) ALTER TABLE pSrc DROP CONSTRAINT pCons -** (2) ALTER TABLE pSrc ALTER pCol DROP NOT NULL -** -** One of pCons and pCol must be NULL and the other non-null. -*/ -void sqlite3AlterDropConstraint( - Parse *pParse, /* Parsing context */ - SrcList *pSrc, /* The table being altered */ - Token *pCons, /* Name of the constraint to drop */ - Token *pCol /* Name of the column from which to remove the NOT NULL */ -){ - sqlite3 *db = pParse->db; - Table *pTab = 0; - int iDb = 0; - const char *zDb = 0; - char *zArg = 0; - - assert( (pCol==0)!=(pCons==0) ); - assert( pSrc->nSrc==1 ); - pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, pCons!=0); - if( !pTab ) return; - - if( pCons ){ - zArg = sqlite3MPrintf(db, "%.*Q", pCons->n, pCons->z); - }else{ - int iCol; - if( alterFindCol(pParse, pTab, pCol, &iCol) ) return; - zArg = sqlite3MPrintf(db, "%d", iCol); - } - - /* Edit the SQL for the named table. */ - sqlite3NestedParse(pParse, - "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " - "sql = sqlite_drop_constraint(sql, %s) " - "WHERE type='table' AND tbl_name=%Q COLLATE nocase" - , zDb, zArg, pTab->zName - ); - sqlite3DbFree(db, zArg); - - /* Finally, reload the database schema. */ - renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); -} - -/* -** The implementation of SQL function sqlite_fail(MSG). This takes a single -** argument, and returns it as an error message with the error code set to -** SQLITE_CONSTRAINT. -*/ -static void failConstraintFunc( - sqlite3_context *ctx, - int NotUsed, - sqlite3_value **argv -){ - const char *zText = (const char*)sqlite3_value_text(argv[0]); - int err = sqlite3_value_int(argv[1]); - (void)NotUsed; - sqlite3_result_error(ctx, zText, -1); - sqlite3_result_error_code(ctx, err); -} - -/* -** Buffer pCons, which is nCons bytes in size, contains the text of a -** NOT NULL or CHECK constraint that will be inserted into a CREATE TABLE -** statement. If successful, this function returns the size of the buffer in -** bytes not including any trailing whitespace or "--" style comments. Or, -** if an OOM occurs, it returns 0 and sets db->mallocFailed to true. -** -** C-style comments at the end are preserved. "--" style comments are -** removed because the comment terminator might be \000, and we are about -** to insert the pCons[] text into the middle of a larger string, and that -** will have the effect of removing the comment terminator and messing up -** the syntax. -*/ -static int alterRtrimConstraint( - sqlite3 *db, /* used to record OOM error */ - const char *pCons, /* Buffer containing constraint */ - int nCons /* Size of pCons in bytes */ -){ - u8 *zTmp = (u8*)sqlite3MPrintf(db, "%.*s", nCons, pCons); - int iOff = 0; - int iEnd = 0; - - if( zTmp==0 ) return 0; - - while( 1 ){ - int t = 0; - int nToken = sqlite3GetToken(&zTmp[iOff], &t); - if( t==TK_ILLEGAL ) break; - if( t!=TK_SPACE && (t!=TK_COMMENT || zTmp[iOff]!='-') ){ - iEnd = iOff+nToken; - } - iOff += nToken; - } - - sqlite3DbFree(db, zTmp); - return iEnd; -} - -/* -** Prepare a statement of the form: -** -** ALTER TABLE pSrc ALTER pCol SET NOT NULL -*/ -void sqlite3AlterSetNotNull( - Parse *pParse, /* Parsing context */ - SrcList *pSrc, /* Name of the table being altered */ - Token *pCol, /* Name of the column to add a NOT NULL constraint to */ - Token *pFirst /* The NOT token of the NOT NULL constraint text */ -){ - Table *pTab = 0; - int iCol = 0; - int iDb = 0; - const char *zDb = 0; - const char *pCons = 0; - int nCons = 0; - - /* Look up the table being altered. */ - assert( pSrc->nSrc==1 ); - pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 0); - if( !pTab ) return; - - /* Find the column being altered. */ - if( alterFindCol(pParse, pTab, pCol, &iCol) ){ - return; - } - - /* Find the length in bytes of the constraint definition */ - pCons = pFirst->z; - nCons = alterRtrimConstraint(pParse->db, pCons, pParse->sLastToken.z - pCons); - - /* Search for a constraint violation. Throw an exception if one is found. */ - sqlite3NestedParse(pParse, - "SELECT sqlite_fail('constraint failed', %d) " - "FROM %Q.%Q AS x WHERE x.%.*s IS NULL", - SQLITE_CONSTRAINT, zDb, pTab->zName, (int)pCol->n, pCol->z - ); - - /* Edit the SQL for the named table. */ - sqlite3NestedParse(pParse, - "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " - "sql = sqlite_add_constraint(sqlite_drop_constraint(sql, %d), %.*Q, %d) " - "WHERE type='table' AND tbl_name=%Q COLLATE nocase" - , zDb, iCol, nCons, pCons, iCol, pTab->zName - ); - - /* Finally, reload the database schema. */ - renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); -} - -/* -** Implementation of internal SQL function: -** -** sqlite_find_constraint(SQL, CONSTRAINT-NAME) -** -** This function returns true if the SQL passed as the first argument is a -** CREATE TABLE that contains a constraint with the name CONSTRAINT-NAME, -** or false otherwise. -*/ -static void findConstraintFunc( - sqlite3_context *ctx, - int NotUsed, - sqlite3_value **argv -){ - const u8 *zSql = 0; - const u8 *zCons = 0; - int iOff = 0; - int t = 0; - - (void)NotUsed; - zSql = sqlite3_value_text(argv[0]); - zCons = sqlite3_value_text(argv[1]); - - if( zSql==0 || zCons==0 ) return; - while( t!=TK_LP && t!=TK_ILLEGAL ){ - iOff += sqlite3GetToken(&zSql[iOff], &t); - } - - while( 1 ){ - iOff += getConstraintToken(&zSql[iOff], &t); - if( t==TK_CONSTRAINT ){ - int nTok = 0; - int cmp = 0; - iOff += getWhitespace(&zSql[iOff]); - nTok = getConstraintToken(&zSql[iOff], &t); - if( quotedCompare(ctx, t, &zSql[iOff], nTok, zCons, &cmp) ) return; - if( cmp==0 ){ - sqlite3_result_int(ctx, 1); - return; - } - }else if( t==TK_ILLEGAL ){ - break; - } - } - - sqlite3_result_int(ctx, 0); -} - -/* -** Generate bytecode to implement: -** -** ALTER TABLE pSrc ADD [CONSTRAINT pName] CHECK(pExpr) -** -** Any "ON CONFLICT" text that occurs after the "CHECK(...)", up -** until pParse->sLastToken, is included as part of the new constraint. -*/ -void sqlite3AlterAddConstraint( - Parse *pParse, /* Parse context */ - SrcList *pSrc, /* Table to add constraint to */ - Token *pFirst, /* First token of new constraint */ - Token *pName, /* Name of new constraint. NULL if name omitted. */ - const char *pExpr, /* Text of CHECK expression */ - int nExpr /* Size of pExpr in bytes */ -){ - Table *pTab = 0; /* Table identified by pSrc */ - int iDb = 0; /* Which schema does pTab live in */ - const char *zDb = 0; /* Name of the schema in which pTab lives */ - const char *pCons = 0; /* Text of the constraint */ - int nCons; /* Bytes of text to use from pCons[] */ - - /* Look up the table being altered. */ - assert( pSrc->nSrc==1 ); - pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 1); - if( !pTab ) return; - - /* If this new constraint has a name, check that it is not a duplicate of - ** an existing constraint. It is an error if it is. */ - if( pName ){ - char *zName = sqlite3NameFromToken(pParse->db, pName); - - sqlite3NestedParse(pParse, - "SELECT sqlite_fail('constraint %q already exists', %d) " - "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " - "WHERE type='table' AND tbl_name=%Q COLLATE nocase " - "AND sqlite_find_constraint(sql, %Q)", - zName, SQLITE_ERROR, zDb, pTab->zName, zName - ); - sqlite3DbFree(pParse->db, zName); - } - - /* Search for a constraint violation. Throw an exception if one is found. */ - sqlite3NestedParse(pParse, - "SELECT sqlite_fail('constraint failed', %d) " - "FROM %Q.%Q WHERE (%.*s) IS NOT TRUE", - SQLITE_CONSTRAINT, zDb, pTab->zName, nExpr, pExpr - ); - - /* Edit the SQL for the named table. */ - pCons = pFirst->z; - nCons = alterRtrimConstraint(pParse->db, pCons, pParse->sLastToken.z - pCons); - - sqlite3NestedParse(pParse, - "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " - "sql = sqlite_add_constraint(sql, %.*Q, -1) " - "WHERE type='table' AND tbl_name=%Q COLLATE nocase" - , zDb, nCons, pCons, pTab->zName - ); - - /* Finally, reload the database schema. */ - renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); -} - /* ** Register built-in functions used to help implement ALTER TABLE */ @@ -3046,10 +2323,6 @@ void sqlite3AlterFunctions(void){ INTERNAL_FUNCTION(sqlite_rename_test, 7, renameTableTest), INTERNAL_FUNCTION(sqlite_drop_column, 3, dropColumnFunc), INTERNAL_FUNCTION(sqlite_rename_quotefix,2, renameQuotefixFunc), - INTERNAL_FUNCTION(sqlite_drop_constraint,2, dropConstraintFunc), - INTERNAL_FUNCTION(sqlite_fail, 2, failConstraintFunc), - INTERNAL_FUNCTION(sqlite_add_constraint, 3, addConstraintFunc), - INTERNAL_FUNCTION(sqlite_find_constraint,2, findConstraintFunc), }; sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); } diff --git a/src/attach.c b/src/attach.c index f27c1e6be..085e1b0ec 100644 --- a/src/attach.c +++ b/src/attach.c @@ -596,7 +596,7 @@ int sqlite3FixTriggerStep( if( sqlite3WalkSelect(&pFix->w, pStep->pSelect) || sqlite3WalkExpr(&pFix->w, pStep->pWhere) || sqlite3WalkExprList(&pFix->w, pStep->pExprList) - || sqlite3FixSrcList(pFix, pStep->pSrc) + || sqlite3FixSrcList(pFix, pStep->pFrom) ){ return 1; } diff --git a/src/auth.c b/src/auth.c index 1088f844a..9ec2e7d04 100644 --- a/src/auth.c +++ b/src/auth.c @@ -78,7 +78,7 @@ int sqlite3_set_authorizer( sqlite3_mutex_enter(db->mutex); db->xAuth = (sqlite3_xauth)xAuth; db->pAuthArg = pArg; - sqlite3ExpirePreparedStatements(db, 1); + if( db->xAuth ) sqlite3ExpirePreparedStatements(db, 1); sqlite3_mutex_leave(db->mutex); return SQLITE_OK; } diff --git a/src/btree.c b/src/btree.c index 7e73c7fd7..8d0f92222 100644 --- a/src/btree.c +++ b/src/btree.c @@ -3673,30 +3673,6 @@ static SQLITE_NOINLINE int btreeBeginTrans( } #endif -#ifdef SQLITE_EXPERIMENTAL_PRAGMA_20251114 - /* If both a read and write transaction will be opened by this call, - ** then issue a file-control as if the following pragma command had - ** been evaluated: - ** - ** PRAGMA experimental_pragma_20251114 = 1|2 - ** - ** where the RHS is "1" if wrflag is 1 (RESERVED lock), or "2" if wrflag - ** is 2 (EXCLUSIVE lock). Ignore any result or error returned by the VFS. - ** - ** WARNING: This code will likely remain part of SQLite only temporarily - - ** it exists to allow users to experiment with certain types of blocking - ** locks in custom VFS implementations. It MAY BE REMOVED AT ANY TIME. */ - if( pBt->pPage1==0 && wrflag ){ - sqlite3_file *fd = sqlite3PagerFile(pPager); - char *aFcntl[3] = {0,0,0}; - aFcntl[1] = "experimental_pragma_20251114"; - assert( wrflag==1 || wrflag==2 ); - aFcntl[2] = (wrflag==1 ? "1" : "2"); - sqlite3OsFileControlHint(fd, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); - sqlite3_free(aFcntl[0]); - } -#endif - /* Call lockBtree() until either pBt->pPage1 is populated or ** lockBtree() returns something other than SQLITE_OK. lockBtree() ** may return SQLITE_OK but leave pBt->pPage1 set to 0 if after @@ -5701,7 +5677,7 @@ int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes){ assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - if( NEVER(pCur->eState==CURSOR_VALID) ){ + if( pCur->eState==CURSOR_VALID ){ *pRes = 0; return SQLITE_OK; } @@ -9782,7 +9758,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ }while( rc==SQLITE_OK && nOut>0 ); if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ - Pgno pgnoNew = 0; /* Prevent harmless static-analyzer warning */ + Pgno pgnoNew; MemPage *pNew = 0; rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); put4byte(pPgnoOut, pgnoNew); diff --git a/src/build.c b/src/build.c index c45194145..de890c2e9 100644 --- a/src/build.c +++ b/src/build.c @@ -486,7 +486,6 @@ Table *sqlite3LocateTableItem( const char *zDb; if( p->fg.fixedSchema ){ int iDb = sqlite3SchemaToIndex(pParse->db, p->u4.pSchema); - assert( iDb>=0 && iDb<pParse->db->nDb ); zDb = pParse->db->aDb[iDb].zDbSName; }else{ assert( !p->fg.isSubquery ); @@ -2060,8 +2059,8 @@ void sqlite3ChangeCookie(Parse *pParse, int iDb){ ** The estimate is conservative. It might be larger that what is ** really needed. */ -static i64 identLength(const char *z){ - i64 n; +static int identLength(const char *z){ + int n; for(n=0; *z; n++, z++){ if( *z=='"' ){ n++; } } @@ -2571,14 +2570,13 @@ void sqlite3MarkAllShadowTablesOf(sqlite3 *db, Table *pTab){ ** restored to its original value prior to this routine returning. */ int sqlite3ShadowTableName(sqlite3 *db, const char *zName){ - const char *zTail; /* Pointer to the last "_" in zName */ + char *zTail; /* Pointer to the last "_" in zName */ Table *pTab; /* Table that zName is a shadow of */ - char *zCopy; zTail = strrchr(zName, '_'); if( zTail==0 ) return 0; - zCopy = sqlite3DbStrNDup(db, zName, (int)(zTail-zName)); - pTab = zCopy ? sqlite3FindTable(db, zCopy, 0) : 0; - sqlite3DbFree(db, zCopy); + *zTail = 0; + pTab = sqlite3FindTable(db, zName, 0); + *zTail = '_'; if( pTab==0 ) return 0; if( !IsVirtual(pTab) ) return 0; return sqlite3IsShadowTableOf(db, pTab, zName); @@ -2731,7 +2729,6 @@ void sqlite3EndTable( convertToWithoutRowidTable(pParse, p); } iDb = sqlite3SchemaToIndex(db, p->pSchema); - assert( iDb>=0 && iDb<=db->nDb ); #ifndef SQLITE_OMIT_CHECK /* Resolve names in all CHECK constraint expressions. @@ -3027,7 +3024,6 @@ void sqlite3CreateView( sqlite3TwoPartName(pParse, pName1, pName2, &pName); iDb = sqlite3SchemaToIndex(db, p->pSchema); - assert( iDb>=0 && iDb<db->nDb ); sqlite3FixInit(&sFix, pParse, iDb, "view", pName); if( sqlite3FixSelect(&sFix, pSelect) ) goto create_view_fail; @@ -4624,7 +4620,6 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ goto exit_drop_index; } iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); - assert( iDb>=0 && iDb<db->nDb ); #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_DROP_INDEX; diff --git a/src/carray.c b/src/carray.c index ff0691a85..154d107dd 100644 --- a/src/carray.c +++ b/src/carray.c @@ -79,7 +79,6 @@ struct carray_bind { int nData; /* Number of elements */ int mFlags; /* Control flags */ void (*xDel)(void*); /* Destructor for aData */ - void *pDel; /* Alternative argument to xDel() */ }; @@ -412,7 +411,7 @@ static sqlite3_module carrayModule = { static void carrayBindDel(void *pPtr){ carray_bind *p = (carray_bind*)pPtr; if( p->xDel!=SQLITE_STATIC ){ - p->xDel(p->pDel); + p->xDel(p->aData); } sqlite3_free(p); } @@ -420,26 +419,14 @@ static void carrayBindDel(void *pPtr){ /* ** Invoke this interface in order to bind to the single-argument ** version of CARRAY(). -** -** pStmt The prepared statement to which to bind -** idx The index of the parameter of pStmt to which to bind -** aData The data to be bound -** nData The number of elements in aData -** mFlags One of SQLITE_CARRAY_xxxx indicating datatype of aData -** xDestroy Destructor for pDestroy or aData if pDestroy==NULL. -** pDestroy Invoke xDestroy on this pointer if not NULL -** -** The destructor is called pDestroy if pDestroy!=NULL, or against -** aData if pDestroy==NULL. */ -SQLITE_API int sqlite3_carray_bind_v2( +SQLITE_API int sqlite3_carray_bind( sqlite3_stmt *pStmt, int idx, void *aData, int nData, int mFlags, - void (*xDestroy)(void*), - void *pDestroy + void (*xDestroy)(void*) ){ carray_bind *pNew = 0; int i; @@ -516,38 +503,20 @@ SQLITE_API int sqlite3_carray_bind_v2( memcpy(pNew->aData, aData, sz); } pNew->xDel = sqlite3_free; - pNew->pDel = pNew->aData; }else{ pNew->aData = aData; pNew->xDel = xDestroy; - pNew->pDel = pDestroy; } return sqlite3_bind_pointer(pStmt, idx, pNew, "carray-bind", carrayBindDel); carray_bind_error: if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){ - xDestroy(pDestroy); + xDestroy(aData); } sqlite3_free(pNew); return rc; } -/* -** Invoke this interface in order to bind to the single-argument -** version of CARRAY(). Same as sqlite3_carray_bind_v2() with the -** pDestroy parameter set to NULL. -*/ -SQLITE_API int sqlite3_carray_bind( - sqlite3_stmt *pStmt, - int idx, - void *aData, - int nData, - int mFlags, - void (*xDestroy)(void*) -){ - return sqlite3_carray_bind_v2(pStmt,idx,aData,nData,mFlags,xDestroy,aData); -} - /* ** Invoke this routine to register the carray() function. */ diff --git a/src/date.c b/src/date.c index 58a7ce544..5e7ae6f1f 100644 --- a/src/date.c +++ b/src/date.c @@ -429,7 +429,7 @@ static int parseDateOrTime( return 0; }else if( sqlite3StrICmp(zDate,"now")==0 && sqlite3NotPureFunc(context) ){ return setDateTimeToCurrent(context, p); - }else if( sqlite3AtoF(zDate, &r)>0 ){ + }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8)>0 ){ setRawDateNumber(p, r); return 0; }else if( (sqlite3StrICmp(zDate,"subsec")==0 @@ -875,7 +875,7 @@ static int parseModifier( ** date is already on the appropriate weekday, this is a no-op. */ if( sqlite3_strnicmp(z, "weekday ", 8)==0 - && sqlite3AtoF(&z[8], &r)>0 + && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8)>0 && r>=0.0 && r<7.0 && (n=(int)r)==r ){ sqlite3_int64 Z; computeYMD_HMS(p); @@ -946,11 +946,9 @@ static int parseModifier( case '8': case '9': { double rRounder; - int i, rx; + int i; int Y,M,D,h,m,x; const char *z2 = z; - char *zCopy; - sqlite3 *db = sqlite3_context_db_handle(pCtx); char z0 = z[0]; for(n=1; z[n]; n++){ if( z[n]==':' ) break; @@ -960,11 +958,7 @@ static int parseModifier( if( n==6 && getDigits(&z[1], "50f", &Y)==1 ) break; } } - zCopy = sqlite3DbStrNDup(db, z, n); - if( zCopy==0 ) break; - rx = sqlite3AtoF(zCopy, &r)<=0; - sqlite3DbFree(db, zCopy); - if( rx ){ + if( sqlite3AtoF(z, &r, n, SQLITE_UTF8)<=0 ){ assert( rc==1 ); break; } @@ -1784,7 +1778,7 @@ static void datedebugFunc( char *zJson; zJson = sqlite3_mprintf( "{iJD:%lld,Y:%d,M:%d,D:%d,h:%d,m:%d,tz:%d," - "s:%.3f,validJD:%d,validYMD:%d,validHMS:%d," + "s:%.3f,validJD:%d,validYMS:%d,validHMS:%d," "nFloor:%d,rawS:%d,isError:%d,useSubsec:%d," "isUtc:%d,isLocal:%d}", x.iJD, x.Y, x.M, x.D, x.h, x.m, x.tz, diff --git a/src/delete.c b/src/delete.c index d4d2337c7..8fac7c2f3 100644 --- a/src/delete.c +++ b/src/delete.c @@ -86,7 +86,7 @@ static int vtabIsReadOnly(Parse *pParse, Table *pTab){ ** * Only allow DELETE, INSERT, or UPDATE of non-SQLITE_VTAB_INNOCUOUS ** virtual tables if PRAGMA trusted_schema=ON. */ - if( (pParse->pToplevel!=0 || (pParse->prepFlags & SQLITE_PREPARE_FROM_DDL)) + if( pParse->pToplevel!=0 && pTab->u.vtab.p->eVtabRisk > ((pParse->db->flags & SQLITE_TrustedSchema)!=0) ){ @@ -924,6 +924,7 @@ void sqlite3GenerateRowIndexDelete( &iPartIdxLabel, pPrior, r1); sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1, pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn); + sqlite3VdbeChangeP5(v, 1); /* Cause IdxDelete to error if no entry found */ sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); pPrior = pIdx; } diff --git a/src/expr.c b/src/expr.c index d486e48e3..fdc05366c 100644 --- a/src/expr.c +++ b/src/expr.c @@ -935,22 +935,34 @@ Expr *sqlite3ExprAlloc( int dequote /* True to dequote */ ){ Expr *pNew; - int nExtra = pToken ? pToken->n+1 : 0; + int nExtra = 0; + int iValue = 0; assert( db!=0 ); + if( pToken ){ + if( op!=TK_INTEGER || pToken->z==0 + || sqlite3GetInt32(pToken->z, &iValue)==0 ){ + nExtra = pToken->n+1; /* tag-20240227-a */ + assert( iValue>=0 ); + } + } pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)+nExtra); if( pNew ){ memset(pNew, 0, sizeof(Expr)); pNew->op = (u8)op; pNew->iAgg = -1; - if( nExtra ){ - assert( pToken!=0 ); - pNew->u.zToken = (char*)&pNew[1]; - assert( pToken->z!=0 || pToken->n==0 ); - if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); - pNew->u.zToken[pToken->n] = 0; - if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ - sqlite3DequoteExpr(pNew); + if( pToken ){ + if( nExtra==0 ){ + pNew->flags |= EP_IntValue|EP_Leaf|(iValue?EP_IsTrue:EP_IsFalse); + pNew->u.iValue = iValue; + }else{ + pNew->u.zToken = (char*)&pNew[1]; + assert( pToken->z!=0 || pToken->n==0 ); + if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); + pNew->u.zToken[pToken->n] = 0; + if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ + sqlite3DequoteExpr(pNew); + } } } #if SQLITE_MAX_EXPR_DEPTH>0 @@ -975,24 +987,6 @@ Expr *sqlite3Expr( return sqlite3ExprAlloc(db, op, &x, 0); } -/* -** Allocate an expression for a 32-bit signed integer literal. -*/ -Expr *sqlite3ExprInt32(sqlite3 *db, int iVal){ - Expr *pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)); - if( pNew ){ - memset(pNew, 0, sizeof(Expr)); - pNew->op = TK_INTEGER; - pNew->iAgg = -1; - pNew->flags = EP_IntValue|EP_Leaf|(iVal?EP_IsTrue:EP_IsFalse); - pNew->u.iValue = iVal; -#if SQLITE_MAX_EXPR_DEPTH>0 - pNew->nHeight = 1; -#endif - } - return pNew; -} - /* ** Attach subtrees pLeft and pRight to the Expr node pRoot. ** @@ -1155,7 +1149,7 @@ Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ ){ sqlite3ExprDeferredDelete(pParse, pLeft); sqlite3ExprDeferredDelete(pParse, pRight); - return sqlite3ExprInt32(db, 0); + return sqlite3Expr(db, TK_INTEGER, "0"); }else{ return sqlite3PExpr(pParse, TK_AND, pLeft, pRight); } @@ -1280,9 +1274,7 @@ void sqlite3ExprFunctionUsable( ){ assert( !IN_RENAME_OBJECT ); assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 ); - if( ExprHasProperty(pExpr, EP_FromDDL) - || pParse->prepFlags & SQLITE_PREPARE_FROM_DDL - ){ + if( ExprHasProperty(pExpr, EP_FromDDL) ){ if( (pDef->funcFlags & SQLITE_FUNC_DIRECT)!=0 || (pParse->db->flags & SQLITE_TrustedSchema)==0 ){ @@ -1978,7 +1970,9 @@ Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int flags){ pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags); pNew->iLimit = 0; pNew->iOffset = 0; - pNew->selFlags = p->selFlags; + pNew->selFlags = p->selFlags & ~(u32)SF_UsesEphemeral; + pNew->addrOpenEphm[0] = -1; + pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = p->nSelectRow; pNew->pWith = sqlite3WithDup(db, p->pWith); #ifndef SQLITE_OMIT_WINDOWFUNC @@ -2630,7 +2624,7 @@ static int exprIsConst(Parse *pParse, Expr *p, int initFlag){ /* ** Walk an expression tree. Return non-zero if the expression is constant -** or return zero if the expression involves variables or function calls. +** and 0 if it involves variables or function calls. ** ** For the purposes of this function, a double-quoted string (ex: "abc") ** is considered a variable but a single-quoted string (ex: 'abc') is @@ -3420,7 +3414,6 @@ int sqlite3FindInIndex( */ u32 savedNQueryLoop = pParse->nQueryLoop; int rMayHaveNull = 0; - int bloomOk = (inFlags & IN_INDEX_MEMBERSHIP)!=0; eType = IN_INDEX_EPH; if( inFlags & IN_INDEX_LOOP ){ pParse->nQueryLoop = 0; @@ -3428,13 +3421,7 @@ int sqlite3FindInIndex( *prRhsHasNull = rMayHaveNull = ++pParse->nMem; } assert( pX->op==TK_IN ); - if( !bloomOk - && ExprUseXSelect(pX) - && (pX->x.pSelect->selFlags & SF_ClonedRhsIn)!=0 - ){ - bloomOk = 1; - } - sqlite3CodeRhsOfIN(pParse, pX, iTab, bloomOk); + sqlite3CodeRhsOfIN(pParse, pX, iTab); if( rMayHaveNull ){ sqlite3SetHasNullFlag(v, iTab, rMayHaveNull); } @@ -3592,8 +3579,7 @@ static int findCompatibleInRhsSubrtn( void sqlite3CodeRhsOfIN( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The IN operator */ - int iTab, /* Use this cursor number */ - int allowBloom /* True to allow the use of a Bloom filter */ + int iTab /* Use this cursor number */ ){ int addrOnce = 0; /* Address of the OP_Once instruction at top */ int addr; /* Address of OP_OpenEphemeral instruction */ @@ -3715,10 +3701,7 @@ void sqlite3CodeRhsOfIN( sqlite3SelectDestInit(&dest, SRT_Set, iTab); dest.zAffSdst = exprINAffinity(pParse, pExpr); pSelect->iLimit = 0; - if( addrOnce - && allowBloom - && OptimizationEnabled(pParse->db, SQLITE_BloomFilter) - ){ + if( addrOnce && OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ){ int regBloom = ++pParse->nMem; addrBloom = sqlite3VdbeAddOp2(v, OP_Blob, 10000, regBloom); VdbeComment((v, "Bloom filter")); @@ -3939,7 +3922,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ || (pLeft->u.iValue!=1 && pLeft->u.iValue!=0) ){ sqlite3 *db = pParse->db; - pLimit = sqlite3ExprInt32(db, 0); + pLimit = sqlite3Expr(db, TK_INTEGER, "0"); if( pLimit ){ pLimit->affExpr = SQLITE_AFF_NUMERIC; pLimit = sqlite3PExpr(pParse, TK_NE, @@ -3950,7 +3933,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ } }else{ /* If there is no pre-existing limit add a limit of 1 */ - pLimit = sqlite3ExprInt32(pParse->db, 1); + pLimit = sqlite3Expr(pParse->db, TK_INTEGER, "1"); pSel->pLimit = sqlite3PExpr(pParse, TK_LIMIT, pLimit, 0); } pSel->iLimit = 0; @@ -4211,9 +4194,8 @@ static void sqlite3ExprCodeIN( if( ExprHasProperty(pExpr, EP_Subrtn) ){ const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); assert( pOp->opcode==OP_Once || pParse->nErr ); - if( pOp->p3>0 ){ /* tag-202407032019 */ - assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) - || pParse->nErr ); + if( pOp->opcode==OP_Once && pOp->p3>0 ){ /* tag-202407032019 */ + assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ); sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse, rLhs, nVector); VdbeCoverage(v); } @@ -4303,7 +4285,7 @@ static void sqlite3ExprCodeIN( static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){ if( ALWAYS(z!=0) ){ double value; - sqlite3AtoF(z, &value); + sqlite3AtoF(z, &value, sqlite3Strlen30(z), SQLITE_UTF8); assert( !sqlite3IsNaN(value) ); /* The new AtoF never returns NaN */ if( negateFlag ) value = -value; sqlite3VdbeAddOp4Dup8(v, OP_Real, 0, iMem, 0, (u8*)&value, P4_REAL); @@ -7404,10 +7386,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ if( pIEpr==0 ) break; if( NEVER(!ExprUseYTab(pExpr)) ) break; for(i=0; i<pSrcList->nSrc; i++){ - if( pSrcList->a[i].iCursor==pIEpr->iDataCur ){ - testcase( i>0 ); - break; - } + if( pSrcList->a[0].iCursor==pIEpr->iDataCur ) break; } if( i>=pSrcList->nSrc ) break; if( NEVER(pExpr->pAggInfo!=0) ) break; /* Resolved by outer context */ diff --git a/src/fkey.c b/src/fkey.c index 59edd8810..f1117a884 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -688,7 +688,6 @@ FKey *sqlite3FkReferences(Table *pTab){ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ if( p ){ TriggerStep *pStep = p->step_list; - sqlite3SrcListDelete(dbMem, pStep->pSrc); sqlite3ExprDelete(dbMem, pStep->pWhere); sqlite3ExprListDelete(dbMem, pStep->pExprList); sqlite3SelectDelete(dbMem, pStep->pSelect); @@ -908,7 +907,6 @@ void sqlite3FkCheck( if( !IsOrdinaryTable(pTab) ) return; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - assert( iDb>=00 && iDb<db->nDb ); zDb = db->aDb[iDb].zDbSName; /* Loop through all the foreign key constraints for which pTab is the @@ -1356,14 +1354,14 @@ static Trigger *fkActionTrigger( pTrigger = (Trigger *)sqlite3DbMallocZero(db, sizeof(Trigger) + /* struct Trigger */ - sizeof(TriggerStep) /* Single step in trigger program */ + sizeof(TriggerStep) + /* Single step in trigger program */ + nFrom + 1 /* Space for pStep->zTarget */ ); if( pTrigger ){ pStep = pTrigger->step_list = (TriggerStep *)&pTrigger[1]; - pStep->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); - if( pStep->pSrc ){ - pStep->pSrc->a[0].zName = sqlite3DbStrNDup(db, zFrom, nFrom); - } + pStep->zTarget = (char *)&pStep[1]; + memcpy((char *)pStep->zTarget, zFrom, nFrom); + pStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE); pStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); diff --git a/src/func.c b/src/func.c index d9d8f59ad..6dac7195a 100644 --- a/src/func.c +++ b/src/func.c @@ -466,7 +466,7 @@ static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ sqlite3_result_error_nomem(context); return; } - sqlite3AtoF(zBuf, &r); + sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); sqlite3_free(zBuf); } sqlite3_result_double(context, r); @@ -1104,7 +1104,7 @@ void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue, int bEscape){ sqlite3_str_appendf(pStr, "%!0.15g", r1); zVal = sqlite3_str_value(pStr); if( zVal ){ - sqlite3AtoF(zVal, &r2); + sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8); if( r1!=r2 ){ sqlite3_str_reset(pStr); sqlite3_str_appendf(pStr, "%!0.20e", r1); @@ -1201,7 +1201,7 @@ static void unistrFunc( } i = j = 0; while( i<nIn ){ - const char *z = strchr(&zIn[i],'\\'); + char *z = strchr(&zIn[i],'\\'); if( z==0 ){ n = nIn - i; memmove(&zOut[j], &zIn[i], n); @@ -1238,7 +1238,7 @@ static void unistrFunc( } } zOut[j] = 0; - sqlite3_result_text64(context, zOut, j, sqlite3_free, SQLITE_UTF8_ZT); + sqlite3_result_text64(context, zOut, j, sqlite3_free, SQLITE_UTF8); return; unistr_error: @@ -1331,7 +1331,7 @@ static void charFunc( } \ } *zOut = 0; - sqlite3_result_text64(context, (char*)z, zOut-z,sqlite3_free,SQLITE_UTF8_ZT); + sqlite3_result_text64(context, (char*)z, zOut-z, sqlite3_free, SQLITE_UTF8); } /* @@ -1360,7 +1360,7 @@ static void hexFunc( } *z = 0; sqlite3_result_text64(context, zHex, (u64)(z-zHex), - sqlite3_free, SQLITE_UTF8_ZT); + sqlite3_free, SQLITE_UTF8); } } @@ -1698,7 +1698,7 @@ static void concatFuncCore( } z[j] = 0; assert( j<=n ); - sqlite3_result_text64(context, z, j, sqlite3_free, SQLITE_UTF8_ZT); + sqlite3_result_text64(context, z, j, sqlite3_free, SQLITE_UTF8); } /* @@ -2364,8 +2364,6 @@ void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){ sqlite3CreateFunc(db, "like", nArg, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0); pDef = sqlite3FindFunction(db, "like", nArg, SQLITE_UTF8, 0); - assert( pDef!=0 ); /* The sqlite3CreateFunc() call above cannot fail - ** because the "like" SQL-function already exists */ pDef->funcFlags |= flags; pDef->funcFlags &= ~SQLITE_FUNC_UNSAFE; } diff --git a/src/hwtime.h b/src/hwtime.h index cf637edaf..f808fa40e 100644 --- a/src/hwtime.h +++ b/src/hwtime.h @@ -16,26 +16,35 @@ #ifndef SQLITE_HWTIME_H #define SQLITE_HWTIME_H -#if defined(_MSC_VER) && defined(_WIN32) - - #include "windows.h" - #include <profileapi.h> - - __inline sqlite3_uint64 sqlite3Hwtime(void){ - LARGE_INTEGER tm; - QueryPerformanceCounter(&tm); - return (sqlite3_uint64)tm.QuadPart; - } - -#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && \ +/* +** The following routine only works on Pentium-class (or newer) processors. +** It uses the RDTSC opcode to read the cycle count value out of the +** processor and returns that value. This can be used for high-res +** profiling. +*/ +#if !defined(__STRICT_ANSI__) && \ + (defined(__GNUC__) || defined(_MSC_VER)) && \ (defined(i386) || defined(__i386__) || defined(_M_IX86)) + #if defined(__GNUC__) + __inline__ sqlite_uint64 sqlite3Hwtime(void){ unsigned int lo, hi; __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return (sqlite_uint64)hi << 32 | lo; } + #elif defined(_MSC_VER) + + __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ + __asm { + rdtsc + ret ; return value at EDX:EAX + } + } + + #endif + #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) __inline__ sqlite_uint64 sqlite3Hwtime(void){ @@ -43,14 +52,6 @@ __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return (sqlite_uint64)hi << 32 | lo; } - -#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && defined(__aarch64__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - sqlite3_uint64 cnt; - __asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (cnt)); - return cnt; - } #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) diff --git a/src/json.c b/src/json.c index 795d3ed73..d0d3c53a2 100644 --- a/src/json.c +++ b/src/json.c @@ -328,10 +328,7 @@ struct JsonString { #define JSON_SQL 0x02 /* Result is always SQL */ #define JSON_ABPATH 0x03 /* Allow abbreviated JSON path specs */ #define JSON_ISSET 0x04 /* json_set(), not json_insert() */ -#define JSON_AINS 0x08 /* json_array_insert(), not json_insert() */ -#define JSON_BLOB 0x10 /* Use the BLOB output format */ - -#define JSON_INSERT_TYPE(X) (((X)&0xC)>>2) +#define JSON_BLOB 0x08 /* Use the BLOB output format */ /* A parsed JSON value. Lifecycle: @@ -377,7 +374,6 @@ struct JsonParse { #define JEDIT_REPL 2 /* Overwrite if exists */ #define JEDIT_INS 3 /* Insert if not exists */ #define JEDIT_SET 4 /* Insert or overwrite */ -#define JEDIT_AINS 5 /* array_insert() */ /* ** Maximum nesting depth of JSON for this implementation. @@ -2874,8 +2870,7 @@ static int jsonLabelCompare( */ #define JSON_LOOKUP_ERROR 0xffffffff #define JSON_LOOKUP_NOTFOUND 0xfffffffe -#define JSON_LOOKUP_NOTARRAY 0xfffffffd -#define JSON_LOOKUP_PATHERROR 0xfffffffc +#define JSON_LOOKUP_PATHERROR 0xfffffffd #define JSON_LOOKUP_ISERROR(x) ((x)>=JSON_LOOKUP_PATHERROR) /* Forward declaration */ @@ -2904,7 +2899,7 @@ static u32 jsonLookupStep(JsonParse*,u32,const char*,u32); static u32 jsonCreateEditSubstructure( JsonParse *pParse, /* The original JSONB that is being edited */ JsonParse *pIns, /* Populate this with the blob data to insert */ - const char *zTail /* Tail of the path that determines substructure */ + const char *zTail /* Tail of the path that determins substructure */ ){ static const u8 emptyObject[] = { JSONB_ARRAY, JSONB_OBJECT }; int rc; @@ -2939,9 +2934,9 @@ static u32 jsonCreateEditSubstructure( ** Return one of the JSON_LOOKUP error codes if problems are seen. ** ** This routine will also modify the blob. If pParse->eEdit is one of -** JEDIT_DEL, JEDIT_REPL, JEDIT_INS, JEDIT_SET, or JEDIT_AINS, then changes -** might be made to the selected value. If an edit is performed, then the -** return value does not necessarily point to the select element. If an edit +** JEDIT_DEL, JEDIT_REPL, JEDIT_INS, or JEDIT_SET, then changes might be +** made to the selected value. If an edit is performed, then the return +** value does not necessarily point to the select element. If an edit ** is performed, the return value is only useful for detecting error ** conditions. */ @@ -2967,13 +2962,6 @@ static u32 jsonLookupStep( jsonBlobEdit(pParse, iRoot, sz, 0, 0); }else if( pParse->eEdit==JEDIT_INS ){ /* Already exists, so json_insert() is a no-op */ - }else if( pParse->eEdit==JEDIT_AINS ){ - /* json_array_insert() */ - if( zPath[-1]!=']' ){ - return JSON_LOOKUP_NOTARRAY; - }else{ - jsonBlobEdit(pParse, iRoot, 0, pParse->aIns, pParse->nIns); - } }else{ /* json_set() or json_replace() */ jsonBlobEdit(pParse, iRoot, sz, pParse->aIns, pParse->nIns); @@ -3045,10 +3033,6 @@ static u32 jsonLookupStep( JsonParse ix; /* Header of the label to be inserted */ testcase( pParse->eEdit==JEDIT_INS ); testcase( pParse->eEdit==JEDIT_SET ); - testcase( pParse->eEdit==JEDIT_AINS ); - if( pParse->eEdit==JEDIT_AINS && sqlite3_strglob("*]",&zPath[i])!=0 ){ - return JSON_LOOKUP_NOTARRAY; - } memset(&ix, 0, sizeof(ix)); ix.db = pParse->db; jsonBlobAppendNode(&ix, rawKey?JSONB_TEXTRAW:JSONB_TEXT5, nKey, 0); @@ -3076,32 +3060,28 @@ static u32 jsonLookupStep( return rc; } }else if( zPath[0]=='[' ){ - u64 kk = 0; x = pParse->aBlob[iRoot] & 0x0f; if( x!=JSONB_ARRAY ) return JSON_LOOKUP_NOTFOUND; n = jsonbPayloadSize(pParse, iRoot, &sz); + k = 0; i = 1; while( sqlite3Isdigit(zPath[i]) ){ - if( kk<0xffffffff ) kk = kk*10 + zPath[i] - '0'; - /* ^^^^^^^^^^--- Allow kk to be bigger than any JSON array so that - ** we get NOTFOUND instead of PATHERROR, without overflowing kk. */ + k = k*10 + zPath[i] - '0'; i++; } if( i<2 || zPath[i]!=']' ){ if( zPath[1]=='#' ){ - kk = jsonbArrayCount(pParse, iRoot); + k = jsonbArrayCount(pParse, iRoot); i = 2; if( zPath[2]=='-' && sqlite3Isdigit(zPath[3]) ){ - u64 nn = 0; + unsigned int nn = 0; i = 3; do{ - if( nn<0xffffffff ) nn = nn*10 + zPath[i] - '0'; - /* ^^^^^^^^^^--- Allow nn to be bigger than any JSON array to - ** get NOTFOUND instead of PATHERROR, without overflowing nn. */ + nn = nn*10 + zPath[i] - '0'; i++; }while( sqlite3Isdigit(zPath[i]) ); - if( nn>kk ) return JSON_LOOKUP_NOTFOUND; - kk -= nn; + if( nn>k ) return JSON_LOOKUP_NOTFOUND; + k -= nn; } if( zPath[i]!=']' ){ return JSON_LOOKUP_PATHERROR; @@ -3113,22 +3093,21 @@ static u32 jsonLookupStep( j = iRoot+n; iEnd = j+sz; while( j<iEnd ){ - if( kk==0 ){ + if( k==0 ){ rc = jsonLookupStep(pParse, j, &zPath[i+1], 0); if( pParse->delta ) jsonAfterEditSizeAdjust(pParse, iRoot); return rc; } - kk--; + k--; n = jsonbPayloadSize(pParse, j, &sz); if( n==0 ) return JSON_LOOKUP_ERROR; j += n+sz; } if( j>iEnd ) return JSON_LOOKUP_ERROR; - if( kk>0 ) return JSON_LOOKUP_NOTFOUND; + if( k>0 ) return JSON_LOOKUP_NOTFOUND; if( pParse->eEdit>=JEDIT_INS ){ JsonParse v; testcase( pParse->eEdit==JEDIT_INS ); - testcase( pParse->eEdit==JEDIT_AINS ); testcase( pParse->eEdit==JEDIT_SET ); rc = jsonCreateEditSubstructure(pParse, &v, &zPath[i+1]); if( !JSON_LOOKUP_ISERROR(rc) @@ -3266,7 +3245,7 @@ static void jsonReturnFromBlob( to_double: z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); if( z==0 ) goto returnfromblob_oom; - rc = sqlite3AtoF(z, &r); + rc = sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); sqlite3DbFree(db, z); if( rc<=0 ) goto returnfromblob_malformed; sqlite3_result_double(pCtx, r); @@ -3453,15 +3432,9 @@ static int jsonFunctionArgToBlob( */ static char *jsonBadPathError( sqlite3_context *ctx, /* The function call containing the error */ - const char *zPath, /* The path with the problem */ - int rc /* Maybe JSON_LOOKUP_NOTARRAY */ + const char *zPath /* The path with the problem */ ){ - char *zMsg; - if( rc==(int)JSON_LOOKUP_NOTARRAY ){ - zMsg = sqlite3_mprintf("not an array element: %Q", zPath); - }else{ - zMsg = sqlite3_mprintf("bad JSON path: %Q", zPath); - } + char *zMsg = sqlite3_mprintf("bad JSON path: %Q", zPath); if( ctx==0 ) return zMsg; if( zMsg ){ sqlite3_result_error(ctx, zMsg, -1); @@ -3478,13 +3451,13 @@ static char *jsonBadPathError( ** and return the result. ** ** The specific operation is determined by eEdit, which can be one -** of JEDIT_INS, JEDIT_REPL, JEDIT_SET, or JEDIT_AINS. +** of JEDIT_INS, JEDIT_REPL, or JEDIT_SET. */ static void jsonInsertIntoBlob( sqlite3_context *ctx, int argc, sqlite3_value **argv, - int eEdit /* JEDIT_INS, JEDIT_REPL, JEDIT_SET, JEDIT_AINS */ + int eEdit /* JEDIT_INS, JEDIT_REPL, or JEDIT_SET */ ){ int i; u32 rc = 0; @@ -3536,7 +3509,7 @@ static void jsonInsertIntoBlob( if( rc==JSON_LOOKUP_ERROR ){ sqlite3_result_error(ctx, "malformed JSON", -1); }else{ - jsonBadPathError(ctx, zPath, rc); + jsonBadPathError(ctx, zPath); } return; } @@ -3978,7 +3951,7 @@ static void jsonArrayLengthFunc( if( i==JSON_LOOKUP_NOTFOUND ){ /* no-op */ }else if( i==JSON_LOOKUP_PATHERROR ){ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); }else{ sqlite3_result_error(ctx, "malformed JSON", -1); } @@ -4083,7 +4056,7 @@ static void jsonExtractFunc( j = jsonLookupStep(p, 0, jx.zBuf, 0); jsonStringReset(&jx); }else{ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); goto json_extract_error; } if( j<p->nBlob ){ @@ -4118,7 +4091,7 @@ static void jsonExtractFunc( sqlite3_result_error(ctx, "malformed JSON", -1); goto json_extract_error; }else{ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); goto json_extract_error; } } @@ -4447,7 +4420,7 @@ static void jsonRemoveFunc( if( rc==JSON_LOOKUP_NOTFOUND ){ continue; /* No-op */ }else if( rc==JSON_LOOKUP_PATHERROR ){ - jsonBadPathError(ctx, zPath, rc); + jsonBadPathError(ctx, zPath); }else{ sqlite3_result_error(ctx, "malformed JSON", -1); } @@ -4459,7 +4432,7 @@ static void jsonRemoveFunc( return; json_remove_patherror: - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); json_remove_done: jsonParseFree(p); @@ -4503,18 +4476,16 @@ static void jsonSetFunc( int argc, sqlite3_value **argv ){ + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); - int eInsType = JSON_INSERT_TYPE(flags); - static const char *azInsType[] = { "insert", "set", "array_insert" }; - static const u8 aEditType[] = { JEDIT_INS, JEDIT_SET, JEDIT_AINS }; + int bIsSet = (flags&JSON_ISSET)!=0; if( argc<1 ) return; - assert( eInsType>=0 && eInsType<=2 ); if( (argc&1)==0 ) { - jsonWrongNumArgs(ctx, azInsType[eInsType]); + jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); return; } - jsonInsertIntoBlob(ctx, argc, argv, aEditType[eInsType]); + jsonInsertIntoBlob(ctx, argc, argv, bIsSet ? JEDIT_SET : JEDIT_INS); } /* @@ -4539,7 +4510,7 @@ static void jsonTypeFunc( zPath = (const char*)sqlite3_value_text(argv[1]); if( zPath==0 ) goto json_type_done; if( zPath[0]!='$' ){ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); goto json_type_done; } i = jsonLookupStep(p, 0, zPath+1, 0); @@ -4547,7 +4518,7 @@ static void jsonTypeFunc( if( i==JSON_LOOKUP_NOTFOUND ){ /* no-op */ }else if( i==JSON_LOOKUP_PATHERROR ){ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); }else{ sqlite3_result_error(ctx, "malformed JSON", -1); } @@ -4803,11 +4774,12 @@ static void jsonArrayStep( } static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; - int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ + int flags; pStr->pCtx = ctx; jsonAppendChar(pStr, ']'); + flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); if( pStr->eErr ){ jsonReturnString(pStr, 0, 0); return; @@ -4828,9 +4800,6 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); jsonStringTrimOneChar(pStr); } - }else if( flags & JSON_BLOB ){ - static const u8 emptyArray = 0x0b; - sqlite3_result_blob(ctx, &emptyArray, 1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC); } @@ -4927,11 +4896,12 @@ static void jsonObjectStep( } static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; - int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ + int flags; jsonAppendChar(pStr, '}'); pStr->pCtx = ctx; + flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); if( pStr->eErr ){ jsonReturnString(pStr, 0, 0); return; @@ -4952,9 +4922,6 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); jsonStringTrimOneChar(pStr); } - }else if( flags & JSON_BLOB ){ - static const unsigned char emptyObject = 0x0c; - sqlite3_result_blob(ctx, &emptyObject, 1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC); } @@ -5455,7 +5422,7 @@ static int jsonEachFilter( if( zRoot==0 ) return SQLITE_OK; if( zRoot[0]!='$' ){ sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot, 0); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot); jsonEachCursorReset(p); return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; } @@ -5473,7 +5440,7 @@ static int jsonEachFilter( return SQLITE_OK; } sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot, 0); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot); jsonEachCursorReset(p); return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; } @@ -5563,8 +5530,6 @@ void sqlite3RegisterJsonFunctions(void){ JFUNCTION(jsonb, 1,1,0, 0,1,0, jsonRemoveFunc), JFUNCTION(json_array, -1,0,1, 1,0,0, jsonArrayFunc), JFUNCTION(jsonb_array, -1,0,1, 1,1,0, jsonArrayFunc), - JFUNCTION(json_array_insert, -1,1,1, 1,0,JSON_AINS, jsonSetFunc), - JFUNCTION(jsonb_array_insert,-1,1,0, 1,1,JSON_AINS, jsonSetFunc), JFUNCTION(json_array_length, 1,1,0, 0,0,0, jsonArrayLengthFunc), JFUNCTION(json_array_length, 2,1,0, 0,0,0, jsonArrayLengthFunc), JFUNCTION(json_error_position,1,1,0, 0,0,0, jsonErrorFunc), diff --git a/src/loadext.c b/src/loadext.c index 55325edd7..c5177715e 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -522,17 +522,7 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_setlk_timeout, /* Version 3.51.0 and later */ sqlite3_set_errmsg, - sqlite3_db_status64, - /* Version 3.52.0 and later */ - sqlite3_str_truncate, - sqlite3_str_free, -#ifdef SQLITE_ENABLE_CARRAY - sqlite3_carray_bind, - sqlite3_carray_bind_v2 -#else - 0, - 0 -#endif + sqlite3_db_status64 }; /* True if x is the directory separator character @@ -634,42 +624,33 @@ static int sqlite3LoadExtension( ** entry point name "sqlite3_extension_init" was not found, then ** construct an entry point name "sqlite3_X_init" where the X is ** replaced by the lowercase value of every ASCII alphabetic - ** character in the filename after the last "/" up to the first ".", - ** and skipping the first three characters if they are "lib". + ** character in the filename after the last "/" upto the first ".", + ** and eliding the first three characters if they are "lib". ** Examples: ** ** /usr/local/lib/libExample5.4.3.so ==> sqlite3_example_init ** C:/lib/mathfuncs.dll ==> sqlite3_mathfuncs_init - ** - ** If that still finds no entry point, repeat a second time but this - ** time include both alphabetic and numeric characters up to the first - ** ".". Example: - ** - ** /usr/local/lib/libExample5.4.3.so ==> sqlite3_example5_init */ if( xInit==0 && zProc==0 ){ int iFile, iEntry, c; int ncFile = sqlite3Strlen30(zFile); - int cnt = 0; zAltEntry = sqlite3_malloc64(ncFile+30); if( zAltEntry==0 ){ sqlite3OsDlClose(pVfs, handle); return SQLITE_NOMEM_BKPT; } - do{ - memcpy(zAltEntry, "sqlite3_", 8); - for(iFile=ncFile-1; iFile>=0 && !DirSep(zFile[iFile]); iFile--){} - iFile++; - if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; - for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ - if( sqlite3Isalpha(c) || (cnt && sqlite3Isdigit(c)) ){ - zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; - } + memcpy(zAltEntry, "sqlite3_", 8); + for(iFile=ncFile-1; iFile>=0 && !DirSep(zFile[iFile]); iFile--){} + iFile++; + if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; + for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ + if( sqlite3Isalpha(c) ){ + zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; } - memcpy(zAltEntry+iEntry, "_init", 6); - zEntry = zAltEntry; - xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); - }while( xInit==0 && (++cnt)<2 ); + } + memcpy(zAltEntry+iEntry, "_init", 6); + zEntry = zAltEntry; + xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); } if( xInit==0 ){ if( pzErrMsg ){ diff --git a/src/main.c b/src/main.c index b44ac8dca..6efe538d4 100644 --- a/src/main.c +++ b/src/main.c @@ -971,14 +971,6 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ rc = setupLookaside(db, pBuf, sz, cnt); break; } - case SQLITE_DBCONFIG_FP_DIGITS: { - int nIn = va_arg(ap, int); - int *pOut = va_arg(ap, int*); - if( nIn>3 && nIn<24 ) db->nFpDigit = (u8)nIn; - if( pOut ) *pOut = db->nFpDigit; - rc = SQLITE_OK; - break; - } default: { static const struct { int op; /* The opcode */ @@ -2534,9 +2526,6 @@ void *sqlite3_wal_hook( sqlite3_mutex_leave(db->mutex); return pRet; #else - UNUSED_PARAMETER(db); - UNUSED_PARAMETER(xCallback); - UNUSED_PARAMETER(pArg); return 0; #endif } @@ -2552,11 +2541,6 @@ int sqlite3_wal_checkpoint_v2( int *pnCkpt /* OUT: Total number of frames checkpointed */ ){ #ifdef SQLITE_OMIT_WAL - UNUSED_PARAMETER(db); - UNUSED_PARAMETER(zDb); - UNUSED_PARAMETER(eMode); - UNUSED_PARAMETER(pnLog); - UNUSED_PARAMETER(pnCkpt); return SQLITE_OK; #else int rc; /* Return code */ @@ -2570,12 +2554,11 @@ int sqlite3_wal_checkpoint_v2( if( pnLog ) *pnLog = -1; if( pnCkpt ) *pnCkpt = -1; - assert( SQLITE_CHECKPOINT_NOOP==-1 ); assert( SQLITE_CHECKPOINT_PASSIVE==0 ); assert( SQLITE_CHECKPOINT_FULL==1 ); assert( SQLITE_CHECKPOINT_RESTART==2 ); assert( SQLITE_CHECKPOINT_TRUNCATE==3 ); - if( eMode<SQLITE_CHECKPOINT_NOOP || eMode>SQLITE_CHECKPOINT_TRUNCATE ){ + if( eMode<SQLITE_CHECKPOINT_PASSIVE || eMode>SQLITE_CHECKPOINT_TRUNCATE ){ /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint ** mode: */ return SQLITE_MISUSE_BKPT; @@ -2939,7 +2922,6 @@ static const int aHardLimit[] = { SQLITE_MAX_VARIABLE_NUMBER, /* IMP: R-38091-32352 */ SQLITE_MAX_TRIGGER_DEPTH, SQLITE_MAX_WORKER_THREADS, - SQLITE_MAX_PARSER_DEPTH, }; /* @@ -2954,9 +2936,6 @@ static const int aHardLimit[] = { #if SQLITE_MAX_SQL_LENGTH>SQLITE_MAX_LENGTH # error SQLITE_MAX_SQL_LENGTH must not be greater than SQLITE_MAX_LENGTH #endif -#if SQLITE_MAX_SQL_LENGTH>2147482624 /* 1024 less than 2^31 */ -# error SQLITE_MAX_SQL_LENGTH must not be greater than 2147482624 -#endif #if SQLITE_MAX_COMPOUND_SELECT<2 # error SQLITE_MAX_COMPOUND_SELECT must be at least 2 #endif @@ -3012,7 +2991,6 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ assert( aHardLimit[SQLITE_LIMIT_SQL_LENGTH]==SQLITE_MAX_SQL_LENGTH ); assert( aHardLimit[SQLITE_LIMIT_COLUMN]==SQLITE_MAX_COLUMN ); assert( aHardLimit[SQLITE_LIMIT_EXPR_DEPTH]==SQLITE_MAX_EXPR_DEPTH ); - assert( aHardLimit[SQLITE_LIMIT_PARSER_DEPTH]==SQLITE_MAX_PARSER_DEPTH ); assert( aHardLimit[SQLITE_LIMIT_COMPOUND_SELECT]==SQLITE_MAX_COMPOUND_SELECT); assert( aHardLimit[SQLITE_LIMIT_VDBE_OP]==SQLITE_MAX_VDBE_OP ); assert( aHardLimit[SQLITE_LIMIT_FUNCTION_ARG]==SQLITE_MAX_FUNCTION_ARG ); @@ -3022,7 +3000,7 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ assert( aHardLimit[SQLITE_LIMIT_VARIABLE_NUMBER]==SQLITE_MAX_VARIABLE_NUMBER); assert( aHardLimit[SQLITE_LIMIT_TRIGGER_DEPTH]==SQLITE_MAX_TRIGGER_DEPTH ); assert( aHardLimit[SQLITE_LIMIT_WORKER_THREADS]==SQLITE_MAX_WORKER_THREADS ); - assert( SQLITE_LIMIT_PARSER_DEPTH==(SQLITE_N_LIMIT-1) ); + assert( SQLITE_LIMIT_WORKER_THREADS==(SQLITE_N_LIMIT-1) ); if( limitId<0 || limitId>=SQLITE_N_LIMIT ){ @@ -3386,7 +3364,7 @@ static int openDatabase( db = sqlite3MallocZero( sizeof(sqlite3) ); if( db==0 ) goto opendb_out; if( isThreadsafe -#if defined(SQLITE_THREAD_MISUSE_WARNINGS) +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS || sqlite3GlobalConfig.bCoreMutex #endif ){ @@ -3407,7 +3385,6 @@ static int openDatabase( db->aDb = db->aDbStatic; db->lookaside.bDisable = 1; db->lookaside.sz = 0; - db->nFpDigit = 17; assert( sizeof(db->aLimit)==sizeof(aHardLimit) ); memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit)); @@ -3853,12 +3830,6 @@ int sqlite3_collation_needed16( */ void *sqlite3_get_clientdata(sqlite3 *db, const char *zName){ DbClientData *p; -#ifdef SQLITE_ENABLE_API_ARMOR - if( !zName || !sqlite3SafetyCheckOk(db) ){ - (void)SQLITE_MISUSE_BKPT; - return 0; - } -#endif sqlite3_mutex_enter(db->mutex); for(p=db->pDbData; p; p=p->pNext){ if( strcmp(p->zName, zName)==0 ){ @@ -4932,7 +4903,6 @@ const char *sqlite3_filename_journal(const char *zFilename){ } const char *sqlite3_filename_wal(const char *zFilename){ #ifdef SQLITE_OMIT_WAL - UNUSED_PARAMETER(zFilename); return 0; #else zFilename = sqlite3_filename_journal(zFilename); diff --git a/src/mutex.c b/src/mutex.c index 21b47e511..62e09cb4f 100644 --- a/src/mutex.c +++ b/src/mutex.c @@ -27,28 +27,23 @@ static SQLITE_WSD int mutexIsInit = 0; #ifndef SQLITE_MUTEX_OMIT -#ifdef SQLITE_THREAD_MISUSE_WARNINGS +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS /* -** This block (enclosed by SQLITE_THREAD_MISUSE_WARNINGS) contains +** This block (enclosed by SQLITE_ENABLE_MULTITHREADED_CHECKS) contains ** the implementation of a wrapper around the system default mutex ** implementation (sqlite3DefaultMutex()). ** ** Most calls are passed directly through to the underlying default ** mutex implementation. Except, if a mutex is configured by calling ** sqlite3MutexWarnOnContention() on it, then if contention is ever -** encountered within xMutexEnter() then a warning is emitted via -** sqlite3_log(). Furthermore, if SQLITE_THREAD_MISUSE_ABORT is -** defined then abort() is called after the sqlite3_log() warning. +** encountered within xMutexEnter() a warning is emitted via sqlite3_log(). ** -** This type of mutex is used on the database handle mutex when testing -** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. A failure -** indicates that the app ought to be using SQLITE_OPEN_FULLMUTEX or -** similar because it is trying to use the same database handle from -** two different connections at the same time. +** This type of mutex is used as the database handle mutex when testing +** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. */ /* -** Type for all mutexes used when SQLITE_THREAD_MISUSE_WARNINGS +** Type for all mutexes used when SQLITE_ENABLE_MULTITHREADED_CHECKS ** is defined. Variable CheckMutex.mutex is a pointer to the real mutex ** allocated by the system mutex implementation. Variable iType is usually set ** to the type of mutex requested - SQLITE_MUTEX_RECURSIVE, SQLITE_MUTEX_FAST @@ -84,12 +79,11 @@ static int checkMutexNotheld(sqlite3_mutex *p){ */ static int checkMutexInit(void){ pGlobalMutexMethods = sqlite3DefaultMutex(); - return pGlobalMutexMethods->xMutexInit(); + return SQLITE_OK; } static int checkMutexEnd(void){ - int rc = pGlobalMutexMethods->xMutexEnd(); pGlobalMutexMethods = 0; - return rc; + return SQLITE_OK; } /* @@ -166,9 +160,6 @@ static void checkMutexEnter(sqlite3_mutex *p){ sqlite3_log(SQLITE_MISUSE, "illegal multi-threaded access to database connection" ); -#if SQLITE_THREAD_MISUSE_ABORT - abort(); -#endif } pGlobalMutexMethods->xMutexEnter(pCheck->mutex); } @@ -220,7 +211,7 @@ void sqlite3MutexWarnOnContention(sqlite3_mutex *p){ pCheck->iType = SQLITE_MUTEX_WARNONCONTENTION; } } -#endif /* ifdef SQLITE_THREAD_MISUSE_WARNINGS */ +#endif /* ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS */ /* ** Initialize the mutex system. @@ -237,7 +228,7 @@ int sqlite3MutexInit(void){ sqlite3_mutex_methods *pTo = &sqlite3GlobalConfig.mutex; if( sqlite3GlobalConfig.bCoreMutex ){ -#ifdef SQLITE_THREAD_MISUSE_WARNINGS +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS pFrom = multiThreadedCheckMutex(); #else pFrom = sqlite3DefaultMutex(); diff --git a/src/mutex_w32.c b/src/mutex_w32.c index 20e41b161..7eb5b50be 100644 --- a/src/mutex_w32.c +++ b/src/mutex_w32.c @@ -129,7 +129,11 @@ static int winMutexInit(void){ if( InterlockedCompareExchange(&winMutex_lock, 1, 0)==0 ){ int i; for(i=0; i<ArraySize(winMutex_staticMutexes); i++){ +#if SQLITE_OS_WINRT + InitializeCriticalSectionEx(&winMutex_staticMutexes[i].mutex, 0, 0); +#else InitializeCriticalSection(&winMutex_staticMutexes[i].mutex); +#endif } winMutex_isInit = 1; }else{ @@ -219,7 +223,11 @@ static sqlite3_mutex *winMutexAlloc(int iType){ p->trace = 1; #endif #endif +#if SQLITE_OS_WINRT + InitializeCriticalSectionEx(&p->mutex, 0, 0); +#else InitializeCriticalSection(&p->mutex); +#endif } break; } diff --git a/src/os_kv.c b/src/os_kv.c index 1fd1c8e8c..c2d1f9b7a 100644 --- a/src/os_kv.c +++ b/src/os_kv.c @@ -21,7 +21,7 @@ ** Debugging logic */ -/* SQLITE_KV_TRACE() is used for tracing calls to kvrecord routines. */ +/* SQLITE_KV_TRACE() is used for tracing calls to kvstorage routines. */ #if 0 #define SQLITE_KV_TRACE(X) printf X #else @@ -35,6 +35,7 @@ #define SQLITE_KV_LOG(X) #endif + /* ** Forward declaration of objects used by this VFS implementation */ @@ -42,11 +43,6 @@ typedef struct KVVfsFile KVVfsFile; /* A single open file. There are only two files represented by this ** VFS - the database and the rollback journal. -** -** Maintenance reminder: if this struct changes in any way, the JSON -** rendering of its structure must be updated in -** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary -** compatibility concerns, so it does not need an iVersion member. */ struct KVVfsFile { sqlite3_file base; /* IO methods */ @@ -96,7 +92,7 @@ static int kvvfsCurrentTime(sqlite3_vfs*, double*); static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); static sqlite3_vfs sqlite3OsKvvfsObject = { - 2, /* iVersion */ + 1, /* iVersion */ sizeof(KVVfsFile), /* szOsFile */ 1024, /* mxPathname */ 0, /* pNext */ @@ -172,37 +168,23 @@ static sqlite3_io_methods kvvfs_jrnl_io_methods = { /* Forward declarations for the low-level storage engine */ -#ifndef SQLITE_WASM -/* In WASM builds these are implemented in JS. */ -static int kvrecordWrite(const char*, const char *zKey, const char *zData); -static int kvrecordDelete(const char*, const char *zKey); -static int kvrecordRead(const char*, const char *zKey, char *zBuf, int nBuf); -#endif -#ifndef KVRECORD_KEY_SZ -#define KVRECORD_KEY_SZ 32 -#endif +static int kvstorageWrite(const char*, const char *zKey, const char *zData); +static int kvstorageDelete(const char*, const char *zKey); +static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); +#define KVSTORAGE_KEY_SZ 32 /* Expand the key name with an appropriate prefix and put the result ** in zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least -** KVRECORD_KEY_SZ bytes. +** KVSTORAGE_KEY_SZ bytes. */ -static void kvrecordMakeKey( +static void kvstorageMakeKey( const char *zClass, const char *zKeyIn, char *zKeyOut ){ - assert( zKeyIn ); - assert( zKeyOut ); - assert( zClass ); - sqlite3_snprintf(KVRECORD_KEY_SZ, zKeyOut, "kvvfs-%s-%s", - zClass, zKeyIn); + sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn); } -#ifndef SQLITE_WASM -/* In WASM builds do not define APIs which use fopen(), fwrite(), -** and the like because those APIs are a portability issue for -** WASM. -*/ /* Write content into a key. zClass is the particular namespace of the ** underlying key/value store to use - either "local" or "session". ** @@ -210,14 +192,14 @@ static void kvrecordMakeKey( ** ** Return the number of errors. */ -static int kvrecordWrite( +static int kvstorageWrite( const char *zClass, const char *zKey, const char *zData ){ FILE *fd; - char zXKey[KVRECORD_KEY_SZ]; - kvrecordMakeKey(zClass, zKey, zXKey); + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); fd = fopen(zXKey, "wb"); if( fd ){ SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey, @@ -235,9 +217,9 @@ static int kvrecordWrite( ** namespace given by zClass. If the key does not previously exist, ** this routine is a no-op. */ -static int kvrecordDelete(const char *zClass, const char *zKey){ - char zXKey[KVRECORD_KEY_SZ]; - kvrecordMakeKey(zClass, zKey, zXKey); +static int kvstorageDelete(const char *zClass, const char *zKey){ + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); unlink(zXKey); SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey)); return 0; @@ -258,7 +240,7 @@ static int kvrecordDelete(const char *zClass, const char *zKey){ ** zero-terminates zBuf at zBuf[0] and returns the size of the data ** without reading it. */ -static int kvrecordRead( +static int kvstorageRead( const char *zClass, const char *zKey, char *zBuf, @@ -266,8 +248,8 @@ static int kvrecordRead( ){ FILE *fd; struct stat buf; - char zXKey[KVRECORD_KEY_SZ]; - kvrecordMakeKey(zClass, zKey, zXKey); + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); if( access(zXKey, R_OK)!=0 || stat(zXKey, &buf)!=0 || !S_ISREG(buf.st_mode) @@ -299,8 +281,6 @@ static int kvrecordRead( return (int)n; } } -#endif /* #ifndef SQLITE_WASM */ - /* ** An internal level of indirection which enables us to replace the @@ -308,27 +288,17 @@ static int kvrecordRead( ** Maintenance reminder: if this struct changes in any way, the JSON ** rendering of its structure must be updated in ** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary -** compatibility concerns, so it does not need an iVersion member. +** compatibility concerns, so it does not need an iVersion +** member. */ typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; struct sqlite3_kvvfs_methods { - int (*xRcrdRead)(const char*, const char *zKey, char *zBuf, int nBuf); - int (*xRcrdWrite)(const char*, const char *zKey, const char *zData); - int (*xRcrdDelete)(const char*, const char *zKey); + int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf); + int (*xWrite)(const char *zClass, const char *zKey, const char *zData); + int (*xDelete)(const char *zClass, const char *zKey); const int nKeySize; - const int nBufferSize; -#ifndef SQLITE_WASM -# define MAYBE_CONST const -#else -# define MAYBE_CONST -#endif - MAYBE_CONST sqlite3_vfs * pVfs; - MAYBE_CONST sqlite3_io_methods *pIoDb; - MAYBE_CONST sqlite3_io_methods *pIoJrnl; -#undef MAYBE_CONST }; - /* ** This object holds the kvvfs I/O methods which may be swapped out ** for JavaScript-side implementations in WASM builds. In such builds @@ -343,20 +313,10 @@ struct sqlite3_kvvfs_methods { const #endif sqlite3_kvvfs_methods sqlite3KvvfsMethods = { -#ifndef SQLITE_WASM - .xRcrdRead = kvrecordRead, - .xRcrdWrite = kvrecordWrite, - .xRcrdDelete = kvrecordDelete, -#else - .xRcrdRead = 0, - .xRcrdWrite = 0, - .xRcrdDelete = 0, -#endif - .nKeySize = KVRECORD_KEY_SZ, - .nBufferSize = SQLITE_KVOS_SZ, - .pVfs = &sqlite3OsKvvfsObject, - .pIoDb = &kvvfs_db_io_methods, - .pIoJrnl = &kvvfs_jrnl_io_methods +kvstorageRead, +kvstorageWrite, +kvstorageDelete, +KVSTORAGE_KEY_SZ }; /****** Utility subroutines ************************************************/ @@ -383,10 +343,7 @@ sqlite3_kvvfs_methods sqlite3KvvfsMethods = { ** of hexadecimal and base-26 numbers, it is always clear where ** one stops and the next begins. */ -#ifndef SQLITE_WASM -static -#endif -int kvvfsEncode(const char *aData, int nData, char *aOut){ +static int kvvfsEncode(const char *aData, int nData, char *aOut){ int i, j; const unsigned char *a = (const unsigned char*)aData; for(i=j=0; i<nData; i++){ @@ -437,13 +394,9 @@ static const signed char kvvfsHexValue[256] = { ** Decode the text encoding back to binary. The binary content is ** written into pOut, which must be at least nOut bytes in length. ** -** The return value is the number of bytes actually written into aOut[], or -** -1 for malformed inputs. +** The return value is the number of bytes actually written into aOut[]. */ -#ifndef SQLITE_WASM -static -#endif -int kvvfsDecode(const char *a, char *aOut, int nOut){ +static int kvvfsDecode(const char *a, char *aOut, int nOut){ int i, j; int c; const unsigned char *aIn = (const unsigned char*)a; @@ -468,7 +421,7 @@ int kvvfsDecode(const char *a, char *aOut, int nOut){ }else{ aOut[j] = c<<4; c = kvvfsHexValue[aIn[++i]]; - if( c<0 ) return -1 /* hex bytes are always in pairs */; + if( c<0 ) break; aOut[j++] += c; i++; } @@ -521,14 +474,13 @@ static void kvvfsDecodeJournal( static sqlite3_int64 kvvfsReadFileSize(KVVfsFile *pFile){ char zData[50]; zData[0] = 0; - sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "sz", zData, - sizeof(zData)-1); + sqlite3KvvfsMethods.xRead(pFile->zClass, "sz", zData, sizeof(zData)-1); return strtoll(zData, 0, 0); } static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){ char zData[50]; sqlite3_snprintf(sizeof(zData), zData, "%lld", sz); - return sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, "sz", zData); + return sqlite3KvvfsMethods.xWrite(pFile->zClass, "sz", zData); } /****** sqlite3_io_methods methods ******************************************/ @@ -539,13 +491,10 @@ static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){ static int kvvfsClose(sqlite3_file *pProtoFile){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; - SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, + SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, pFile->isJournal ? "journal" : "db")); sqlite3_free(pFile->aJrnl); sqlite3_free(pFile->aData); -#ifdef SQLITE_WASM - memset(pFile, 0, sizeof(*pFile)); -#endif return SQLITE_OK; } @@ -554,30 +503,24 @@ static int kvvfsClose(sqlite3_file *pProtoFile){ */ static int kvvfsReadJrnl( sqlite3_file *pProtoFile, - void *zBuf, - int iAmt, + void *zBuf, + int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; assert( pFile->isJournal ); SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); if( pFile->aJrnl==0 ){ - int rc; - int szTxt = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "jrnl", - 0, 0); + int szTxt = kvstorageRead(pFile->zClass, "jrnl", 0, 0); char *aTxt; if( szTxt<=4 ){ return SQLITE_IOERR; } aTxt = sqlite3_malloc64( szTxt+1 ); if( aTxt==0 ) return SQLITE_NOMEM; - rc = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "jrnl", - aTxt, szTxt+1); - if( rc>=0 ){ - kvvfsDecodeJournal(pFile, aTxt, szTxt); - } + kvstorageRead(pFile->zClass, "jrnl", aTxt, szTxt+1); + kvvfsDecodeJournal(pFile, aTxt, szTxt); sqlite3_free(aTxt); - if( rc ) return rc; if( pFile->aJrnl==0 ) return SQLITE_IOERR; } if( iOfst+iAmt>pFile->nJrnl ){ @@ -592,8 +535,8 @@ static int kvvfsReadJrnl( */ static int kvvfsReadDb( sqlite3_file *pProtoFile, - void *zBuf, - int iAmt, + void *zBuf, + int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; @@ -617,8 +560,8 @@ static int kvvfsReadDb( pgno = 1; } sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); - got = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, zKey, - aData, SQLITE_KVOS_SZ-1); + got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, + aData, SQLITE_KVOS_SZ-1); if( got<0 ){ n = 0; }else{ @@ -650,8 +593,8 @@ static int kvvfsReadDb( */ static int kvvfsWriteJrnl( sqlite3_file *pProtoFile, - const void *zBuf, - int iAmt, + const void *zBuf, + int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; @@ -678,15 +621,14 @@ static int kvvfsWriteJrnl( */ static int kvvfsWriteDb( sqlite3_file *pProtoFile, - const void *zBuf, - int iAmt, + const void *zBuf, + int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; unsigned int pgno; char zKey[30]; char *aData = pFile->aData; - int rc; SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); assert( iAmt>=512 && iAmt<=65536 ); assert( (iAmt & (iAmt-1))==0 ); @@ -695,13 +637,13 @@ static int kvvfsWriteDb( pgno = 1 + iOfst/iAmt; sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); kvvfsEncode(zBuf, iAmt, aData); - rc = sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, zKey, aData); - if( 0==rc ){ - if( iOfst+iAmt > pFile->szDb ){ - pFile->szDb = iOfst + iAmt; - } + if( sqlite3KvvfsMethods.xWrite(pFile->zClass, zKey, aData) ){ + return SQLITE_IOERR; } - return rc; + if( iOfst+iAmt > pFile->szDb ){ + pFile->szDb = iOfst + iAmt; + } + return SQLITE_OK; } /* @@ -711,7 +653,7 @@ static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size)); assert( size==0 ); - sqlite3KvvfsMethods.xRcrdDelete(pFile->zClass, "jrnl"); + sqlite3KvvfsMethods.xDelete(pFile->zClass, "jrnl"); sqlite3_free(pFile->aJrnl); pFile->aJrnl = 0; pFile->nJrnl = 0; @@ -720,7 +662,7 @@ static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){ static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; if( pFile->szDb>size - && pFile->szPage>0 + && pFile->szPage>0 && (size % pFile->szPage)==0 ){ char zKey[50]; @@ -730,7 +672,7 @@ static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){ pgnoMax = 2 + pFile->szDb/pFile->szPage; while( pgno<=pgnoMax ){ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); - sqlite3KvvfsMethods.xRcrdDelete(pFile->zClass, zKey); + sqlite3KvvfsMethods.xDelete(pFile->zClass, zKey); pgno++; } pFile->szDb = size; @@ -762,7 +704,7 @@ static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){ }while( n>0 ); zOut[i++] = ' '; kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]); - i = sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, "jrnl", zOut); + i = sqlite3KvvfsMethods.xWrite(pFile->zClass, "jrnl", zOut); sqlite3_free(zOut); return i ? SQLITE_IOERR : SQLITE_OK; } @@ -876,32 +818,33 @@ static int kvvfsOpen( KVVfsFile *pFile = (KVVfsFile*)pProtoFile; if( zName==0 ) zName = ""; SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName)); - assert(!pFile->zClass); - assert(!pFile->aData); - assert(!pFile->aJrnl); - assert(!pFile->nJrnl); - assert(!pFile->base.pMethods); - pFile->szPage = -1; - pFile->szDb = -1; - if( 0==sqlite3_strglob("*-journal", zName) ){ + if( strcmp(zName, "local")==0 + || strcmp(zName, "session")==0 + ){ + pFile->isJournal = 0; + pFile->base.pMethods = &kvvfs_db_io_methods; + }else + if( strcmp(zName, "local-journal")==0 + || strcmp(zName, "session-journal")==0 + ){ pFile->isJournal = 1; pFile->base.pMethods = &kvvfs_jrnl_io_methods; - if( 0==strcmp("session-journal",zName) ){ - pFile->zClass = "session"; - }else if( 0==strcmp("local-journal",zName) ){ - pFile->zClass = "local"; - } }else{ - pFile->isJournal = 0; - pFile->base.pMethods = &kvvfs_db_io_methods; + return SQLITE_CANTOPEN; } - if( !pFile->zClass ){ - pFile->zClass = zName; + if( zName[0]=='s' ){ + pFile->zClass = "session"; + }else{ + pFile->zClass = "local"; } pFile->aData = sqlite3_malloc64(SQLITE_KVOS_SZ); if( pFile->aData==0 ){ return SQLITE_NOMEM; } + pFile->aJrnl = 0; + pFile->nJrnl = 0; + pFile->szPage = -1; + pFile->szDb = -1; return SQLITE_OK; } @@ -911,17 +854,13 @@ static int kvvfsOpen( ** returning. */ static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ - int rc /* The JS impl can fail with OOM in argument conversion */; if( strcmp(zPath, "local-journal")==0 ){ - rc = sqlite3KvvfsMethods.xRcrdDelete("local", "jrnl"); + sqlite3KvvfsMethods.xDelete("local", "jrnl"); }else if( strcmp(zPath, "session-journal")==0 ){ - rc = sqlite3KvvfsMethods.xRcrdDelete("session", "jrnl"); - } - else{ - rc = 0; + sqlite3KvvfsMethods.xDelete("session", "jrnl"); } - return rc; + return SQLITE_OK; } /* @@ -929,48 +868,27 @@ static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ ** is available, or false otherwise. */ static int kvvfsAccess( - sqlite3_vfs *pProtoVfs, - const char *zPath, - int flags, + sqlite3_vfs *pProtoVfs, + const char *zPath, + int flags, int *pResOut ){ SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath)); -#if 0 && defined(SQLITE_WASM) - /* - ** This is not having the desired effect in the JS bindings. - ** It's ostensibly the same logic as the #else block, but - ** it's not behaving that way. - ** - ** In JS we map all zPaths to Storage objects, and -journal files - ** are mapped to the storage for the main db (which is is exactly - ** what the mapping of "local-journal" -> "local" is doing). - */ - const char *zKey = (0==sqlite3_strglob("*-journal", zPath)) - ? "jrnl" : "sz"; - *pResOut = - sqlite3KvvfsMethods.xRcrdRead(zPath, zKey, 0, 0)>0; -#else if( strcmp(zPath, "local-journal")==0 ){ - *pResOut = - sqlite3KvvfsMethods.xRcrdRead("local", "jrnl", 0, 0)>0; + *pResOut = sqlite3KvvfsMethods.xRead("local", "jrnl", 0, 0)>0; }else if( strcmp(zPath, "session-journal")==0 ){ - *pResOut = - sqlite3KvvfsMethods.xRcrdRead("session", "jrnl", 0, 0)>0; + *pResOut = sqlite3KvvfsMethods.xRead("session", "jrnl", 0, 0)>0; }else if( strcmp(zPath, "local")==0 ){ - *pResOut = - sqlite3KvvfsMethods.xRcrdRead("local", "sz", 0, 0)>0; + *pResOut = sqlite3KvvfsMethods.xRead("local", "sz", 0, 0)>0; }else if( strcmp(zPath, "session")==0 ){ - *pResOut = - sqlite3KvvfsMethods.xRcrdRead("session", "sz", 0, 0)>0; + *pResOut = sqlite3KvvfsMethods.xRead("session", "sz", 0, 0)>0; }else { *pResOut = 0; } - /*all current JS tests avoid triggering: assert( *pResOut == 0 ); */ -#endif SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut)); return SQLITE_OK; } @@ -981,9 +899,9 @@ static int kvvfsAccess( ** of at least (INST_MAX_PATHNAME+1) bytes. */ static int kvvfsFullPathname( - sqlite3_vfs *pVfs, - const char *zPath, - int nOut, + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, char *zOut ){ size_t nPath; @@ -1006,7 +924,7 @@ static void *kvvfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){ } /* -** Populate the buffer pointed to by zBufOut with nByte bytes of +** Populate the buffer pointed to by zBufOut with nByte bytes of ** random data. */ static int kvvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ @@ -1015,7 +933,7 @@ static int kvvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ } /* -** Sleep for nMicro microseconds. Return the number of microseconds +** Sleep for nMicro microseconds. Return the number of microseconds ** actually slept. */ static int kvvfsSleep(sqlite3_vfs *pVfs, int nMicro){ @@ -1043,7 +961,7 @@ static int kvvfsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ #endif /* SQLITE_OS_KV || SQLITE_OS_UNIX */ #if SQLITE_OS_KV -/* +/* ** This routine is called initialize the KV-vfs as the default VFS. */ int sqlite3_os_init(void){ diff --git a/src/os_unix.c b/src/os_unix.c index 2f75829c8..d73d89924 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5190,7 +5190,7 @@ static int unixShmMap( } /* Map the requested memory region into this processes address space. */ - apNew = (char **)sqlite3_realloc64( + apNew = (char **)sqlite3_realloc( pShmNode->apRegion, nReqRegion*sizeof(char *) ); if( !apNew ){ diff --git a/src/os_win.c b/src/os_win.c index 7583ecc1f..a6b25f2e8 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -43,7 +43,7 @@ ** Are most of the Win32 ANSI APIs available (i.e. with certain exceptions ** based on the sub-platform)? */ -#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_ANSI) +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(SQLITE_WIN32_NO_ANSI) # define SQLITE_WIN32_HAS_ANSI #endif @@ -51,7 +51,7 @@ ** Are most of the Win32 Unicode APIs available (i.e. with certain exceptions ** based on the sub-platform)? */ -#if (SQLITE_OS_WINCE || SQLITE_OS_WINNT) && \ +#if (SQLITE_OS_WINCE || SQLITE_OS_WINNT || SQLITE_OS_WINRT) && \ !defined(SQLITE_WIN32_NO_WIDE) # define SQLITE_WIN32_HAS_WIDE #endif @@ -190,7 +190,16 @@ */ #if SQLITE_WIN32_FILEMAPPING_API && \ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) +/* +** Two of the file mapping APIs are different under WinRT. Figure out which +** set we need. +*/ +#if SQLITE_OS_WINRT +WINBASEAPI HANDLE WINAPI CreateFileMappingFromApp(HANDLE, \ + LPSECURITY_ATTRIBUTES, ULONG, ULONG64, LPCWSTR); +WINBASEAPI LPVOID WINAPI MapViewOfFileFromApp(HANDLE, ULONG, ULONG64, SIZE_T); +#else #if defined(SQLITE_WIN32_HAS_ANSI) WINBASEAPI HANDLE WINAPI CreateFileMappingA(HANDLE, LPSECURITY_ATTRIBUTES, \ DWORD, DWORD, DWORD, LPCSTR); @@ -202,6 +211,7 @@ WINBASEAPI HANDLE WINAPI CreateFileMappingW(HANDLE, LPSECURITY_ATTRIBUTES, \ #endif /* defined(SQLITE_WIN32_HAS_WIDE) */ WINBASEAPI LPVOID WINAPI MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, SIZE_T); +#endif /* SQLITE_OS_WINRT */ /* ** These file mapping APIs are common to both Win32 and WinRT. @@ -492,7 +502,7 @@ static LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; ** This function is not available on Windows CE or WinRT. */ -#if SQLITE_OS_WINCE +#if SQLITE_OS_WINCE || SQLITE_OS_WINRT # define osAreFileApisANSI() 1 #endif @@ -507,7 +517,7 @@ static struct win_syscall { sqlite3_syscall_ptr pCurrent; /* Current value of the system call */ sqlite3_syscall_ptr pDefault; /* Default value */ } aSyscall[] = { -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "AreFileApisANSI", (SYSCALL)AreFileApisANSI, 0 }, #else { "AreFileApisANSI", (SYSCALL)0, 0 }, @@ -546,7 +556,7 @@ static struct win_syscall { #define osCreateFileA ((HANDLE(WINAPI*)(LPCSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[4].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "CreateFileW", (SYSCALL)CreateFileW, 0 }, #else { "CreateFileW", (SYSCALL)0, 0 }, @@ -555,7 +565,7 @@ static struct win_syscall { #define osCreateFileW ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[5].pCurrent) -#if defined(SQLITE_WIN32_HAS_ANSI) && \ +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) && \ SQLITE_WIN32_CREATEFILEMAPPINGA { "CreateFileMappingA", (SYSCALL)CreateFileMappingA, 0 }, @@ -566,8 +576,8 @@ static struct win_syscall { #define osCreateFileMappingA ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ DWORD,DWORD,DWORD,LPCSTR))aSyscall[6].pCurrent) -#if (SQLITE_OS_WINCE || defined(SQLITE_WIN32_HAS_WIDE)) && \ - (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) +#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "CreateFileMappingW", (SYSCALL)CreateFileMappingW, 0 }, #else { "CreateFileMappingW", (SYSCALL)0, 0 }, @@ -576,7 +586,7 @@ static struct win_syscall { #define osCreateFileMappingW ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ DWORD,DWORD,DWORD,LPCWSTR))aSyscall[7].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "CreateMutexW", (SYSCALL)CreateMutexW, 0 }, #else { "CreateMutexW", (SYSCALL)0, 0 }, @@ -662,7 +672,7 @@ static struct win_syscall { #define osGetDiskFreeSpaceA ((BOOL(WINAPI*)(LPCSTR,LPDWORD,LPDWORD,LPDWORD, \ LPDWORD))aSyscall[18].pCurrent) -#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "GetDiskFreeSpaceW", (SYSCALL)GetDiskFreeSpaceW, 0 }, #else { "GetDiskFreeSpaceW", (SYSCALL)0, 0 }, @@ -679,7 +689,7 @@ static struct win_syscall { #define osGetFileAttributesA ((DWORD(WINAPI*)(LPCSTR))aSyscall[20].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "GetFileAttributesW", (SYSCALL)GetFileAttributesW, 0 }, #else { "GetFileAttributesW", (SYSCALL)0, 0 }, @@ -696,7 +706,11 @@ static struct win_syscall { #define osGetFileAttributesExW ((BOOL(WINAPI*)(LPCWSTR,GET_FILEEX_INFO_LEVELS, \ LPVOID))aSyscall[22].pCurrent) +#if !SQLITE_OS_WINRT { "GetFileSize", (SYSCALL)GetFileSize, 0 }, +#else + { "GetFileSize", (SYSCALL)0, 0 }, +#endif #define osGetFileSize ((DWORD(WINAPI*)(HANDLE,LPDWORD))aSyscall[23].pCurrent) @@ -709,7 +723,7 @@ static struct win_syscall { #define osGetFullPathNameA ((DWORD(WINAPI*)(LPCSTR,DWORD,LPSTR, \ LPSTR*))aSyscall[24].pCurrent) -#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "GetFullPathNameW", (SYSCALL)GetFullPathNameW, 0 }, #else { "GetFullPathNameW", (SYSCALL)0, 0 }, @@ -744,10 +758,16 @@ static struct win_syscall { #define osGetProcAddressA ((FARPROC(WINAPI*)(HMODULE, \ LPCSTR))aSyscall[27].pCurrent) +#if !SQLITE_OS_WINRT { "GetSystemInfo", (SYSCALL)GetSystemInfo, 0 }, +#else + { "GetSystemInfo", (SYSCALL)0, 0 }, +#endif + #define osGetSystemInfo ((VOID(WINAPI*)(LPSYSTEM_INFO))aSyscall[28].pCurrent) { "GetSystemTime", (SYSCALL)GetSystemTime, 0 }, + #define osGetSystemTime ((VOID(WINAPI*)(LPSYSTEMTIME))aSyscall[29].pCurrent) #if !SQLITE_OS_WINCE @@ -767,7 +787,7 @@ static struct win_syscall { #define osGetTempPathA ((DWORD(WINAPI*)(DWORD,LPSTR))aSyscall[31].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "GetTempPathW", (SYSCALL)GetTempPathW, 0 }, #else { "GetTempPathW", (SYSCALL)0, 0 }, @@ -775,7 +795,11 @@ static struct win_syscall { #define osGetTempPathW ((DWORD(WINAPI*)(DWORD,LPWSTR))aSyscall[32].pCurrent) +#if !SQLITE_OS_WINRT { "GetTickCount", (SYSCALL)GetTickCount, 0 }, +#else + { "GetTickCount", (SYSCALL)0, 0 }, +#endif #define osGetTickCount ((DWORD(WINAPI*)(VOID))aSyscall[33].pCurrent) @@ -788,7 +812,7 @@ static struct win_syscall { #define osGetVersionExA ((BOOL(WINAPI*)( \ LPOSVERSIONINFOA))aSyscall[34].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) && \ +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ SQLITE_WIN32_GETVERSIONEX { "GetVersionExW", (SYSCALL)GetVersionExW, 0 }, #else @@ -803,12 +827,20 @@ static struct win_syscall { #define osHeapAlloc ((LPVOID(WINAPI*)(HANDLE,DWORD, \ SIZE_T))aSyscall[36].pCurrent) +#if !SQLITE_OS_WINRT { "HeapCreate", (SYSCALL)HeapCreate, 0 }, +#else + { "HeapCreate", (SYSCALL)0, 0 }, +#endif #define osHeapCreate ((HANDLE(WINAPI*)(DWORD,SIZE_T, \ SIZE_T))aSyscall[37].pCurrent) +#if !SQLITE_OS_WINRT { "HeapDestroy", (SYSCALL)HeapDestroy, 0 }, +#else + { "HeapDestroy", (SYSCALL)0, 0 }, +#endif #define osHeapDestroy ((BOOL(WINAPI*)(HANDLE))aSyscall[38].pCurrent) @@ -826,12 +858,16 @@ static struct win_syscall { #define osHeapSize ((SIZE_T(WINAPI*)(HANDLE,DWORD, \ LPCVOID))aSyscall[41].pCurrent) +#if !SQLITE_OS_WINRT { "HeapValidate", (SYSCALL)HeapValidate, 0 }, +#else + { "HeapValidate", (SYSCALL)0, 0 }, +#endif #define osHeapValidate ((BOOL(WINAPI*)(HANDLE,DWORD, \ LPCVOID))aSyscall[42].pCurrent) -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "HeapCompact", (SYSCALL)HeapCompact, 0 }, #else { "HeapCompact", (SYSCALL)0, 0 }, @@ -847,7 +883,7 @@ static struct win_syscall { #define osLoadLibraryA ((HMODULE(WINAPI*)(LPCSTR))aSyscall[44].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) && \ +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ !defined(SQLITE_OMIT_LOAD_EXTENSION) { "LoadLibraryW", (SYSCALL)LoadLibraryW, 0 }, #else @@ -856,11 +892,15 @@ static struct win_syscall { #define osLoadLibraryW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[45].pCurrent) +#if !SQLITE_OS_WINRT { "LocalFree", (SYSCALL)LocalFree, 0 }, +#else + { "LocalFree", (SYSCALL)0, 0 }, +#endif #define osLocalFree ((HLOCAL(WINAPI*)(HLOCAL))aSyscall[46].pCurrent) -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "LockFile", (SYSCALL)LockFile, 0 }, #else { "LockFile", (SYSCALL)0, 0 }, @@ -882,7 +922,8 @@ static struct win_syscall { LPOVERLAPPED))aSyscall[48].pCurrent) #endif -#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 +#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "MapViewOfFile", (SYSCALL)MapViewOfFile, 0 }, #else { "MapViewOfFile", (SYSCALL)0, 0 }, @@ -910,12 +951,20 @@ static struct win_syscall { #define osSetEndOfFile ((BOOL(WINAPI*)(HANDLE))aSyscall[53].pCurrent) +#if !SQLITE_OS_WINRT { "SetFilePointer", (SYSCALL)SetFilePointer, 0 }, +#else + { "SetFilePointer", (SYSCALL)0, 0 }, +#endif #define osSetFilePointer ((DWORD(WINAPI*)(HANDLE,LONG,PLONG, \ DWORD))aSyscall[54].pCurrent) +#if !SQLITE_OS_WINRT { "Sleep", (SYSCALL)Sleep, 0 }, +#else + { "Sleep", (SYSCALL)0, 0 }, +#endif #define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent) @@ -924,7 +973,7 @@ static struct win_syscall { #define osSystemTimeToFileTime ((BOOL(WINAPI*)(const SYSTEMTIME*, \ LPFILETIME))aSyscall[56].pCurrent) -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "UnlockFile", (SYSCALL)UnlockFile, 0 }, #else { "UnlockFile", (SYSCALL)0, 0 }, @@ -962,6 +1011,15 @@ static struct win_syscall { #define osWriteFile ((BOOL(WINAPI*)(HANDLE,LPCVOID,DWORD,LPDWORD, \ LPOVERLAPPED))aSyscall[61].pCurrent) +#if SQLITE_OS_WINRT + { "CreateEventExW", (SYSCALL)CreateEventExW, 0 }, +#else + { "CreateEventExW", (SYSCALL)0, 0 }, +#endif + +#define osCreateEventExW ((HANDLE(WINAPI*)(LPSECURITY_ATTRIBUTES,LPCWSTR, \ + DWORD,DWORD))aSyscall[62].pCurrent) + /* ** For WaitForSingleObject(), MSDN says: ** @@ -971,7 +1029,7 @@ static struct win_syscall { { "WaitForSingleObject", (SYSCALL)WaitForSingleObject, 0 }, #define osWaitForSingleObject ((DWORD(WINAPI*)(HANDLE, \ - DWORD))aSyscall[62].pCurrent) + DWORD))aSyscall[63].pCurrent) #if !SQLITE_OS_WINCE { "WaitForSingleObjectEx", (SYSCALL)WaitForSingleObjectEx, 0 }, @@ -980,12 +1038,69 @@ static struct win_syscall { #endif #define osWaitForSingleObjectEx ((DWORD(WINAPI*)(HANDLE,DWORD, \ - BOOL))aSyscall[63].pCurrent) + BOOL))aSyscall[64].pCurrent) + +#if SQLITE_OS_WINRT + { "SetFilePointerEx", (SYSCALL)SetFilePointerEx, 0 }, +#else + { "SetFilePointerEx", (SYSCALL)0, 0 }, +#endif + +#define osSetFilePointerEx ((BOOL(WINAPI*)(HANDLE,LARGE_INTEGER, \ + PLARGE_INTEGER,DWORD))aSyscall[65].pCurrent) +#if SQLITE_OS_WINRT + { "GetFileInformationByHandleEx", (SYSCALL)GetFileInformationByHandleEx, 0 }, +#else + { "GetFileInformationByHandleEx", (SYSCALL)0, 0 }, +#endif + +#define osGetFileInformationByHandleEx ((BOOL(WINAPI*)(HANDLE, \ + FILE_INFO_BY_HANDLE_CLASS,LPVOID,DWORD))aSyscall[66].pCurrent) + +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) + { "MapViewOfFileFromApp", (SYSCALL)MapViewOfFileFromApp, 0 }, +#else + { "MapViewOfFileFromApp", (SYSCALL)0, 0 }, +#endif + +#define osMapViewOfFileFromApp ((LPVOID(WINAPI*)(HANDLE,ULONG,ULONG64, \ + SIZE_T))aSyscall[67].pCurrent) + +#if SQLITE_OS_WINRT + { "CreateFile2", (SYSCALL)CreateFile2, 0 }, +#else + { "CreateFile2", (SYSCALL)0, 0 }, +#endif + +#define osCreateFile2 ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD,DWORD, \ + LPCREATEFILE2_EXTENDED_PARAMETERS))aSyscall[68].pCurrent) + +#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_LOAD_EXTENSION) + { "LoadPackagedLibrary", (SYSCALL)LoadPackagedLibrary, 0 }, +#else + { "LoadPackagedLibrary", (SYSCALL)0, 0 }, +#endif + +#define osLoadPackagedLibrary ((HMODULE(WINAPI*)(LPCWSTR, \ + DWORD))aSyscall[69].pCurrent) + +#if SQLITE_OS_WINRT + { "GetTickCount64", (SYSCALL)GetTickCount64, 0 }, +#else + { "GetTickCount64", (SYSCALL)0, 0 }, +#endif + +#define osGetTickCount64 ((ULONGLONG(WINAPI*)(VOID))aSyscall[70].pCurrent) + +#if SQLITE_OS_WINRT { "GetNativeSystemInfo", (SYSCALL)GetNativeSystemInfo, 0 }, +#else + { "GetNativeSystemInfo", (SYSCALL)0, 0 }, +#endif #define osGetNativeSystemInfo ((VOID(WINAPI*)( \ - LPSYSTEM_INFO))aSyscall[64].pCurrent) + LPSYSTEM_INFO))aSyscall[71].pCurrent) #if defined(SQLITE_WIN32_HAS_ANSI) { "OutputDebugStringA", (SYSCALL)OutputDebugStringA, 0 }, @@ -993,7 +1108,7 @@ static struct win_syscall { { "OutputDebugStringA", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[65].pCurrent) +#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[72].pCurrent) #if defined(SQLITE_WIN32_HAS_WIDE) { "OutputDebugStringW", (SYSCALL)OutputDebugStringW, 0 }, @@ -1001,11 +1116,20 @@ static struct win_syscall { { "OutputDebugStringW", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[66].pCurrent) +#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[73].pCurrent) { "GetProcessHeap", (SYSCALL)GetProcessHeap, 0 }, -#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[67].pCurrent) +#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[74].pCurrent) + +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) + { "CreateFileMappingFromApp", (SYSCALL)CreateFileMappingFromApp, 0 }, +#else + { "CreateFileMappingFromApp", (SYSCALL)0, 0 }, +#endif + +#define osCreateFileMappingFromApp ((HANDLE(WINAPI*)(HANDLE, \ + LPSECURITY_ATTRIBUTES,ULONG,ULONG64,LPCWSTR))aSyscall[75].pCurrent) /* ** NOTE: On some sub-platforms, the InterlockedCompareExchange "function" @@ -1020,25 +1144,25 @@ static struct win_syscall { { "InterlockedCompareExchange", (SYSCALL)InterlockedCompareExchange, 0 }, #define osInterlockedCompareExchange ((LONG(WINAPI*)(LONG \ - SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[68].pCurrent) + SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[76].pCurrent) #endif /* defined(InterlockedCompareExchange) */ -#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID { "UuidCreate", (SYSCALL)UuidCreate, 0 }, #else { "UuidCreate", (SYSCALL)0, 0 }, #endif -#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[69].pCurrent) +#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[77].pCurrent) -#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID { "UuidCreateSequential", (SYSCALL)UuidCreateSequential, 0 }, #else { "UuidCreateSequential", (SYSCALL)0, 0 }, #endif #define osUuidCreateSequential \ - ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[70].pCurrent) + ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[78].pCurrent) #if !defined(SQLITE_NO_SYNC) && SQLITE_MAX_MMAP_SIZE>0 { "FlushViewOfFile", (SYSCALL)FlushViewOfFile, 0 }, @@ -1047,7 +1171,7 @@ static struct win_syscall { #endif #define osFlushViewOfFile \ - ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[71].pCurrent) + ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[79].pCurrent) /* ** If SQLITE_ENABLE_SETLK_TIMEOUT is defined, we require CreateEvent() @@ -1064,7 +1188,7 @@ static struct win_syscall { #define osCreateEvent ( \ (HANDLE(WINAPI*) (LPSECURITY_ATTRIBUTES,BOOL,BOOL,LPCSTR)) \ - aSyscall[72].pCurrent \ + aSyscall[80].pCurrent \ ) /* @@ -1081,7 +1205,7 @@ static struct win_syscall { { "CancelIo", (SYSCALL)0, 0 }, #endif -#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[73].pCurrent) +#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent) #if defined(SQLITE_WIN32_HAS_WIDE) && defined(_WIN32) { "GetModuleHandleW", (SYSCALL)GetModuleHandleW, 0 }, @@ -1089,7 +1213,7 @@ static struct win_syscall { { "GetModuleHandleW", (SYSCALL)0, 0 }, #endif -#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[74].pCurrent) +#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[82].pCurrent) #ifndef _WIN32 { "getenv", (SYSCALL)getenv, 0 }, @@ -1097,7 +1221,7 @@ static struct win_syscall { { "getenv", (SYSCALL)0, 0 }, #endif -#define osGetenv ((const char *(*)(const char *))aSyscall[75].pCurrent) +#define osGetenv ((const char *(*)(const char *))aSyscall[83].pCurrent) #ifndef _WIN32 { "getcwd", (SYSCALL)getcwd, 0 }, @@ -1105,7 +1229,7 @@ static struct win_syscall { { "getcwd", (SYSCALL)0, 0 }, #endif -#define osGetcwd ((char*(*)(char*,size_t))aSyscall[76].pCurrent) +#define osGetcwd ((char*(*)(char*,size_t))aSyscall[84].pCurrent) #ifndef _WIN32 { "readlink", (SYSCALL)readlink, 0 }, @@ -1113,7 +1237,7 @@ static struct win_syscall { { "readlink", (SYSCALL)0, 0 }, #endif -#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[77].pCurrent) +#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[85].pCurrent) #ifndef _WIN32 { "lstat", (SYSCALL)lstat, 0 }, @@ -1121,7 +1245,7 @@ static struct win_syscall { { "lstat", (SYSCALL)0, 0 }, #endif -#define osLstat ((int(*)(const char*,struct stat*))aSyscall[78].pCurrent) +#define osLstat ((int(*)(const char*,struct stat*))aSyscall[86].pCurrent) #ifndef _WIN32 { "__errno", (SYSCALL)__errno, 0 }, @@ -1129,7 +1253,7 @@ static struct win_syscall { { "__errno", (SYSCALL)0, 0 }, #endif -#define osErrno (*((int*(*)(void))aSyscall[79].pCurrent)()) +#define osErrno (*((int*(*)(void))aSyscall[87].pCurrent)()) #ifndef _WIN32 { "cygwin_conv_path", (SYSCALL)cygwin_conv_path, 0 }, @@ -1138,7 +1262,7 @@ static struct win_syscall { #endif #define osCygwin_conv_path ((size_t(*)(unsigned int, \ - const void *, void *, size_t))aSyscall[80].pCurrent) + const void *, void *, size_t))aSyscall[88].pCurrent) }; /* End of the overrideable system calls */ @@ -1242,10 +1366,10 @@ int sqlite3_win32_compact_heap(LPUINT pnLargest){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT if( (nLargest=osHeapCompact(hHeap, SQLITE_WIN32_HEAP_FLAGS))==0 ){ DWORD lastErrno = osGetLastError(); if( lastErrno==NO_ERROR ){ @@ -1358,11 +1482,28 @@ void sqlite3_win32_write_debug(const char *zBuf, int nBuf){ } #endif /* _WIN32 */ +/* +** The following routine suspends the current thread for at least ms +** milliseconds. This is equivalent to the Win32 Sleep() interface. +*/ +#if SQLITE_OS_WINRT +static HANDLE sleepObj = NULL; +#endif + void sqlite3_win32_sleep(DWORD milliseconds){ +#if SQLITE_OS_WINRT + if ( sleepObj==NULL ){ + sleepObj = osCreateEventExW(NULL, NULL, CREATE_EVENT_MANUAL_RESET, + SYNCHRONIZE); + } + assert( sleepObj!=NULL ); + osWaitForSingleObjectEx(sleepObj, milliseconds, FALSE); +#else osSleep(milliseconds); +#endif } -#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && \ +#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ SQLITE_THREADSAFE>0 DWORD sqlite3Win32Wait(HANDLE hObject){ DWORD rc; @@ -1386,7 +1527,7 @@ DWORD sqlite3Win32Wait(HANDLE hObject){ #if !SQLITE_WIN32_GETVERSIONEX # define osIsNT() (1) -#elif SQLITE_OS_WINCE || !defined(SQLITE_WIN32_HAS_ANSI) +#elif SQLITE_OS_WINCE || SQLITE_OS_WINRT || !defined(SQLITE_WIN32_HAS_ANSI) # define osIsNT() (1) #elif !defined(SQLITE_WIN32_HAS_WIDE) # define osIsNT() (0) @@ -1399,7 +1540,13 @@ DWORD sqlite3Win32Wait(HANDLE hObject){ ** based on the NT kernel. */ int sqlite3_win32_is_nt(void){ -#if SQLITE_WIN32_GETVERSIONEX +#if SQLITE_OS_WINRT + /* + ** NOTE: The WinRT sub-platform is always assumed to be based on the NT + ** kernel. + */ + return 1; +#elif SQLITE_WIN32_GETVERSIONEX if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){ #if defined(SQLITE_WIN32_HAS_ANSI) OSVERSIONINFOA sInfo; @@ -1441,7 +1588,7 @@ static void *winMemMalloc(int nBytes){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif assert( nBytes>=0 ); @@ -1463,7 +1610,7 @@ static void winMemFree(void *pPrior){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif if( !pPrior ) return; /* Passing NULL to HeapFree is undefined. */ @@ -1484,7 +1631,7 @@ static void *winMemRealloc(void *pPrior, int nBytes){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif assert( nBytes>=0 ); @@ -1512,7 +1659,7 @@ static int winMemSize(void *p){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, p) ); #endif if( !p ) return 0; @@ -1542,7 +1689,7 @@ static int winMemInit(void *pAppData){ assert( pWinMemData->magic1==WINMEM_MAGIC1 ); assert( pWinMemData->magic2==WINMEM_MAGIC2 ); -#if SQLITE_WIN32_HEAP_CREATE +#if !SQLITE_OS_WINRT && SQLITE_WIN32_HEAP_CREATE if( !pWinMemData->hHeap ){ DWORD dwInitialSize = SQLITE_WIN32_HEAP_INIT_SIZE; DWORD dwMaximumSize = (DWORD)sqlite3GlobalConfig.nHeap; @@ -1575,7 +1722,7 @@ static int winMemInit(void *pAppData){ #endif assert( pWinMemData->hHeap!=0 ); assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif return SQLITE_OK; @@ -1593,7 +1740,7 @@ static void winMemShutdown(void *pAppData){ if( pWinMemData->hHeap ){ assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif if( pWinMemData->bOwned ){ @@ -1974,6 +2121,17 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ char *zOut = 0; if( osIsNT() ){ +#if SQLITE_OS_WINRT + WCHAR zTempWide[SQLITE_WIN32_MAX_ERRMSG_CHARS+1]; + dwLen = osFormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + lastErrno, + 0, + zTempWide, + SQLITE_WIN32_MAX_ERRMSG_CHARS, + 0); +#else LPWSTR zTempWide = NULL; dwLen = osFormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | @@ -1984,13 +2142,16 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ (LPWSTR) &zTempWide, 0, 0); +#endif if( dwLen > 0 ){ /* allocate a buffer and convert to UTF8 */ sqlite3BeginBenignMalloc(); zOut = winUnicodeToUtf8(zTempWide); sqlite3EndBenignMalloc(); +#if !SQLITE_OS_WINRT /* free the system buffer allocated by FormatMessage */ osLocalFree(zTempWide); +#endif } } #ifdef SQLITE_WIN32_HAS_ANSI @@ -2651,6 +2812,7 @@ static int winHandleUnlock(HANDLE h, int iOff, int nByte){ static int winHandleSeek(HANDLE h, sqlite3_int64 iOffset){ int rc = SQLITE_OK; /* Return value */ +#if !SQLITE_OS_WINRT LONG upperBits; /* Most sig. 32 bits of new offset */ LONG lowerBits; /* Least sig. 32 bits of new offset */ DWORD dwRet; /* Value returned by SetFilePointer() */ @@ -2672,7 +2834,20 @@ static int winHandleSeek(HANDLE h, sqlite3_int64 iOffset){ rc = SQLITE_IOERR_SEEK; } } - OSTRACE(("SEEK file=%p, offset=%lld rc=%s\n", h, iOffset,sqlite3ErrName(rc))); +#else + /* This implementation works for WinRT. */ + LARGE_INTEGER x; /* The new offset */ + BOOL bRet; /* Value returned by SetFilePointerEx() */ + + x.QuadPart = iOffset; + bRet = osSetFilePointerEx(h, x, 0, FILE_BEGIN); + + if(!bRet){ + rc = SQLITE_IOERR_SEEK; + } +#endif + + OSTRACE(("SEEK file=%p, offset=%lld rc=%s\n", h, iOffset, sqlite3ErrName(rc))); return rc; } @@ -2973,6 +3148,17 @@ static int winHandleTruncate(HANDLE h, sqlite3_int64 nByte){ */ static int winHandleSize(HANDLE h, sqlite3_int64 *pnByte){ int rc = SQLITE_OK; + +#if SQLITE_OS_WINRT + FILE_STANDARD_INFO info; + BOOL b; + b = osGetFileInformationByHandleEx(h, FileStandardInfo, &info, sizeof(info)); + if( b ){ + *pnByte = info.EndOfFile.QuadPart; + }else{ + rc = SQLITE_IOERR_FSTAT; + } +#else DWORD upperBits = 0; DWORD lowerBits = 0; @@ -2982,6 +3168,8 @@ static int winHandleSize(HANDLE h, sqlite3_int64 *pnByte){ if( lowerBits==INVALID_FILE_SIZE && osGetLastError()!=NO_ERROR ){ rc = SQLITE_IOERR_FSTAT; } +#endif + return rc; } @@ -3180,6 +3368,20 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ assert( pSize!=0 ); SimulateIOError(return SQLITE_IOERR_FSTAT); OSTRACE(("SIZE file=%p, pSize=%p\n", pFile->h, pSize)); + +#if SQLITE_OS_WINRT + { + FILE_STANDARD_INFO info; + if( osGetFileInformationByHandleEx(pFile->h, FileStandardInfo, + &info, sizeof(info)) ){ + *pSize = info.EndOfFile.QuadPart; + }else{ + pFile->lastErrno = osGetLastError(); + rc = winLogError(SQLITE_IOERR_FSTAT, pFile->lastErrno, + "winFileSize", pFile->zPath); + } + } +#else { DWORD upperBits; DWORD lowerBits; @@ -3194,6 +3396,7 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ "winFileSize", pFile->zPath); } } +#endif OSTRACE(("SIZE file=%p, pSize=%p, *pSize=%lld, rc=%s\n", pFile->h, pSize, *pSize, sqlite3ErrName(rc))); return rc; @@ -4155,6 +4358,20 @@ static int winHandleOpen( ** TODO: retry-on-ioerr. */ if( osIsNT() ){ +#if SQLITE_OS_WINRT + CREATEFILE2_EXTENDED_PARAMETERS extendedParameters; + memset(&extendedParameters, 0, sizeof(extendedParameters)); + extendedParameters.dwSize = sizeof(extendedParameters); + extendedParameters.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; + extendedParameters.dwFileFlags = flag_overlapped; + extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS; + h = osCreateFile2((LPCWSTR)zConverted, + (GENERIC_READ | (bReadonly ? 0 : GENERIC_WRITE)),/* dwDesiredAccess */ + FILE_SHARE_READ | FILE_SHARE_WRITE, /* dwShareMode */ + OPEN_ALWAYS, /* dwCreationDisposition */ + &extendedParameters + ); +#else h = osCreateFileW((LPCWSTR)zConverted, /* lpFileName */ (GENERIC_READ | (bReadonly ? 0 : GENERIC_WRITE)), /* dwDesiredAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, /* dwShareMode */ @@ -4163,6 +4380,7 @@ static int winHandleOpen( FILE_ATTRIBUTE_NORMAL|flag_overlapped, NULL ); +#endif }else{ /* Due to pre-processor directives earlier in this file, ** SQLITE_WIN32_HAS_ANSI is always defined if osIsNT() is false. */ @@ -4630,7 +4848,9 @@ static int winShmMap( HANDLE hMap = NULL; /* file-mapping handle */ void *pMap = 0; /* Mapped memory region */ -#if defined(SQLITE_WIN32_HAS_WIDE) +#if SQLITE_OS_WINRT + hMap = osCreateFileMappingFromApp(hShared, NULL, protect, nByte, NULL); +#elif defined(SQLITE_WIN32_HAS_WIDE) hMap = osCreateFileMappingW(hShared, NULL, protect, 0, nByte, NULL); #elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA hMap = osCreateFileMappingA(hShared, NULL, protect, 0, nByte, NULL); @@ -4642,9 +4862,15 @@ static int winShmMap( if( hMap ){ int iOffset = pShmNode->nRegion*szRegion; int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; +#if SQLITE_OS_WINRT + pMap = osMapViewOfFileFromApp(hMap, flags, + iOffset - iOffsetShift, szRegion + iOffsetShift + ); +#else pMap = osMapViewOfFile(hMap, flags, 0, iOffset - iOffsetShift, szRegion + iOffsetShift ); +#endif OSTRACE(("SHM-MAP-MAP pid=%lu, region=%d, offset=%d, size=%d, rc=%s\n", osGetCurrentProcessId(), pShmNode->nRegion, iOffset, szRegion, pMap ? "ok" : "failed")); @@ -4777,7 +5003,9 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ flags |= FILE_MAP_WRITE; } #endif -#if defined(SQLITE_WIN32_HAS_WIDE) +#if SQLITE_OS_WINRT + pFd->hMap = osCreateFileMappingFromApp(pFd->h, NULL, protect, nMap, NULL); +#elif defined(SQLITE_WIN32_HAS_WIDE) pFd->hMap = osCreateFileMappingW(pFd->h, NULL, protect, (DWORD)((nMap>>32) & 0xffffffff), (DWORD)(nMap & 0xffffffff), NULL); @@ -4797,7 +5025,11 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ } assert( (nMap % winSysInfo.dwPageSize)==0 ); assert( sizeof(SIZE_T)==sizeof(sqlite3_int64) || nMap<=0xffffffff ); +#if SQLITE_OS_WINRT + pNew = osMapViewOfFileFromApp(pFd->hMap, flags, 0, (SIZE_T)nMap); +#else pNew = osMapViewOfFile(pFd->hMap, flags, 0, 0, (SIZE_T)nMap); +#endif if( pNew==NULL ){ osCloseHandle(pFd->hMap); pFd->hMap = NULL; @@ -5132,6 +5364,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ } #endif +#if !SQLITE_OS_WINRT && defined(_WIN32) else if( osIsNT() ){ char *zMulti; LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) ); @@ -5185,6 +5418,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ } } #endif /* SQLITE_WIN32_HAS_ANSI */ +#endif /* !SQLITE_OS_WINRT */ /* ** Check to make sure the temporary directory ends with an appropriate @@ -5359,6 +5593,13 @@ static int winOpen( memset(pFile, 0, sizeof(winFile)); pFile->h = INVALID_HANDLE_VALUE; +#if SQLITE_OS_WINRT + if( !zUtf8Name && !sqlite3_temp_directory ){ + sqlite3_log(SQLITE_ERROR, + "sqlite3_temp_directory variable should be set for WinRT"); + } +#endif + /* If the second argument to this function is NULL, generate a ** temporary file name to use */ @@ -5441,6 +5682,31 @@ static int winOpen( #endif if( osIsNT() ){ +#if SQLITE_OS_WINRT + CREATEFILE2_EXTENDED_PARAMETERS extendedParameters; + extendedParameters.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS); + extendedParameters.dwFileAttributes = + dwFlagsAndAttributes & FILE_ATTRIBUTE_MASK; + extendedParameters.dwFileFlags = dwFlagsAndAttributes & FILE_FLAG_MASK; + extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS; + extendedParameters.lpSecurityAttributes = NULL; + extendedParameters.hTemplateFile = NULL; + do{ + h = osCreateFile2((LPCWSTR)zConverted, + dwDesiredAccess, + dwShareMode, + dwCreationDisposition, + &extendedParameters); + if( h!=INVALID_HANDLE_VALUE ) break; + if( isReadWrite ){ + int rc2; + sqlite3BeginBenignMalloc(); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); + sqlite3EndBenignMalloc(); + if( rc2==SQLITE_OK && isRO ) break; + } + }while( winRetryIoerr(&cnt, &lastErrno) ); +#else do{ h = osCreateFileW((LPCWSTR)zConverted, dwDesiredAccess, @@ -5457,6 +5723,7 @@ static int winOpen( if( rc2==SQLITE_OK && isRO ) break; } }while( winRetryIoerr(&cnt, &lastErrno) ); +#endif } #ifdef SQLITE_WIN32_HAS_ANSI else{ @@ -5593,7 +5860,25 @@ static int winDelete( } if( osIsNT() ){ do { +#if SQLITE_OS_WINRT + WIN32_FILE_ATTRIBUTE_DATA sAttrData; + memset(&sAttrData, 0, sizeof(sAttrData)); + if ( osGetFileAttributesExW(zConverted, GetFileExInfoStandard, + &sAttrData) ){ + attr = sAttrData.dwFileAttributes; + }else{ + lastErrno = osGetLastError(); + if( lastErrno==ERROR_FILE_NOT_FOUND + || lastErrno==ERROR_PATH_NOT_FOUND ){ + rc = SQLITE_IOERR_DELETE_NOENT; /* Already gone? */ + }else{ + rc = SQLITE_ERROR; + } + break; + } +#else attr = osGetFileAttributesW(zConverted); +#endif if ( attr==INVALID_FILE_ATTRIBUTES ){ lastErrno = osGetLastError(); if( lastErrno==ERROR_FILE_NOT_FOUND @@ -5716,7 +6001,6 @@ static int winAccess( attr = sAttrData.dwFileAttributes; } }else{ - if( noRetry ) lastErrno = osGetLastError(); winLogIoerr(cnt, __LINE__); if( lastErrno!=ERROR_FILE_NOT_FOUND && lastErrno!=ERROR_PATH_NOT_FOUND ){ sqlite3_free(zConverted); @@ -5885,7 +6169,7 @@ static int winFullPathnameNoMutex( int nFull, /* Size of output buffer in bytes */ char *zFull /* Output buffer */ ){ -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT int nByte; void *zConverted; char *zOut; @@ -5974,7 +6258,7 @@ static int winFullPathnameNoMutex( } #endif /* __CYGWIN__ */ -#if SQLITE_OS_WINCE && defined(_WIN32) +#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && defined(_WIN32) SimulateIOError( return SQLITE_ERROR ); /* WinCE has no concept of a relative pathname, or so I am told. */ /* WinRT has no way to convert a relative path to an absolute one. */ @@ -5993,7 +6277,7 @@ static int winFullPathnameNoMutex( return SQLITE_OK; #endif -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT #if defined(_WIN32) /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this @@ -6125,7 +6409,11 @@ static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){ return 0; } if( osIsNT() ){ +#if SQLITE_OS_WINRT + h = osLoadPackagedLibrary((LPCWSTR)zConverted, 0); +#else h = osLoadLibraryW((LPCWSTR)zConverted); +#endif } #ifdef SQLITE_WIN32_HAS_ANSI else{ @@ -6207,16 +6495,23 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ DWORD pid = osGetCurrentProcessId(); xorMemory(&e, (unsigned char*)&pid, sizeof(DWORD)); } +#if SQLITE_OS_WINRT + { + ULONGLONG cnt = osGetTickCount64(); + xorMemory(&e, (unsigned char*)&cnt, sizeof(ULONGLONG)); + } +#else { DWORD cnt = osGetTickCount(); xorMemory(&e, (unsigned char*)&cnt, sizeof(DWORD)); } +#endif /* SQLITE_OS_WINRT */ { LARGE_INTEGER i; osQueryPerformanceCounter(&i); xorMemory(&e, (unsigned char*)&i, sizeof(LARGE_INTEGER)); } -#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID { UUID id; memset(&id, 0, sizeof(UUID)); @@ -6226,7 +6521,7 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ osUuidCreateSequential(&id); xorMemory(&e, (unsigned char*)&id, sizeof(UUID)); } -#endif /* !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID */ +#endif /* !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID */ return e.nXor>nBuf ? nBuf : e.nXor; #endif /* defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS) */ } @@ -6457,16 +6752,15 @@ int sqlite3_os_init(void){ /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==81 ); - assert( strcmp(aSyscall[0].zName,"AreFileApisANSI")==0 ); - assert( strcmp(aSyscall[20].zName,"GetFileAttributesA")==0 ); - assert( strcmp(aSyscall[40].zName,"HeapReAlloc")==0 ); - assert( strcmp(aSyscall[60].zName,"WideCharToMultiByte")==0 ); - assert( strcmp(aSyscall[80].zName,"cygwin_conv_path")==0 ); + assert( ArraySize(aSyscall)==89 ); /* get memory map allocation granularity */ memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); +#if SQLITE_OS_WINRT + osGetNativeSystemInfo(&winSysInfo); +#else osGetSystemInfo(&winSysInfo); +#endif assert( winSysInfo.dwAllocationGranularity>0 ); assert( winSysInfo.dwPageSize>0 ); @@ -6490,9 +6784,17 @@ int sqlite3_os_init(void){ } int sqlite3_os_end(void){ +#if SQLITE_OS_WINRT + if( sleepObj!=NULL ){ + osCloseHandle(sleepObj); + sleepObj = NULL; + } +#endif + #ifndef SQLITE_OMIT_WAL winBigLock = 0; #endif + return SQLITE_OK; } diff --git a/src/os_win.h b/src/os_win.h index 696486c19..a0845f003 100644 --- a/src/os_win.h +++ b/src/os_win.h @@ -58,6 +58,14 @@ # define SQLITE_OS_WINCE 0 #endif +/* +** Determine if we are dealing with WinRT, which provides only a subset of +** the full Win32 API. +*/ +#if !defined(SQLITE_OS_WINRT) +# define SQLITE_OS_WINRT 0 +#endif + /* ** For WinCE, some API function parameters do not appear to be declared as ** volatile. @@ -72,7 +80,7 @@ ** For some Windows sub-platforms, the _beginthreadex() / _endthreadex() ** functions are not available (e.g. those not using MSVC, Cygwin, etc). */ -#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && \ +#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ SQLITE_THREADSAFE>0 && !defined(__CYGWIN__) # define SQLITE_OS_WIN_THREADS 1 #else diff --git a/src/pager.c b/src/pager.c index 61b391d6b..cbaef186a 100644 --- a/src/pager.c +++ b/src/pager.c @@ -813,8 +813,6 @@ int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); if( iRead ) return 0; /* Case (4) */ } -#else - UNUSED_PARAMETER(pgno); #endif assert( pPager->fd->pMethods->xDeviceCharacteristics!=0 ); if( (pPager->fd->pMethods->xDeviceCharacteristics(pPager->fd) @@ -1235,17 +1233,17 @@ static int jrnlBufferSize(Pager *pPager){ */ #ifdef SQLITE_CHECK_PAGES /* -** Return a 64-bit hash of the page data for pPage. +** Return a 32-bit hash of the page data for pPage. */ -static u64 pager_datahash(int nByte, unsigned char *pData){ - u64 hash = 0; +static u32 pager_datahash(int nByte, unsigned char *pData){ + u32 hash = 0; int i; for(i=0; i<nByte; i++){ hash = (hash*1039) + pData[i]; } return hash; } -static u64 pager_pagehash(PgHdr *pPage){ +static u32 pager_pagehash(PgHdr *pPage){ return pager_datahash(pPage->pPager->pageSize, (unsigned char *)pPage->pData); } static void pager_set_pagehash(PgHdr *pPage){ @@ -4194,8 +4192,6 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,a); pPager->pWal = 0; } -#else - UNUSED_PARAMETER(db); #endif pager_reset(pPager); if( MEMDB ){ diff --git a/src/parse.y b/src/parse.y index f5a6bed14..617eb7303 100644 --- a/src/parse.y +++ b/src/parse.y @@ -21,11 +21,9 @@ */ } -// Setup for the parser stack -%stack_size 50 // Initial stack size -%stack_size_limit parserStackSizeLimit // Function returning max stack size -%realloc parserStackRealloc // realloc() for the stack -%free parserStackFree // free() for the stack +// Function used to enlarge the parser stack, if needed +%realloc parserStackRealloc +%free sqlite3_free // All token codes are small integers with #defines that begin with "TK_" %token_prefix TK_ @@ -51,7 +49,7 @@ } } %stack_overflow { - if( pParse->nErr==0 ) sqlite3ErrorMsg(pParse, "Recursion limit"); + sqlite3OomFault(pParse->db); } // The name of the generated procedure that implements the parser @@ -585,23 +583,8 @@ cmd ::= select(X). { ** sqlite3_realloc() that includes a call to sqlite3FaultSim() to facilitate ** testing. */ - static void *parserStackRealloc( - void *pOld, /* Prior allocation */ - sqlite3_uint64 newSize, /* Requested new alloation size */ - Parse *pParse /* Parsing context */ - ){ - void *p = sqlite3FaultSim(700) ? 0 : sqlite3_realloc(pOld, newSize); - if( p==0 ) sqlite3OomFault(pParse->db); - return p; - } - static void parserStackFree(void *pOld, Parse *pParse){ - (void)pParse; - sqlite3_free(pOld); - } - - /* Return an integer that is the maximum allowed stack size */ - static int parserStackSizeLimit(Parse *pParse){ - return pParse->db->aLimit[SQLITE_LIMIT_PARSER_DEPTH]; + static void *parserStackRealloc(void *pOld, sqlite3_uint64 newSize){ + return sqlite3FaultSim(700) ? 0 : sqlite3_realloc(pOld, newSize); } } @@ -833,36 +816,19 @@ fullname(A) ::= nm(X) DOT nm(Y). { %type xfullname {SrcList*} %destructor xfullname {sqlite3SrcListDelete(pParse->db, $$);} -xfullname(A) ::= nm(X). { - A = sqlite3SrcListAppend(pParse,0,&X,0); - if( IN_RENAME_OBJECT && A ) sqlite3RenameTokenMap(pParse, A->a[0].zName, &X); +xfullname(A) ::= nm(X). + {A = sqlite3SrcListAppend(pParse,0,&X,0); /*A-overwrites-X*/} +xfullname(A) ::= nm(X) DOT nm(Y). + {A = sqlite3SrcListAppend(pParse,0,&X,&Y); /*A-overwrites-X*/} +xfullname(A) ::= nm(X) DOT nm(Y) AS nm(Z). { + A = sqlite3SrcListAppend(pParse,0,&X,&Y); /*A-overwrites-X*/ + if( A ) A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z); } -xfullname(A) ::= nm(X) DOT nm(Y). { - A = sqlite3SrcListAppend(pParse,0,&X,&Y); - if( IN_RENAME_OBJECT && A ) sqlite3RenameTokenMap(pParse, A->a[0].zName, &Y); -} -xfullname(A) ::= nm(X) AS nm(Z). { - A = sqlite3SrcListAppend(pParse,0,&X,0); - if( A ){ - if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, A->a[0].zName, &X); - }else{ - A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z); - } - } -} -xfullname(A) ::= nm(X) DOT nm(Y) AS nm(Z). { - A = sqlite3SrcListAppend(pParse,0,&X,&Y); - if( A ){ - if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, A->a[0].zName, &Y); - }else{ - A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z); - } - } +xfullname(A) ::= nm(X) AS nm(Z). { + A = sqlite3SrcListAppend(pParse,0,&X,0); /*A-overwrites-X*/ + if( A ) A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z); } - %type joinop {int} joinop(X) ::= COMMA|JOIN. { X = JT_INNER; } joinop(X) ::= JOIN_KW(A) JOIN. @@ -1193,12 +1159,7 @@ expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). { term(A) ::= NULL|FLOAT|BLOB(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} term(A) ::= STRING(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} term(A) ::= INTEGER(X). { - int iValue; - if( sqlite3GetInt32(X.z, &iValue)==0 ){ - A = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &X, 0); - }else{ - A = sqlite3ExprInt32(pParse->db, iValue); - } + A = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &X, 1); if( A ) A->w.iOfst = (int)(X.z - pParse->zTail); } expr(A) ::= VARIABLE(X). { @@ -1380,67 +1341,43 @@ expr(A) ::= expr(A) likeop(OP) expr(Y) ESCAPE expr(E). [LIKE_KW] { if( A ) A->flags |= EP_InfixFunc; } -%include { - /* Create a TK_ISNULL or TK_NOTNULL expression, perhaps optimized to - ** to TK_TRUEFALSE, if possible */ - static Expr *sqlite3PExprIsNull( - Parse *pParse, /* Parsing context */ - int op, /* TK_ISNULL or TK_NOTNULL */ - Expr *pLeft /* Operand */ - ){ - Expr *p = pLeft; - assert( op==TK_ISNULL || op==TK_NOTNULL ); - assert( pLeft!=0 ); - while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ - p = p->pLeft; - assert( p!=0 ); - } - switch( p->op ){ - case TK_INTEGER: - case TK_STRING: - case TK_FLOAT: - case TK_BLOB: - sqlite3ExprDeferredDelete(pParse, pLeft); - return sqlite3ExprInt32(pParse->db, op==TK_NOTNULL); - default: - break; - } - return sqlite3PExpr(pParse, op, pLeft, 0); - } +expr(A) ::= expr(A) ISNULL|NOTNULL(E). {A = sqlite3PExpr(pParse,@E,A,0);} +expr(A) ::= expr(A) NOT NULL. {A = sqlite3PExpr(pParse,TK_NOTNULL,A,0);} - /* Create a TK_IS or TK_ISNOT operator, perhaps optimized to - ** TK_ISNULL or TK_NOTNULL or TK_TRUEFALSE. */ - static Expr *sqlite3PExprIs( - Parse *pParse, /* Parsing context */ - int op, /* TK_IS or TK_ISNOT */ - Expr *pLeft, /* Left operand */ - Expr *pRight /* Right operand */ - ){ - if( pRight && pRight->op==TK_NULL ){ - sqlite3ExprDeferredDelete(pParse, pRight); - return sqlite3PExprIsNull(pParse, op==TK_IS ? TK_ISNULL : TK_NOTNULL, pLeft); +%include { + /* A routine to convert a binary TK_IS or TK_ISNOT expression into a + ** unary TK_ISNULL or TK_NOTNULL expression. */ + static void binaryToUnaryIfNull(Parse *pParse, Expr *pY, Expr *pA, int op){ + sqlite3 *db = pParse->db; + if( pA && pY && pY->op==TK_NULL && !IN_RENAME_OBJECT ){ + pA->op = (u8)op; + sqlite3ExprDelete(db, pA->pRight); + pA->pRight = 0; } - return sqlite3PExpr(pParse, op, pLeft, pRight); } } -expr(A) ::= expr(A) ISNULL|NOTNULL(E). {A = sqlite3PExprIsNull(pParse,@E,A);} -expr(A) ::= expr(A) NOT NULL. {A = sqlite3PExprIsNull(pParse,TK_NOTNULL,A);} - -// expr1 IS expr2 same as expr1 IS NOT DISTINCT FROM expr2 -// expr1 IS NOT expr2 same as expr1 IS DISTINCT FROM expr2 +// expr1 IS expr2 +// expr1 IS NOT expr2 // +// If expr2 is NULL then code as TK_ISNULL or TK_NOTNULL. If expr2 +// is any other expression, code as TK_IS or TK_ISNOT. +// expr(A) ::= expr(A) IS expr(Y). { - A = sqlite3PExprIs(pParse, TK_IS, A, Y); + A = sqlite3PExpr(pParse,TK_IS,A,Y); + binaryToUnaryIfNull(pParse, Y, A, TK_ISNULL); } expr(A) ::= expr(A) IS NOT expr(Y). { - A = sqlite3PExprIs(pParse, TK_ISNOT, A, Y); + A = sqlite3PExpr(pParse,TK_ISNOT,A,Y); + binaryToUnaryIfNull(pParse, Y, A, TK_NOTNULL); } expr(A) ::= expr(A) IS NOT DISTINCT FROM expr(Y). { - A = sqlite3PExprIs(pParse, TK_IS, A, Y); + A = sqlite3PExpr(pParse,TK_IS,A,Y); + binaryToUnaryIfNull(pParse, Y, A, TK_ISNULL); } expr(A) ::= expr(A) IS DISTINCT FROM expr(Y). { - A = sqlite3PExprIs(pParse, TK_ISNOT, A, Y); + A = sqlite3PExpr(pParse,TK_ISNOT,A,Y); + binaryToUnaryIfNull(pParse, Y, A, TK_NOTNULL); } expr(A) ::= NOT(B) expr(X). @@ -1770,13 +1707,28 @@ when_clause(A) ::= WHEN expr(X). { A = X; } %type trigger_cmd_list {TriggerStep*} %destructor trigger_cmd_list {sqlite3DeleteTriggerStep(pParse->db, $$);} trigger_cmd_list(A) ::= trigger_cmd_list(A) trigger_cmd(X) SEMI. { + assert( A!=0 ); A->pLast->pNext = X; A->pLast = X; } trigger_cmd_list(A) ::= trigger_cmd(A) SEMI. { + assert( A!=0 ); A->pLast = A; } +// Disallow qualified table names on INSERT, UPDATE, and DELETE statements +// within a trigger. The table to INSERT, UPDATE, or DELETE is always in +// the same database as the table that the trigger fires on. +// +%type trnm {Token} +trnm(A) ::= nm(A). +trnm(A) ::= nm DOT nm(X). { + A = X; + sqlite3ErrorMsg(pParse, + "qualified table names are not allowed on INSERT, UPDATE, and DELETE " + "statements within triggers"); +} + // Disallow the INDEX BY and NOT INDEXED clauses on UPDATE and DELETE // statements within triggers. We make a specific error message for this // since it is an exception to the default grammar rules. @@ -1799,17 +1751,17 @@ tridxby ::= NOT INDEXED. { %destructor trigger_cmd {sqlite3DeleteTriggerStep(pParse->db, $$);} // UPDATE trigger_cmd(A) ::= - UPDATE(B) orconf(R) xfullname(X) tridxby SET setlist(Y) from(F) where_opt(Z) scanpt(E). - {A = sqlite3TriggerUpdateStep(pParse, X, F, Y, Z, R, B.z, E);} + UPDATE(B) orconf(R) trnm(X) tridxby SET setlist(Y) from(F) where_opt(Z) scanpt(E). + {A = sqlite3TriggerUpdateStep(pParse, &X, F, Y, Z, R, B.z, E);} // INSERT trigger_cmd(A) ::= scanpt(B) insert_cmd(R) INTO - xfullname(X) idlist_opt(F) select(S) upsert(U) scanpt(Z). { - A = sqlite3TriggerInsertStep(pParse,X,F,S,R,U,B,Z);/*A-overwrites-R*/ + trnm(X) idlist_opt(F) select(S) upsert(U) scanpt(Z). { + A = sqlite3TriggerInsertStep(pParse,&X,F,S,R,U,B,Z);/*A-overwrites-R*/ } // DELETE -trigger_cmd(A) ::= DELETE(B) FROM xfullname(X) tridxby where_opt(Y) scanpt(E). - {A = sqlite3TriggerDeleteStep(pParse, X, Y, B.z, E);} +trigger_cmd(A) ::= DELETE(B) FROM trnm(X) tridxby where_opt(Y) scanpt(E). + {A = sqlite3TriggerDeleteStep(pParse, &X, Y, B.z, E);} // SELECT trigger_cmd(A) ::= scanpt(B) select(X) scanpt(E). @@ -1879,42 +1831,22 @@ cmd ::= ANALYZE nm(X) dbnm(Y). {sqlite3Analyze(pParse, &X, &Y);} cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). { sqlite3AlterRenameTable(pParse,X,&Z); } - -// The ALTER TABLE ADD COLUMN command. This is broken into two sections so -// that sqlite3AlterBeginAddColumn() is called before parsing the various -// constraints and so on (carglist) attached to the new column definition. -cmd ::= alter_add(Y) carglist. { +cmd ::= ALTER TABLE add_column_fullname + ADD kwcolumn_opt columnname(Y) carglist. { Y.n = (int)(pParse->sLastToken.z-Y.z) + pParse->sLastToken.n; sqlite3AlterFinishAddColumn(pParse, &Y); } -alter_add(A) ::= ALTER TABLE fullname(X) ADD kwcolumn_opt nm(Y) typetoken(Z). { - disableLookaside(pParse); - sqlite3AlterBeginAddColumn(pParse, X); - sqlite3AddColumn(pParse, Y, Z); - A = Y; -} - cmd ::= ALTER TABLE fullname(X) DROP kwcolumn_opt nm(Y). { sqlite3AlterDropColumn(pParse, X, &Y); } + +add_column_fullname ::= fullname(X). { + disableLookaside(pParse); + sqlite3AlterBeginAddColumn(pParse, X); +} cmd ::= ALTER TABLE fullname(X) RENAME kwcolumn_opt nm(Y) TO nm(Z). { sqlite3AlterRenameColumn(pParse, X, &Y, &Z); } -cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Y). { - sqlite3AlterDropConstraint(pParse, X, &Y, 0); -} -cmd ::= ALTER TABLE fullname(X) ALTER kwcolumn_opt nm(Y) DROP NOT NULL. { - sqlite3AlterDropConstraint(pParse, X, 0, &Y); -} -cmd ::= ALTER TABLE fullname(X) ALTER kwcolumn_opt nm(Y) SET NOT(Z) NULL onconf. { - sqlite3AlterSetNotNull(pParse, X, &Y, &Z); -} -cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT(Y) nm(Z) CHECK LP(A) expr RP(B) onconf. { - sqlite3AlterAddConstraint(pParse, X, &Y, &Z, A.z+1, (B.z-A.z-1)); -} -cmd ::= ALTER TABLE fullname(X) ADD CHECK(Y) LP(A) expr RP(B) onconf. { - sqlite3AlterAddConstraint(pParse, X, &Y, 0, A.z+1, (B.z-A.z-1)); -} kwcolumn_opt ::= . kwcolumn_opt ::= COLUMNKW. diff --git a/src/pcache.h b/src/pcache.h index dafb59390..f945dab1a 100644 --- a/src/pcache.h +++ b/src/pcache.h @@ -29,10 +29,10 @@ struct PgHdr { PCache *pCache; /* PRIVATE: Cache that owns this page */ PgHdr *pDirty; /* Transient list of dirty sorted by pgno */ Pager *pPager; /* The pager this page is part of */ + Pgno pgno; /* Page number for this page */ #ifdef SQLITE_CHECK_PAGES - u64 pageHash; /* Hash of page content */ + u32 pageHash; /* Hash of page content */ #endif - Pgno pgno; /* Page number for this page */ u16 flags; /* PGHDR flags defined below */ /********************************************************************** diff --git a/src/prepare.c b/src/prepare.c index be9e496f1..539360b74 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -33,8 +33,7 @@ static void corruptSchema( static const char *azAlterType[] = { "rename", "drop column", - "add column", - "drop constraint" + "add column" }; *pData->pzErrMsg = sqlite3MPrintf(db, "error in %s %s after %s: %s", azObj[0], azObj[1], diff --git a/src/printf.c b/src/printf.c index d9f3c229d..f75ed3b8a 100644 --- a/src/printf.c +++ b/src/printf.c @@ -549,7 +549,7 @@ void sqlite3_str_vappendf( }else{ iRound = precision+1; } - sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 20 : 16); + sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 26 : 16); if( s.isSpecial ){ if( s.isSpecial==2 ){ bufpt = flag_zeropad ? "null" : "NaN"; @@ -1228,14 +1228,6 @@ int sqlite3_str_length(sqlite3_str *p){ return p ? p->nChar : 0; } -/* Truncate the text of the string to be no more than N bytes. */ -void sqlite3_str_truncate(sqlite3_str *p, int N){ - if( p!=0 && N>=0 && (u32)N<p->nChar ){ - p->nChar = N; - p->zText[p->nChar] = 0; - } -} - /* Return the current value for p */ char *sqlite3_str_value(sqlite3_str *p){ if( p==0 || p->nChar==0 ) return 0; @@ -1256,17 +1248,6 @@ void sqlite3_str_reset(StrAccum *p){ p->zText = 0; } -/* -** Destroy a dynamically allocate sqlite3_str object and all -** of its content, all in one call. -*/ -void sqlite3_str_free(sqlite3_str *p){ - if( p ){ - sqlite3_str_reset(p); - sqlite3_free(p); - } -} - /* ** Initialize a string accumulator. ** diff --git a/src/resolve.c b/src/resolve.c index 5b6d6c9c5..c3ec56136 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -936,7 +936,7 @@ static int exprProbability(Expr *p){ double r = -1.0; if( p->op!=TK_FLOAT ) return -1; assert( !ExprHasProperty(p, EP_IntValue) ); - sqlite3AtoF(p->u.zToken, &r); + sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8); assert( r>=0.0 ); if( r>1.0 ) return -1; return (int)(r*134217728.0); @@ -1656,8 +1656,10 @@ static int resolveCompoundOrderBy( /* Convert the ORDER BY term into an integer column number iCol, ** taking care to preserve the COLLATE clause if it exists. */ if( !IN_RENAME_OBJECT ){ - Expr *pNew = sqlite3ExprInt32(db, iCol); + Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); if( pNew==0 ) return 1; + pNew->flags |= EP_IntValue; + pNew->u.iValue = iCol; if( pItem->pExpr==pE ){ pItem->pExpr = pNew; }else{ @@ -2011,6 +2013,10 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } #endif + /* The ORDER BY and GROUP BY clauses may not refer to terms in + ** outer queries + */ + sNC.pNext = 0; sNC.ncFlags |= NC_AllowAgg|NC_AllowWin; /* If this is a converted compound query, move the ORDER BY clause from diff --git a/src/select.c b/src/select.c index e8e9f36a8..3ef7cc0a3 100644 --- a/src/select.c +++ b/src/select.c @@ -21,7 +21,7 @@ */ typedef struct DistinctCtx DistinctCtx; struct DistinctCtx { - u8 isTnct; /* 0: Not distinct. 1: DISTINCT 2: DISTINCT and ORDER BY */ + u8 isTnct; /* 0: Not distinct. 1: DISTICT 2: DISTINCT and ORDER BY */ u8 eTnctType; /* One of the WHERE_DISTINCT_* operators */ int tabTnct; /* Ephemeral table used for DISTINCT processing */ int addrTnct; /* Address of OP_OpenEphemeral opcode for tabTnct */ @@ -151,6 +151,8 @@ Select *sqlite3SelectNew( pNew->iLimit = 0; pNew->iOffset = 0; pNew->selId = ++pParse->nSelect; + pNew->addrOpenEphm[0] = -1; + pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = 0; if( pSrc==0 ) pSrc = sqlite3DbMallocZero(pParse->db, SZ_SRCLIST_1); pNew->pSrc = pSrc; @@ -659,10 +661,6 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ pRight->fg.isOn = 1; p->selFlags |= SF_OnToWhere; } - - if( IsVirtual(pRightTab) && joinType==EP_OuterON && pRight->u1.pFuncArg ){ - p->selFlags |= SF_OnToWhere; - } } return 0; } @@ -1302,6 +1300,29 @@ static void selectInnerLoop( } switch( eDest ){ + /* In this mode, write each query result to the key of the temporary + ** table iParm. + */ +#ifndef SQLITE_OMIT_COMPOUND_SELECT + case SRT_Union: { + int r1; + r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); + sqlite3ReleaseTempReg(pParse, r1); + break; + } + + /* Construct a record from the query result, but instead of + ** saving that record, use it as a key to delete elements from + ** the temporary table iParm. + */ + case SRT_Except: { + sqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nResultCol); + break; + } +#endif /* SQLITE_OMIT_COMPOUND_SELECT */ + /* Store the result as data using a unique key. */ case SRT_Fifo: @@ -2414,8 +2435,8 @@ void sqlite3SubqueryColumnTypes( } } if( zType ){ - const i64 k = strlen(zType); - n = strlen(pCol->zCnName); + const i64 k = sqlite3Strlen30(zType); + n = sqlite3Strlen30(pCol->zCnName); pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+k+2); pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); if( pCol->zCnName ){ @@ -2588,9 +2609,9 @@ static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){ ** function is responsible for ensuring that this structure is eventually ** freed. */ -static KeyInfo *multiSelectByMergeKeyInfo(Parse *pParse, Select *p, int nExtra){ +static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ ExprList *pOrderBy = p->pOrderBy; - int nOrderBy = (pOrderBy!=0) ? pOrderBy->nExpr : 0; + int nOrderBy = ALWAYS(pOrderBy!=0) ? pOrderBy->nExpr : 0; sqlite3 *db = pParse->db; KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1); if( pRet ){ @@ -2723,7 +2744,7 @@ static void generateWithRecursiveQuery( regCurrent = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol); if( pOrderBy ){ - KeyInfo *pKeyInfo = multiSelectByMergeKeyInfo(pParse, p, 1); + KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1); sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iQueue, pOrderBy->nExpr+2, 0, (char*)pKeyInfo, P4_KEYINFO); destQueue.pOrderBy = pOrderBy; @@ -2732,28 +2753,8 @@ static void generateWithRecursiveQuery( } VdbeComment((v, "Queue table")); if( iDistinct ){ - /* Generate an ephemeral table used to enforce distinctness on the - ** output of the recursive part of the CTE. - */ - KeyInfo *pKeyInfo; /* Collating sequence for the result set */ - CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ - - assert( p->pNext==0 ); - assert( p->pEList!=0 ); - nCol = p->pEList->nExpr; - pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCol, 1); - if( pKeyInfo ){ - for(i=0, apColl=pKeyInfo->aColl; i<nCol; i++, apColl++){ - *apColl = multiSelectCollSeq(pParse, p, i); - if( 0==*apColl ){ - *apColl = pParse->db->pDfltColl; - } - } - sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iDistinct, nCol, 0, - (void*)pKeyInfo, P4_KEYINFO); - }else{ - assert( pParse->nErr>0 ); - } + p->addrOpenEphm[0] = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iDistinct, 0); + p->selFlags |= SF_UsesEphemeral; } /* Detach the ORDER BY clause from the compound SELECT */ @@ -2828,7 +2829,7 @@ static void generateWithRecursiveQuery( #endif /* SQLITE_OMIT_CTE */ /* Forward references */ -static int multiSelectByMerge( +static int multiSelectOrderBy( Parse *pParse, /* Parsing context */ Select *p, /* The right-most of SELECTs to be coded */ SelectDest *pDest /* What to do with query results */ @@ -2977,26 +2978,12 @@ static int multiSelect( generateWithRecursiveQuery(pParse, p, &dest); }else #endif + + /* Compound SELECTs that have an ORDER BY clause are handled separately. + */ if( p->pOrderBy ){ - /* If the compound has an ORDER BY clause, then always use the merge - ** algorithm. */ - return multiSelectByMerge(pParse, p, pDest); - }else if( p->op!=TK_ALL ){ - /* If the compound is EXCEPT, INTERSECT, or UNION (anything other than - ** UNION ALL) then also always use the merge algorithm. However, the - ** multiSelectByMerge() routine requires that the compound have an - ** ORDER BY clause, and it doesn't right now. So invent one first. */ - Expr *pOne = sqlite3ExprInt32(db, 1); - p->pOrderBy = sqlite3ExprListAppend(pParse, 0, pOne); - if( pParse->nErr ) goto multi_select_end; - assert( p->pOrderBy!=0 ); - p->pOrderBy->a[0].u.x.iOrderByCol = 1; - return multiSelectByMerge(pParse, p, pDest); + return multiSelectOrderBy(pParse, p, pDest); }else{ - /* For a UNION ALL compound without ORDER BY, simply run the left - ** query, then run the right query */ - int addr = 0; - int nLimit = 0; /* Initialize to suppress harmless compiler warning */ #ifndef SQLITE_OMIT_EXPLAIN if( pPrior->pPrior==0 ){ @@ -3004,49 +2991,300 @@ static int multiSelect( ExplainQueryPlan((pParse, 1, "LEFT-MOST SUBQUERY")); } #endif - assert( !pPrior->pLimit ); - pPrior->iLimit = p->iLimit; - pPrior->iOffset = p->iOffset; - pPrior->pLimit = sqlite3ExprDup(db, p->pLimit, 0); - TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); - rc = sqlite3Select(pParse, pPrior, &dest); - sqlite3ExprDelete(db, pPrior->pLimit); - pPrior->pLimit = 0; - if( rc ){ - goto multi_select_end; - } - p->pPrior = 0; - p->iLimit = pPrior->iLimit; - p->iOffset = pPrior->iOffset; - if( p->iLimit ){ - addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); - VdbeComment((v, "Jump ahead if LIMIT reached")); - if( p->iOffset ){ - sqlite3VdbeAddOp3(v, OP_OffsetLimit, - p->iLimit, p->iOffset+1, p->iOffset); - } - } - ExplainQueryPlan((pParse, 1, "UNION ALL")); - TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); - rc = sqlite3Select(pParse, p, &dest); - testcase( rc!=SQLITE_OK ); - pDelete = p->pPrior; - p->pPrior = pPrior; - p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); - if( p->pLimit - && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse) - && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) - ){ - p->nSelectRow = sqlite3LogEst((u64)nLimit); - } - if( addr ){ - sqlite3VdbeJumpHere(v, addr); + + /* Generate code for the left and right SELECT statements. + */ + switch( p->op ){ + case TK_ALL: { + int addr = 0; + int nLimit = 0; /* Initialize to suppress harmless compiler warning */ + assert( !pPrior->pLimit ); + pPrior->iLimit = p->iLimit; + pPrior->iOffset = p->iOffset; + pPrior->pLimit = p->pLimit; + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); + rc = sqlite3Select(pParse, pPrior, &dest); + pPrior->pLimit = 0; + if( rc ){ + goto multi_select_end; + } + p->pPrior = 0; + p->iLimit = pPrior->iLimit; + p->iOffset = pPrior->iOffset; + if( p->iLimit ){ + addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); + VdbeComment((v, "Jump ahead if LIMIT reached")); + if( p->iOffset ){ + sqlite3VdbeAddOp3(v, OP_OffsetLimit, + p->iLimit, p->iOffset+1, p->iOffset); + } + } + ExplainQueryPlan((pParse, 1, "UNION ALL")); + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); + rc = sqlite3Select(pParse, p, &dest); + testcase( rc!=SQLITE_OK ); + pDelete = p->pPrior; + p->pPrior = pPrior; + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + if( p->pLimit + && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse) + && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) + ){ + p->nSelectRow = sqlite3LogEst((u64)nLimit); + } + if( addr ){ + sqlite3VdbeJumpHere(v, addr); + } + break; + } + case TK_EXCEPT: + case TK_UNION: { + int unionTab; /* Cursor number of the temp table holding result */ + u8 op = 0; /* One of the SRT_ operations to apply to self */ + int priorOp; /* The SRT_ operation to apply to prior selects */ + Expr *pLimit; /* Saved values of p->nLimit */ + int addr; + int emptyBypass = 0; /* IfEmpty opcode to bypass RHS */ + SelectDest uniondest; + + + testcase( p->op==TK_EXCEPT ); + testcase( p->op==TK_UNION ); + priorOp = SRT_Union; + if( dest.eDest==priorOp ){ + /* We can reuse a temporary table generated by a SELECT to our + ** right. + */ + assert( p->pLimit==0 ); /* Not allowed on leftward elements */ + unionTab = dest.iSDParm; + }else{ + /* We will need to create our own temporary table to hold the + ** intermediate results. + */ + unionTab = pParse->nTab++; + assert( p->pOrderBy==0 ); + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0); + assert( p->addrOpenEphm[0] == -1 ); + p->addrOpenEphm[0] = addr; + findRightmost(p)->selFlags |= SF_UsesEphemeral; + assert( p->pEList ); + } + + + /* Code the SELECT statements to our left + */ + assert( !pPrior->pOrderBy ); + sqlite3SelectDestInit(&uniondest, priorOp, unionTab); + TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION left...\n")); + rc = sqlite3Select(pParse, pPrior, &uniondest); + if( rc ){ + goto multi_select_end; + } + + /* Code the current SELECT statement + */ + if( p->op==TK_EXCEPT ){ + op = SRT_Except; + emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, unionTab); + VdbeCoverage(v); + }else{ + assert( p->op==TK_UNION ); + op = SRT_Union; + } + p->pPrior = 0; + pLimit = p->pLimit; + p->pLimit = 0; + uniondest.eDest = op; + ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", + sqlite3SelectOpName(p->op))); + TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION right...\n")); + rc = sqlite3Select(pParse, p, &uniondest); + testcase( rc!=SQLITE_OK ); + assert( p->pOrderBy==0 ); + pDelete = p->pPrior; + p->pPrior = pPrior; + p->pOrderBy = 0; + if( p->op==TK_UNION ){ + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + } + if( emptyBypass ) sqlite3VdbeJumpHere(v, emptyBypass); + sqlite3ExprDelete(db, p->pLimit); + p->pLimit = pLimit; + p->iLimit = 0; + p->iOffset = 0; + + /* Convert the data in the temporary table into whatever form + ** it is that we currently need. + */ + assert( unionTab==dest.iSDParm || dest.eDest!=priorOp ); + assert( p->pEList || db->mallocFailed ); + if( dest.eDest!=priorOp && db->mallocFailed==0 ){ + int iCont, iBreak, iStart; + iBreak = sqlite3VdbeMakeLabel(pParse); + iCont = sqlite3VdbeMakeLabel(pParse); + computeLimitRegisters(pParse, p, iBreak); + sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v); + iStart = sqlite3VdbeCurrentAddr(v); + selectInnerLoop(pParse, p, unionTab, + 0, 0, &dest, iCont, iBreak); + sqlite3VdbeResolveLabel(v, iCont); + sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v); + sqlite3VdbeResolveLabel(v, iBreak); + sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0); + } + break; + } + default: assert( p->op==TK_INTERSECT ); { + int tab1, tab2; + int iCont, iBreak, iStart; + Expr *pLimit; + int addr, iLimit, iOffset; + SelectDest intersectdest; + int r1; + int emptyBypass; + + /* INTERSECT is different from the others since it requires + ** two temporary tables. Hence it has its own case. Begin + ** by allocating the tables we will need. + */ + tab1 = pParse->nTab++; + tab2 = pParse->nTab++; + assert( p->pOrderBy==0 ); + + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0); + assert( p->addrOpenEphm[0] == -1 ); + p->addrOpenEphm[0] = addr; + findRightmost(p)->selFlags |= SF_UsesEphemeral; + assert( p->pEList ); + + /* Code the SELECTs to our left into temporary table "tab1". + */ + sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); + TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT left...\n")); + rc = sqlite3Select(pParse, pPrior, &intersectdest); + if( rc ){ + goto multi_select_end; + } + + /* Initialize LIMIT counters before checking to see if the LHS + ** is empty, in case the jump is taken */ + iBreak = sqlite3VdbeMakeLabel(pParse); + computeLimitRegisters(pParse, p, iBreak); + emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, tab1); VdbeCoverage(v); + + /* Code the current SELECT into temporary table "tab2" + */ + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0); + assert( p->addrOpenEphm[1] == -1 ); + p->addrOpenEphm[1] = addr; + + /* Disable prior SELECTs and the LIMIT counters during the computation + ** of the RHS select */ + pLimit = p->pLimit; + iLimit = p->iLimit; + iOffset = p->iOffset; + p->pPrior = 0; + p->pLimit = 0; + p->iLimit = 0; + p->iOffset = 0; + + intersectdest.iSDParm = tab2; + ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", + sqlite3SelectOpName(p->op))); + TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT right...\n")); + rc = sqlite3Select(pParse, p, &intersectdest); + testcase( rc!=SQLITE_OK ); + pDelete = p->pPrior; + p->pPrior = pPrior; + if( p->nSelectRow>pPrior->nSelectRow ){ + p->nSelectRow = pPrior->nSelectRow; + } + sqlite3ExprDelete(db, p->pLimit); + + /* Reinstate the LIMIT counters prior to running the final intersect */ + p->pLimit = pLimit; + p->iLimit = iLimit; + p->iOffset = iOffset; + + /* Generate code to take the intersection of the two temporary + ** tables. + */ + if( rc ) break; + assert( p->pEList ); + sqlite3VdbeAddOp1(v, OP_Rewind, tab1); + r1 = sqlite3GetTempReg(pParse); + iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1); + iCont = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, r1); + selectInnerLoop(pParse, p, tab1, + 0, 0, &dest, iCont, iBreak); + sqlite3VdbeResolveLabel(v, iCont); + sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v); + sqlite3VdbeResolveLabel(v, iBreak); + sqlite3VdbeAddOp2(v, OP_Close, tab2, 0); + sqlite3VdbeJumpHere(v, emptyBypass); + sqlite3VdbeAddOp2(v, OP_Close, tab1, 0); + break; + } } -#ifndef SQLITE_OMIT_EXPLAIN + + #ifndef SQLITE_OMIT_EXPLAIN if( p->pNext==0 ){ ExplainQueryPlanPop(pParse); } -#endif + #endif + } + if( pParse->nErr ) goto multi_select_end; + + /* Compute collating sequences used by + ** temporary tables needed to implement the compound select. + ** Attach the KeyInfo structure to all temporary tables. + ** + ** This section is run by the right-most SELECT statement only. + ** SELECT statements to the left always skip this part. The right-most + ** SELECT might also skip this part if it has no ORDER BY clause and + ** no temp tables are required. + */ + if( p->selFlags & SF_UsesEphemeral ){ + int i; /* Loop counter */ + KeyInfo *pKeyInfo; /* Collating sequence for the result set */ + Select *pLoop; /* For looping through SELECT statements */ + CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ + int nCol; /* Number of columns in result set */ + + assert( p->pNext==0 ); + assert( p->pEList!=0 ); + nCol = p->pEList->nExpr; + pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); + if( !pKeyInfo ){ + rc = SQLITE_NOMEM_BKPT; + goto multi_select_end; + } + for(i=0, apColl=pKeyInfo->aColl; i<nCol; i++, apColl++){ + *apColl = multiSelectCollSeq(pParse, p, i); + if( 0==*apColl ){ + *apColl = db->pDfltColl; + } + } + + for(pLoop=p; pLoop; pLoop=pLoop->pPrior){ + for(i=0; i<2; i++){ + int addr = pLoop->addrOpenEphm[i]; + if( addr<0 ){ + /* If [0] is unused then [1] is also unused. So we can + ** always safely abort as soon as the first unused slot is found */ + assert( pLoop->addrOpenEphm[1]<0 ); + break; + } + sqlite3VdbeChangeP2(v, addr, nCol); + sqlite3VdbeChangeP4(v, addr, (char*)sqlite3KeyInfoRef(pKeyInfo), + P4_KEYINFO); + pLoop->addrOpenEphm[i] = -1; + } + } + sqlite3KeyInfoUnref(pKeyInfo); } multi_select_end: @@ -3078,8 +3316,8 @@ void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){ ** Code an output subroutine for a coroutine implementation of a ** SELECT statement. ** -** The data to be output is contained in an array of pIn->nSdst registers -** starting at register pIn->iSdst. pDest is where the output should +** The data to be output is contained in pIn->iSdst. There are +** pIn->nSdst columns to be output. pDest is where the output should ** be sent. ** ** regReturn is the number of the register holding the subroutine @@ -3108,8 +3346,6 @@ static int generateOutputSubroutine( int iContinue; int addr; - assert( pIn->eDest==SRT_Coroutine ); - addr = sqlite3VdbeCurrentAddr(v); iContinue = sqlite3VdbeMakeLabel(pParse); @@ -3131,60 +3367,23 @@ static int generateOutputSubroutine( */ codeOffset(v, p->iOffset, iContinue); + assert( pDest->eDest!=SRT_Exists ); + assert( pDest->eDest!=SRT_Table ); switch( pDest->eDest ){ /* Store the result as data using a unique key. */ - case SRT_Fifo: - case SRT_DistFifo: - case SRT_Table: case SRT_EphemTab: { int r1 = sqlite3GetTempReg(pParse); int r2 = sqlite3GetTempReg(pParse); - int iParm = pDest->iSDParm; - testcase( pDest->eDest==SRT_Table ); - testcase( pDest->eDest==SRT_EphemTab ); - testcase( pDest->eDest==SRT_Fifo ); - testcase( pDest->eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r1); -#if !defined(SQLITE_ENABLE_NULL_TRIM) && defined(SQLITE_DEBUG) - /* A destination of SRT_Table and a non-zero iSDParm2 parameter means - ** that this is an "UPDATE ... FROM" on a virtual table or view. In this - ** case set the p5 parameter of the OP_MakeRecord to OPFLAG_NOCHNG_MAGIC. - ** This does not affect operation in any way - it just allows MakeRecord - ** to process OPFLAG_NOCHANGE values without an assert() failing. */ - if( pDest->eDest==SRT_Table && pDest->iSDParm2 ){ - sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); - } -#endif -#ifndef SQLITE_OMIT_CTE - if( pDest->eDest==SRT_DistFifo ){ - /* If the destination is DistFifo, then cursor (iParm+1) is open - ** on an ephemeral index that is used to enforce uniqueness on the - ** total result. At this point, we are processing the setup portion - ** of the recursive CTE using the merge algorithm, so the results are - ** guaranteed to be unique anyhow. But we still need to populate the - ** (iParm+1) cursor for use by the subsequent recursive phase. - */ - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm+1, r1, - pIn->iSdst, pIn->nSdst); - } -#endif - sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2); - sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2); + sqlite3VdbeAddOp2(v, OP_NewRowid, pDest->iSDParm, r2); + sqlite3VdbeAddOp3(v, OP_Insert, pDest->iSDParm, r1, r2); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3ReleaseTempReg(pParse, r2); sqlite3ReleaseTempReg(pParse, r1); break; } - /* If any row exist in the result set, record that fact and abort. - */ - case SRT_Exists: { - sqlite3VdbeAddOp2(v, OP_Integer, 1, pDest->iSDParm); - /* The LIMIT clause will terminate the loop for us */ - break; - } - #ifndef SQLITE_OMIT_SUBQUERY /* If we are creating a set for an "expr IN (SELECT ...)". */ @@ -3231,51 +3430,9 @@ static int generateOutputSubroutine( break; } -#ifndef SQLITE_OMIT_CTE - /* Write the results into a priority queue that is order according to - ** pDest->pOrderBy (in pSO). pDest->iSDParm (in iParm) is the cursor for an - ** index with pSO->nExpr+2 columns. Build a key using pSO for the first - ** pSO->nExpr columns, then make sure all keys are unique by adding a - ** final OP_Sequence column. The last column is the record as a blob. - */ - case SRT_DistQueue: - case SRT_Queue: { - int nKey; - int r1, r2, r3, ii; - ExprList *pSO; - int iParm = pDest->iSDParm; - pSO = pDest->pOrderBy; - assert( pSO ); - nKey = pSO->nExpr; - r1 = sqlite3GetTempReg(pParse); - r2 = sqlite3GetTempRange(pParse, nKey+2); - r3 = r2+nKey+1; - - sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r3); - if( pDest->eDest==SRT_DistQueue ){ - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r3); - } - for(ii=0; ii<nKey; ii++){ - sqlite3VdbeAddOp2(v, OP_SCopy, - pIn->iSdst + pSO->a[ii].u.x.iOrderByCol - 1, - r2+ii); - } - sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey); - sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1); - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, r2, nKey+2); - sqlite3ReleaseTempReg(pParse, r1); - sqlite3ReleaseTempRange(pParse, r2, nKey+2); - break; - } -#endif /* SQLITE_OMIT_CTE */ - - /* Ignore the output */ - case SRT_Discard: { - break; - } - /* If none of the above, then the result destination must be - ** SRT_Output. + ** SRT_Output. This routine is never called with any other + ** destination other than the ones handled above or SRT_Output. ** ** For SRT_Output, results are stored in a sequence of registers. ** Then the OP_ResultRow opcode is used to cause sqlite3_step() to @@ -3303,9 +3460,8 @@ static int generateOutputSubroutine( } /* -** Generate code for a compound SELECT statement using a merge -** algorithm. The compound must have an ORDER BY clause for this -** to work. +** Alternative compound select code generator for cases when there +** is an ORDER BY clause. ** ** We assume a query of the following form: ** @@ -3322,7 +3478,7 @@ static int generateOutputSubroutine( ** ** outB: Move the output of the selectB coroutine into the output ** of the compound query. (Only generated for UNION and -** UNION ALL. EXCEPT and INTERSECT never output a row that +** UNION ALL. EXCEPT and INSERTSECT never output a row that ** appears only in B.) ** ** AltB: Called when there is data from both coroutines and A<B. @@ -3375,8 +3531,10 @@ static int generateOutputSubroutine( ** AeqB: ... ** AgtB: ... ** Init: initialize coroutine registers -** yield coA, on eof goto EofA -** yield coB, on eof goto EofB +** yield coA +** if eof(A) goto EofA +** yield coB +** if eof(B) goto EofB ** Cmpr: Compare A, B ** Jump AltB, AeqB, AgtB ** End: ... @@ -3384,10 +3542,10 @@ static int generateOutputSubroutine( ** We call AltB, AeqB, AgtB, EofA, and EofB "subroutines" but they are not ** actually called using Gosub and they do not Return. EofA and EofB loop ** until all data is exhausted then jump to the "end" label. AltB, AeqB, -** and AgtB jump to either Cmpr or to one of EofA or EofB. +** and AgtB jump to either L2 or to one of EofA or EofB. */ #ifndef SQLITE_OMIT_COMPOUND_SELECT -static int multiSelectByMerge( +static int multiSelectOrderBy( Parse *pParse, /* Parsing context */ Select *p, /* The right-most of SELECTs to be coded */ SelectDest *pDest /* What to do with query results */ @@ -3459,8 +3617,10 @@ static int multiSelectByMerge( if( pItem->u.x.iOrderByCol==i ) break; } if( j==nOrderBy ){ - Expr *pNew = sqlite3ExprInt32(db, i); + Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); if( pNew==0 ) return SQLITE_NOMEM_BKPT; + pNew->flags |= EP_IntValue; + pNew->u.iValue = i; p->pOrderBy = pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew); if( pOrderBy ) pOrderBy->a[nOrderBy++].u.x.iOrderByCol = (u16)i; } @@ -3468,29 +3628,26 @@ static int multiSelectByMerge( } /* Compute the comparison permutation and keyinfo that is used with - ** the permutation to determine if the next row of results comes - ** from selectA or selectB. Also add literal collations to the - ** ORDER BY clause terms so that when selectA and selectB are - ** evaluated, they use the correct collation. + ** the permutation used to determine if the next + ** row of results comes from selectA or selectB. Also add explicit + ** collations to the ORDER BY clause terms so that when the subqueries + ** to the right and the left are evaluated, they use the correct + ** collation. */ aPermute = sqlite3DbMallocRawNN(db, sizeof(u32)*(nOrderBy + 1)); if( aPermute ){ struct ExprList_item *pItem; - int bKeep = 0; aPermute[0] = nOrderBy; for(i=1, pItem=pOrderBy->a; i<=nOrderBy; i++, pItem++){ assert( pItem!=0 ); assert( pItem->u.x.iOrderByCol>0 ); assert( pItem->u.x.iOrderByCol<=p->pEList->nExpr ); aPermute[i] = pItem->u.x.iOrderByCol - 1; - if( aPermute[i]!=(u32)i-1 ) bKeep = 1; - } - if( bKeep==0 ){ - sqlite3DbFreeNN(db, aPermute); - aPermute = 0; } + pKeyMerge = multiSelectOrderByKeyInfo(pParse, p, 1); + }else{ + pKeyMerge = 0; } - pKeyMerge = multiSelectByMergeKeyInfo(pParse, p, 1); /* Allocate a range of temporary registers and the KeyInfo needed ** for the logic that removes duplicate result rows when the @@ -3569,7 +3726,7 @@ static int multiSelectByMerge( */ addrSelectA = sqlite3VdbeCurrentAddr(v) + 1; addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrA, 0, addrSelectA); - VdbeComment((v, "SUBR: next-A")); + VdbeComment((v, "left SELECT")); pPrior->iLimit = regLimitA; ExplainQueryPlan((pParse, 1, "LEFT")); sqlite3Select(pParse, pPrior, &destA); @@ -3581,7 +3738,7 @@ static int multiSelectByMerge( */ addrSelectB = sqlite3VdbeCurrentAddr(v) + 1; addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrB, 0, addrSelectB); - VdbeComment((v, "SUBR: next-B")); + VdbeComment((v, "right SELECT")); savedLimit = p->iLimit; savedOffset = p->iOffset; p->iLimit = regLimitB; @@ -3595,7 +3752,7 @@ static int multiSelectByMerge( /* Generate a subroutine that outputs the current row of the A ** select as the next output row of the compound select. */ - VdbeNoopComment((v, "SUBR: out-A")); + VdbeNoopComment((v, "Output routine for A")); addrOutA = generateOutputSubroutine(pParse, p, &destA, pDest, regOutA, regPrev, pKeyDup, labelEnd); @@ -3604,7 +3761,7 @@ static int multiSelectByMerge( ** select as the next output row of the compound select. */ if( op==TK_ALL || op==TK_UNION ){ - VdbeNoopComment((v, "SUBR: out-B")); + VdbeNoopComment((v, "Output routine for B")); addrOutB = generateOutputSubroutine(pParse, p, &destB, pDest, regOutB, regPrev, pKeyDup, labelEnd); @@ -3617,12 +3774,10 @@ static int multiSelectByMerge( if( op==TK_EXCEPT || op==TK_INTERSECT ){ addrEofA_noB = addrEofA = labelEnd; }else{ - VdbeNoopComment((v, "SUBR: eof-A")); + VdbeNoopComment((v, "eof-A subroutine")); addrEofA = sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); - VdbeComment((v, "out-B")); addrEofA_noB = sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd); VdbeCoverage(v); - VdbeComment((v, "next-B")); sqlite3VdbeGoto(v, addrEofA); p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); } @@ -3634,20 +3789,17 @@ static int multiSelectByMerge( addrEofB = addrEofA; if( p->nSelectRow > pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow; }else{ - VdbeNoopComment((v, "SUBR: eof-B")); + VdbeNoopComment((v, "eof-B subroutine")); addrEofB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); - VdbeComment((v, "out-A")); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, labelEnd); VdbeCoverage(v); - VdbeComment((v, "next-A")); sqlite3VdbeGoto(v, addrEofB); } /* Generate code to handle the case of A<B */ + VdbeNoopComment((v, "A-lt-B subroutine")); addrAltB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); - VdbeComment((v, "out-A")); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v); - VdbeComment((v, "next-A")); sqlite3VdbeGoto(v, labelCmpr); /* Generate code to handle the case of A==B @@ -3658,48 +3810,36 @@ static int multiSelectByMerge( addrAeqB = addrAltB; addrAltB++; }else{ - addrAeqB = addrAltB + 1; + VdbeNoopComment((v, "A-eq-B subroutine")); + addrAeqB = + sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v); + sqlite3VdbeGoto(v, labelCmpr); } /* Generate code to handle the case of A>B */ + VdbeNoopComment((v, "A-gt-B subroutine")); addrAgtB = sqlite3VdbeCurrentAddr(v); if( op==TK_ALL || op==TK_UNION ){ sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); - VdbeComment((v, "out-B")); - sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); - VdbeComment((v, "next-B")); - sqlite3VdbeGoto(v, labelCmpr); - }else{ - addrAgtB++; /* Just do next-B. Might as well use the next-B call - ** in the next code block */ } + sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); + sqlite3VdbeGoto(v, labelCmpr); /* This code runs once to initialize everything. */ sqlite3VdbeJumpHere(v, addr1); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA_noB); VdbeCoverage(v); - VdbeComment((v, "next-A")); - /* v--- Also the A>B case for EXCEPT and INTERSECT */ sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); - VdbeComment((v, "next-B")); /* Implement the main merge loop */ - if( aPermute!=0 ){ - sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); - } sqlite3VdbeResolveLabel(v, labelCmpr); + sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy, (char*)pKeyMerge, P4_KEYINFO); - if( aPermute!=0 ){ - sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); - } - sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); - VdbeCoverageIf(v, op==TK_ALL); - VdbeCoverageIf(v, op==TK_UNION); - VdbeCoverageIf(v, op==TK_EXCEPT); - VdbeCoverageIf(v, op==TK_INTERSECT); + sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); + sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); VdbeCoverage(v); /* Jump to the this point in order to terminate the query. */ @@ -4616,7 +4756,7 @@ static int flattenSubquery( } pSubitem->fg.jointype |= jointype; - /* Begin substituting subquery result set expressions for + /* Now begin substituting subquery result set expressions for ** references to the iParent in the outer query. ** ** Example: @@ -4628,7 +4768,7 @@ static int flattenSubquery( ** We look at every expression in the outer query and every place we see ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10". */ - if( pSub->pOrderBy ){ + if( pSub->pOrderBy && (pParent->selFlags & SF_NoopOrderBy)==0 ){ /* At this point, any non-zero iOrderByCol values indicate that the ** ORDER BY column expression is identical to the iOrderByCol'th ** expression returned by SELECT statement pSub. Since these values @@ -4636,9 +4776,9 @@ static int flattenSubquery( ** zero them before transferring the ORDER BY clause. ** ** Not doing this may cause an error if a subsequent call to this - ** function attempts to flatten a compound sub-query into pParent. - ** See ticket [d11a6e908f]. - */ + ** function attempts to flatten a compound sub-query into pParent + ** (the only way this can happen is if the compound sub-query is + ** currently part of pSub->pSrc). See ticket [d11a6e908f]. */ ExprList *pOrderBy = pSub->pOrderBy; for(i=0; i<pOrderBy->nExpr; i++){ pOrderBy->a[i].u.x.iOrderByCol = 0; @@ -5256,16 +5396,6 @@ static int pushDownWhereTerms( x.pEList = pSubq->pEList; x.pCList = findLeftmostExprlist(pSubq); pNew = substExpr(&x, pNew); - assert( pNew!=0 || pParse->nErr!=0 ); - if( pParse->nErr==0 && pNew->op==TK_IN && ExprUseXSelect(pNew) ){ - assert( pNew->x.pSelect!=0 ); - pNew->x.pSelect->selFlags |= SF_ClonedRhsIn; - assert( pWhere!=0 ); - assert( pWhere->op==TK_IN ); - assert( ExprUseXSelect(pWhere) ); - assert( pWhere->x.pSelect!=0 ); - pWhere->x.pSelect->selFlags |= SF_ClonedRhsIn; - } #ifndef SQLITE_OMIT_WINDOWFUNC if( pSubq->pWin && 0==pushDownWindowCheck(pParse, pSubq, pNew) ){ /* Restriction 6c has prevented push-down in this case */ @@ -5500,14 +5630,14 @@ int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ ** SELECT * FROM (SELECT ... FROM t1 EXCEPT SELECT ... FROM t2) ** ORDER BY ... COLLATE ... ** -** This transformation is necessary because the multiSelectByMerge() routine +** This transformation is necessary because the multiSelectOrderBy() routine ** above that generates the code for a compound SELECT with an ORDER BY clause ** uses a merge algorithm that requires the same collating sequence on the ** result columns as on the ORDER BY clause. See ticket ** http://sqlite.org/src/info/6709574d2a ** ** This transformation is only needed for EXCEPT, INTERSECT, and UNION. -** The UNION ALL operator works fine with multiSelectByMerge() even when +** The UNION ALL operator works fine with multiSelectOrderBy() even when ** there are COLLATE terms in the ORDER BY. */ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ @@ -6053,7 +6183,7 @@ static int selectExpander(Walker *pWalker, Select *p){ } #ifndef SQLITE_OMIT_VIRTUALTABLE else if( ALWAYS(IsVirtual(pTab)) - && (pFrom->fg.fromDDL || (pParse->prepFlags & SQLITE_PREPARE_FROM_DDL)) + && pFrom->fg.fromDDL && ALWAYS(pTab->u.vtab.p!=0) && pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0) ){ @@ -7006,7 +7136,7 @@ static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){ && pExpr->pAggInfo==0 ){ sqlite3 *db = pWalker->pParse->db; - Expr *pNew = sqlite3ExprInt32(db, 1); + Expr *pNew = sqlite3Expr(db, TK_INTEGER, "1"); if( pNew ){ Expr *pWhere = pS->pWhere; SWAP(Expr, *pNew, *pExpr); @@ -7357,6 +7487,7 @@ static SQLITE_NOINLINE void existsToJoin( ExprSetProperty(pWhere, EP_IntValue); assert( p->pWhere!=0 ); pSub->pSrc->a[0].fg.fromExists = 1; + pSub->pSrc->a[0].fg.jointype |= JT_CROSS; p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc); if( pSubWhere ){ p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSubWhere); @@ -7384,7 +7515,6 @@ typedef struct CheckOnCtx CheckOnCtx; struct CheckOnCtx { SrcList *pSrc; /* SrcList for this context */ int iJoin; /* Cursor numbers must be =< than this */ - int bFuncArg; /* True for table-function arg */ CheckOnCtx *pParent; /* Parent context */ }; @@ -7436,9 +7566,7 @@ static int selectCheckOnClausesExpr(Walker *pWalker, Expr *pExpr){ if( iTab>=pSrc->a[0].iCursor && iTab<=pSrc->a[pSrc->nSrc-1].iCursor ){ if( pCtx->iJoin && iTab>pCtx->iJoin ){ sqlite3ErrorMsg(pWalker->pParse, - "%s references tables to its right", - (pCtx->bFuncArg ? "table-function argument" : "ON clause") - ); + "ON clause references tables to its right"); return WRC_Abort; } break; @@ -7476,7 +7604,6 @@ static int selectCheckOnClausesSelect(Walker *pWalker, Select *pSelect){ void sqlite3SelectCheckOnClauses(Parse *pParse, Select *pSelect){ Walker w; CheckOnCtx sCtx; - int ii; assert( pSelect->selFlags & SF_OnToWhere ); assert( pSelect->pSrc!=0 && pSelect->pSrc->nSrc>=2 ); memset(&w, 0, sizeof(w)); @@ -7486,46 +7613,8 @@ void sqlite3SelectCheckOnClauses(Parse *pParse, Select *pSelect){ w.u.pCheckOnCtx = &sCtx; memset(&sCtx, 0, sizeof(sCtx)); sCtx.pSrc = pSelect->pSrc; - sqlite3WalkExpr(&w, pSelect->pWhere); + sqlite3WalkExprNN(&w, pSelect->pWhere); pSelect->selFlags &= ~SF_OnToWhere; - - /* Check for any table-function args that are attached to virtual tables - ** on the RHS of an outer join. They are subject to the same constraints - ** as ON clauses. */ - sCtx.bFuncArg = 1; - for(ii=0; ii<pSelect->pSrc->nSrc; ii++){ - SrcItem *pItem = &pSelect->pSrc->a[ii]; - if( pItem->fg.isTabFunc - && (pItem->fg.jointype & JT_OUTER) - ){ - sCtx.iJoin = pItem->iCursor; - sqlite3WalkExprList(&w, pItem->u1.pFuncArg); - } - } -} - -/* -** If p2 exists and p1 and p2 have the same number of terms, then change -** every term of p1 to have the same sort order as p2 and return true. -** -** If p2 is NULL or p1 and p2 are different lengths, then make no changes -** and return false. -** -** p1 must be non-NULL. -*/ -static int sqlite3CopySortOrder(ExprList *p1, ExprList *p2){ - assert( p1 ); - if( p2 && p1->nExpr==p2->nExpr ){ - int ii; - for(ii=0; ii<p1->nExpr; ii++){ - u8 sortFlags; - sortFlags = p2->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; - p1->a[ii].fg.sortFlags = sortFlags; - } - return 1; - }else{ - return 0; - } } /* @@ -7623,7 +7712,8 @@ int sqlite3Select( assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_Queue ); if( IgnorableDistinct(pDest) ){ - assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Discard || + assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union || + pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard || pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); /* All of these destinations are also able to ignore the ORDER BY clause */ if( p->pOrderBy ){ @@ -7639,6 +7729,7 @@ int sqlite3Select( p->pOrderBy = 0; } p->selFlags &= ~(u32)SF_Distinct; + p->selFlags |= SF_NoopOrderBy; } sqlite3SelectPrep(pParse, p, 0); if( pParse->nErr ){ @@ -8166,8 +8257,7 @@ int sqlite3Select( ** BY and DISTINCT, and an index or separate temp-table for the other. */ if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct - && sqlite3CopySortOrder(pEList, sSort.pOrderBy) - && sqlite3ExprListCompare(pEList, sSort.pOrderBy, -1)==0 + && sqlite3ExprListCompare(sSort.pOrderBy, pEList, -1)==0 && OptimizationEnabled(db, SQLITE_GroupByOrder) #ifndef SQLITE_OMIT_WINDOWFUNC && p->pWin==0 @@ -8381,10 +8471,21 @@ int sqlite3Select( ** but not actually sorted. Either way, record the fact that the ** ORDER BY and GROUP BY clauses are the same by setting the orderByGrp ** variable. */ - if( sqlite3CopySortOrder(pGroupBy, sSort.pOrderBy) - && sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 - ){ - orderByGrp = 1; + if( sSort.pOrderBy && pGroupBy->nExpr==sSort.pOrderBy->nExpr ){ + int ii; + /* The GROUP BY processing doesn't care whether rows are delivered in + ** ASC or DESC order - only that each group is returned contiguously. + ** So set the ASC/DESC flags in the GROUP BY to match those in the + ** ORDER BY to maximize the chances of rows being delivered in an + ** order that makes the ORDER BY redundant. */ + for(ii=0; ii<pGroupBy->nExpr; ii++){ + u8 sortFlags; + sortFlags = sSort.pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; + pGroupBy->a[ii].fg.sortFlags = sortFlags; + } + if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){ + orderByGrp = 1; + } } }else{ assert( 0==sqlite3LogEst(1) ); diff --git a/src/shell.c.in b/src/shell.c.in index ef30194fa..bd4483ff7 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file contains code to implement the "sqlite3" command line +** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. */ #if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) @@ -31,6 +31,14 @@ typedef unsigned short int u16; # include SHELL_STRINGIFY(SQLITE_CUSTOM_INCLUDE) #endif +/* +** Determine if we are dealing with WinRT, which provides only a subset of +** the full Win32 API. +*/ +#if !defined(SQLITE_OS_WINRT) +# define SQLITE_OS_WINRT 0 +#endif + /* ** If SQLITE_SHELL_FIDDLE is defined then the shell is modified ** somewhat for use as a WASM module in a web browser. This flag @@ -39,10 +47,6 @@ typedef unsigned short int u16; ** and this build mode rewires the user input subsystem to account for ** that. */ -#if defined(SQLITE_SHELL_FIDDLE) -# undef SQLITE_OMIT_LOAD_EXTENSION -# define SQLITE_OMIT_LOAD_EXTENSION 1 -#endif /* ** Warning pragmas copied from msvc.h in the core. @@ -104,7 +108,6 @@ typedef unsigned char u8; #include <stdarg.h> #ifndef _WIN32 # include <sys/time.h> -# include <sys/ioctl.h> #endif #if !defined(_WIN32) && !defined(WIN32) @@ -174,6 +177,9 @@ typedef unsigned char u8; #endif #if defined(_WIN32) || defined(WIN32) +# if SQLITE_OS_WINRT +# define SQLITE_OMIT_POPEN 1 +# else # include <io.h> # include <fcntl.h> # define isatty(h) _isatty(h) @@ -188,6 +194,7 @@ typedef unsigned char u8; # endif # undef pclose # define pclose _pclose +# endif #else /* Make sure isatty() has a prototype. */ extern int isatty(int); @@ -218,6 +225,9 @@ typedef unsigned char u8; #define IsAlpha(X) isalpha((unsigned char)X) #if defined(_WIN32) || defined(WIN32) +#if SQLITE_OS_WINRT +#include <intrin.h> +#endif #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include <windows.h> @@ -229,8 +239,6 @@ extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); INCLUDE ../ext/misc/sqlite3_stdio.h INCLUDE ../ext/misc/sqlite3_stdio.c -INCLUDE ../ext/qrf/qrf.h -INCLUDE ../ext/qrf/qrf.c /* Use console I/O package as a direct INCLUDE. */ #define SQLITE_INTERNAL_LINKAGE static @@ -244,67 +252,11 @@ INCLUDE ../ext/qrf/qrf.c # define SQLITE_CIO_NO_FLUSH #endif -/* -** Output routines that are able to redirect to memory rather than -** doing actually I/O. -** Works like. -** -------------- -** cli_printf(FILE*, const char*, ...); fprintf() -** cli_puts(const char*, FILE*); fputs() -** cli_vprintf(FILE*, const char*, va_list); vfprintf() -** -** These are just thin wrappers with the following added semantics: -** If the file-scope variable cli_output_capture is not NULL, and -** if the FILE* argument is stdout or stderr, then rather than -** writing to stdout/stdout, append the text to the cli_output_capture -** variable. -** -** The cli_exit(int) routine works like exit() except that it -** first dumps any capture output to stdout. -*/ -static sqlite3_str *cli_output_capture = 0; -static int cli_printf(FILE *out, const char *zFormat, ...){ - va_list ap; - int rc; - va_start(ap,zFormat); - if( cli_output_capture && (out==stdout || out==stderr) ){ - sqlite3_str_vappendf(cli_output_capture, zFormat, ap); - rc = 1; - }else{ - rc = sqlite3_vfprintf(out, zFormat, ap); - } - va_end(ap); - return rc; -} -static int cli_puts(const char *zText, FILE *out){ - if( cli_output_capture && (out==stdout || out==stderr) ){ - sqlite3_str_appendall(cli_output_capture, zText); - return 1; - } - return sqlite3_fputs(zText, out); -} -#if 0 /* Not currently used - available if we need it later */ -static int cli_vprintf(FILE *out, const char *zFormat, va_list ap){ - if( cli_output_capture && (out==stdout || out==stderr) ){ - sqlite3_str_vappendf(cli_output_capture, zFormat, ap); - return 1; - }else{ - return sqlite3_vfprintf(out, zFormat, ap); - } -} -#endif -static void cli_exit(int rc){ - if( cli_output_capture ){ - char *z = sqlite3_str_finish(cli_output_capture); - sqlite3_fputs(z, stdout); - fflush(stdout); - } - exit(rc); -} - +#define eputz(z) sqlite3_fputs(z,stderr) +#define sputz(fp,z) sqlite3_fputs(z,fp) -#define eputz(z) cli_puts(z,stderr) -#define sputz(fp,z) cli_puts(z,fp) +/* True if the timer is enabled */ +static int enableTimer = 0; /* A version of strcmp() that works with NULL values */ static int cli_strcmp(const char *a, const char *b){ @@ -322,7 +274,7 @@ static int cli_strncmp(const char *a, const char *b, size_t n){ ** Unix epoch (1970-01-01T00:00:00Z) */ static sqlite3_int64 timeOfDay(void){ -#if defined(_WIN64) && _WIN32_WINNT >= _WIN32_WINNT_WIN8 +#if defined(_WIN64) sqlite3_uint64 t; FILETIME tm; GetSystemTimePreciseAsFileTime(&tm); @@ -350,6 +302,152 @@ static sqlite3_int64 timeOfDay(void){ #endif } +#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) +#include <sys/time.h> +#include <sys/resource.h> + +/* VxWorks does not support getrusage() as far as we can determine */ +#if defined(_WRS_KERNEL) || defined(__RTP__) +struct rusage { + struct timeval ru_utime; /* user CPU time used */ + struct timeval ru_stime; /* system CPU time used */ +}; +#define getrusage(A,B) memset(B,0,sizeof(*B)) +#endif + + +/* Saved resource information for the beginning of an operation */ +static struct rusage sBegin; /* CPU time at start */ +static sqlite3_int64 iBegin; /* Wall-clock time at start */ + +/* +** Begin timing an operation +*/ +static void beginTimer(void){ + if( enableTimer ){ + getrusage(RUSAGE_SELF, &sBegin); + iBegin = timeOfDay(); + } +} + +/* Return the difference of two time_structs in seconds */ +static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ + return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + + (double)(pEnd->tv_sec - pStart->tv_sec); +} + +/* +** Print the timing results. +*/ +static void endTimer(FILE *out){ + if( enableTimer ){ + sqlite3_int64 iEnd = timeOfDay(); + struct rusage sEnd; + getrusage(RUSAGE_SELF, &sEnd); + sqlite3_fprintf(out, "Run Time: real %.6f user %.6f sys %.6f\n", + (iEnd - iBegin)*0.000001, + timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); + } +} + +#define BEGIN_TIMER beginTimer() +#define END_TIMER(X) endTimer(X) +#define HAS_TIMER 1 + +#elif (defined(_WIN32) || defined(WIN32)) + +/* Saved resource information for the beginning of an operation */ +static HANDLE hProcess; +static FILETIME ftKernelBegin; +static FILETIME ftUserBegin; +static sqlite3_int64 ftWallBegin; +typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, + LPFILETIME, LPFILETIME); +static GETPROCTIMES getProcessTimesAddr = NULL; + +/* +** Check to see if we have timer support. Return 1 if necessary +** support found (or found previously). +*/ +static int hasTimer(void){ + if( getProcessTimesAddr ){ + return 1; + } else { +#if !SQLITE_OS_WINRT + /* GetProcessTimes() isn't supported in WIN95 and some other Windows + ** versions. See if the version we are running on has it, and if it + ** does, save off a pointer to it and the current process handle. + */ + hProcess = GetCurrentProcess(); + if( hProcess ){ + HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); + if( NULL != hinstLib ){ + getProcessTimesAddr = + (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); + if( NULL != getProcessTimesAddr ){ + return 1; + } + FreeLibrary(hinstLib); + } + } +#endif + } + return 0; +} + +/* +** Begin timing an operation +*/ +static void beginTimer(void){ + if( enableTimer && getProcessTimesAddr ){ + FILETIME ftCreation, ftExit; + getProcessTimesAddr(hProcess,&ftCreation,&ftExit, + &ftKernelBegin,&ftUserBegin); + ftWallBegin = timeOfDay(); + } +} + +/* Return the difference of two FILETIME structs in seconds */ +static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ + sqlite_int64 i64Start = *((sqlite_int64 *) pStart); + sqlite_int64 i64End = *((sqlite_int64 *) pEnd); + return (double) ((i64End - i64Start) / 10000000.0); +} + +/* +** Print the timing results. +*/ +static void endTimer(FILE *out){ + if( enableTimer && getProcessTimesAddr){ + FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; + sqlite3_int64 ftWallEnd = timeOfDay(); + getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); +#ifdef _WIN64 + /* microsecond precision on 64-bit windows */ + sqlite3_fprintf(out, "Run Time: real %.6f user %f sys %f\n", + (ftWallEnd - ftWallBegin)*0.000001, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); +#else + /* millisecond precisino on 32-bit windows */ + sqlite3_fprintf(out, "Run Time: real %.3f user %.3f sys %.3f\n", + (ftWallEnd - ftWallBegin)*0.000001, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); +#endif + } +} + +#define BEGIN_TIMER beginTimer() +#define END_TIMER(X) endTimer(X) +#define HAS_TIMER hasTimer() + +#else +#define BEGIN_TIMER +#define END_TIMER(X) /*no-op*/ +#define HAS_TIMER 0 +#endif /* ** Used to prevent warnings about unused parameters @@ -374,17 +472,12 @@ static int bail_on_error = 0; static int stdin_is_interactive = 1; /* -** Treat stdout like a TTY if true. +** On Windows systems we need to know if standard output is a console +** in order to show that UTF-16 translation is done in the sign-on +** banner. The following variable is true if it is the console. */ static int stdout_is_console = 1; -/* -** Use this value as the width of the output device. Or, figure it -** out at runtime if the value is negative. Or use a default width -** if this value is zero. -*/ -static int stdout_tty_width = -1; - /* ** The following is the open SQLite database. We make a pointer ** to this database a static variable so that it can be accessed @@ -413,14 +506,6 @@ static char mainPrompt[PROMPT_LEN_MAX]; /* Continuation prompt. default: " ...> " */ static char continuePrompt[PROMPT_LEN_MAX]; -/* -** Write I/O traces to the following stream. -*/ -#ifdef SQLITE_ENABLE_IOTRACE -static FILE *iotrace = 0; -#endif - - /* This is variant of the standard-library strncpy() routine with the ** one change that the destination string is always zero-terminated, even ** if there is no zero-terminator in the first n-1 characters of the source @@ -530,7 +615,7 @@ static char *dynamicContinuePrompt(void){ /* Indicate out-of-memory and exit. */ static void shell_out_of_memory(void){ eputz("Error: out of memory\n"); - cli_exit(1); + exit(1); } /* Check a pointer to see if it is NULL. If it is NULL, exit with an @@ -540,6 +625,13 @@ static void shell_check_oom(const void *p){ if( p==0 ) shell_out_of_memory(); } +/* +** Write I/O traces to the following stream. +*/ +#ifdef SQLITE_ENABLE_IOTRACE +static FILE *iotrace = 0; +#endif + /* ** This routine works like printf in that its first argument is a ** format string and subsequent arguments are values to be substituted @@ -554,91 +646,373 @@ static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ va_start(ap, zFormat); z = sqlite3_vmprintf(zFormat, ap); va_end(ap); - cli_printf(iotrace, "%s", z); + sqlite3_fprintf(iotrace, "%s", z); sqlite3_free(z); } #endif +/* 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 */ +} aUWidth[] = { + /* {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, 0x01038}, {0, 0x01039}, {1, 0x0103a}, {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} +}; + /* -** Compute a string length that is limited to what can be stored in -** lower 30 bits of a 32-bit signed integer. -*/ -static int strlen30(const char *z){ - size_t n; - if( z==0 ) return 0; - n = strlen(z); - return n>0x3fffffff ? 0x3fffffff : (int)n; +** 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 cli_wcwidth(int c){ + int iFirst, iLast; + + /* Fast path for common characters */ + if( c<=0x300 ) return 1; + + /* The general case */ + iFirst = 0; + iLast = sizeof(aUWidth)/sizeof(aUWidth[0]) - 1; + while( iFirst<iLast-1 ){ + int iMid = (iFirst+iLast)/2; + int cMid = aUWidth[iMid].iFirst; + if( cMid < c ){ + iFirst = iMid; + }else if( cMid > c ){ + iLast = iMid - 1; + }else{ + return aUWidth[iMid].w; + } + } + if( aUWidth[iLast].iFirst > c ) return aUWidth[iFirst].w; + return aUWidth[iLast].w; } /* -** Return open FILE * if zFile exists, can be opened for read -** and is an ordinary file or a character stream source. -** Otherwise return 0. +** 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. */ -static FILE * openChrSource(const char *zFile){ -#if defined(_WIN32) || defined(WIN32) - struct __stat64 x = {0}; -# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) - /* On Windows, open first, then check the stream nature. This order - ** is necessary because _stat() and sibs, when checking a named pipe, - ** effectively break the pipe as its supplier sees it. */ - FILE *rv = sqlite3_fopen(zFile, "rb"); - if( rv==0 ) return 0; - if( _fstat64(_fileno(rv), &x) != 0 - || !STAT_CHR_SRC(x.st_mode)){ - fclose(rv); - rv = 0; +static int decodeUtf8(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; } - return rv; -#else - struct stat x = {0}; - int rc = stat(zFile, &x); -# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode)) - if( rc!=0 ) return 0; - if( STAT_CHR_SRC(x.st_mode) ){ - return sqlite3_fopen(zFile, "rb"); - }else{ - return 0; + 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; } -#endif -#undef STAT_CHR_SRC + 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; } + +#if 0 /* NOT USED */ /* -** This routine reads a line of text from FILE in, stores -** the text in memory obtained from malloc() and returns a pointer -** to the text. NULL is returned at end of file, or if malloc() -** fails, or if the length of the line is longer than about a gigabyte. +** Return the width, in display columns, of a UTF-8 string. ** -** If zLine is not NULL then it is a malloced buffer returned from -** a previous call to this routine that may be reused. +** Each normal character counts as 1. Zero-width characters count +** as zero, and double-width characters count as 2. */ -static char *local_getline(char *zLine, FILE *in){ - int nLine = zLine==0 ? 0 : 100; +int cli_wcswidth(const char *z){ + const unsigned char *a = (const unsigned char*)z; int n = 0; - - while( 1 ){ - if( n+100>nLine ){ - if( nLine>=1073741773 ){ - free(zLine); - return 0; - } - nLine = nLine*2 + 100; - zLine = realloc(zLine, nLine); - shell_check_oom(zLine); - } - if( sqlite3_fgets(&zLine[n], nLine - n, in)==0 ){ - if( n==0 ){ - free(zLine); - return 0; - } - zLine[n] = 0; - break; + int i = 0; + unsigned char c; + while( (c = a[i])!=0 ){ + if( c>=0xc0 ){ + int u; + int len = decodeUtf8(&a[i], &u); + i += len; + n += cli_wcwidth(u); + }else if( c>=' ' ){ + n++; + i++; + }else{ + i++; } - while( zLine[n] ) n++; - if( n>0 && zLine[n-1]=='\n' ){ - n--; - if( n>0 && zLine[n-1]=='\r' ) n--; + } + return n; +} +#endif + +/* +** 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 isVt100(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; +} + +/* +** Output string zUtf to stdout as w characters. If w is negative, +** then right-justify the text. W is the width in UTF-8 characters, not +** in bytes. This is different from the %*.*s specification in printf +** since with %*.*s the width is measured in bytes, not characters. +** +** Take into account zero-width and double-width Unicode characters. +** In other words, a zero-width character does not count toward the +** the w limit. A double-width character counts as two. +** +** w should normally be a small number. A couple hundred at most. This +** routine caps w at 100 million to avoid integer overflow issues. +*/ +static void utf8_width_print(FILE *out, 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; + if( w<-mxW ){ + w = -mxW; + }else if( w>mxW ){ + w= mxW; + } + aw = w<0 ? -w : w; + if( zUtf==0 ) zUtf = ""; + while( (c = a[i])!=0 ){ + if( (c&0xc0)==0xc0 ){ + int u; + int len = decodeUtf8(a+i, &u); + int x = cli_wcwidth(u); + if( x+n>aw ){ + break; + } + i += len; + n += x; + }else if( c==0x1b && (k = isVt100(&a[i]))>0 ){ + i += k; + }else if( n>=aw ){ + break; + }else{ + n++; + i++; + } + } + if( n>=aw ){ + sqlite3_fprintf(out, "%.*s", i, zUtf); + }else if( w<0 ){ + sqlite3_fprintf(out, "%*s%s", aw-n, "", zUtf); + }else{ + sqlite3_fprintf(out, "%s%*s", zUtf, aw-n, ""); + } +} + + +/* +** Determines if a string is a number of not. +*/ +static int isNumber(const char *z, int *realnum){ + if( *z=='-' || *z=='+' ) z++; + if( !IsDigit(*z) ){ + return 0; + } + z++; + if( realnum ) *realnum = 0; + while( IsDigit(*z) ){ z++; } + if( *z=='.' ){ + z++; + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + if( *z=='e' || *z=='E' ){ + z++; + if( *z=='+' || *z=='-' ) z++; + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + return *z==0; +} + +/* +** Compute a string length that is limited to what can be stored in +** lower 30 bits of a 32-bit signed integer. +*/ +static int strlen30(const char *z){ + const char *z2 = z; + while( *z2 ){ z2++; } + return 0x3fffffff & (int)(z2 - z); +} + +/* +** Return the length of a string in characters. Multibyte UTF8 characters +** count as a single character for single-width characters, or as two +** characters for double-width characters. +*/ +static int strlenChar(const char *z){ + int n = 0; + while( *z ){ + if( (0x80&z[0])==0 ){ + n++; + z++; + }else{ + int u = 0; + int len = decodeUtf8((const u8*)z, &u); + z += len; + n += cli_wcwidth(u); + } + } + return n; +} + +/* +** Return open FILE * if zFile exists, can be opened for read +** and is an ordinary file or a character stream source. +** Otherwise return 0. +*/ +static FILE * openChrSource(const char *zFile){ +#if defined(_WIN32) || defined(WIN32) + struct __stat64 x = {0}; +# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) + /* On Windows, open first, then check the stream nature. This order + ** is necessary because _stat() and sibs, when checking a named pipe, + ** effectively break the pipe as its supplier sees it. */ + FILE *rv = sqlite3_fopen(zFile, "rb"); + if( rv==0 ) return 0; + if( _fstat64(_fileno(rv), &x) != 0 + || !STAT_CHR_SRC(x.st_mode)){ + fclose(rv); + rv = 0; + } + return rv; +#else + struct stat x = {0}; + int rc = stat(zFile, &x); +# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode)) + if( rc!=0 ) return 0; + if( STAT_CHR_SRC(x.st_mode) ){ + return sqlite3_fopen(zFile, "rb"); + }else{ + return 0; + } +#endif +#undef STAT_CHR_SRC +} + +/* +** This routine reads a line of text from FILE in, stores +** the text in memory obtained from malloc() and returns a pointer +** to the text. NULL is returned at end of file, or if malloc() +** fails, or if the length of the line is longer than about a gigabyte. +** +** If zLine is not NULL then it is a malloced buffer returned from +** a previous call to this routine that may be reused. +*/ +static char *local_getline(char *zLine, FILE *in){ + int nLine = zLine==0 ? 0 : 100; + int n = 0; + + while( 1 ){ + if( n+100>nLine ){ + if( nLine>=1073741773 ){ + free(zLine); + return 0; + } + nLine = nLine*2 + 100; + zLine = realloc(zLine, nLine); + shell_check_oom(zLine); + } + if( sqlite3_fgets(&zLine[n], nLine - n, in)==0 ){ + if( n==0 ){ + free(zLine); + return 0; + } + zLine[n] = 0; + break; + } + while( zLine[n] ) n++; + if( n>0 && zLine[n-1]=='\n' ){ + n--; + if( n>0 && zLine[n-1]=='\r' ) n--; zLine[n] = 0; break; } @@ -931,7 +1305,7 @@ static void shellDtostr( char z[400]; if( n<1 ) n = 1; if( n>350 ) n = 350; - sprintf(z, "%#+.*e", n, r); + sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r); sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); } @@ -1089,24 +1463,31 @@ struct ExpertInfo { }; #endif -/* All the parameters that determine how to render query results. -*/ -typedef struct Mode { - u8 autoExplain; /* Automatically turn on .explain mode */ - u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */ - u8 autoEQPtrace; /* autoEQP is in trace mode */ - u8 scanstatsOn; /* True to display scan stats before each finalize */ - u8 bAutoScreenWidth; /* Using the TTY to determine screen width */ - u8 mFlags; /* MFLG_ECHO, MFLG_CRLF, etc. */ - u8 eMode; /* One of the MODE_ values */ - sqlite3_qrf_spec spec; /* Spec to be passed into QRF */ -} Mode; +/* A single line in the EQP output */ +typedef struct EQPGraphRow EQPGraphRow; +struct EQPGraphRow { + int iEqpId; /* ID for this row */ + int iParentId; /* ID of the parent row */ + EQPGraphRow *pNext; /* Next row in sequence */ + char zText[1]; /* Text to display for this row */ +}; -/* Flags for Mode.mFlags */ -#define MFLG_ECHO 0x01 /* Echo inputs to output */ -#define MFLG_CRLF 0x02 /* Use CR/LF output line endings */ -#define MFLG_HDR 0x04 /* .header used to change headers on/off */ +/* All EQP output is collected into an instance of the following */ +typedef struct EQPGraph EQPGraph; +struct EQPGraph { + EQPGraphRow *pRow; /* Linked list of all rows of the EQP output */ + EQPGraphRow *pLast; /* Last element of the pRow list */ + char zPrefix[100]; /* Graph prefix */ +}; +/* Parameters affecting columnar mode result display (defaulting together) */ +typedef struct ColModeOpts { + int iWrap; /* In columnar modes, wrap lines reaching this limit */ + u8 bQuote; /* Quote results for .mode box and table */ + u8 bWordWrap; /* In columnar modes, wrap at word boundaries */ +} ColModeOpts; +#define ColModeOpts_default { 60, 0, 0 } +#define ColModeOpts_default_qbox { 60, 1, 0 } /* ** State information about the database connection is contained in an @@ -1115,6 +1496,11 @@ typedef struct Mode { typedef struct ShellState ShellState; struct ShellState { sqlite3 *db; /* The database */ + u8 autoExplain; /* Automatically turn on .explain mode */ + u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */ + u8 autoEQPtest; /* autoEQP is in test mode */ + u8 autoEQPtrace; /* autoEQP is in trace mode */ + u8 scanstatsOn; /* True to display scan stats before each finalize */ u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */ u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */ u8 nEqpLevel; /* Depth of the EQP output graph */ @@ -1122,44 +1508,48 @@ struct ShellState { u8 bSafeMode; /* True to prohibit unsafe operations */ u8 bSafeModePersist; /* The long-term value of bSafeMode */ u8 eRestoreState; /* See comments above doAutoDetectRestore() */ + u8 crlfMode; /* Do NL-to-CRLF translations when enabled (maybe) */ + u8 eEscMode; /* Escape mode for text output */ + ColModeOpts cmOpts; /* Option values affecting columnar mode output */ unsigned statsOn; /* True to display memory stats before each finalize */ unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */ - u8 nPopOutput; /* Revert .output settings when reaching zero */ - u8 nPopMode; /* Revert .mode settings when reaching zero */ - u8 enableTimer; /* Enable the timer. 2: permanently 1: only once */ int inputNesting; /* Track nesting level of .read and other redirects */ - double prevTimer; /* Last reported timer value */ - double tmProgress; /* --timeout option for .progress */ + int outCount; /* Revert to stdout when reaching zero */ + int cnt; /* Number of records displayed so far */ i64 lineno; /* Line number of last line read from in */ - const char *zInFile; /* Name of the input file */ int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */ FILE *in; /* Read commands from this stream */ FILE *out; /* Write results here */ FILE *traceOut; /* Output for sqlite3_trace() */ int nErr; /* Number of errors seen */ + int mode; /* An output mode setting */ + int modePrior; /* Saved mode */ + int cMode; /* temporary output mode for the current query */ + int normalMode; /* Output mode before ".explain on" */ int writableSchema; /* True if PRAGMA writable_schema=ON */ + int showHeader; /* True to show column names in List or Column mode */ int nCheck; /* Number of ".check" commands run */ unsigned nProgress; /* Number of progress callbacks encountered */ unsigned mxProgress; /* Maximum progress callbacks before failing */ unsigned flgProgress; /* Flags for the progress callback */ unsigned shellFlgs; /* Various flags */ - unsigned nTestRun; /* Number of test cases run */ - unsigned nTestErr; /* Number of test cases that failed */ + unsigned priorShFlgs; /* Saved copy of flags */ sqlite3_int64 szMax; /* --maxsize argument to .open */ char *zDestTable; /* Name of destination table when MODE_Insert */ char *zTempFile; /* Temporary file that might need deleting */ - char *zErrPrefix; /* Alternative error message prefix */ char zTestcase[30]; /* Name of current test case */ + char colSeparator[20]; /* Column separator character for several modes */ + char rowSeparator[20]; /* Row separator character for MODE_Ascii */ + char colSepPrior[20]; /* Saved column separator */ + char rowSepPrior[20]; /* Saved row separator */ + int *colWidth; /* Requested width of each column in columnar modes */ + int *actualWidth; /* Actual width of each column */ + int nWidth; /* Number of slots in colWidth[] and actualWidth[] */ + char nullValue[20]; /* The text to print when a NULL comes back from + ** the database */ char outfile[FILENAME_MAX]; /* Filename for *out */ sqlite3_stmt *pStmt; /* Current statement if any. */ FILE *pLog; /* Write log output here */ - Mode mode; /* Current display mode */ - Mode modePrior; /* Backup */ - struct SavedMode { /* Ability to define custom mode configurations */ - char *zTag; /* Name of this saved mode */ - Mode mode; /* The saved mode */ - } *aSavedModes; /* Array of saved .mode settings. system malloc() */ - int nSavedModes; /* Number of saved .mode settings */ struct AuxDb { /* Storage space for auxiliary database connections */ sqlite3 *db; /* Connection pointer */ const char *zDbFilename; /* Filename used to open the connection */ @@ -1170,19 +1560,14 @@ struct ShellState { #endif } aAuxDb[5], /* Array of all database connections */ *pAuxDb; /* Currently active database connection */ + int *aiIndent; /* Array of indents used in MODE_Explain */ + int nIndent; /* Size of array aiIndent[] */ + int iIndent; /* Index of current op in aiIndent[] */ char *zNonce; /* Nonce for temporary safe-mode escapes */ + EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ #endif - struct DotCmdLine { /* Info about arguments to a dot-command */ - const char *zOrig; /* Original text of the dot-command */ - char *zCopy; /* Copy of zOrig, from malloc() */ - int nAlloc; /* Size of allocates for arrays below */ - int nArg; /* Number of argument slots actually used */ - char **azArg; /* Pointer to each argument, dequoted */ - int *aiOfst; /* Offset into zOrig[] for start of each arg */ - char *abQuot; /* True if the argment was originally quoted */ - } dot; #ifdef SQLITE_SHELL_FIDDLE struct { const char * zInput; /* Input string from wasm/JS proxy */ @@ -1197,7 +1582,7 @@ static ShellState shellState; #endif -/* Allowed values for ShellState.mode.autoEQP +/* Allowed values for ShellState.autoEQP */ #define AUTOEQP_off 0 /* Automatic EXPLAIN QUERY PLAN is off */ #define AUTOEQP_on 1 /* Automatic EQP is on */ @@ -1225,13 +1610,15 @@ static ShellState shellState; ** callback limit is reached, and for each ** top-level SQL statement */ #define SHELL_PROGRESS_ONCE 0x04 /* Cancel the --limit after firing once */ -#define SHELL_PROGRESS_TMOUT 0x08 /* Stop after tmProgress seconds */ -/* Names of values for Mode.spec.eEsc and Mode.spec.eText +/* Allowed values for ShellState.eEscMode. The default value should +** be 0, so to change the default, reorder the names. */ -static const char *qrfEscNames[] = { "auto", "off", "ascii", "symbol" }; -static const char *qrfQuoteNames[] = - { "off","off","sql","hex","csv","tcl","json","relaxed"}; +#define SHELL_ESC_ASCII 0 /* Substitute ^Y for X where Y=X+0x40 */ +#define SHELL_ESC_SYMBOL 1 /* Substitute U+2400 graphics */ +#define SHELL_ESC_OFF 2 /* Send characters verbatim */ + +static const char *shell_EscModeNames[] = { "ascii", "symbol", "off" }; /* ** These are the allowed shellFlgs values @@ -1240,8 +1627,10 @@ static const char *qrfQuoteNames[] = #define SHFLG_Lookaside 0x00000002 /* Lookaside memory is used */ #define SHFLG_Backslash 0x00000004 /* The --backslash option is used */ #define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */ -#define SHFLG_NoErrLineno 0x00000010 /* Omit line numbers from error msgs */ +#define SHFLG_Newlines 0x00000010 /* .dump --newline flag */ #define SHFLG_CountChanges 0x00000020 /* .changes setting */ +#define SHFLG_Echo 0x00000040 /* .echo on/off, or --echo setting */ +#define SHFLG_HeaderSet 0x00000080 /* showHeader has been specified */ #define SHFLG_DumpDataOnly 0x00000100 /* .dump show data only */ #define SHFLG_DumpNoSys 0x00000200 /* .dump omits system tables */ #define SHFLG_TestingMode 0x00000400 /* allow unsafe testing features */ @@ -1254,107 +1643,54 @@ static const char *qrfQuoteNames[] = #define ShellClearFlag(P,X) ((P)->shellFlgs&=(~(X))) /* -** These are the allowed values for Mode.eMode. There is a lot of overlap -** between these values and the Mode.spec.eStyle values, but they are not -** one-to-one, and thus need to be tracked separately. -*/ -#define MODE_Ascii 0 /* Use ASCII unit and record separators (0x1F/0x1E) */ -#define MODE_Box 1 /* Unicode box-drawing characters */ -#define MODE_C 2 /* Comma-separated list of C-strings */ -#define MODE_Column 3 /* One record per line in neat columns */ -#define MODE_Count 4 /* Output only a count of the rows of output */ -#define MODE_Csv 5 /* Quote strings, numbers are plain */ -#define MODE_Html 6 /* Generate an XHTML table */ -#define MODE_Insert 7 /* Generate SQL "insert" statements */ -#define MODE_JAtom 8 /* Comma-separated list of JSON atoms */ -#define MODE_JObject 9 /* One JSON object per row */ -#define MODE_Json 10 /* Output JSON */ -#define MODE_Line 11 /* One column per line. Blank line between records */ -#define MODE_List 12 /* One record per line with a separator */ -#define MODE_Markdown 13 /* Markdown formatting */ -#define MODE_Off 14 /* No query output shown */ -#define MODE_Psql 15 /* Similar to psql */ -#define MODE_QBox 16 /* BOX with SQL-quoted content */ -#define MODE_Quote 17 /* Quote values as for SQL */ -#define MODE_Split 18 /* Split-column mode */ -#define MODE_Table 19 /* MySQL-style table formatting */ -#define MODE_Tabs 20 /* Tab-separated values */ -#define MODE_Tcl 21 /* Space-separated list of TCL strings */ -#define MODE_Www 22 /* Full web-page output */ - -#define MODE_BUILTIN 22 /* Maximum built-in mode */ -#define MODE_BATCH 50 /* Default mode for batch processing */ -#define MODE_TTY 51 /* Default mode for interactive processing */ -#define MODE_USER 75 /* First user-defined mode */ -#define MODE_N_USER 25 /* Maximum number of user-defined modes */ - -/* -** Information about built-in display modes -*/ -typedef struct ModeInfo ModeInfo; -struct ModeInfo { - char zName[9]; /* Symbolic name of the mode */ - unsigned char eCSep; /* Column separator */ - unsigned char eRSep; /* Row separator */ - unsigned char eNull; /* Null representation */ - unsigned char eText; /* Default text encoding */ - unsigned char eHdr; /* Default header encoding. */ - unsigned char eBlob; /* Default blob encoding. */ - unsigned char bHdr; /* Show headers by default. 0: n/a, 1: no 2: yes */ - unsigned char eStyle; /* Underlying QRF style */ - unsigned char eCx; /* 0: other, 1: line, 2: columnar */ - unsigned char mFlg; /* Flags. 1=border-off 2=split-column */ +** These are the allowed modes. +*/ +#define MODE_Line 0 /* One column per line. Blank line between records */ +#define MODE_Column 1 /* One record per line in neat columns */ +#define MODE_List 2 /* One record per line with a separator */ +#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ +#define MODE_Html 4 /* Generate an XHTML table */ +#define MODE_Insert 5 /* Generate SQL "insert" statements */ +#define MODE_Quote 6 /* Quote values as for SQL */ +#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */ +#define MODE_Csv 8 /* Quote strings, numbers are plain */ +#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */ +#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */ +#define MODE_Pretty 11 /* Pretty-print schemas */ +#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */ +#define MODE_Json 13 /* Output JSON */ +#define MODE_Markdown 14 /* Markdown formatting */ +#define MODE_Table 15 /* MySQL-style table formatting */ +#define MODE_Box 16 /* Unicode box-drawing characters */ +#define MODE_Count 17 /* Output only a count of the rows of output */ +#define MODE_Off 18 /* No query output shown */ +#define MODE_ScanExp 19 /* Like MODE_Explain, but for ".scanstats vm" */ +#define MODE_Www 20 /* Full web-page output */ + +static const char *modeDescr[] = { + "line", + "column", + "list", + "semi", + "html", + "insert", + "quote", + "tcl", + "csv", + "explain", + "ascii", + "prettyprint", + "eqp", + "json", + "markdown", + "table", + "box", + "count", + "off", + "scanexp", + "www", }; -/* String constants used by built-in modes */ -static const char *aModeStr[] = - /* 0 1 2 3 4 5 6 7 8 */ - { 0, "\n", "|", " ", ",", "\r\n", "\036", "\037", "\t", - "", "NULL", "null", "\"\"", ": ", }; - /* 9 10 11 12 13 */ - -static const ModeInfo aModeInfo[] = { -/* zName eCSep eRSep eNull eText eHdr eBlob bHdr eStyle eCx mFlg */ - { "ascii", 7, 6, 9, 1, 1, 0, 1, 12, 0, 0 }, - { "box", 0, 0, 9, 1, 1, 0, 2, 1, 2, 0 }, - { "c", 4, 1, 10, 5, 5, 4, 1, 12, 0, 0 }, - { "column", 0, 0, 9, 1, 1, 0, 2, 2, 2, 0 }, - { "count", 0, 0, 0, 0, 0, 0, 0, 3, 0, 0 }, - { "csv", 4, 5, 9, 3, 3, 0, 1, 12, 0, 0 }, - { "html", 0, 0, 9, 4, 4, 0, 2, 7, 0, 0 }, - { "insert", 0, 0, 10, 2, 2, 0, 1, 8, 0, 0 }, - { "jatom", 4, 1, 11, 6, 6, 0, 1, 12, 0, 0 }, - { "jobject", 0, 1, 11, 6, 6, 0, 0, 10, 0, 0 }, - { "json", 0, 0, 11, 6, 6, 0, 0, 9, 0, 0 }, - { "line", 13, 1, 9, 1, 1, 0, 0, 11, 1, 0 }, - { "list", 2, 1, 9, 1, 1, 0, 1, 12, 0, 0 }, - { "markdown", 0, 0, 9, 1, 1, 0, 2, 13, 2, 0 }, - { "off", 0, 0, 0, 0, 0, 0, 0, 14, 0, 0 }, - { "psql", 0, 0, 9, 1, 1, 0, 2, 19, 2, 1 }, - { "qbox", 0, 0, 10, 2, 1, 0, 2, 1, 2, 0 }, - { "quote", 4, 1, 10, 2, 2, 0, 1, 12, 0, 0 }, - { "split", 0, 0, 9, 1, 1, 0, 1, 2, 2, 2 }, - { "table", 0, 0, 9, 1, 1, 0, 2, 19, 2, 0 }, - { "tabs", 8, 1, 9, 3, 3, 0, 1, 12, 0, 0 }, - { "tcl", 3, 1, 12, 5, 5, 4, 1, 12, 0, 0 }, - { "www", 0, 0, 9, 4, 4, 0, 2, 7, 0, 0 } -}; /* | / / | / / | | \ - ** | / / | / / | | \_ 2: columnar - ** Index into aModeStr[] | / / | | 1: line - ** | / / | | 0: other - ** | / / | \ - ** text encoding |/ | show | \ - ** v-------------------' | hdrs? | The QRF style - ** 0: n/a blob | v-----' - ** 1: plain v_---------' 0: n/a - ** 2: sql 0: auto 1: no - ** 3: csv 1: as-text 2: yes - ** 4: html 2: sql - ** 5: c 3: hex - ** 6: json 4: c - ** 5: json - ** 6: size - ******************************************************************/ /* ** These are the column/row/line separators used by the various ** import/export modes. @@ -1368,480 +1704,59 @@ static const ModeInfo aModeInfo[] = { #define SEP_Unit "\x1F" #define SEP_Record "\x1E" -/* -** Default values for the various QRF limits -*/ -#ifndef DFLT_CHAR_LIMIT -# define DFLT_CHAR_LIMIT 300 -#endif -#ifndef DFLT_LINE_LIMIT -# define DFLT_LINE_LIMIT 5 -#endif -#ifndef DFLT_TITLE_LIMIT -# define DFLT_TITLE_LIMIT 20 -#endif - /* ** Limit input nesting via .read or any other input redirect. ** It's not too expensive, so a generous allowance can be made. */ #define MAX_INPUT_NESTING 25 -/************************* BEGIN PERFORMANCE TIMER *****************************/ -#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) -#include <sys/time.h> -#include <sys/resource.h> -/* VxWorks does not support getrusage() as far as we can determine */ -#if defined(_WRS_KERNEL) || defined(__RTP__) -struct rusage { - struct timeval ru_utime; /* user CPU time used */ - struct timeval ru_stime; /* system CPU time used */ -}; -#define getrusage(A,B) memset(B,0,sizeof(*B)) -#endif - -/* Saved resource information for the beginning of an operation */ -static struct rusage sBegin; /* CPU time at start */ -static sqlite3_int64 iBegin; /* Wall-clock time at start */ - /* -** Begin timing an operation +** A callback for the sqlite3_log() interface. */ -static void beginTimer(ShellState *p){ - if( p->enableTimer || (p->flgProgress & SHELL_PROGRESS_TMOUT)!=0 ){ - getrusage(RUSAGE_SELF, &sBegin); - iBegin = timeOfDay(); - } -} - -/* Return the difference of two time_structs in seconds */ -static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ - return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + - (double)(pEnd->tv_sec - pStart->tv_sec); -} - -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK -/* Return the time since the start of the timer in -** seconds. */ -static double elapseTime(ShellState *NotUsed){ - (void)NotUsed; - if( iBegin==0 ) return 0.0; - return (timeOfDay() - iBegin)*0.000001; +static void shellLog(void *pArg, int iErrCode, const char *zMsg){ + ShellState *p = (ShellState*)pArg; + if( p->pLog==0 ) return; + sqlite3_fprintf(p->pLog, "(%d) %s\n", iErrCode, zMsg); + fflush(p->pLog); } -#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ /* -** Print the timing results. +** SQL function: shell_putsnl(X) +** +** Write the text X to the screen (or whatever output is being directed) +** adding a newline at the end, and then return X. */ -static void endTimer(ShellState *p){ - if( p->enableTimer ){ - sqlite3_int64 iEnd = timeOfDay(); - struct rusage sEnd; - getrusage(RUSAGE_SELF, &sEnd); - p->prevTimer = (iEnd - iBegin)*0.000001; - cli_printf(p->out, "Run Time: real %.6f user %.6f sys %.6f\n", - p->prevTimer, - timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), - timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); - if( p->enableTimer==1 ) p->enableTimer = 0; - iBegin = 0; - } +static void shellPutsFunc( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + ShellState *p = (ShellState*)sqlite3_user_data(pCtx); + (void)nVal; + sqlite3_fprintf(p->out, "%s\n", sqlite3_value_text(apVal[0])); + sqlite3_result_value(pCtx, apVal[0]); } -#define BEGIN_TIMER(X) beginTimer(X) -#define END_TIMER(X) endTimer(X) -#define ELAPSE_TIME(X) elapseTime(X) -#define HAS_TIMER 1 - -#elif (defined(_WIN32) || defined(WIN32)) - -/* Saved resource information for the beginning of an operation */ -static HANDLE hProcess; -static FILETIME ftKernelBegin; -static FILETIME ftUserBegin; -static sqlite3_int64 ftWallBegin; -typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, - LPFILETIME, LPFILETIME); -static GETPROCTIMES getProcessTimesAddr = NULL; - /* -** Check to see if we have timer support. Return 1 if necessary -** support found (or found previously). +** If in safe mode, print an error message described by the arguments +** and exit immediately. */ -static int hasTimer(void){ - if( getProcessTimesAddr ){ - return 1; - } else { - /* GetProcessTimes() isn't supported in WIN95 and some other Windows - ** versions. See if the version we are running on has it, and if it - ** does, save off a pointer to it and the current process handle. - */ - hProcess = GetCurrentProcess(); - if( hProcess ){ - HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); - if( NULL != hinstLib ){ - getProcessTimesAddr = - (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); - if( NULL != getProcessTimesAddr ){ - return 1; - } - FreeLibrary(hinstLib); - } - } +static void failIfSafeMode( + ShellState *p, + const char *zErrMsg, + ... +){ + if( p->bSafeMode ){ + va_list ap; + char *zMsg; + va_start(ap, zErrMsg); + zMsg = sqlite3_vmprintf(zErrMsg, ap); + va_end(ap); + sqlite3_fprintf(stderr, "line %lld: %s\n", p->lineno, zMsg); + exit(1); } - return 0; } -/* -** Begin timing an operation -*/ -static void beginTimer(ShellState *p){ - if( (p->enableTimer || (p->flgProgress & SHELL_PROGRESS_TMOUT)!=0) - && getProcessTimesAddr - ){ - FILETIME ftCreation, ftExit; - getProcessTimesAddr(hProcess,&ftCreation,&ftExit, - &ftKernelBegin,&ftUserBegin); - ftWallBegin = timeOfDay(); - } -} - -/* Return the difference of two FILETIME structs in seconds */ -static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ - sqlite_int64 i64Start = *((sqlite_int64 *) pStart); - sqlite_int64 i64End = *((sqlite_int64 *) pEnd); - return (double) ((i64End - i64Start) / 10000000.0); -} - -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK -/* Return the time since the start of the timer in -** seconds. */ -static double elapseTime(ShellState *NotUsed){ - (void)NotUsed; - if( ftWallBegin==0 ) return 0.0; - return (timeOfDay() - ftWallBegin)*0.000001; -} -#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ - -/* -** Print the timing results. -*/ -static void endTimer(ShellState *p){ - if( p->enableTimer && getProcessTimesAddr){ - FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; - sqlite3_int64 ftWallEnd = timeOfDay(); - getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); - p->prevTimer = (ftWallEnd - ftWallBegin)*0.000001; -#ifdef _WIN64 - /* microsecond precision on 64-bit windows */ - cli_printf(p->out, "Run Time: real %.6f user %f sys %f\n", - p->prevTimer, - timeDiff(&ftUserBegin, &ftUserEnd), - timeDiff(&ftKernelBegin, &ftKernelEnd)); -#else - /* millisecond precisino on 32-bit windows */ - cli_printf(p->out, "Run Time: real %.3f user %.3f sys %.3f\n", - p->prevTimer, - timeDiff(&ftUserBegin, &ftUserEnd), - timeDiff(&ftKernelBegin, &ftKernelEnd)); -#endif - if( p->enableTimer==1 ) p->enableTimer = 0; - ftWallBegin = 0; - } -} - -#define BEGIN_TIMER(X) beginTimer(X) -#define ELAPSE_TIME(X) elapseTime(X) -#define END_TIMER(X) endTimer(X) -#define HAS_TIMER hasTimer() - -#else -#define BEGIN_TIMER(X) /* no-op */ -#define ELAPSE_TIME(X) 0.0 -#define END_TIMER(X) /*no-op*/ -#define HAS_TIMER 0 -#endif -/************************* END PERFORMANCE TIMER ******************************/ - -/* -** Clear a display mode, freeing any allocated memory that it -** contains. -*/ -static void modeFree(Mode *p){ - u8 autoExplain = p->autoExplain; - free(p->spec.aWidth); - free(p->spec.aAlign); - free(p->spec.zColumnSep); - free(p->spec.zRowSep); - free(p->spec.zTableName); - free(p->spec.zNull); - memset(p, 0, sizeof(*p)); - p->spec.iVersion = 1; - p->autoExplain = autoExplain; -} - -/* -** Duplicate Mode pSrc into pDest. pDest is assumed to be -** uninitialized prior to invoking this routine. -*/ -static void modeDup(Mode *pDest, Mode *pSrc){ - memcpy(pDest, pSrc, sizeof(*pDest)); - if( pDest->spec.aWidth ){ - size_t sz = sizeof(pSrc->spec.aWidth[0]) * pSrc->spec.nWidth; - pDest->spec.aWidth = malloc( sz ); - if( pDest->spec.aWidth ){ - memcpy(pDest->spec.aWidth, pSrc->spec.aWidth, sz); - }else{ - pDest->spec.nWidth = 0; - } - } - if( pDest->spec.aAlign ){ - size_t sz = sizeof(pSrc->spec.aAlign[0]) * pSrc->spec.nAlign; - pDest->spec.aAlign = malloc( sz ); - if( pDest->spec.aAlign ){ - memcpy(pDest->spec.aAlign, pSrc->spec.aAlign, sz); - }else{ - pDest->spec.nAlign = 0; - } - } - if( pDest->spec.zColumnSep ){ - pDest->spec.zColumnSep = strdup(pSrc->spec.zColumnSep); - } - if( pDest->spec.zRowSep ){ - pDest->spec.zRowSep = strdup(pSrc->spec.zRowSep); - } - if( pDest->spec.zTableName ){ - pDest->spec.zTableName = strdup(pSrc->spec.zTableName); - } - if( pDest->spec.zNull ){ - pDest->spec.zNull = strdup(pSrc->spec.zNull); - } -} - -/* -** Set a string value to a copy of the zNew string in memory -** obtained from system malloc(). -*/ -static void modeSetStr(char **az, const char *zNew){ - free(*az); - if( zNew==0 ){ - *az = 0; - }else{ - size_t n = strlen(zNew); - *az = malloc( n+1 ); - if( *az ){ - memcpy(*az, zNew, n+1 ); - } - } -} - -/* -** Change the mode to eMode -*/ -static void modeChange(ShellState *p, unsigned char eMode){ - const ModeInfo *pI; - if( eMode<ArraySize(aModeInfo) ){ - Mode *pM = &p->mode; - pI = &aModeInfo[eMode]; - pM->eMode = eMode; - if( pI->eCSep ) modeSetStr(&pM->spec.zColumnSep, aModeStr[pI->eCSep]); - if( pI->eRSep ) modeSetStr(&pM->spec.zRowSep, aModeStr[pI->eRSep]); - if( pI->eNull ) modeSetStr(&pM->spec.zNull, aModeStr[pI->eNull]); - pM->spec.eText = pI->eText; - pM->spec.eBlob = pI->eBlob; - if( (pM->mFlags & MFLG_HDR)==0 ){ - pM->spec.bTitles = pI->bHdr; - } - pM->spec.eTitle = pI->eHdr; - if( pI->mFlg & 0x01 ){ - pM->spec.bBorder = QRF_No; - }else{ - pM->spec.bBorder = QRF_Auto; - } - if( pI->mFlg & 0x02 ){ - pM->spec.bSplitColumn = QRF_Yes; - pM->bAutoScreenWidth = 1; - }else{ - pM->spec.bSplitColumn = QRF_No; - } - }else if( eMode>=MODE_USER && eMode-MODE_USER<p->nSavedModes ){ - modeFree(&p->mode); - modeDup(&p->mode, &p->aSavedModes[eMode-MODE_USER].mode); - }else if( eMode==MODE_BATCH ){ - u8 mFlags = p->mode.mFlags; - modeFree(&p->mode); - modeChange(p, MODE_List); - p->mode.mFlags = mFlags; - }else if( eMode==MODE_TTY ){ - u8 mFlags = p->mode.mFlags; - modeFree(&p->mode); - modeChange(p, MODE_QBox); - p->mode.bAutoScreenWidth = 1; - p->mode.spec.eText = QRF_TEXT_Relaxed; - p->mode.spec.nCharLimit = DFLT_CHAR_LIMIT; - p->mode.spec.nLineLimit = DFLT_LINE_LIMIT; - p->mode.spec.bTextJsonb = QRF_Yes; - p->mode.spec.nTitleLimit = DFLT_TITLE_LIMIT; - p->mode.mFlags = mFlags; - } -} - -/* -** Set the mode to the default. It assumed that the mode has -** already been freed and zeroed prior to calling this routine. -*/ -static void modeDefault(ShellState *p){ - p->mode.spec.iVersion = 1; - p->mode.autoExplain = 1; - if( stdin_is_interactive || stdout_is_console ){ - modeChange(p, MODE_TTY); - }else{ - modeChange(p, MODE_BATCH); - } -} - -/* -** Find the number of a display mode given its name. Return -1 if -** the name does not match any mode. -** -** Saved modes are also searched if p!=NULL. The number returned -** for a saved mode is the index into the p->aSavedModes[] array -** plus MODE_USER. -** -** Two special mode names are also available: "batch" and "tty". -** evaluate to the default mode for batch operation and interactive -** operation on a TTY, respectively. -*/ -static int modeFind(ShellState *p, const char *zName){ - int i; - for(i=0; i<ArraySize(aModeInfo); i++){ - if( cli_strcmp(aModeInfo[i].zName,zName)==0 ) return i; - } - for(i=0; i<p->nSavedModes; i++){ - if( cli_strcmp(p->aSavedModes[i].zTag,zName)==0 ) return i+MODE_USER; - } - if( strcmp(zName,"batch")==0 ) return MODE_BATCH; - if( strcmp(zName,"tty")==0 ) return MODE_TTY; - return -1; -} - -/* -** Save or restore the current output mode -*/ -static void modePush(ShellState *p){ - if( p->nPopMode==0 ){ - modeFree(&p->modePrior); - modeDup(&p->modePrior,&p->mode); - } -} -static void modePop(ShellState *p){ - if( p->modePrior.spec.iVersion>0 ){ - modeFree(&p->mode); - p->mode = p->modePrior; - memset(&p->modePrior, 0, sizeof(p->modePrior)); - } -} - - -/* -** A callback for the sqlite3_log() interface. -*/ -static void shellLog(void *pArg, int iErrCode, const char *zMsg){ - ShellState *p = (ShellState*)pArg; - if( p->pLog==0 ) return; - cli_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg); - fflush(p->pLog); -} - -/* -** SQL function: shell_putsnl(X) -** -** Write the text X to the screen (or whatever output is being directed) -** adding a newline at the end, and then return X. -*/ -static void shellPutsFunc( - sqlite3_context *pCtx, - int nVal, - sqlite3_value **apVal -){ - ShellState *p = (ShellState*)sqlite3_user_data(pCtx); - (void)nVal; - cli_printf(p->out, "%s\n", sqlite3_value_text(apVal[0])); - sqlite3_result_value(pCtx, apVal[0]); -} - -/* -** Compute the name of the location of an input error in memory -** obtained from sqlite3_malloc(). -*/ -static char *shellErrorLocation(ShellState *p){ - char *zLoc; - if( p->zErrPrefix ){ - zLoc = sqlite3_mprintf("%s", p->zErrPrefix); - }else if( p->zInFile==0 || strcmp(p->zInFile,"<stdin>")==0){ - zLoc = sqlite3_mprintf("line %lld:", p->lineno); - }else{ - zLoc = sqlite3_mprintf("%s:%lld:", p->zInFile, p->lineno); - } - return zLoc; -} - -/* -** If in safe mode, print an error message described by the arguments -** and exit immediately. -*/ -static void failIfSafeMode( - ShellState *p, - const char *zErrMsg, - ... -){ - if( p->bSafeMode ){ - va_list ap; - char *zMsg; - char *zLoc = shellErrorLocation(p); - va_start(ap, zErrMsg); - zMsg = sqlite3_vmprintf(zErrMsg, ap); - va_end(ap); - cli_printf(stderr, "%s %s\n", zLoc, zMsg); - cli_exit(1); - } -} - -/* -** Issue an error message from a dot-command. -*/ -static void dotCmdError( - ShellState *p, /* Shell state */ - int iArg, /* Index of argument on which error occurred */ - const char *zBrief, /* Brief (<20 character) error description */ - const char *zDetail, /* Error details */ - ... -){ - FILE *out = stderr; - char *zLoc = shellErrorLocation(p); - if( zBrief!=0 && iArg>=0 && iArg<p->dot.nArg ){ - int i = p->dot.aiOfst[iArg]; - int nPrompt = strlen30(zBrief) + 5; - cli_printf(out, "%s %s\n", zLoc, p->dot.zOrig); - if( i > nPrompt ){ - cli_printf(out, "%s %*s%s ---^\n", zLoc, 1+i-nPrompt, "", zBrief); - }else{ - cli_printf(out, "%s %*s^--- %s\n", zLoc, i, "", zBrief); - } - } - if( zDetail ){ - char *zMsg; - va_list ap; - va_start(ap, zDetail); - zMsg = sqlite3_vmprintf(zDetail,ap); - va_end(ap); - cli_printf(out,"%s %s\n", zLoc, zMsg); - sqlite3_free(zMsg); - } - sqlite3_free(zLoc); -} - - /* ** SQL function: edit(VALUE) ** edit(VALUE,EDITOR) @@ -1986,12 +1901,28 @@ edit_func_end: } #endif /* SQLITE_NOHAVE_SYSTEM */ +/* +** Save or restore the current output mode +*/ +static void outputModePush(ShellState *p){ + p->modePrior = p->mode; + p->priorShFlgs = p->shellFlgs; + memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator)); + memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator)); +} +static void outputModePop(ShellState *p){ + p->mode = p->modePrior; + p->shellFlgs = p->priorShFlgs; + memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator)); + memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator)); +} + /* ** Set output mode to text or binary for Windows. */ static void setCrlfMode(ShellState *p){ #ifdef _WIN32 - if( p->mode.mFlags & MFLG_CRLF ){ + if( p->crlfMode ){ sqlite3_fsetmode(p->out, _O_TEXT); }else{ sqlite3_fsetmode(p->out, _O_BINARY); @@ -2001,6 +1932,126 @@ static void setCrlfMode(ShellState *p){ #endif } +/* +** Output the given string as a hex-encoded blob (eg. X'1234' ) +*/ +static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ + int i; + unsigned char *aBlob = (unsigned char*)pBlob; + + char *zStr = sqlite3_malloc64((i64)nBlob*2 + 1); + shell_check_oom(zStr); + + for(i=0; i<nBlob; i++){ + static const char aHex[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + zStr[i*2] = aHex[ (aBlob[i] >> 4) ]; + zStr[i*2+1] = aHex[ (aBlob[i] & 0x0F) ]; + } + zStr[i*2] = '\0'; + + sqlite3_fprintf(out, "X'%s'", zStr); + sqlite3_free(zStr); +} + +/* +** Output the given string as a quoted string using SQL quoting conventions: +** +** (1) Single quotes (') within the string are doubled +** (2) The while string is enclosed in '...' +** (3) Control characters other than \n, \t, and \r\n are escaped +** using \u00XX notation and if such substitutions occur, +** the whole string is enclosed in unistr('...') instead of '...'. +** +** Step (3) is omitted if the control-character escape mode is OFF. +** +** See also: output_quoted_escaped_string() which does the same except +** that it does not make exceptions for \n, \t, and \r\n in step (3). +*/ +static void output_quoted_string(ShellState *p, const char *zInX){ + int i; + int needUnistr = 0; + int needDblQuote = 0; + const unsigned char *z = (const unsigned char*)zInX; + unsigned char c; + FILE *out = p->out; + sqlite3_fsetmode(out, _O_BINARY); + if( z==0 ) return; + for(i=0; (c = z[i])!=0; i++){ + if( c=='\'' ){ needDblQuote = 1; } + if( c>0x1f ) continue; + if( c=='\t' || c=='\n' ) continue; + if( c=='\r' && z[i+1]=='\n' ) continue; + needUnistr = 1; + break; + } + if( (needDblQuote==0 && needUnistr==0) + || (needDblQuote==0 && p->eEscMode==SHELL_ESC_OFF) + ){ + sqlite3_fprintf(out, "'%s'",z); + }else if( p->eEscMode==SHELL_ESC_OFF ){ + char *zEncoded = sqlite3_mprintf("%Q", z); + sqlite3_fputs(zEncoded, out); + sqlite3_free(zEncoded); + }else{ + if( needUnistr ){ + sqlite3_fputs("unistr('", out); + }else{ + sqlite3_fputs("'", out); + } + while( *z ){ + for(i=0; (c = z[i])!=0; i++){ + if( c=='\'' ) break; + if( c>0x1f ) continue; + if( c=='\t' || c=='\n' ) continue; + if( c=='\r' && z[i+1]=='\n' ) continue; + break; + } + if( i ){ + sqlite3_fprintf(out, "%.*s", i, z); + z += i; + } + if( c==0 ) break; + if( c=='\'' ){ + sqlite3_fputs("''", out); + }else{ + sqlite3_fprintf(out, "\\u%04x", c); + } + z++; + } + if( needUnistr ){ + sqlite3_fputs("')", out); + }else{ + sqlite3_fputs("'", out); + } + } + setCrlfMode(p); +} + +/* +** Output the given string as a quoted string using SQL quoting conventions. +** Additionallly , escape the "\n" and "\r" characters so that they do not +** get corrupted by end-of-line translation facilities in some operating +** systems. +** +** This is like output_quoted_string() but with the addition of the \r\n +** escape mechanism. +*/ +static void output_quoted_escaped_string(ShellState *p, const char *z){ + char *zEscaped; + sqlite3_fsetmode(p->out, _O_BINARY); + if( p->eEscMode==SHELL_ESC_OFF ){ + zEscaped = sqlite3_mprintf("%Q", z); + }else{ + zEscaped = sqlite3_mprintf("%#Q", z); + } + sqlite3_fputs(zEscaped, p->out); + sqlite3_free(zEscaped); + setCrlfMode(p); +} + /* ** Find earliest of chars within s specified in zAny. ** With ns == ~0, is like strpbrk(s,zAny) and s must be 0-terminated. @@ -2064,14 +2115,13 @@ static void output_c_string(FILE *out, const char *z){ static const char *zDQBSRO = "\"\\\x7f"; /* double-quote, backslash, rubout */ char ace[3] = "\\?"; char cbsSay; - cli_puts(zq, out); - if( z==0 ) z = ""; + sqlite3_fputs(zq, out); while( *z!=0 ){ const char *pcDQBSRO = anyOfInStr(z, zDQBSRO, ~(size_t)0); const char *pcPast = zSkipValidUtf8(z, INT_MAX, ctrlMask); const char *pcEnd = (pcDQBSRO && pcDQBSRO < pcPast)? pcDQBSRO : pcPast; if( pcEnd > z ){ - cli_printf(out, "%.*s", (int)(pcEnd-z), z); + sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z); } if( (c = *pcEnd)==0 ) break; ++pcEnd; @@ -2087,122 +2137,266 @@ static void output_c_string(FILE *out, const char *z){ } if( cbsSay ){ ace[1] = cbsSay; - cli_puts(ace, out); + sqlite3_fputs(ace, out); }else if( !isprint(c&0xff) ){ - cli_printf(out, "\\%03o", c&0xff); + sqlite3_fprintf(out, "\\%03o", c&0xff); }else{ ace[1] = (char)c; - cli_puts(ace+1, out); + sqlite3_fputs(ace+1, out); } z = pcEnd; } - cli_puts(zq, out); + sqlite3_fputs(zq, out); } -/* Encode input string z[] as a C-language string literal and -** append it to the sqlite3_str. If z is NULL render and empty string. +/* +** Output the given string as quoted according to JSON quoting rules. */ -static void append_c_string(sqlite3_str *out, const char *z){ - char c; +static void output_json_string(FILE *out, const char *z, i64 n){ + unsigned char c; static const char *zq = "\""; static long ctrlMask = ~0L; - static const char *zDQBSRO = "\"\\\x7f"; /* double-quote, backslash, rubout */ + static const char *zDQBS = "\"\\"; + const char *pcLimit; char ace[3] = "\\?"; char cbsSay; + if( z==0 ) z = ""; - sqlite3_str_appendall(out,zq); - while( *z!=0 ){ - const char *pcDQBSRO = anyOfInStr(z, zDQBSRO, ~(size_t)0); - const char *pcPast = zSkipValidUtf8(z, INT_MAX, ctrlMask); - const char *pcEnd = (pcDQBSRO && pcDQBSRO < pcPast)? pcDQBSRO : pcPast; + pcLimit = z + ((n<0)? strlen(z) : (size_t)n); + sqlite3_fputs(zq, out); + while( z < pcLimit ){ + const char *pcDQBS = anyOfInStr(z, zDQBS, pcLimit-z); + const char *pcPast = zSkipValidUtf8(z, (int)(pcLimit-z), ctrlMask); + const char *pcEnd = (pcDQBS && pcDQBS < pcPast)? pcDQBS : pcPast; if( pcEnd > z ){ - sqlite3_str_appendf(out, "%.*s", (int)(pcEnd-z), z); + sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z); + z = pcEnd; } - if( (c = *pcEnd)==0 ) break; - ++pcEnd; + if( z >= pcLimit ) break; + c = (unsigned char)*(z++); switch( c ){ - case '\\': case '"': + case '"': case '\\': cbsSay = (char)c; break; - case '\t': cbsSay = 't'; break; + case '\b': cbsSay = 'b'; break; + case '\f': cbsSay = 'f'; break; case '\n': cbsSay = 'n'; break; case '\r': cbsSay = 'r'; break; - case '\f': cbsSay = 'f'; break; + case '\t': cbsSay = 't'; break; default: cbsSay = 0; break; } if( cbsSay ){ ace[1] = cbsSay; - sqlite3_str_appendall(out,ace); - }else if( !isprint(c&0xff) ){ - sqlite3_str_appendf(out, "\\%03o", c&0xff); + sqlite3_fputs(ace, out); + }else if( c<=0x1f || c>=0x7f ){ + sqlite3_fprintf(out, "\\u%04x", c); }else{ ace[1] = (char)c; - sqlite3_str_appendall(out, ace+1); + sqlite3_fputs(ace+1, out); } - z = pcEnd; } - sqlite3_str_appendall(out, zq); + sqlite3_fputs(zq, out); } /* -** This routine runs when the user presses Ctrl-C -*/ -static void interrupt_handler(int NotUsed){ - UNUSED_PARAMETER(NotUsed); - if( ++seenInterrupt>1 ) cli_exit(1); - if( globalDb ) sqlite3_interrupt(globalDb); -} - -/* Try to determine the screen width. Use the default if unable. +** Escape the input string if it is needed and in accordance with +** eEscMode. +** +** 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 *ppFree to NULL +** and return the original string. If escaping is needed, write the +** escaped string into memory obtained from sqlite3_malloc64() or the +** equivalent, and return the new string and set *ppFree to the new string +** as well. +** +** The caller is responsible for freeing *ppFree if it is non-NULL in order +** to reclaim memory. */ -int shellScreenWidth(void){ - if( stdout_tty_width>0 ){ - return stdout_tty_width; - }else{ -#if defined(TIOCGSIZE) - struct ttysize ts; - if( ioctl(STDIN_FILENO, TIOCGSIZE, &ts)>=0 - || ioctl(STDOUT_FILENO, TIOCGSIZE, &ts)>=0 - || ioctl(STDERR_FILENO, TIOCGSIZE, &ts)>=0 +static const char *escapeOutput( + ShellState *p, + const char *zInX, + char **ppFree +){ + i64 i, j; + i64 nCtrl = 0; + unsigned char *zIn; + unsigned char c; + unsigned char *zOut; + + + /* No escaping if disabled */ + if( p->eEscMode==SHELL_ESC_OFF ){ + *ppFree = 0; + return zInX; + } + + /* Count the number of control characters in the string. */ + zIn = (unsigned char*)zInX; + for(i=0; (c = zIn[i])!=0; i++){ + if( c<=0x1f + && c!='\t' + && c!='\n' + && (c!='\r' || zIn[i+1]!='\n') ){ - return ts.ts_cols; + nCtrl++; } -#elif defined(TIOCGWINSZ) - struct winsize ws; - if( ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)>=0 - || ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)>=0 - || ioctl(STDERR_FILENO, TIOCGWINSZ, &ws)>=0 + } + if( nCtrl==0 ){ + *ppFree = 0; + return zInX; + } + if( p->eEscMode==SHELL_ESC_SYMBOL ) nCtrl *= 2; + zOut = sqlite3_malloc64( i + nCtrl + 1 ); + shell_check_oom(zOut); + for(i=j=0; (c = zIn[i])!=0; i++){ + if( c>0x1f + || c=='\t' + || c=='\n' + || (c=='\r' && zIn[i+1]=='\n') ){ - return ws.ws_col; + continue; } -#elif defined(_WIN32) - CONSOLE_SCREEN_BUFFER_INFO csbi; - if( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) - || GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), &csbi) - || GetConsoleScreenBufferInfo(GetStdHandle(STD_INPUT_HANDLE), &csbi) - ){ - return csbi.srWindow.Right - csbi.srWindow.Left + 1; + if( i>0 ){ + memcpy(&zOut[j], zIn, i); + j += i; + } + zIn += i+1; + i = -1; + switch( p->eEscMode ){ + case SHELL_ESC_SYMBOL: + zOut[j++] = 0xe2; + zOut[j++] = 0x90; + zOut[j++] = 0x80+c; + break; + case SHELL_ESC_ASCII: + zOut[j++] = '^'; + zOut[j++] = 0x40+c; + break; } -#endif -#define DEFAULT_SCREEN_WIDTH 80 - return DEFAULT_SCREEN_WIDTH; } + if( i>0 ){ + memcpy(&zOut[j], zIn, i); + j += i; + } + zOut[j] = 0; + *ppFree = (char*)zOut; + return (char*)zOut; } -#if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) /* -** This routine runs for console events (e.g. Ctrl-C) on Win32 +** Output the given string with characters that are special to +** HTML escaped. */ -static BOOL WINAPI ConsoleCtrlHandler( - DWORD dwCtrlType /* One of the CTRL_*_EVENT constants */ -){ - if( dwCtrlType==CTRL_C_EVENT ){ - interrupt_handler(0); - return TRUE; +static void output_html_string(FILE *out, const char *z){ + int i; + if( z==0 ) z = ""; + while( *z ){ + for(i=0; z[i] + && z[i]!='<' + && z[i]!='&' + && z[i]!='>' + && z[i]!='\"' + && z[i]!='\''; + i++){} + if( i>0 ){ + sqlite3_fprintf(out, "%.*s",i,z); + } + if( z[i]=='<' ){ + sqlite3_fputs("&lt;", out); + }else if( z[i]=='&' ){ + sqlite3_fputs("&amp;", out); + }else if( z[i]=='>' ){ + sqlite3_fputs("&gt;", out); + }else if( z[i]=='\"' ){ + sqlite3_fputs("&quot;", out); + }else if( z[i]=='\'' ){ + sqlite3_fputs("&#39;", out); + }else{ + break; + } + z += i + 1; } - return FALSE; } -#endif + +/* +** 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 needCsvQuote[] = { + 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, +}; + +/* +** Output a single term of CSV. Actually, p->colSeparator is used for +** the separator, which may or may not be a comma. p->nullValue is +** the null value. Strings are quoted if necessary. The separator +** is only issued if bSep is true. +*/ +static void output_csv(ShellState *p, const char *z, int bSep){ + if( z==0 ){ + sqlite3_fprintf(p->out, "%s",p->nullValue); + }else{ + unsigned i; + for(i=0; z[i]; i++){ + if( needCsvQuote[((unsigned char*)z)[i]] ){ + i = 0; + break; + } + } + if( i==0 || strstr(z, p->colSeparator)!=0 ){ + char *zQuoted = sqlite3_mprintf("\"%w\"", z); + shell_check_oom(zQuoted); + sqlite3_fputs(zQuoted, p->out); + sqlite3_free(zQuoted); + }else{ + sqlite3_fputs(z, p->out); + } + } + if( bSep ){ + sqlite3_fputs(p->colSeparator, p->out); + } +} + +/* +** This routine runs when the user presses Ctrl-C +*/ +static void interrupt_handler(int NotUsed){ + UNUSED_PARAMETER(NotUsed); + if( ++seenInterrupt>1 ) exit(1); + if( globalDb ) sqlite3_interrupt(globalDb); +} + +#if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) +/* +** This routine runs for console events (e.g. Ctrl-C) on Win32 +*/ +static BOOL WINAPI ConsoleCtrlHandler( + DWORD dwCtrlType /* One of the CTRL_*_EVENT constants */ +){ + if( dwCtrlType==CTRL_C_EVENT ){ + interrupt_handler(0); + return TRUE; + } + return FALSE; +} +#endif #ifndef SQLITE_OMIT_AUTHORIZATION /* @@ -2222,7 +2416,6 @@ static int safeModeAuth( "fts3_tokenizer", "load_extension", "readfile", - "realpath", "writefile", "zipfile", "zipfile_cds", @@ -2285,23 +2478,23 @@ static int shellAuth( az[1] = zA2; az[2] = zA3; az[3] = zA4; - cli_printf(p->out, "authorizer: %s", azAction[op]); + sqlite3_fprintf(p->out, "authorizer: %s", azAction[op]); for(i=0; i<4; i++){ - cli_puts(" ", p->out); + sqlite3_fputs(" ", p->out); if( az[i] ){ output_c_string(p->out, az[i]); }else{ - cli_puts("NULL", p->out); + sqlite3_fputs("NULL", p->out); } } - cli_puts("\n", p->out); + sqlite3_fputs("\n", p->out); if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4); return SQLITE_OK; } #endif /* -** Print a schema statement. This is helper routine to dump_callbac(). +** Print a schema statement. Part of MODE_Semi and MODE_Pretty output. ** ** This routine converts some CREATE TABLE statements for shadow tables ** in FTS3/4/5 into CREATE TABLE IF NOT EXISTS statements. @@ -2332,12 +2525,18 @@ static void printSchemaLine(FILE *out, const char *z, const char *zTail){ } } if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){ - cli_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); + sqlite3_fprintf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); }else{ - cli_printf(out, "%s%s", z, zTail); + sqlite3_fprintf(out, "%s%s", z, zTail); } sqlite3_free(zToFree); } +static void printSchemaLineN(FILE *out, char *z, int n, const char *zTail){ + char c = z[n]; + z[n] = 0; + printSchemaLine(out, z, zTail); + z[n] = c; +} /* ** Return true if string z[] has nothing but whitespace and comments to the @@ -2355,130 +2554,95 @@ static int wsToEol(const char *z){ } /* -** SQL Function: shell_format_schema(SQL,FLAGS) -** -** This function is internally by the CLI to assist with the -** ".schema", ".fullschema", and ".dump" commands. The first -** argument is the value from sqlite_schema.sql. The value returned -** is a modification of the input that can actually be run as SQL -** to recreate the schema object. -** -** When FLAGS is zero, the only changes is to append ";". If the -** 0x01 bit of FLAGS is set, then transformations are made to implement -** ".schema --indent". +** Add a new entry to the EXPLAIN QUERY PLAN data */ -static void shellFormatSchema( - sqlite3_context *pCtx, - int nVal, - sqlite3_value **apVal -){ - int flags; /* Value of 2nd parameter */ - const char *zSql; /* Value of 1st parameter */ - int nSql; /* Bytes of text in zSql[] */ - sqlite3_str *pOut; /* Output buffer */ - char *z; /* Writable copy of zSql */ - int i, j; /* Loop counters */ - int nParen = 0; - char cEnd = 0; - char c; - int nLine = 0; - int isIndex; - int isWhere = 0; - - assert( nVal==2 ); - pOut = sqlite3_str_new(sqlite3_context_db_handle(pCtx)); - nSql = sqlite3_value_bytes(apVal[0]); - zSql = (const char*)sqlite3_value_text(apVal[0]); - if( zSql==0 || zSql[0]==0 ) goto shellFormatSchema_finish; - flags = sqlite3_value_int(apVal[1]); - if( (flags & 0x01)==0 ){ - sqlite3_str_append(pOut, zSql, nSql); - sqlite3_str_append(pOut, ";", 1); - goto shellFormatSchema_finish; - } - if( sqlite3_strlike("CREATE VIEW%", zSql, 0)==0 - || sqlite3_strlike("CREATE TRIG%", zSql, 0)==0 - ){ - sqlite3_str_append(pOut, zSql, nSql); - sqlite3_str_append(pOut, ";", 1); - goto shellFormatSchema_finish; +static void eqp_append(ShellState *p, int iEqpId, int p2, const char *zText){ + EQPGraphRow *pNew; + i64 nText; + if( zText==0 ) return; + nText = strlen(zText); + if( p->autoEQPtest ){ + sqlite3_fprintf(p->out, "%d,%d,%s\n", iEqpId, p2, zText); } - isIndex = sqlite3_strlike("CREATE INDEX%", zSql, 0)==0 - || sqlite3_strlike("CREATE UNIQUE INDEX%", zSql, 0)==0; - z = sqlite3_mprintf("%s", zSql); - if( z==0 ){ - sqlite3_str_free(pOut); - sqlite3_result_error_nomem(pCtx); - return; + pNew = sqlite3_malloc64( sizeof(*pNew) + nText ); + shell_check_oom(pNew); + pNew->iEqpId = iEqpId; + pNew->iParentId = p2; + memcpy(pNew->zText, zText, nText+1); + pNew->pNext = 0; + if( p->sGraph.pLast ){ + p->sGraph.pLast->pNext = pNew; + }else{ + p->sGraph.pRow = pNew; } - j = 0; - for(i=0; IsSpace(z[i]); i++){} - for(; (c = z[i])!=0; i++){ - if( IsSpace(c) ){ - if( z[j-1]=='\r' ) z[j-1] = '\n'; - if( IsSpace(z[j-1]) || z[j-1]=='(' ) continue; - }else if( (c=='(' || c==')') && j>0 && IsSpace(z[j-1]) ){ - j--; - } - z[j++] = c; - } - while( j>0 && IsSpace(z[j-1]) ){ j--; } - z[j] = 0; - if( strlen30(z)>=79 ){ - for(i=j=0; (c = z[i])!=0; i++){ /* Copy from z[i] back to z[j] */ - if( c==cEnd ){ - cEnd = 0; - }else if( cEnd!=0){ - /* No-op */ - }else if( c=='"' || c=='\'' || c=='`' ){ - cEnd = c; - }else if( c=='[' ){ - cEnd = ']'; - }else if( c=='-' && z[i+1]=='-' ){ - cEnd = '\n'; - }else if( c=='(' ){ - nParen++; - }else if( c==')' ){ - nParen--; - if( nLine>0 && nParen==0 && j>0 && !isWhere ){ - sqlite3_str_append(pOut, z, j); - sqlite3_str_append(pOut, "\n", 1); - j = 0; - } - }else if( (c=='w' || c=='W') - && nParen==0 && isIndex - && sqlite3_strnicmp("WHERE",&z[i],5)==0 - && !IsAlnum(z[i+5]) && z[i+5]!='_' ){ - isWhere = 1; - }else if( isWhere && (c=='A' || c=='a') - && nParen==0 - && sqlite3_strnicmp("AND",&z[i],3)==0 - && !IsAlnum(z[i+3]) && z[i+3]!='_' ){ - sqlite3_str_append(pOut, z, j); - sqlite3_str_append(pOut, "\n ", 5); - j = 0; - } - z[j++] = c; - if( nParen==1 && cEnd==0 - && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1))) - && !isWhere - ){ - if( c=='\n' ) j--; - sqlite3_str_append(pOut, z, j); - sqlite3_str_append(pOut, "\n ", 3); - j = 0; - nLine++; - while( IsSpace(z[i+1]) ){ i++; } - } + p->sGraph.pLast = pNew; +} + +/* +** Free and reset the EXPLAIN QUERY PLAN data that has been collected +** in p->sGraph. +*/ +static void eqp_reset(ShellState *p){ + EQPGraphRow *pRow, *pNext; + for(pRow = p->sGraph.pRow; pRow; pRow = pNext){ + pNext = pRow->pNext; + sqlite3_free(pRow); + } + memset(&p->sGraph, 0, sizeof(p->sGraph)); +} + +/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after +** pOld, or return the first such line if pOld is NULL +*/ +static EQPGraphRow *eqp_next_row(ShellState *p, int iEqpId, EQPGraphRow *pOld){ + EQPGraphRow *pRow = pOld ? pOld->pNext : p->sGraph.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 eqp_render_level(ShellState *p, int iEqpId){ + EQPGraphRow *pRow, *pNext; + i64 n = strlen(p->sGraph.zPrefix); + char *z; + for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){ + pNext = eqp_next_row(p, iEqpId, pRow); + z = pRow->zText; + sqlite3_fprintf(p->out, "%s%s%s\n", p->sGraph.zPrefix, + pNext ? "|--" : "`--", z); + if( n<(i64)sizeof(p->sGraph.zPrefix)-7 ){ + memcpy(&p->sGraph.zPrefix[n], pNext ? "| " : " ", 4); + eqp_render_level(p, pRow->iEqpId); + p->sGraph.zPrefix[n] = 0; } - z[j] = 0; } - sqlite3_str_appendall(pOut, z); - sqlite3_str_append(pOut, ";", 1); - sqlite3_free(z); +} -shellFormatSchema_finish: - sqlite3_result_text(pCtx, sqlite3_str_finish(pOut), -1, sqlite3_free); +/* +** Display and reset the EXPLAIN QUERY PLAN data +*/ +static void eqp_render(ShellState *p, i64 nCycle){ + EQPGraphRow *pRow = p->sGraph.pRow; + if( pRow ){ + if( pRow->zText[0]=='-' ){ + if( pRow->pNext==0 ){ + eqp_reset(p); + return; + } + sqlite3_fprintf(p->out, "%s\n", pRow->zText+3); + p->sGraph.pRow = pRow->pNext; + sqlite3_free(pRow); + }else if( nCycle>0 ){ + sqlite3_fprintf(p->out, "QUERY PLAN (cycles=%lld [100%%])\n", nCycle); + }else{ + sqlite3_fputs("QUERY PLAN\n", p->out); + } + p->sGraph.zPrefix[0] = 0; + eqp_render_level(p, 0); + eqp_reset(p); + } } #ifndef SQLITE_OMIT_PROGRESS_CALLBACK @@ -2488,26 +2652,493 @@ shellFormatSchema_finish: static int progress_handler(void *pClientData) { ShellState *p = (ShellState*)pClientData; p->nProgress++; - if( (p->flgProgress & SHELL_PROGRESS_TMOUT)!=0 - && ELAPSE_TIME(p)>=p->tmProgress - ){ - cli_printf(p->out, "Progress timeout after %.6f seconds\n", - ELAPSE_TIME(p)); - return 1; - } if( p->nProgress>=p->mxProgress && p->mxProgress>0 ){ - cli_printf(p->out, "Progress limit reached (%u)\n", p->nProgress); + sqlite3_fprintf(p->out, "Progress limit reached (%u)\n", p->nProgress); if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; if( p->flgProgress & SHELL_PROGRESS_ONCE ) p->mxProgress = 0; return 1; } if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){ - cli_printf(p->out, "Progress %u\n", p->nProgress); + sqlite3_fprintf(p->out, "Progress %u\n", p->nProgress); } return 0; } #endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ +/* +** Print N dashes +*/ +static void print_dashes(FILE *out, int N){ + const char zDash[] = "--------------------------------------------------"; + const int nDash = sizeof(zDash) - 1; + while( N>nDash ){ + sqlite3_fputs(zDash, out); + N -= nDash; + } + sqlite3_fprintf(out, "%.*s", N, zDash); +} + +/* +** Print a markdown or table-style row separator using ascii-art +*/ +static void print_row_separator( + ShellState *p, + int nArg, + const char *zSep +){ + int i; + if( nArg>0 ){ + sqlite3_fputs(zSep, p->out); + print_dashes(p->out, p->actualWidth[0]+2); + for(i=1; i<nArg; i++){ + sqlite3_fputs(zSep, p->out); + print_dashes(p->out, p->actualWidth[i]+2); + } + sqlite3_fputs(zSep, p->out); + } + sqlite3_fputs("\n", p->out); +} + +/* +** This is the callback routine that the shell +** invokes for each row of a query result. +*/ +static int shell_callback( + void *pArg, + int nArg, /* Number of result columns */ + char **azArg, /* Text of each result column */ + char **azCol, /* Column names */ + int *aiType /* Column types. Might be NULL */ +){ + int i; + ShellState *p = (ShellState*)pArg; + + if( azArg==0 ) return 0; + switch( p->cMode ){ + case MODE_Count: + case MODE_Off: { + break; + } + case MODE_Line: { + int w = 5; + if( azArg==0 ) break; + for(i=0; i<nArg; i++){ + int len = strlen30(azCol[i] ? azCol[i] : ""); + if( len>w ) w = len; + } + if( p->cnt++>0 ) sqlite3_fputs(p->rowSeparator, p->out); + for(i=0; i<nArg; i++){ + char *pFree = 0; + const char *pDisplay; + pDisplay = escapeOutput(p, azArg[i] ? azArg[i] : p->nullValue, &pFree); + sqlite3_fprintf(p->out, "%*s = %s%s", w, azCol[i], + pDisplay, p->rowSeparator); + if( pFree ) sqlite3_free(pFree); + } + break; + } + case MODE_ScanExp: + case MODE_Explain: { + 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 = ArraySize(aExplainWidth); + int iIndent = 1; + + if( p->cMode==MODE_ScanExp ){ + aWidth = aScanExpWidth; + aMap = aScanExpMap; + nWidth = ArraySize(aScanExpWidth); + iIndent = 3; + } + if( nArg>nWidth ) nArg = nWidth; + + /* If this is the first row seen, print out the headers */ + if( p->cnt++==0 ){ + for(i=0; i<nArg; i++){ + utf8_width_print(p->out, aWidth[i], azCol[ aMap[i] ]); + sqlite3_fputs(i==nArg-1 ? "\n" : " ", p->out); + } + for(i=0; i<nArg; i++){ + print_dashes(p->out, aWidth[i]); + sqlite3_fputs(i==nArg-1 ? "\n" : " ", p->out); + } + } + + /* If there is no data, exit early. */ + if( azArg==0 ) break; + + for(i=0; i<nArg; i++){ + const char *zSep = " "; + int w = aWidth[i]; + const char *zVal = azArg[ aMap[i] ]; + if( i==nArg-1 ) w = 0; + if( zVal && strlenChar(zVal)>w ){ + w = strlenChar(zVal); + zSep = " "; + } + if( i==iIndent && p->aiIndent && p->pStmt ){ + if( p->iIndent<p->nIndent ){ + sqlite3_fprintf(p->out, "%*.s", p->aiIndent[p->iIndent], ""); + } + p->iIndent++; + } + utf8_width_print(p->out, w, zVal ? zVal : p->nullValue); + sqlite3_fputs(i==nArg-1 ? "\n" : zSep, p->out); + } + break; + } + case MODE_Semi: { /* .schema and .fullschema output */ + printSchemaLine(p->out, azArg[0], ";\n"); + break; + } + case MODE_Pretty: { /* .schema and .fullschema with --indent */ + char *z; + int j; + int nParen = 0; + char cEnd = 0; + char c; + int nLine = 0; + int isIndex; + int isWhere = 0; + assert( nArg==1 ); + if( azArg[0]==0 ) break; + if( sqlite3_strlike("CREATE VIEW%", azArg[0], 0)==0 + || sqlite3_strlike("CREATE TRIG%", azArg[0], 0)==0 + ){ + sqlite3_fprintf(p->out, "%s;\n", azArg[0]); + break; + } + isIndex = sqlite3_strlike("CREATE INDEX%", azArg[0], 0)==0 + || sqlite3_strlike("CREATE UNIQUE INDEX%", azArg[0], 0)==0; + z = sqlite3_mprintf("%s", azArg[0]); + shell_check_oom(z); + j = 0; + for(i=0; IsSpace(z[i]); i++){} + for(; (c = z[i])!=0; i++){ + if( IsSpace(c) ){ + if( z[j-1]=='\r' ) z[j-1] = '\n'; + if( IsSpace(z[j-1]) || z[j-1]=='(' ) continue; + }else if( (c=='(' || c==')') && j>0 && IsSpace(z[j-1]) ){ + j--; + } + z[j++] = c; + } + while( j>0 && IsSpace(z[j-1]) ){ j--; } + z[j] = 0; + if( strlen30(z)>=79 ){ + for(i=j=0; (c = z[i])!=0; i++){ /* Copy from z[i] back to z[j] */ + if( c==cEnd ){ + cEnd = 0; + }else if( c=='"' || c=='\'' || c=='`' ){ + cEnd = c; + }else if( c=='[' ){ + cEnd = ']'; + }else if( c=='-' && z[i+1]=='-' ){ + cEnd = '\n'; + }else if( c=='(' ){ + nParen++; + }else if( c==')' ){ + nParen--; + if( nLine>0 && nParen==0 && j>0 && !isWhere ){ + printSchemaLineN(p->out, z, j, "\n"); + j = 0; + } + }else if( (c=='w' || c=='W') + && nParen==0 && isIndex + && sqlite3_strnicmp("WHERE",&z[i],5)==0 + && !IsAlnum(z[i+5]) && z[i+5]!='_' ){ + isWhere = 1; + }else if( isWhere && (c=='A' || c=='a') + && nParen==0 + && sqlite3_strnicmp("AND",&z[i],3)==0 + && !IsAlnum(z[i+3]) && z[i+3]!='_' ){ + printSchemaLineN(p->out, z, j, "\n "); + j = 0; + } + z[j++] = c; + if( nParen==1 && cEnd==0 + && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1))) + && !isWhere + ){ + if( c=='\n' ) j--; + printSchemaLineN(p->out, z, j, "\n "); + j = 0; + nLine++; + while( IsSpace(z[i+1]) ){ i++; } + } + } + z[j] = 0; + } + printSchemaLine(p->out, z, ";\n"); + sqlite3_free(z); + break; + } + case MODE_List: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + char *z = azCol[i]; + char *pFree; + const char *zOut = escapeOutput(p, z, &pFree); + sqlite3_fprintf(p->out, "%s%s", zOut, + i==nArg-1 ? p->rowSeparator : p->colSeparator); + if( pFree ) sqlite3_free(pFree); + } + } + if( azArg==0 ) break; + for(i=0; i<nArg; i++){ + char *z = azArg[i]; + char *pFree; + const char *zOut; + if( z==0 ) z = p->nullValue; + zOut = escapeOutput(p, z, &pFree); + sqlite3_fputs(zOut, p->out); + if( pFree ) sqlite3_free(pFree); + sqlite3_fputs((i<nArg-1)? p->colSeparator : p->rowSeparator, p->out); + } + break; + } + case MODE_Www: + case MODE_Html: { + if( p->cnt==0 && p->cMode==MODE_Www ){ + sqlite3_fputs( + "</PRE>\n" + "<TABLE border='1' cellspacing='0' cellpadding='2'>\n" + ,p->out + ); + } + if( p->cnt==0 && (p->showHeader || p->cMode==MODE_Www) ){ + sqlite3_fputs("<TR>", p->out); + for(i=0; i<nArg; i++){ + sqlite3_fputs("<TH>", p->out); + output_html_string(p->out, azCol[i]); + sqlite3_fputs("</TH>\n", p->out); + } + sqlite3_fputs("</TR>\n", p->out); + } + p->cnt++; + if( azArg==0 ) break; + sqlite3_fputs("<TR>", p->out); + for(i=0; i<nArg; i++){ + sqlite3_fputs("<TD>", p->out); + output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue); + sqlite3_fputs("</TD>\n", p->out); + } + sqlite3_fputs("</TR>\n", p->out); + break; + } + case MODE_Tcl: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + output_c_string(p->out, azCol[i] ? azCol[i] : ""); + if(i<nArg-1) sqlite3_fputs(p->colSeparator, p->out); + } + sqlite3_fputs(p->rowSeparator, p->out); + } + if( azArg==0 ) break; + for(i=0; i<nArg; i++){ + output_c_string(p->out, azArg[i] ? azArg[i] : p->nullValue); + if(i<nArg-1) sqlite3_fputs(p->colSeparator, p->out); + } + sqlite3_fputs(p->rowSeparator, p->out); + break; + } + case MODE_Csv: { + sqlite3_fsetmode(p->out, _O_BINARY); + if( p->cnt++==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1); + } + sqlite3_fputs(p->rowSeparator, p->out); + } + if( nArg>0 ){ + for(i=0; i<nArg; i++){ + output_csv(p, azArg[i], i<nArg-1); + } + sqlite3_fputs(p->rowSeparator, p->out); + } + setCrlfMode(p); + break; + } + case MODE_Insert: { + if( azArg==0 ) break; + sqlite3_fprintf(p->out, "INSERT INTO %s",p->zDestTable); + if( p->showHeader ){ + sqlite3_fputs("(", p->out); + for(i=0; i<nArg; i++){ + if( i>0 ) sqlite3_fputs(",", p->out); + if( quoteChar(azCol[i]) ){ + char *z = sqlite3_mprintf("\"%w\"", azCol[i]); + shell_check_oom(z); + sqlite3_fputs(z, p->out); + sqlite3_free(z); + }else{ + sqlite3_fprintf(p->out, "%s", azCol[i]); + } + } + sqlite3_fputs(")", p->out); + } + p->cnt++; + for(i=0; i<nArg; i++){ + sqlite3_fputs(i>0 ? "," : " VALUES(", p->out); + if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ + sqlite3_fputs("NULL", p->out); + }else if( aiType && aiType[i]==SQLITE_TEXT ){ + if( ShellHasFlag(p, SHFLG_Newlines) ){ + output_quoted_string(p, azArg[i]); + }else{ + output_quoted_escaped_string(p, azArg[i]); + } + }else if( aiType && aiType[i]==SQLITE_INTEGER ){ + sqlite3_fputs(azArg[i], p->out); + }else if( aiType && aiType[i]==SQLITE_FLOAT ){ + char z[50]; + double r = sqlite3_column_double(p->pStmt, i); + sqlite3_uint64 ur; + memcpy(&ur,&r,sizeof(r)); + if( ur==0x7ff0000000000000LL ){ + sqlite3_fputs("9.0e+999", p->out); + }else if( ur==0xfff0000000000000LL ){ + sqlite3_fputs("-9.0e+999", p->out); + }else{ + sqlite3_int64 ir = (sqlite3_int64)r; + if( r==(double)ir ){ + sqlite3_snprintf(50,z,"%lld.0", ir); + }else{ + sqlite3_snprintf(50,z,"%!.20g", r); + } + sqlite3_fputs(z, p->out); + } + }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ + const void *pBlob = sqlite3_column_blob(p->pStmt, i); + int nBlob = sqlite3_column_bytes(p->pStmt, i); + output_hex_blob(p->out, pBlob, nBlob); + }else if( isNumber(azArg[i], 0) ){ + sqlite3_fputs(azArg[i], p->out); + }else if( ShellHasFlag(p, SHFLG_Newlines) ){ + output_quoted_string(p, azArg[i]); + }else{ + output_quoted_escaped_string(p, azArg[i]); + } + } + sqlite3_fputs(");\n", p->out); + break; + } + case MODE_Json: { + if( azArg==0 ) break; + if( p->cnt==0 ){ + sqlite3_fputs("[{", p->out); + }else{ + sqlite3_fputs(",\n{", p->out); + } + p->cnt++; + for(i=0; i<nArg; i++){ + output_json_string(p->out, azCol[i], -1); + sqlite3_fputs(":", p->out); + if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ + sqlite3_fputs("null", p->out); + }else if( aiType && aiType[i]==SQLITE_FLOAT ){ + char z[50]; + double r = sqlite3_column_double(p->pStmt, i); + sqlite3_uint64 ur; + memcpy(&ur,&r,sizeof(r)); + if( ur==0x7ff0000000000000LL ){ + sqlite3_fputs("9.0e+999", p->out); + }else if( ur==0xfff0000000000000LL ){ + sqlite3_fputs("-9.0e+999", p->out); + }else{ + sqlite3_snprintf(50,z,"%!.20g", r); + sqlite3_fputs(z, p->out); + } + }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ + const void *pBlob = sqlite3_column_blob(p->pStmt, i); + int nBlob = sqlite3_column_bytes(p->pStmt, i); + output_json_string(p->out, pBlob, nBlob); + }else if( aiType && aiType[i]==SQLITE_TEXT ){ + output_json_string(p->out, azArg[i], -1); + }else{ + sqlite3_fputs(azArg[i], p->out); + } + if( i<nArg-1 ){ + sqlite3_fputs(",", p->out); + } + } + sqlite3_fputs("}", p->out); + break; + } + case MODE_Quote: { + if( azArg==0 ) break; + if( p->cnt==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + if( i>0 ) sqlite3_fputs(p->colSeparator, p->out); + output_quoted_string(p, azCol[i]); + } + sqlite3_fputs(p->rowSeparator, p->out); + } + p->cnt++; + for(i=0; i<nArg; i++){ + if( i>0 ) sqlite3_fputs(p->colSeparator, p->out); + if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ + sqlite3_fputs("NULL", p->out); + }else if( aiType && aiType[i]==SQLITE_TEXT ){ + output_quoted_string(p, azArg[i]); + }else if( aiType && aiType[i]==SQLITE_INTEGER ){ + sqlite3_fputs(azArg[i], p->out); + }else if( aiType && aiType[i]==SQLITE_FLOAT ){ + char z[50]; + double r = sqlite3_column_double(p->pStmt, i); + sqlite3_snprintf(50,z,"%!.20g", r); + sqlite3_fputs(z, p->out); + }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ + const void *pBlob = sqlite3_column_blob(p->pStmt, i); + int nBlob = sqlite3_column_bytes(p->pStmt, i); + output_hex_blob(p->out, pBlob, nBlob); + }else if( isNumber(azArg[i], 0) ){ + sqlite3_fputs(azArg[i], p->out); + }else{ + output_quoted_string(p, azArg[i]); + } + } + sqlite3_fputs(p->rowSeparator, p->out); + break; + } + case MODE_Ascii: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + if( i>0 ) sqlite3_fputs(p->colSeparator, p->out); + sqlite3_fputs(azCol[i] ? azCol[i] : "", p->out); + } + sqlite3_fputs(p->rowSeparator, p->out); + } + if( azArg==0 ) break; + for(i=0; i<nArg; i++){ + if( i>0 ) sqlite3_fputs(p->colSeparator, p->out); + sqlite3_fputs(azArg[i] ? azArg[i] : p->nullValue, p->out); + } + sqlite3_fputs(p->rowSeparator, p->out); + break; + } + case MODE_EQP: { + eqp_append(p, atoi(azArg[0]), atoi(azArg[1]), azArg[3]); + break; + } + } + return 0; +} + +/* +** This is the callback routine that the SQLite library +** invokes for each row of a query result. +*/ +static int callback(void *pArg, int nArg, char **azArg, char **azCol){ + /* since we don't have type info, call the shell_callback with a NULL value */ + return shell_callback(pArg, nArg, azArg, azCol, NULL); +} + /* ** This is the callback routine from sqlite3_exec() that appends all ** output onto the end of a ShellText object. @@ -2567,7 +3198,7 @@ static void createSelftestTable(ShellState *p){ "DROP TABLE [_shell$self];" ,0,0,&zErrMsg); if( zErrMsg ){ - cli_printf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg); + sqlite3_fprintf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg); sqlite3_free(zErrMsg); } sqlite3_exec(p->db, "RELEASE selftest_init",0,0,0); @@ -2585,7 +3216,11 @@ static void set_table_name(ShellState *p, const char *zName){ p->zDestTable = 0; } if( zName==0 ) return; - p->zDestTable = sqlite3_mprintf("%s", zName); + if( quoteChar(zName) ){ + p->zDestTable = sqlite3_mprintf("\"%w\"", zName); + }else{ + p->zDestTable = sqlite3_mprintf("%s", zName); + } shell_check_oom(p->zDestTable); } @@ -2655,7 +3290,7 @@ static int run_table_dump_query( rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ char *zContext = shell_error_context(zSelect, p->db); - cli_printf(p->out, "/**** ERROR: (%d) %s *****/\n%s", + sqlite3_fprintf(p->out, "/**** ERROR: (%d) %s *****/\n%s", rc, sqlite3_errmsg(p->db), zContext); sqlite3_free(zContext); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; @@ -2665,22 +3300,22 @@ static int run_table_dump_query( nResult = sqlite3_column_count(pSelect); while( rc==SQLITE_ROW ){ z = (const char*)sqlite3_column_text(pSelect, 0); - cli_printf(p->out, "%s", z); + sqlite3_fprintf(p->out, "%s", z); for(i=1; i<nResult; i++){ - cli_printf(p->out, ",%s", sqlite3_column_text(pSelect, i)); + sqlite3_fprintf(p->out, ",%s", sqlite3_column_text(pSelect, i)); } if( z==0 ) z = ""; while( z[0] && (z[0]!='-' || z[1]!='-') ) z++; if( z[0] ){ - cli_puts("\n;\n", p->out); + sqlite3_fputs("\n;\n", p->out); }else{ - cli_puts(";\n", p->out); + sqlite3_fputs(";\n", p->out); } rc = sqlite3_step(pSelect); } rc = sqlite3_finalize(pSelect); if( rc!=SQLITE_OK ){ - cli_printf(p->out, "/**** ERROR: (%d) %s *****/\n", + sqlite3_fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; } @@ -2740,7 +3375,7 @@ static void displayLinuxIoStats(FILE *out){ for(i=0; i<ArraySize(aTrans); i++){ int n = strlen30(aTrans[i].zPattern); if( cli_strncmp(aTrans[i].zPattern, z, n)==0 ){ - cli_printf(out, "%-36s %s", aTrans[i].zDesc, &z[n]); + sqlite3_fprintf(out, "%-36s %s", aTrans[i].zDesc, &z[n]); break; } } @@ -2772,7 +3407,7 @@ static void displayStatLine( }else{ sqlite3_snprintf(sizeof(zLine), zLine, zFormat, iHiwtr); } - cli_printf(out, "%-36s %s\n", zLabel, zLine); + sqlite3_fprintf(out, "%-36s %s\n", zLabel, zLine); } /* @@ -2794,22 +3429,22 @@ static int display_stats( sqlite3_stmt *pStmt = pArg->pStmt; char z[100]; nCol = sqlite3_column_count(pStmt); - cli_printf(out, "%-36s %d\n", "Number of output columns:", nCol); + sqlite3_fprintf(out, "%-36s %d\n", "Number of output columns:", nCol); for(i=0; i<nCol; i++){ sqlite3_snprintf(sizeof(z),z,"Column %d %nname:", i, &x); - cli_printf(out, "%-36s %s\n", z, sqlite3_column_name(pStmt,i)); + sqlite3_fprintf(out, "%-36s %s\n", z, sqlite3_column_name(pStmt,i)); #ifndef SQLITE_OMIT_DECLTYPE sqlite3_snprintf(30, z+x, "declared type:"); - cli_printf(out, "%-36s %s\n", z, sqlite3_column_decltype(pStmt, i)); + sqlite3_fprintf(out, "%-36s %s\n", z, sqlite3_column_decltype(pStmt, i)); #endif #ifdef SQLITE_ENABLE_COLUMN_METADATA sqlite3_snprintf(30, z+x, "database name:"); - cli_printf(out, "%-36s %s\n", z, + sqlite3_fprintf(out, "%-36s %s\n", z, sqlite3_column_database_name(pStmt,i)); sqlite3_snprintf(30, z+x, "table name:"); - cli_printf(out, "%-36s %s\n", z, sqlite3_column_table_name(pStmt,i)); + sqlite3_fprintf(out, "%-36s %s\n", z, sqlite3_column_table_name(pStmt,i)); sqlite3_snprintf(30, z+x, "origin name:"); - cli_printf(out, "%-36s %s\n", z,sqlite3_column_origin_name(pStmt,i)); + sqlite3_fprintf(out, "%-36s %s\n", z,sqlite3_column_origin_name(pStmt,i)); #endif } } @@ -2817,7 +3452,7 @@ static int display_stats( if( pArg->statsOn==3 ){ if( pArg->pStmt ){ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset); - cli_printf(out, "VM-steps: %d\n", iCur); + sqlite3_fprintf(out, "VM-steps: %d\n", iCur); } return 0; } @@ -2846,55 +3481,55 @@ static int display_stats( iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, &iCur, &iHiwtr, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &iCur, &iHiwtr, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Successful lookaside attempts: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Lookaside failures due to size: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Lookaside failures due to OOM: %d\n", iHiwtr); } iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Pager Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1); - cli_printf(out, + sqlite3_fprintf(out, "Page cache hits: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); - cli_printf(out, + sqlite3_fprintf(out, "Page cache misses: %d\n", iCur); iHiwtr64 = iCur64 = -1; sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64, 0); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); - cli_printf(out, + sqlite3_fprintf(out, "Page cache writes: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1); - cli_printf(out, + sqlite3_fprintf(out, "Page cache spills: %d\n", iCur); - cli_printf(out, + sqlite3_fprintf(out, "Temporary data spilled to disk: %lld\n", iCur64); sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64, 1); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Schema Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Statement Heap/Lookaside Usage: %d bytes\n", iCur); } @@ -2902,33 +3537,33 @@ static int display_stats( int iHit, iMiss; iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Fullscan Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Sort Operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); - cli_printf(out, + sqlite3_fprintf(out, "Autoindex Inserts: %d\n", iCur); iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, bReset); iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, bReset); if( iHit || iMiss ){ - cli_printf(out, + sqlite3_fprintf(out, "Bloom filter bypass taken: %d/%d\n", iHit, iHit+iMiss); } iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Virtual Machine Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset); - cli_printf(out, + sqlite3_fprintf(out, "Reprepare operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Number of times run: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset); - cli_printf(out, + sqlite3_fprintf(out, "Memory used by prepared stmt: %d\n", iCur); } @@ -2941,24 +3576,285 @@ static int display_stats( return 0; } + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +static int scanStatsHeight(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 + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +static void display_explain_scanstats( + sqlite3 *db, /* Database to query */ + ShellState *pArg /* Pointer to ShellState */ +){ + static const int f = SQLITE_SCANSTAT_COMPLEX; + sqlite3_stmt *p = pArg->pStmt; + int ii = 0; + i64 nTotal = 0; + int nWidth = 0; + eqp_reset(pArg); + + for(ii=0; 1; ii++){ + const char *z = 0; + int n = 0; + if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ + break; + } + n = (int)strlen(z) + scanStatsHeight(p, ii)*3; + if( n>nWidth ) nWidth = n; + } + nWidth += 4; + + sqlite3_stmt_scanstatus_v2(p, -1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal); + for(ii=0; 1; ii++){ + i64 nLoop = 0; + i64 nRow = 0; + i64 nCycle = 0; + int iId = 0; + int iPid = 0; + const char *zo = 0; + const char *zName = 0; + char *zText = 0; + double rEst = 0.0; + + if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){ + break; + } + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_EST,f,(void*)&rEst); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName); + + zText = sqlite3_mprintf("%s", zo); + if( nCycle>=0 || nLoop>=0 || nRow>=0 ){ + char *z = 0; + if( nCycle>=0 && nTotal>0 ){ + z = sqlite3_mprintf("%zcycles=%lld [%d%%]", z, + nCycle, ((nCycle*100)+nTotal/2) / nTotal + ); + } + if( nLoop>=0 ){ + z = sqlite3_mprintf("%z%sloops=%lld", z, z ? " " : "", nLoop); + } + if( nRow>=0 ){ + z = sqlite3_mprintf("%z%srows=%lld", z, z ? " " : "", nRow); + } + + if( zName && pArg->scanstatsOn>1 ){ + double rpl = (double)nRow / (double)nLoop; + z = sqlite3_mprintf("%z rpl=%.1f est=%.1f", z, rpl, rEst); + } + + zText = sqlite3_mprintf( + "% *z (%z)", -1*(nWidth-scanStatsHeight(p, ii)*3), zText, z + ); + } + + eqp_append(pArg, iId, iPid, zText); + sqlite3_free(zText); + } + + eqp_render(pArg, nTotal); +} +#endif + + /* -** Disable and restore .wheretrace and .treetrace/.selecttrace settings. +** 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 unsigned int savedSelectTrace; -static unsigned int savedWhereTrace; -static void disable_debug_trace_modes(void){ - unsigned int zero = 0; - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 0, &savedSelectTrace); - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &zero); - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 2, &savedWhereTrace); - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &zero); -} -static void restore_debug_trace_modes(void){ - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &savedSelectTrace); - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace); +static int str_in_array(const char *zStr, const char **azArray){ + int i; + for(i=0; azArray[i]; i++){ + if( 0==cli_strcmp(zStr, azArray[i]) ) return 1; + } + return 0; } -/* Create the TEMP table used to store parameter bindings */ +/* +** If compiled statement pSql appears to be an EXPLAIN statement, allocate +** and populate the ShellState.aiIndent[] array with the number of +** spaces each opcode should be indented before it is output. +** +** 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 explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ + int *abYield = 0; /* True if op is an OP_Yield */ + int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */ + int iOp; /* Index of operation in p->aiIndent[] */ + + 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(pSql)>=4 ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 0), "addr" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 1), "opcode" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 2), "p1" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 3), "p2" ) ); + + for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){ + int i; + int iAddr = sqlite3_column_int(pSql, 0); + const char *zOp = (const char*)sqlite3_column_text(pSql, 1); + int p1 = sqlite3_column_int(pSql, 2); + int p2 = sqlite3_column_int(pSql, 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 p->aiIndent array as required */ + if( iOp>=nAlloc ){ + nAlloc += 100; + p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int)); + shell_check_oom(p->aiIndent); + abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int)); + shell_check_oom(abYield); + } + + abYield[iOp] = str_in_array(zOp, azYield); + p->aiIndent[iOp] = 0; + p->nIndent = iOp+1; + if( str_in_array(zOp, azNext) && p2op>0 ){ + for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2; + } + if( str_in_array(zOp, azGoto) && p2op<iOp && (abYield[p2op] || p1) ){ + for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2; + } + } + + p->iIndent = 0; + sqlite3_free(abYield); + sqlite3_reset(pSql); +} + +/* +** Free the array allocated by explain_data_prepare(). +*/ +static void explain_data_delete(ShellState *p){ + sqlite3_free(p->aiIndent); + p->aiIndent = 0; + p->nIndent = 0; + p->iIndent = 0; +} + +static void exec_prepared_stmt(ShellState*, sqlite3_stmt*); + +/* +** Display scan stats. +*/ +static void display_scanstats( + sqlite3 *db, /* Database to query */ + ShellState *pArg /* Pointer to ShellState */ +){ +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(pArg); +#else + if( pArg->scanstatsOn==3 ){ + 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(?)"; + + int rc = SQLITE_OK; + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_stmt *pSave = pArg->pStmt; + pArg->pStmt = pStmt; + sqlite3_bind_pointer(pStmt, 1, pSave, "stmt-pointer", 0); + + pArg->cnt = 0; + pArg->cMode = MODE_ScanExp; + explain_data_prepare(pArg, pStmt); + exec_prepared_stmt(pArg, pStmt); + explain_data_delete(pArg); + + sqlite3_finalize(pStmt); + pArg->pStmt = pSave; + } + }else{ + display_explain_scanstats(db, pArg); + } +#endif +} + +/* +** Disable and restore .wheretrace and .treetrace/.selecttrace settings. +*/ +static unsigned int savedSelectTrace; +static unsigned int savedWhereTrace; +static void disable_debug_trace_modes(void){ + unsigned int zero = 0; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 0, &savedSelectTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &zero); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 2, &savedWhereTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &zero); +} +static void restore_debug_trace_modes(void){ + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &savedSelectTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace); +} + +/* Create the TEMP table used to store parameter bindings */ static void bind_table_init(ShellState *p){ int wrSchema = 0; int defensiveMode = 0; @@ -3032,8 +3928,6 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ memcpy(zBuf, &zVar[6], szVar-5); sqlite3_bind_text64(pStmt, i, zBuf, szVar-6, sqlite3_free, SQLITE_UTF8); } - }else if( strcmp(zVar, "$TIMER")==0 ){ - sqlite3_bind_double(pStmt, i, pArg->prevTimer); #ifdef SQLITE_ENABLE_CARRAY }else if( strncmp(zVar, "$carray_", 8)==0 ){ static char *azColorNames[] = { @@ -3072,6 +3966,580 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ sqlite3_finalize(pQ); } +/* +** 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 -|- */ + +/* Draw horizontal line N characters long using unicode box +** characters +*/ +static void print_box_line(FILE *out, int N){ + const char zDash[] = + BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 + BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24; + const int nDash = sizeof(zDash) - 1; + N *= 3; + while( N>nDash ){ + sqlite3_fputs(zDash, out); + N -= nDash; + } + sqlite3_fprintf(out, "%.*s", N, zDash); +} + +/* +** Draw a horizontal separator for a MODE_Box table. +*/ +static void print_box_row_separator( + ShellState *p, + int nArg, + const char *zSep1, + const char *zSep2, + const char *zSep3 +){ + int i; + if( nArg>0 ){ + sqlite3_fputs(zSep1, p->out); + print_box_line(p->out, p->actualWidth[0]+2); + for(i=1; i<nArg; i++){ + sqlite3_fputs(zSep2, p->out); + print_box_line(p->out, p->actualWidth[i]+2); + } + sqlite3_fputs(zSep3, p->out); + } + sqlite3_fputs("\n", p->out); +} + +/* +** z[] is a line of text that is to be displayed the .mode box or table or +** similar tabular formats. z[] might contain control characters such +** as \n, \t, \f, or \r. +** +** Compute characters to display on the first line of z[]. Stop at the +** first \r, \n, or \f. Expand \t into spaces. Return a copy (obtained +** from malloc()) of that first line, which caller should free sometime. +** Write anything to display on the next line into *pzTail. If this is +** the last line, write a NULL into *pzTail. (*pzTail is not allocated.) +*/ +static char *translateForDisplayAndDup( + ShellState *p, /* To access current settings */ + const unsigned char *z, /* Input text to be transformed */ + const unsigned char **pzTail, /* OUT: Tail of the input for next line */ + int mxWidth, /* Max width. 0 means no limit */ + u8 bWordWrap /* If true, avoid breaking mid-word */ +){ + int i; /* Input bytes consumed */ + int j; /* Output bytes generated */ + int k; /* Input bytes to be displayed */ + int n; /* Output column number */ + unsigned char *zOut; /* Output text */ + + if( z==0 ){ + *pzTail = 0; + return 0; + } + if( mxWidth<0 ) mxWidth = -mxWidth; + if( mxWidth==0 ) mxWidth = 1000000; + i = j = n = 0; + while( n<mxWidth ){ + unsigned char c = z[i]; + if( c>=0xc0 ){ + int u; + int len = decodeUtf8(&z[i], &u); + i += len; + j += len; + n += cli_wcwidth(u); + continue; + } + if( c>=' ' ){ + n++; + i++; + j++; + continue; + } + if( c==0 || c=='\n' || (c=='\r' && z[i+1]=='\n') ) break; + if( c=='\t' ){ + do{ + n++; + j++; + }while( (n&7)!=0 && n<mxWidth ); + i++; + continue; + } + if( c==0x1b && p->eEscMode==SHELL_ESC_OFF && (k = isVt100(&z[i]))>0 ){ + i += k; + j += k; + }else{ + n++; + j += 3; + i++; + } + } + if( n>=mxWidth && bWordWrap ){ + /* Perhaps try to back up to a better place to break the line */ + for(k=i; k>i/2; k--){ + if( IsSpace(z[k-1]) ) break; + } + if( k<=i/2 ){ + for(k=i; k>i/2; k--){ + if( IsAlnum(z[k-1])!=IsAlnum(z[k]) && (z[k]&0xc0)!=0x80 ) break; + } + } + if( k<=i/2 ){ + k = i; + }else{ + i = k; + while( z[i]==' ' ) i++; + } + }else{ + k = i; + } + if( n>=mxWidth && z[i]>=' ' ){ + *pzTail = &z[i]; + }else if( z[i]=='\r' && z[i+1]=='\n' ){ + *pzTail = z[i+2] ? &z[i+2] : 0; + }else if( z[i]==0 || z[i+1]==0 ){ + *pzTail = 0; + }else{ + *pzTail = &z[i+1]; + } + zOut = malloc( j+1 ); + shell_check_oom(zOut); + i = j = n = 0; + while( i<k ){ + unsigned char c = z[i]; + if( c>=0xc0 ){ + int u; + int len = decodeUtf8(&z[i], &u); + do{ zOut[j++] = z[i++]; }while( (--len)>0 ); + n += cli_wcwidth(u); + continue; + } + if( c>=' ' ){ + n++; + zOut[j++] = z[i++]; + continue; + } + if( c==0 ) break; + if( z[i]=='\t' ){ + do{ + n++; + zOut[j++] = ' '; + }while( (n&7)!=0 && n<mxWidth ); + i++; + continue; + } + switch( p->eEscMode ){ + case SHELL_ESC_SYMBOL: + zOut[j++] = 0xe2; + zOut[j++] = 0x90; + zOut[j++] = 0x80 + c; + break; + case SHELL_ESC_ASCII: + zOut[j++] = '^'; + zOut[j++] = 0x40 + c; + break; + case SHELL_ESC_OFF: { + int nn; + if( c==0x1b && (nn = isVt100(&z[i]))>0 ){ + memcpy(&zOut[j], &z[i], nn); + j += nn; + i += nn - 1; + }else{ + zOut[j++] = c; + } + break; + } + } + i++; + } + zOut[j] = 0; + return (char*)zOut; +} + +/* Return true if the text string z[] contains characters that need +** unistr() escaping. +*/ +static int needUnistr(const unsigned char *z){ + unsigned char c; + if( z==0 ) return 0; + while( (c = *z)>0x1f || c=='\t' || c=='\n' || (c=='\r' && z[1]=='\n') ){ z++; } + return c!=0; +} + +/* Extract the value of the i-th current column for pStmt as an SQL literal +** value. Memory is obtained from sqlite3_malloc64() and must be freed by +** the caller. +*/ +static char *quoted_column(sqlite3_stmt *pStmt, int i){ + switch( sqlite3_column_type(pStmt, i) ){ + case SQLITE_NULL: { + return sqlite3_mprintf("NULL"); + } + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + return sqlite3_mprintf("%s",sqlite3_column_text(pStmt,i)); + } + case SQLITE_TEXT: { + const unsigned char *zText = sqlite3_column_text(pStmt,i); + return sqlite3_mprintf(needUnistr(zText)?"%#Q":"%Q",zText); + } + case SQLITE_BLOB: { + int j; + sqlite3_str *pStr = sqlite3_str_new(0); + const unsigned char *a = sqlite3_column_blob(pStmt,i); + int n = sqlite3_column_bytes(pStmt,i); + sqlite3_str_append(pStr, "x'", 2); + for(j=0; j<n; j++){ + sqlite3_str_appendf(pStr, "%02x", a[j]); + } + sqlite3_str_append(pStr, "'", 1); + return sqlite3_str_finish(pStr); + } + } + return 0; /* Not reached */ +} + +/* +** Run a prepared statement and output the result in one of the +** table-oriented formats: MODE_Column, MODE_Markdown, MODE_Table, +** or MODE_Box. +** +** This is different from ordinary exec_prepared_stmt() in that +** it has to run the entire query and gather the results into memory +** first, in order to determine column widths, before providing +** any output. +*/ +static void exec_prepared_stmt_columnar( + ShellState *p, /* Pointer to ShellState */ + sqlite3_stmt *pStmt /* Statement to run */ +){ + sqlite3_int64 nRow = 0; + int nColumn = 0; + char **azData = 0; + sqlite3_int64 nAlloc = 0; + char *abRowDiv = 0; + const unsigned char *uz; + const char *z; + char **azQuoted = 0; + int rc; + sqlite3_int64 i, nData; + int j, nTotal, w, n; + const char *colSep = 0; + const char *rowSep = 0; + const unsigned char **azNextLine = 0; + int bNextLine = 0; + int bMultiLineRowExists = 0; + int bw = p->cmOpts.bWordWrap; + const char *zEmpty = ""; + const char *zShowNull = p->nullValue; + + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_ROW ) return; + nColumn = sqlite3_column_count(pStmt); + if( nColumn==0 ) goto columnar_end; + nAlloc = nColumn*4; + if( nAlloc<=0 ) nAlloc = 1; + azData = sqlite3_malloc64( nAlloc*sizeof(char*) ); + shell_check_oom(azData); + azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) ); + shell_check_oom(azNextLine); + memset((void*)azNextLine, 0, nColumn*sizeof(char*) ); + if( p->cmOpts.bQuote ){ + azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) ); + shell_check_oom(azQuoted); + memset(azQuoted, 0, nColumn*sizeof(char*) ); + } + abRowDiv = sqlite3_malloc64( nAlloc/nColumn ); + shell_check_oom(abRowDiv); + if( nColumn>p->nWidth ){ + p->colWidth = realloc(p->colWidth, (nColumn+1)*2*sizeof(int)); + shell_check_oom(p->colWidth); + for(i=p->nWidth; i<nColumn; i++) p->colWidth[i] = 0; + p->nWidth = nColumn; + p->actualWidth = &p->colWidth[nColumn]; + } + memset(p->actualWidth, 0, nColumn*sizeof(int)); + for(i=0; i<nColumn; i++){ + w = p->colWidth[i]; + if( w<0 ) w = -w; + p->actualWidth[i] = w; + } + for(i=0; i<nColumn; i++){ + const unsigned char *zNotUsed; + int wx = p->colWidth[i]; + if( wx==0 ){ + wx = p->cmOpts.iWrap; + } + if( wx<0 ) wx = -wx; + uz = (const unsigned char*)sqlite3_column_name(pStmt,i); + if( uz==0 ) uz = (u8*)""; + azData[i] = translateForDisplayAndDup(p, uz, &zNotUsed, wx, bw); + } + do{ + int useNextLine = bNextLine; + bNextLine = 0; + if( (nRow+2)*nColumn >= nAlloc ){ + nAlloc *= 2; + azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*)); + shell_check_oom(azData); + abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn); + shell_check_oom(abRowDiv); + } + abRowDiv[nRow] = 1; + nRow++; + for(i=0; i<nColumn; i++){ + int wx = p->colWidth[i]; + if( wx==0 ){ + wx = p->cmOpts.iWrap; + } + if( wx<0 ) wx = -wx; + if( useNextLine ){ + uz = azNextLine[i]; + if( uz==0 ) uz = (u8*)zEmpty; + }else if( p->cmOpts.bQuote ){ + assert( azQuoted!=0 ); + sqlite3_free(azQuoted[i]); + azQuoted[i] = quoted_column(pStmt,i); + uz = (const unsigned char*)azQuoted[i]; + }else{ + uz = (const unsigned char*)sqlite3_column_text(pStmt,i); + if( uz==0 ) uz = (u8*)zShowNull; + } + azData[nRow*nColumn + i] + = translateForDisplayAndDup(p, uz, &azNextLine[i], wx, bw); + if( azNextLine[i] ){ + bNextLine = 1; + abRowDiv[nRow-1] = 0; + bMultiLineRowExists = 1; + } + } + }while( bNextLine || sqlite3_step(pStmt)==SQLITE_ROW ); + nTotal = nColumn*(nRow+1); + for(i=0; i<nTotal; i++){ + z = azData[i]; + if( z==0 ) z = (char*)zEmpty; + n = strlenChar(z); + j = i%nColumn; + if( n>p->actualWidth[j] ) p->actualWidth[j] = n; + } + if( seenInterrupt ) goto columnar_end; + switch( p->cMode ){ + case MODE_Column: { + colSep = " "; + rowSep = "\n"; + if( p->showHeader ){ + for(i=0; i<nColumn; i++){ + w = p->actualWidth[i]; + if( p->colWidth[i]<0 ) w = -w; + utf8_width_print(p->out, w, azData[i]); + sqlite3_fputs(i==nColumn-1?"\n":" ", p->out); + } + for(i=0; i<nColumn; i++){ + print_dashes(p->out, p->actualWidth[i]); + sqlite3_fputs(i==nColumn-1?"\n":" ", p->out); + } + } + break; + } + case MODE_Table: { + colSep = " | "; + rowSep = " |\n"; + print_row_separator(p, nColumn, "+"); + sqlite3_fputs("| ", p->out); + for(i=0; i<nColumn; i++){ + w = p->actualWidth[i]; + n = strlenChar(azData[i]); + sqlite3_fprintf(p->out, "%*s%s%*s", (w-n)/2, "", + azData[i], (w-n+1)/2, ""); + sqlite3_fputs(i==nColumn-1?" |\n":" | ", p->out); + } + print_row_separator(p, nColumn, "+"); + break; + } + case MODE_Markdown: { + colSep = " | "; + rowSep = " |\n"; + sqlite3_fputs("| ", p->out); + for(i=0; i<nColumn; i++){ + w = p->actualWidth[i]; + n = strlenChar(azData[i]); + sqlite3_fprintf(p->out, "%*s%s%*s", (w-n)/2, "", + azData[i], (w-n+1)/2, ""); + sqlite3_fputs(i==nColumn-1?" |\n":" | ", p->out); + } + print_row_separator(p, nColumn, "|"); + break; + } + case MODE_Box: { + colSep = " " BOX_13 " "; + rowSep = " " BOX_13 "\n"; + print_box_row_separator(p, nColumn, BOX_23, BOX_234, BOX_34); + sqlite3_fputs(BOX_13 " ", p->out); + for(i=0; i<nColumn; i++){ + w = p->actualWidth[i]; + n = strlenChar(azData[i]); + sqlite3_fprintf(p->out, "%*s%s%*s%s", + (w-n)/2, "", azData[i], (w-n+1)/2, "", + i==nColumn-1?" "BOX_13"\n":" "BOX_13" "); + } + print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); + break; + } + } + for(i=nColumn, j=0; i<nTotal; i++, j++){ + if( j==0 && p->cMode!=MODE_Column ){ + sqlite3_fputs(p->cMode==MODE_Box?BOX_13" ":"| ", p->out); + } + z = azData[i]; + if( z==0 ) z = p->nullValue; + w = p->actualWidth[j]; + if( p->colWidth[j]<0 ) w = -w; + utf8_width_print(p->out, w, z); + if( j==nColumn-1 ){ + sqlite3_fputs(rowSep, p->out); + if( bMultiLineRowExists && abRowDiv[i/nColumn-1] && i+1<nTotal ){ + if( p->cMode==MODE_Table ){ + print_row_separator(p, nColumn, "+"); + }else if( p->cMode==MODE_Box ){ + print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); + }else if( p->cMode==MODE_Column ){ + sqlite3_fputs("\n", p->out); + } + } + j = -1; + if( seenInterrupt ) goto columnar_end; + }else{ + sqlite3_fputs(colSep, p->out); + } + } + if( p->cMode==MODE_Table ){ + print_row_separator(p, nColumn, "+"); + }else if( p->cMode==MODE_Box ){ + print_box_row_separator(p, nColumn, BOX_12, BOX_124, BOX_14); + } +columnar_end: + if( seenInterrupt ){ + sqlite3_fputs("Interrupt\n", p->out); + } + nData = (nRow+1)*nColumn; + for(i=0; i<nData; i++){ + z = azData[i]; + if( z!=zEmpty && z!=zShowNull ) free(azData[i]); + } + sqlite3_free(azData); + sqlite3_free((void*)azNextLine); + sqlite3_free(abRowDiv); + if( azQuoted ){ + for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]); + sqlite3_free(azQuoted); + } +} + +/* +** Run a prepared statement +*/ +static void exec_prepared_stmt( + ShellState *pArg, /* Pointer to ShellState */ + sqlite3_stmt *pStmt /* Statement to run */ +){ + int rc; + sqlite3_uint64 nRow = 0; + + if( pArg->cMode==MODE_Column + || pArg->cMode==MODE_Table + || pArg->cMode==MODE_Box + || pArg->cMode==MODE_Markdown + ){ + exec_prepared_stmt_columnar(pArg, pStmt); + return; + } + + /* perform the first step. this will tell us if we + ** have a result set or not and how wide it is. + */ + rc = sqlite3_step(pStmt); + /* if we have a result set... */ + if( SQLITE_ROW == rc ){ + /* allocate space for col name ptr, value ptr, and type */ + int nCol = sqlite3_column_count(pStmt); + void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1); + if( !pData ){ + shell_out_of_memory(); + }else{ + char **azCols = (char **)pData; /* Names of result columns */ + char **azVals = &azCols[nCol]; /* Results */ + int *aiTypes = (int *)&azVals[nCol]; /* Result types */ + int i, x; + assert(sizeof(int) <= sizeof(char *)); + /* save off ptrs to column names */ + for(i=0; i<nCol; i++){ + azCols[i] = (char *)sqlite3_column_name(pStmt, i); + } + do{ + nRow++; + /* extract the data and data types */ + for(i=0; i<nCol; i++){ + aiTypes[i] = x = sqlite3_column_type(pStmt, i); + if( x==SQLITE_BLOB + && pArg + && (pArg->cMode==MODE_Insert || pArg->cMode==MODE_Quote) + ){ + azVals[i] = ""; + }else{ + azVals[i] = (char*)sqlite3_column_text(pStmt, i); + } + if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){ + rc = SQLITE_NOMEM; + break; /* from for */ + } + } /* end for */ + + /* if data and types extracted successfully... */ + if( SQLITE_ROW == rc ){ + /* call the supplied callback with the result row data */ + if( shell_callback(pArg, nCol, azVals, azCols, aiTypes) ){ + rc = SQLITE_ABORT; + }else{ + rc = sqlite3_step(pStmt); + } + } + } while( SQLITE_ROW == rc ); + sqlite3_free(pData); + if( pArg->cMode==MODE_Json ){ + sqlite3_fputs("]\n", pArg->out); + }else if( pArg->cMode==MODE_Www ){ + sqlite3_fputs("</TABLE>\n<PRE>\n", pArg->out); + }else if( pArg->cMode==MODE_Count ){ + char zBuf[200]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "%llu row%s\n", + nRow, nRow!=1 ? "s" : ""); + printf("%s", zBuf); + } + } + } +} + #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) /* ** This function is called to process SQL if the previous shell command @@ -3123,8 +4591,8 @@ static int expertFinish( if( bVerbose ){ const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES); - cli_puts("-- Candidates -----------------------------\n", out); - cli_printf(out, "%s\n", zCand); + sqlite3_fputs("-- Candidates -----------------------------\n", out); + sqlite3_fprintf(out, "%s\n", zCand); } for(i=0; i<nQuery; i++){ const char *zSql = sqlite3_expert_report(p, i, EXPERT_REPORT_SQL); @@ -3132,12 +4600,12 @@ static int expertFinish( const char *zEQP = sqlite3_expert_report(p, i, EXPERT_REPORT_PLAN); if( zIdx==0 ) zIdx = "(no new indexes)\n"; if( bVerbose ){ - cli_printf(out, + sqlite3_fprintf(out, "-- Query %d --------------------------------\n" "%s\n\n" ,i+1, zSql); } - cli_printf(out, "%s\n%s\n", zIdx, zEQP); + sqlite3_fprintf(out, "%s\n%s\n", zIdx, zEQP); } } } @@ -3172,18 +4640,18 @@ static int expertDotCommand( } else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){ if( i==(nArg-1) ){ - cli_printf(stderr, "option requires an argument: %s\n", z); + sqlite3_fprintf(stderr, "option requires an argument: %s\n", z); rc = SQLITE_ERROR; }else{ iSample = (int)integerValue(azArg[++i]); if( iSample<0 || iSample>100 ){ - cli_printf(stderr,"value out of range: %s\n", azArg[i]); + sqlite3_fprintf(stderr,"value out of range: %s\n", azArg[i]); rc = SQLITE_ERROR; } } } else{ - cli_printf(stderr,"unknown option: %s\n", z); + sqlite3_fprintf(stderr,"unknown option: %s\n", z); rc = SQLITE_ERROR; } } @@ -3191,7 +4659,7 @@ static int expertDotCommand( if( rc==SQLITE_OK ){ pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); if( pState->expert.pExpert==0 ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory"); rc = SQLITE_ERROR; }else{ @@ -3206,15 +4674,6 @@ static int expertDotCommand( } #endif /* !SQLITE_OMIT_VIRTUALTABLE && !SQLITE_OMIT_AUTHORIZATION */ -/* -** QRF write callback -*/ -static int shellWriteQR(void *pX, const char *z, sqlite3_int64 n){ - ShellState *pArg = (ShellState*)pX; - cli_printf(pArg->out, "%.*s", (int)n, z); - return SQLITE_OK; -} - /* ** Execute a statement or set of statements. Print ** any result rows/columns depending on the current mode @@ -3234,31 +4693,10 @@ static int shell_exec( int rc2; const char *zLeftover; /* Tail of unprocessed SQL */ sqlite3 *db = pArg->db; - unsigned char eStyle; - sqlite3_qrf_spec spec; if( pzErrMsg ){ *pzErrMsg = NULL; } - memcpy(&spec, &pArg->mode.spec, sizeof(spec)); - spec.xWrite = shellWriteQR; - spec.pWriteArg = (void*)pArg; - if( pArg->mode.eMode==MODE_Insert && ShellHasFlag(pArg, SHFLG_PreserveRowid) ){ - spec.bTitles = QRF_SW_On; - } - assert( pArg->mode.eMode>=0 && pArg->mode.eMode<ArraySize(aModeInfo) ); - eStyle = aModeInfo[pArg->mode.eMode].eStyle; - if( pArg->mode.bAutoScreenWidth ){ - spec.nScreenWidth = shellScreenWidth(); - } - if( spec.eBlob==QRF_BLOB_Auto ){ - switch( spec.eText ){ - case QRF_TEXT_Relaxed: /* fall through */ - case QRF_TEXT_Sql: spec.eBlob = QRF_BLOB_Sql; break; - case QRF_TEXT_Json: spec.eBlob = QRF_BLOB_Json; break; - default: spec.eBlob = QRF_BLOB_Text; break; - } - } #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) if( pArg->expert.pExpert ){ @@ -3267,7 +4705,7 @@ static int shell_exec( } #endif - while( zSql && zSql[0] && (SQLITE_OK == rc) ){ + while( zSql[0] && (SQLITE_OK == rc) ){ static const char *zStmtSql; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); if( SQLITE_OK != rc ){ @@ -3275,7 +4713,6 @@ static int shell_exec( *pzErrMsg = save_err_msg(db, "in prepare", rc, zSql); } }else{ - int isExplain; if( !pStmt ){ /* this happens for a comment or white-space */ zSql = zLeftover; @@ -3286,78 +4723,89 @@ static int shell_exec( if( zStmtSql==0 ) zStmtSql = ""; while( IsSpace(zStmtSql[0]) ) zStmtSql++; - /* save off the prepared statement handle */ + /* save off the prepared statement handle and reset row count */ if( pArg ){ pArg->pStmt = pStmt; + pArg->cnt = 0; } - + /* Show the EXPLAIN QUERY PLAN if .eqp is on */ - isExplain = sqlite3_stmt_isexplain(pStmt); - if( pArg && pArg->mode.autoEQP && isExplain==0 && pArg->dot.nArg==0 ){ + if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){ + sqlite3_stmt *pExplain; int triggerEQP = 0; - u8 savedEnableTimer = pArg->enableTimer; - pArg->enableTimer = 0; disable_debug_trace_modes(); sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP); - if( pArg->mode.autoEQP>=AUTOEQP_trigger ){ + if( pArg->autoEQP>=AUTOEQP_trigger ){ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0); } - sqlite3_reset(pStmt); - spec.eStyle = QRF_STYLE_Auto; - sqlite3_stmt_explain(pStmt, 2); - sqlite3_format_query_result(pStmt, &spec, 0); - if( pArg->mode.autoEQP>=AUTOEQP_full ){ - sqlite3_reset(pStmt); - sqlite3_stmt_explain(pStmt, 1); - sqlite3_format_query_result(pStmt, &spec, 0); + pExplain = pStmt; + sqlite3_reset(pExplain); + rc = sqlite3_stmt_explain(pExplain, 2); + if( rc==SQLITE_OK ){ + bind_prepared_stmt(pArg, pExplain); + while( sqlite3_step(pExplain)==SQLITE_ROW ){ + const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3); + int iEqpId = sqlite3_column_int(pExplain, 0); + int iParentId = sqlite3_column_int(pExplain, 1); + if( zEQPLine==0 ) zEQPLine = ""; + if( zEQPLine[0]=='-' ) eqp_render(pArg, 0); + eqp_append(pArg, iEqpId, iParentId, zEQPLine); + } + eqp_render(pArg, 0); } - - if( pArg->mode.autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){ + if( pArg->autoEQP>=AUTOEQP_full ){ + /* Also do an EXPLAIN for ".eqp full" mode */ + sqlite3_reset(pExplain); + rc = sqlite3_stmt_explain(pExplain, 1); + if( rc==SQLITE_OK ){ + pArg->cMode = MODE_Explain; + assert( sqlite3_stmt_isexplain(pExplain)==1 ); + bind_prepared_stmt(pArg, pExplain); + explain_data_prepare(pArg, pExplain); + exec_prepared_stmt(pArg, pExplain); + explain_data_delete(pArg); + } + } + if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0); } sqlite3_reset(pStmt); sqlite3_stmt_explain(pStmt, 0); restore_debug_trace_modes(); - pArg->enableTimer = savedEnableTimer; } - bind_prepared_stmt(pArg, pStmt); - if( isExplain && pArg->mode.autoExplain ){ - spec.eStyle = isExplain==1 ? QRF_STYLE_Explain : QRF_STYLE_Eqp; - sqlite3_format_query_result(pStmt, &spec, pzErrMsg); - }else if( pArg->mode.eMode==MODE_Www ){ - cli_printf(pArg->out, - "</PRE>\n" - "<TABLE border='1' cellspacing='0' cellpadding='2'>\n"); - spec.eStyle = QRF_STYLE_Html; - sqlite3_format_query_result(pStmt, &spec, pzErrMsg); - cli_printf(pArg->out, - "</TABLE>\n" - "<PRE>"); - }else{ - spec.eStyle = eStyle; - sqlite3_format_query_result(pStmt, &spec, pzErrMsg); + if( pArg ){ + int bIsExplain = (sqlite3_stmt_isexplain(pStmt)==1); + pArg->cMode = pArg->mode; + if( pArg->autoExplain ){ + if( bIsExplain ){ + pArg->cMode = MODE_Explain; + } + if( sqlite3_stmt_isexplain(pStmt)==2 ){ + pArg->cMode = MODE_EQP; + } + } + + /* If the shell is currently in ".explain" mode, gather the extra + ** data required to add indents to the output.*/ + if( pArg->cMode==MODE_Explain && bIsExplain ){ + explain_data_prepare(pArg, pStmt); + } } + bind_prepared_stmt(pArg, pStmt); + exec_prepared_stmt(pArg, pStmt); + explain_data_delete(pArg); + eqp_render(pArg, 0); + /* print usage stats if stats on */ if( pArg && pArg->statsOn ){ display_stats(db, pArg, 0); } /* print loop-counters if required */ - if( pArg && pArg->mode.scanstatsOn ){ - char *zErr = 0; - switch( pArg->mode.scanstatsOn ){ - case 1: spec.eStyle = QRF_STYLE_Stats; break; - case 2: spec.eStyle = QRF_STYLE_StatsEst; break; - default: spec.eStyle = QRF_STYLE_StatsVm; break; - } - sqlite3_reset(pStmt); - rc = sqlite3_format_query_result(pStmt, &spec, &zErr); - if( rc ){ - cli_printf(stderr, "Stats query failed: %s\n", zErr); - sqlite3_free(zErr); - } + if( pArg && pArg->scanstatsOn ){ + display_scanstats(db, pArg); } /* Finalize the statement just executed. If this fails, save a @@ -3553,14 +5001,14 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ */ if( db_int(p->db, "SELECT count(*) FROM sqlite_sequence")>0 ){ if( !p->writableSchema ){ - cli_puts("PRAGMA writable_schema=ON;\n", p->out); + sqlite3_fputs("PRAGMA writable_schema=ON;\n", p->out); p->writableSchema = 1; } - cli_puts("CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq);\n" + sqlite3_fputs("CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq);\n" "DELETE FROM sqlite_sequence;\n", p->out); } }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 && !noSys ){ - if( !dataOnly ) cli_puts("ANALYZE sqlite_schema;\n", p->out); + if( !dataOnly ) sqlite3_fputs("ANALYZE sqlite_schema;\n", p->out); }else if( cli_strncmp(zTable, "sqlite_", 7)==0 ){ return 0; }else if( dataOnly ){ @@ -3568,7 +5016,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ }else if( cli_strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){ char *zIns; if( !p->writableSchema ){ - cli_puts("PRAGMA writable_schema=ON;\n", p->out); + sqlite3_fputs("PRAGMA writable_schema=ON;\n", p->out); p->writableSchema = 1; } zIns = sqlite3_mprintf( @@ -3576,7 +5024,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ "VALUES('table','%q','%q',0,'%q');", zTable, zTable, zSql); shell_check_oom(zIns); - cli_printf(p->out, "%s\n", zIns); + sqlite3_fprintf(p->out, "%s\n", zIns); sqlite3_free(zIns); return 0; }else{ @@ -3588,7 +5036,8 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ ShellText sTable; char **azCol; int i; - Mode savedMode; + char *savedDestTable; + int savedMode; azCol = tableColumnList(p, zTable); if( azCol==0 ){ @@ -3631,20 +5080,18 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ appendText(&sSelect, " FROM ", 0); appendText(&sSelect, zTable, quoteChar(zTable)); - + savedDestTable = p->zDestTable; savedMode = p->mode; - p->mode.spec.zTableName = (char*)zTable; - p->mode.eMode = MODE_Insert; - p->mode.spec.eText = QRF_TEXT_Sql; - p->mode.spec.eBlob = QRF_BLOB_Sql; - p->mode.spec.bTitles = QRF_No; + p->zDestTable = sTable.zTxt; + p->mode = p->cMode = MODE_Insert; rc = shell_exec(p, sSelect.zTxt, 0); if( (rc&0xff)==SQLITE_CORRUPT ){ - cli_puts("/****** CORRUPTION ERROR *******/\n", p->out); + sqlite3_fputs("/****** CORRUPTION ERROR *******/\n", p->out); toggleSelectOrder(p->db); shell_exec(p, sSelect.zTxt, 0); toggleSelectOrder(p->db); } + p->zDestTable = savedDestTable; p->mode = savedMode; freeText(&sTable); freeText(&sSelect); @@ -3670,9 +5117,9 @@ static int run_schema_dump_query( if( rc==SQLITE_CORRUPT ){ char *zQ2; int len = strlen30(zQuery); - cli_puts("/****** CORRUPTION ERROR *******/\n", p->out); + sqlite3_fputs("/****** CORRUPTION ERROR *******/\n", p->out); if( zErr ){ - cli_printf(p->out, "/****** %s ******/\n", zErr); + sqlite3_fprintf(p->out, "/****** %s ******/\n", zErr); sqlite3_free(zErr); zErr = 0; } @@ -3681,7 +5128,7 @@ static int run_schema_dump_query( sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery); rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); if( rc ){ - cli_printf(p->out, "/****** ERROR: %s ******/\n", zErr); + sqlite3_fprintf(p->out, "/****** ERROR: %s ******/\n", zErr); }else{ rc = SQLITE_CORRUPT; } @@ -3739,8 +5186,8 @@ static const char *(azHelp[]) = { ".cd DIRECTORY Change the working directory to DIRECTORY", #endif ".changes on|off Show number of rows changed by SQL", - ".check OPTIONS ... Verify the results of a .testcase", #ifndef SQLITE_SHELL_FIDDLE + ".check GLOB Fail if output since .testcase does not match", ".clone NEWDB Clone data into NEWDB from the existing database", #endif ".connection [close] [#] Open or close an auxiliary database connection", @@ -3782,10 +5229,23 @@ static const char *(azHelp[]) = { " --schema SCHEMA Use SCHEMA instead of \"main\"", " --help Show CMD details", ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", - ",headers on|off Turn display of headers on or off", + ".headers on|off Turn display of headers on or off", ".help ?-all? ?PATTERN? Show help text for PATTERN", #ifndef SQLITE_SHELL_FIDDLE ".import FILE TABLE Import data from FILE into TABLE", + " Options:", + " --ascii Use \\037 and \\036 as column and row separators", + " --csv Use , and \\n as column and row separators", + " --skip N Skip the first N rows of input", + " --schema S Target table to be S.TABLE", + " -v \"Verbose\" - increase auxiliary output", + " Notes:", + " * If TABLE does not exist, it is created. The first row of input", + " determines the column names.", + " * If neither --csv or --ascii are used, the input mode is derived", + " from the \".mode\" output mode", + " * If FILE begins with \"|\" then it is a command that generates the", + " input text.", #endif #ifndef SQLITE_OMIT_TEST_CONTROL ".imposter INDEX TABLE Create imposter table TABLE on index INDEX", @@ -3810,12 +5270,42 @@ static const char *(azHelp[]) = { ".log on|off Turn logging on or off.", #endif ".mode ?MODE? ?OPTIONS? Set output mode", + " MODE is one of:", + " ascii Columns/rows delimited by 0x1F and 0x1E", + " box Tables using unicode box-drawing characters", + " csv Comma-separated values", + " column Output in columns. (See .width)", + " html HTML <table> code", + " insert SQL insert statements for TABLE", + " json Results in a JSON array", + " line One value per line", + " list Values delimited by \"|\"", + " markdown Markdown table format", + " qbox Shorthand for \"box --wrap 60 --quote\"", + " quote Escape answers as for SQL", + " table ASCII-art table", + " tabs Tab-separated values", + " tcl TCL list elements", + " OPTIONS: (for columnar modes or insert mode):", + " --escape T ctrl-char escape; T is one of: symbol, ascii, off", + " --wrap N Wrap output lines to no longer than N characters", + " --wordwrap B Wrap or not at word boundaries per B (on/off)", + " --ww Shorthand for \"--wordwrap 1\"", + " --quote Quote output text as SQL literals", + " --noquote Do not quote output text", + " TABLE The name of SQL table used for \"insert\" mode", #ifndef SQLITE_SHELL_FIDDLE ".nonce STRING Suspend safe mode for one command if nonce matches", #endif ".nullvalue STRING Use STRING in place of NULL values", #ifndef SQLITE_SHELL_FIDDLE ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", + " If FILE begins with '|' then open as a pipe", + " --bom Put a UTF8 byte-order mark at the beginning", + " -e Send output to the system text editor", + " --plain Use text/plain output instead of HTML for -w option", + " -w Send output as HTML to a web browser (same as \".www\")", + " -x Send output as CSV to a spreadsheet (same as \".excel\")", /* Note that .open is (partially) available in WASM builds but is ** currently only intended to be used by the fiddle tool, not ** end users, so is "undocumented." */ @@ -3841,6 +5331,14 @@ static const char *(azHelp[]) = { " --zip FILE is a ZIP archive", #ifndef SQLITE_SHELL_FIDDLE ".output ?FILE? Send output to FILE or stdout if FILE is omitted", + " If FILE begins with '|' then open it as a pipe.", + " If FILE is 'off' then output is disabled.", + " Options:", + " --bom Prefix output with a UTF8 byte-order mark", + " -e Send output to the system text editor", + " --plain Use text/plain for -w option", + " -w Send output to a web browser", + " -x Send output as CSV to a spreadsheet", #endif ".parameter CMD ... Manage SQL parameter bindings", " clear Erase all bindings", @@ -3856,7 +5354,6 @@ static const char *(azHelp[]) = { " --once Do no more than one progress interrupt", " --quiet|-q No output except at interrupts", " --reset Reset the count for each input and interrupt", - " --timeout S Halt after running for S seconds", #endif ".prompt MAIN CONTINUE Replace the standard prompts", #ifndef SQLITE_SHELL_FIDDLE @@ -3884,7 +5381,7 @@ static const char *(azHelp[]) = { " Options:", " --init Create a new SELFTEST table", " -v Verbose output", - ",separator COL ?ROW? Change the column and row separators", + ".separator COL ?ROW? Change the column and row separators", #if defined(SQLITE_ENABLE_SESSION) ".session ?NAME? CMD ... Create or control sessions", " Subcommands:", @@ -3911,7 +5408,7 @@ static const char *(azHelp[]) = { #if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) ".shell CMD ARGS... Run CMD ARGS... in a system shell", #endif - ",show Show the current values for various settings", + ".show Show the current values for various settings", ".stats ?ARG? Show stats or turn stats on or off", " off Turn off automatic stat display", " on Turn on automatic stat display", @@ -3921,11 +5418,13 @@ static const char *(azHelp[]) = { ".system CMD ARGS... Run CMD ARGS... in a system shell", #endif ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", - ".testcase NAME Begin a test case.", +#ifndef SQLITE_SHELL_FIDDLE + ",testcase NAME Begin redirecting output to 'testcase-out.txt'", +#endif ",testctrl CMD ... Run various sqlite3_test_control() operations", " Run \".testctrl\" with no arguments for details", ".timeout MS Try opening locked tables for MS milliseconds", - ".timer on|off|once Turn SQL timer on or off.", + ".timer on|off Turn SQL timer on or off", #ifndef SQLITE_OMIT_TRACE ".trace ?OPTIONS? Output each SQL statement as it is run", " FILE Send output to FILE", @@ -3950,7 +5449,7 @@ static const char *(azHelp[]) = { ".vfsinfo ?AUX? Information about the top-level VFS", ".vfslist List all available VFSes", ".vfsname ?AUX? Print the name of the VFS stack", - ",width NUM1 NUM2 ... Set minimum column widths for columnar output", + ".width NUM1 NUM2 ... Set minimum column widths for columnar output", " Negative values right-justify", #ifndef SQLITE_SHELL_FIDDLE ".www Display output of the next command in web browser", @@ -3958,19 +5457,6 @@ static const char *(azHelp[]) = { #endif }; -INSERT-USAGE-TEXT-HERE - -/* -** Return a pointer to usage text for zCmd, or NULL if none exists. -*/ -static const char *findUsage(const char *zCmd){ - int i; - for(i=0; i<ArraySize(aUsage); i++){ - if( sqlite3_strglob(zCmd, aUsage[i].zCmd)==0 ) return aUsage[i].zUsage; - } - return 0; -} - /* ** Output help text for commands that match zPattern. ** @@ -4000,7 +5486,6 @@ static int showHelp(FILE *out, const char *zPattern){ int j = 0; int n = 0; char *zPat; - const char *zHit = 0; if( zPattern==0 ){ /* Show just the first line for all help topics */ zPattern = "[a-z]"; @@ -4018,46 +5503,37 @@ static int showHelp(FILE *out, const char *zPattern){ show = 0; }else if( azHelp[i][0]==',' ){ show = 1; - cli_printf(out, ".%s\n", &azHelp[i][1]); + sqlite3_fprintf(out, ".%s\n", &azHelp[i][1]); n++; }else if( show ){ - cli_printf(out, "%s\n", azHelp[i]); + sqlite3_fprintf(out, "%s\n", azHelp[i]); } } return n; } /* Seek documented commands for which zPattern is an exact prefix */ - zPat = sqlite3_mprintf(".%s*", zPattern[0]=='.' ? &zPattern[1] : zPattern); + zPat = sqlite3_mprintf(".%s*", zPattern); shell_check_oom(zPat); for(i=0; i<ArraySize(azHelp); i++){ if( sqlite3_strglob(zPat, azHelp[i])==0 ){ - if( zHit ) cli_printf(out, "%s\n", zHit); - zHit = azHelp[i]; + sqlite3_fprintf(out, "%s\n", azHelp[i]); j = i+1; n++; } } + sqlite3_free(zPat); if( n ){ if( n==1 ){ - const char *zUsage = findUsage(zPat); - if( zUsage ){ - cli_puts(zUsage, out); - }else{ - /* when zPattern is a prefix of exactly one command, then include - ** the details of that command, which should begin at offset j */ - cli_printf(out, "%s\n", zHit); - while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){ - cli_printf(out, "%s\n", azHelp[j]); - j++; - } + /* when zPattern is a prefix of exactly one command, then include + ** the details of that command, which should begin at offset j */ + while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){ + sqlite3_fprintf(out, "%s\n", azHelp[j]); + j++; } - }else{ - cli_printf(out, "%s\n", zHit); } + return n; } - sqlite3_free(zPat); - if( n ) return n; /* Look for documented commands that contain zPattern anywhere. ** Show complete text of all documented commands that match. */ @@ -4070,10 +5546,10 @@ static int showHelp(FILE *out, const char *zPattern){ } if( azHelp[i][0]=='.' ) j = i; if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){ - cli_printf(out, "%s\n", azHelp[j]); + sqlite3_fprintf(out, "%s\n", azHelp[j]); while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){ j++; - cli_printf(out, "%s\n", azHelp[j]); + sqlite3_fprintf(out, "%s\n", azHelp[j]); } i = j; n++; @@ -4110,7 +5586,7 @@ static char *readFile(const char *zName, int *pnByte){ if( in==0 ) return 0; rc = fseek(in, 0, SEEK_END); if( rc!=0 ){ - cli_printf(stderr,"Error: '%s' not seekable\n", zName); + sqlite3_fprintf(stderr,"Error: '%s' not seekable\n", zName); fclose(in); return 0; } @@ -4118,7 +5594,7 @@ static char *readFile(const char *zName, int *pnByte){ rewind(in); pBuf = sqlite3_malloc64( nIn+1 ); if( pBuf==0 ){ - cli_puts("Error: out of memory\n", stderr); + sqlite3_fputs("Error: out of memory\n", stderr); fclose(in); return 0; } @@ -4126,7 +5602,7 @@ static char *readFile(const char *zName, int *pnByte){ fclose(in); if( nRead!=1 ){ sqlite3_free(pBuf); - cli_printf(stderr,"Error: cannot read '%s'\n", zName); + sqlite3_fprintf(stderr,"Error: cannot read '%s'\n", zName); return 0; } pBuf[nIn] = 0; @@ -4183,54 +5659,8 @@ static int session_filter(void *pCtx, const char *zTab){ #endif /* -** Return the size of the named file in bytes. Or return a negative -** number if the file does not exist. -*/ -static sqlite3_int64 fileSize(const char *zFile){ -#if defined(_WIN32) || defined(WIN32) - struct _stat64 x; - if( _stat64(zFile, &x)!=0 ) return -1; - return (sqlite3_int64)x.st_size; -#else - struct stat x; - if( stat(zFile, &x)!=0 ) return -1; - return (sqlite3_int64)x.st_size; -#endif -} - -/* -** Return true if zFile is an SQLite database. -** -** Algorithm: -** * If the file does not exist -> return false -** * If the size of the file is not a multiple of 512 -> return false -** * If sqlite3_open() fails -> return false -** * if sqlite3_prepare() or sqlite3_step() fails -> return false -** * Otherwise -> return true -*/ -static int isDatabaseFile(const char *zFile, int openFlags){ - sqlite3 *db = 0; - sqlite3_stmt *pStmt = 0; - int rc; - sqlite3_int64 sz = fileSize(zFile); - if( sz<512 || (sz%512)!=0 ) return 0; - if( sqlite3_open_v2(zFile, &db, openFlags, 0)==SQLITE_OK - && sqlite3_prepare_v2(db,"SELECT count(*) FROM sqlite_schema",-1,&pStmt,0) - ==SQLITE_OK - && sqlite3_step(pStmt)==SQLITE_ROW - ){ - rc = 1; - }else{ - rc = 0; - } - sqlite3_finalize(pStmt); - sqlite3_close(db); - return rc; -} - -/* -** Try to deduce the type of file for zName based on its content. Return -** one of the SHELL_OPEN_* constants. +** Try to deduce the type of file for zName based on its content. Return +** one of the SHELL_OPEN_* constants. ** ** If the file does not exist or is empty but its name looks like a ZIP ** archive and the dfltZip flag is true, then assume it is a ZIP archive. @@ -4240,12 +5670,20 @@ static int isDatabaseFile(const char *zFile, int openFlags){ int deduceDatabaseType(const char *zName, int dfltZip, int openFlags){ FILE *f; size_t n; + sqlite3 *db = 0; + sqlite3_stmt *pStmt = 0; int rc = SHELL_OPEN_UNSPEC; char zBuf[100]; if( access(zName,0)!=0 ) goto database_type_by_name; - if( isDatabaseFile(zName, openFlags) ){ + if( sqlite3_open_v2(zName, &db, openFlags, 0)==SQLITE_OK + && sqlite3_prepare_v2(db,"SELECT count(*) FROM sqlite_schema",-1,&pStmt,0) + ==SQLITE_OK + && sqlite3_step(pStmt)==SQLITE_ROW + ){ rc = SHELL_OPEN_NORMAL; } + sqlite3_finalize(pStmt); + sqlite3_close(db); if( rc==SHELL_OPEN_NORMAL ) return SHELL_OPEN_NORMAL; f = sqlite3_fopen(zName, "rb"); if( f==0 ) goto database_type_by_name; @@ -4280,35 +5718,6 @@ database_type_by_name: return rc; } -/* -** If the text in z[] is the name of a readable file and that file appears -** to contain SQL text and/or dot-commands, then return true. If z[] is -** not a file, or if the file is unreadable, or if the file is a database -** or anything else that is not SQL text and dot-commands, then return false. -** -** If the bLeaveUninit flag is set, then be sure to leave SQLite in an -** uninitialized state. This means invoking sqlite3_shutdown() after any -** SQLite API is used. -** -** Some amount of guesswork is involved in this decision. -*/ -static int isScriptFile(const char *z, int bLeaveUninit){ - sqlite3_int64 sz = fileSize(z); - if( sz<=0 ) return 0; - if( (sz%512)==0 ){ - int rc; - sqlite3_initialize(); - rc = isDatabaseFile(z, SQLITE_OPEN_READONLY); - if( bLeaveUninit ){ - sqlite3_shutdown(); - } - if( rc ) return 0; /* Is a database */ - } - if( sqlite3_strlike("%.sql",z,0)==0 ) return 1; - if( sqlite3_strlike("%.txt",z,0)==0 ) return 1; - return 0; -} - #ifndef SQLITE_OMIT_DESERIALIZE /* ** Reconstruct an in-memory database using the output from the "dbtotxt" @@ -4330,7 +5739,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){ if( zDbFilename ){ in = sqlite3_fopen(zDbFilename, "r"); if( in==0 ){ - cli_printf(stderr,"cannot open \"%s\" for reading\n", zDbFilename); + sqlite3_fprintf(stderr,"cannot open \"%s\" for reading\n", zDbFilename); return 0; } nLine = 0; @@ -4346,7 +5755,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){ if( rc!=2 ) goto readHexDb_error; if( n<0 ) goto readHexDb_error; if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ - cli_puts("invalid pagesize\n", stderr); + sqlite3_fputs("invalid pagesize\n", stderr); goto readHexDb_error; } sz = ((i64)n+pgsz-1)&~(pgsz-1); /* Round up to nearest multiple of pgsz */ @@ -4357,7 +5766,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){ int j = 0; /* Page number from "| page" line */ int k = 0; /* Offset from "| page" line */ if( nLine>=2000000000 ){ - cli_printf(stderr, "input too big\n"); + sqlite3_fprintf(stderr, "input too big\n"); goto readHexDb_error; } rc = sscanf(zLine, "| page %d offset %d", &j, &k); @@ -4398,7 +5807,7 @@ readHexDb_error: p->lineno = nLine; } sqlite3_free(a); - cli_printf(stderr,"Error on line %lld of --hexdb input\n", nLine); + sqlite3_fprintf(stderr,"Error on line %lld of --hexdb input\n", nLine); return 0; } #endif /* SQLITE_OMIT_DESERIALIZE */ @@ -4503,19 +5912,19 @@ static void open_db(ShellState *p, int openFlags){ } } if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ - cli_printf(stderr,"Error: unable to open database \"%s\": %s\n", + sqlite3_fprintf(stderr,"Error: unable to open database \"%s\": %s\n", zDbFilename, sqlite3_errmsg(p->db)); if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ - cli_exit(1); + exit(1); } sqlite3_close(p->db); sqlite3_open(":memory:", &p->db); if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ - cli_puts("Also: unable to open substitute in-memory database.\n", + sqlite3_fputs("Also: unable to open substitute in-memory database.\n", stderr); - cli_exit(1); + exit(1); }else{ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Notice: using substitute in-memory database instead of \"%s\"\n", zDbFilename); } @@ -4593,8 +6002,6 @@ static void open_db(ShellState *p, int openFlags){ shellModuleSchema, 0, 0); sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, shellPutsFunc, 0, 0); - sqlite3_create_function(p->db, "shell_format_schema", 2, SQLITE_UTF8, p, - shellFormatSchema, 0, 0); sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, shellUSleepFunc, 0, 0); #ifndef SQLITE_NOHAVE_SYSTEM @@ -4629,7 +6036,7 @@ static void open_db(ShellState *p, int openFlags){ SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE); if( rc ){ - cli_printf(stderr,"Error: sqlite3_deserialize() returns %d\n", rc); + sqlite3_fprintf(stderr,"Error: sqlite3_deserialize() returns %d\n", rc); } if( p->szMax>0 ){ sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax); @@ -4644,7 +6051,7 @@ static void open_db(ShellState *p, int openFlags){ } #endif sqlite3_db_config( - p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->mode.scanstatsOn, (int*)0 + p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 ); } } @@ -4655,7 +6062,7 @@ static void open_db(ShellState *p, int openFlags){ void close_db(sqlite3 *db){ int rc = sqlite3_close(db); if( rc ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); } } @@ -4828,7 +6235,7 @@ static int booleanValue(const char *zArg){ if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ return 0; } - cli_printf(stderr, + sqlite3_fprintf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", zArg); return 0; } @@ -4856,18 +6263,18 @@ static void output_file_close(FILE *f){ ** recognized and do the right thing. NULL is returned if the output ** filename is "off". */ -static FILE *output_file_open(ShellState *p, const char *zFile){ +static FILE *output_file_open(const char *zFile){ FILE *f; if( cli_strcmp(zFile,"stdout")==0 ){ f = stdout; }else if( cli_strcmp(zFile, "stderr")==0 ){ f = stderr; - }else if( cli_strcmp(zFile, "off")==0 || p->bSafeMode ){ + }else if( cli_strcmp(zFile, "off")==0 ){ f = 0; }else{ f = sqlite3_fopen(zFile, "w"); if( f==0 ){ - cli_printf(stderr,"Error: cannot open \"%s\"\n", zFile); + sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zFile); } } return f; @@ -4920,12 +6327,12 @@ static int sql_trace_callback( switch( mType ){ case SQLITE_TRACE_ROW: case SQLITE_TRACE_STMT: { - cli_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql); + sqlite3_fprintf(p->traceOut, "%.*s;\n", (int)nSql, zSql); break; } case SQLITE_TRACE_PROFILE: { sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0; - cli_printf(p->traceOut, + sqlite3_fprintf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); break; } @@ -4954,9 +6361,7 @@ struct ImportCtx { const char *zFile; /* Name of the input file */ FILE *in; /* Read the CSV text from this input stream */ int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ - char *zIn; /* Input text */ char *z; /* Accumulated text for a field */ - i64 nUsed; /* Bytes of zIn[] used so far */ i64 n; /* Number of bytes in z */ i64 nAlloc; /* Space allocated for z[] */ int nLine; /* Current line number */ @@ -4966,8 +6371,6 @@ struct ImportCtx { int cTerm; /* Character that terminated the most recent field */ int cColSep; /* The column separator character. (Usually ",") */ int cRowSep; /* The row separator character. (Usually "\n") */ - int cQEscape; /* Escape character with "...". 0 for none */ - int cUQEscape; /* Escape character not with "...". 0 for none */ }; /* Clean up resourced used by an ImportCtx */ @@ -4978,28 +6381,9 @@ static void import_cleanup(ImportCtx *p){ } sqlite3_free(p->z); p->z = 0; - if( p->zIn ){ - sqlite3_free(p->zIn); - p->zIn = 0; - } -} - -/* Read a single character of the .import input text. Return EOF -** at end-of-file. -*/ -static int import_getc(ImportCtx *p){ - if( p->in ){ - return fgetc(p->in); - }else if( p->zIn && p->zIn[p->nUsed]!=0 ){ - return p->zIn[p->nUsed++]; - }else{ - return EOF; - } } -/* Append a single byte to the field value begin constructed -** in the p->z[] buffer -*/ +/* Append a single byte to z[] */ static void import_append_char(ImportCtx *p, int c){ if( p->n+1>=p->nAlloc ){ p->nAlloc += p->nAlloc + 100; @@ -5015,8 +6399,8 @@ static void import_append_char(ImportCtx *p, int c){ ** + Input comes from p->in. ** + Store results in p->z of length p->n. Space to hold p->z comes ** from sqlite3_malloc64(). -** + Use p->cColSep as the column separator. The default is ",". -** + Use p->cRowSep as the row separator. The default is "\n". +** + Use p->cSep as the column separator. The default is ",". +** + Use p->rSep as the row separator. The default is "\n". ** + Keep track of the line number in p->nLine. ** + Store the character that terminates the field in p->cTerm. Store ** EOF on end-of-file. @@ -5027,7 +6411,7 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ int cSep = (u8)p->cColSep; int rSep = (u8)p->cRowSep; p->n = 0; - c = import_getc(p); + c = fgetc(p->in); if( c==EOF || seenInterrupt ){ p->cTerm = EOF; return 0; @@ -5036,17 +6420,10 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ int pc, ppc; int startLine = p->nLine; int cQuote = c; - int cEsc = (u8)p->cQEscape; pc = ppc = 0; while( 1 ){ - c = import_getc(p); + c = fgetc(p->in); if( c==rSep ) p->nLine++; - if( c==cEsc && cEsc!=0 ){ - c = import_getc(p); - import_append_char(p, c); - ppc = pc = 0; - continue; - } if( c==cQuote ){ if( pc==cQuote ){ pc = 0; @@ -5063,11 +6440,11 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ break; } if( pc==cQuote && c!='\r' ){ - cli_printf(stderr,"%s:%d: unescaped %c character\n", - p->zFile, p->nLine, cQuote); + sqlite3_fprintf(stderr,"%s:%d: unescaped %c character\n", + p->zFile, p->nLine, cQuote); } if( c==EOF ){ - cli_printf(stderr,"%s:%d: unterminated %c-quoted field\n", + sqlite3_fprintf(stderr,"%s:%d: unterminated %c-quoted field\n", p->zFile, startLine, cQuote); p->cTerm = c; break; @@ -5079,13 +6456,12 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ }else{ /* If this is the first field being parsed and it begins with the ** UTF-8 BOM (0xEF BB BF) then skip the BOM */ - int cEsc = p->cUQEscape; if( (c&0xff)==0xef && p->bNotFirst==0 ){ import_append_char(p, c); - c = import_getc(p); + c = fgetc(p->in); if( (c&0xff)==0xbb ){ import_append_char(p, c); - c = import_getc(p); + c = fgetc(p->in); if( (c&0xff)==0xbf ){ p->bNotFirst = 1; p->n = 0; @@ -5094,9 +6470,8 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ } } while( c!=EOF && c!=cSep && c!=rSep ){ - if( c==cEsc && cEsc!=0 ) c = import_getc(p); import_append_char(p, c); - c = import_getc(p); + c = fgetc(p->in); } if( c==rSep ){ p->nLine++; @@ -5114,8 +6489,8 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ ** + Input comes from p->in. ** + Store results in p->z of length p->n. Space to hold p->z comes ** from sqlite3_malloc64(). -** + Use p->cColSep as the column separator. The default is "\x1F". -** + Use p->cRowSep as the row separator. The default is "\x1E". +** + Use p->cSep as the column separator. The default is "\x1F". +** + Use p->rSep as the row separator. The default is "\x1E". ** + Keep track of the row number in p->nLine. ** + Store the character that terminates the field in p->cTerm. Store ** EOF on end-of-file. @@ -5126,14 +6501,14 @@ static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ int cSep = (u8)p->cColSep; int rSep = (u8)p->cRowSep; p->n = 0; - c = import_getc(p); + c = fgetc(p->in); if( c==EOF || seenInterrupt ){ p->cTerm = EOF; return 0; } while( c!=EOF && c!=cSep && c!=rSep ){ import_append_char(p, c); - c = import_getc(p); + c = fgetc(p->in); } if( c==rSep ){ p->nLine++; @@ -5168,7 +6543,7 @@ static void tryToCloneData( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - cli_printf(stderr,"Error %d: %s on [%s]\n", + sqlite3_fprintf(stderr,"Error %d: %s on [%s]\n", sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_data_xfer; } @@ -5185,7 +6560,7 @@ static void tryToCloneData( memcpy(zInsert+i, ");", 3); rc = sqlite3_prepare_v2(newDb, zInsert, -1, &pInsert, 0); if( rc ){ - cli_printf(stderr,"Error %d: %s on [%s]\n", + sqlite3_fprintf(stderr,"Error %d: %s on [%s]\n", sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb), zInsert); goto end_data_xfer; } @@ -5221,7 +6596,7 @@ static void tryToCloneData( } /* End for */ rc = sqlite3_step(pInsert); if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){ - cli_printf(stderr,"Error %d: %s\n", + sqlite3_fprintf(stderr,"Error %d: %s\n", sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb)); } sqlite3_reset(pInsert); @@ -5239,7 +6614,7 @@ static void tryToCloneData( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - cli_printf(stderr,"Warning: cannot step \"%s\" backwards", zTable); + sqlite3_fprintf(stderr,"Warning: cannot step \"%s\" backwards", zTable); break; } } /* End for(k=0...) */ @@ -5276,7 +6651,7 @@ static void tryToCloneSchema( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Error: (%d) %s on [%s]\n", sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_schema_xfer; @@ -5286,10 +6661,10 @@ static void tryToCloneSchema( zSql = sqlite3_column_text(pQuery, 1); if( zName==0 || zSql==0 ) continue; if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){ - cli_printf(stdout, "%s... ", zName); fflush(stdout); + sqlite3_fprintf(stdout, "%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ - cli_printf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + sqlite3_fprintf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql); sqlite3_free(zErrMsg); zErrMsg = 0; } @@ -5307,7 +6682,7 @@ static void tryToCloneSchema( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - cli_printf(stderr,"Error: (%d) %s on [%s]\n", + sqlite3_fprintf(stderr,"Error: (%d) %s on [%s]\n", sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_schema_xfer; } @@ -5316,10 +6691,10 @@ static void tryToCloneSchema( zSql = sqlite3_column_text(pQuery, 1); if( zName==0 || zSql==0 ) continue; if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue; - cli_printf(stdout, "%s... ", zName); fflush(stdout); + sqlite3_fprintf(stdout, "%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ - cli_printf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + sqlite3_fprintf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql); sqlite3_free(zErrMsg); zErrMsg = 0; } @@ -5343,12 +6718,12 @@ static void tryToClone(ShellState *p, const char *zNewDb){ int rc; sqlite3 *newDb = 0; if( access(zNewDb,0)==0 ){ - cli_printf(stderr,"File \"%s\" already exists.\n", zNewDb); + sqlite3_fprintf(stderr,"File \"%s\" already exists.\n", zNewDb); return; } rc = sqlite3_open(zNewDb, &newDb); if( rc ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Cannot create output database: %s\n", sqlite3_errmsg(newDb)); }else{ sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0); @@ -5367,12 +6742,12 @@ static void tryToClone(ShellState *p, const char *zNewDb){ */ static void output_redir(ShellState *p, FILE *pfNew){ if( p->out != stdout ){ - cli_puts("Output already redirected.\n", stderr); + sqlite3_fputs("Output already redirected.\n", stderr); }else{ p->out = pfNew; setCrlfMode(p); - if( p->mode.eMode==MODE_Www ){ - cli_puts( + if( p->mode==MODE_Www ){ + sqlite3_fputs( "<!DOCTYPE html>\n" "<HTML><BODY><PRE>\n", p->out @@ -5394,8 +6769,8 @@ static void output_reset(ShellState *p){ pclose(p->out); #endif }else{ - if( p->mode.eMode==MODE_Www ){ - cli_puts("</PRE></BODY></HTML>\n", p->out); + if( p->mode==MODE_Www ){ + sqlite3_fputs("</PRE></BODY></HTML>\n", p->out); } output_file_close(p->out); #ifndef SQLITE_NOHAVE_SYSTEM @@ -5411,7 +6786,7 @@ static void output_reset(ShellState *p){ char *zCmd; zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile); if( system(zCmd) ){ - cli_printf(stderr,"Failed: [%s]\n", zCmd); + sqlite3_fprintf(stderr,"Failed: [%s]\n", zCmd); }else{ /* Give the start/open/xdg-open command some time to get ** going before we continue, and potential delete the @@ -5419,7 +6794,7 @@ static void output_reset(ShellState *p){ sqlite3_sleep(2000); } sqlite3_free(zCmd); - modePop(p); + outputModePop(p); p->doXdgOpen = 0; } #endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ @@ -5427,10 +6802,6 @@ static void output_reset(ShellState *p){ p->outfile[0] = 0; p->out = stdout; setCrlfMode(p); - if( cli_output_capture ){ - sqlite3_str_free(cli_output_capture); - cli_output_capture = 0; - } } #else # define output_redir(SS,pfO) @@ -5515,7 +6886,7 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1", -1, &pStmt, 0); if( rc ){ - cli_printf(stderr,"error: %s\n", sqlite3_errmsg(p->db)); + sqlite3_fprintf(stderr,"error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); return 1; } @@ -5528,28 +6899,28 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ memcpy(aHdr, pb, 100); sqlite3_finalize(pStmt); }else{ - cli_puts("unable to read database header\n", stderr); + sqlite3_fputs("unable to read database header\n", stderr); sqlite3_finalize(pStmt); return 1; } i = get2byteInt(aHdr+16); if( i==1 ) i = 65536; - cli_printf(p->out, "%-20s %d\n", "database page size:", i); - cli_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]); - cli_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]); - cli_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); + sqlite3_fprintf(p->out, "%-20s %d\n", "database page size:", i); + sqlite3_fprintf(p->out, "%-20s %d\n", "write format:", aHdr[18]); + sqlite3_fprintf(p->out, "%-20s %d\n", "read format:", aHdr[19]); + sqlite3_fprintf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); for(i=0; i<ArraySize(aField); i++){ int ofst = aField[i].ofst; unsigned int val = get4byteInt(aHdr + ofst); - cli_printf(p->out, "%-20s %u", aField[i].zName, val); + sqlite3_fprintf(p->out, "%-20s %u", aField[i].zName, val); switch( ofst ){ case 56: { - if( val==1 ) cli_puts(" (utf8)", p->out); - if( val==2 ) cli_puts(" (utf16le)", p->out); - if( val==3 ) cli_puts(" (utf16be)", p->out); + if( val==1 ) sqlite3_fputs(" (utf8)", p->out); + if( val==2 ) sqlite3_fputs(" (utf16le)", p->out); + if( val==3 ) sqlite3_fputs(" (utf16be)", p->out); } } - cli_puts("\n", p->out); + sqlite3_fputs("\n", p->out); } if( zDb==0 ){ zSchemaTab = sqlite3_mprintf("main.sqlite_schema"); @@ -5560,11 +6931,11 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ } for(i=0; i<ArraySize(aQuery); i++){ int val = db_int(p->db, aQuery[i].zSql, zSchemaTab); - cli_printf(p->out, "%-20s %d\n", aQuery[i].zName, val); + sqlite3_fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val); } sqlite3_free(zSchemaTab); sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); - cli_printf(p->out, "%-20s %u\n", "data version", iDataVersion); + sqlite3_fprintf(p->out, "%-20s %u\n", "data version", iDataVersion); return 0; } #endif /* SQLITE_SHELL_HAVE_RECOVER */ @@ -5624,7 +6995,7 @@ static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){ } zName = strdup(zTail); shell_check_oom(zName); - cli_printf(p->out, "| size %lld pagesize %d filename %s\n", + sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n", nPage*pgSz, pgSz, zName); sqlite3_finalize(pStmt); pStmt = 0; @@ -5640,27 +7011,27 @@ static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){ for(j=0; j<16 && aLine[j]==0; j++){} if( j==16 ) continue; if( !seenPageLabel ){ - cli_printf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz); + sqlite3_fprintf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz); seenPageLabel = 1; } - cli_printf(p->out, "| %5d:", i); - for(j=0; j<16; j++) cli_printf(p->out, " %02x", aLine[j]); - cli_printf(p->out, " "); + sqlite3_fprintf(p->out, "| %5d:", i); + for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]); + sqlite3_fprintf(p->out, " "); for(j=0; j<16; j++){ unsigned char c = (unsigned char)aLine[j]; - cli_printf(p->out, "%c", bShow[c]); + sqlite3_fprintf(p->out, "%c", bShow[c]); } - cli_printf(p->out, "\n"); + sqlite3_fprintf(p->out, "\n"); } } sqlite3_finalize(pStmt); - cli_printf(p->out, "| end %s\n", zName); + sqlite3_fprintf(p->out, "| end %s\n", zName); free(zName); return 0; dbtotxt_error: if( rc ){ - cli_printf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db)); + sqlite3_fprintf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db)); } sqlite3_finalize(pStmt); free(zName); @@ -5671,7 +7042,7 @@ dbtotxt_error: ** Print the given string as an error message. */ static void shellEmitError(const char *zErr){ - cli_printf(stderr,"Error: %s\n", zErr); + sqlite3_fprintf(stderr,"Error: %s\n", zErr); } /* ** Print the current sqlite3_errmsg() value to stderr and return 1. @@ -5854,42 +7225,39 @@ static void clearTempFile(ShellState *p){ p->zTempFile = 0; } -/* Forward reference */ -static char *find_home_dir(int clearFlag); - /* ** Create a new temp file name with the given suffix. -** -** Because the classic temp folders like /tmp are no longer -** accessible to web browsers, for security reasons, create the -** temp file in the user's home directory. */ static void newTempFile(ShellState *p, const char *zSuffix){ - char *zHome; /* Home directory */ - int i; /* Loop counter */ - sqlite3_uint64 r = 0; /* Integer with 64 bits of randomness */ - char zRand[32]; /* Text string with 160 bits of randomness */ + clearTempFile(p); + sqlite3_free(p->zTempFile); + p->zTempFile = 0; + if( p->db ){ + sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile); + } + if( p->zTempFile==0 ){ + /* If p->db is an in-memory database then the TEMPFILENAME file-control + ** will not work and we will need to fallback to guessing */ + char *zTemp; + sqlite3_uint64 r; + sqlite3_randomness(sizeof(r), &r); + zTemp = getenv("TEMP"); + if( zTemp==0 ) zTemp = getenv("TMP"); + if( zTemp==0 ){ #ifdef _WIN32 - const char cDirSep = '\\'; + zTemp = "\\tmp"; #else - const char cDirSep = '/'; + zTemp = "/tmp"; #endif - - for(i=0; i<31; i++){ - if( (i%12)==0 ) sqlite3_randomness(sizeof(r),&r); - zRand[i] = "0123456789abcdefghijklmnopqrstuvwxyz"[r%36]; - r /= 36; + } + p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix); + }else{ + p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix); } - zRand[i] = 0; - clearTempFile(p); - sqlite3_free(p->zTempFile); - p->zTempFile = 0; - zHome = find_home_dir(0); - p->zTempFile = sqlite3_mprintf("%s%ctemp-%s.%s", - zHome,cDirSep,zRand,zSuffix); shell_check_oom(p->zTempFile); } + /* ** The implementation of SQL scalar function fkey_collate_clause(), used ** by the ".lint fkey-indexes" command. This scalar function is always @@ -6034,7 +7402,7 @@ static int lintFkeyIndexes( zIndent = " "; } else{ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", azArg[0], azArg[1]); return SQLITE_ERROR; } @@ -6079,22 +7447,22 @@ static int lintFkeyIndexes( if( rc!=SQLITE_OK ) break; if( res<0 ){ - cli_puts("Error: internal error", stderr); + sqlite3_fputs("Error: internal error", stderr); break; }else{ if( bGroupByParent && (bVerbose || res==0) && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) ){ - cli_printf(out, "-- Parent table %s\n", zParent); + sqlite3_fprintf(out, "-- Parent table %s\n", zParent); sqlite3_free(zPrev); zPrev = sqlite3_mprintf("%s", zParent); } if( res==0 ){ - cli_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); + sqlite3_fprintf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); }else if( bVerbose ){ - cli_printf(out, + sqlite3_fprintf(out, "%s/* no extra indexes required for %s -> %s */\n", zIndent, zFrom, zTarget ); @@ -6104,16 +7472,16 @@ static int lintFkeyIndexes( sqlite3_free(zPrev); if( rc!=SQLITE_OK ){ - cli_printf(stderr,"%s\n", sqlite3_errmsg(db)); + sqlite3_fprintf(stderr,"%s\n", sqlite3_errmsg(db)); } rc2 = sqlite3_finalize(pSql); if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ rc = rc2; - cli_printf(stderr,"%s\n", sqlite3_errmsg(db)); + sqlite3_fprintf(stderr,"%s\n", sqlite3_errmsg(db)); } }else{ - cli_printf(stderr,"%s\n", sqlite3_errmsg(db)); + sqlite3_fprintf(stderr,"%s\n", sqlite3_errmsg(db)); } return rc; @@ -6133,9 +7501,9 @@ static int lintDotCommand( return lintFkeyIndexes(pState, azArg, nArg); usage: - cli_printf(stderr,"Usage %s sub-command ?switches...?\n", azArg[0]); - cli_printf(stderr, "Where sub-commands are:\n"); - cli_printf(stderr, " fkey-indexes\n"); + sqlite3_fprintf(stderr,"Usage %s sub-command ?switches...?\n", azArg[0]); + sqlite3_fprintf(stderr, "Where sub-commands are:\n"); + sqlite3_fprintf(stderr, " fkey-indexes\n"); return SQLITE_ERROR; } @@ -6149,7 +7517,7 @@ static void shellPrepare( if( *pRc==SQLITE_OK ){ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); if( rc!=SQLITE_OK ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "sql error: %s (%d)\n", sqlite3_errmsg(db), sqlite3_errcode(db)); *pRc = rc; } @@ -6194,7 +7562,7 @@ static void shellFinalize( int rc = sqlite3_finalize(pStmt); if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ - cli_printf(stderr,"SQL error: %s\n", sqlite3_errmsg(db)); + sqlite3_fprintf(stderr,"SQL error: %s\n", sqlite3_errmsg(db)); } *pRc = rc; } @@ -6216,7 +7584,7 @@ void shellReset( if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ sqlite3 *db = sqlite3_db_handle(pStmt); - cli_printf(stderr,"SQL error: %s\n", sqlite3_errmsg(db)); + sqlite3_fprintf(stderr,"SQL error: %s\n", sqlite3_errmsg(db)); } *pRc = rc; } @@ -6269,9 +7637,9 @@ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ va_end(ap); shellEmitError(z); if( pAr->fromCmdLine ){ - cli_puts("Use \"-A\" for more help\n", stderr); + sqlite3_fputs("Use \"-A\" for more help\n", stderr); }else{ - cli_puts("Use \".archive --help\" for more help\n", stderr); + sqlite3_fputs("Use \".archive --help\" for more help\n", stderr); } sqlite3_free(z); return SQLITE_ERROR; @@ -6371,7 +7739,7 @@ static int arParseCommand( struct ArSwitch *pEnd = &aSwitch[nSwitch]; if( nArg<=1 ){ - cli_printf(stderr, "Wrong number of arguments. Usage:\n"); + sqlite3_fprintf(stderr, "Wrong number of arguments. Usage:\n"); return arUsage(stderr); }else{ char *z = azArg[1]; @@ -6477,7 +7845,7 @@ static int arParseCommand( } } if( pAr->eCmd==0 ){ - cli_printf(stderr, "Required argument missing. Usage:\n"); + sqlite3_fprintf(stderr, "Required argument missing. Usage:\n"); return arUsage(stderr); } return SQLITE_OK; @@ -6520,7 +7888,7 @@ static int arCheckEntries(ArCommand *pAr){ } shellReset(&rc, pTest); if( rc==SQLITE_OK && bOk==0 ){ - cli_printf(stderr,"not found in archive: %s\n", z); + sqlite3_fprintf(stderr,"not found in archive: %s\n", z); rc = SQLITE_ERROR; } } @@ -6603,15 +7971,15 @@ static int arListCommand(ArCommand *pAr){ shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], pAr->zSrcTable, zWhere); if( pAr->bDryRun ){ - cli_printf(pAr->out, "%s\n", sqlite3_sql(pSql)); + sqlite3_fprintf(pAr->out, "%s\n", sqlite3_sql(pSql)); }else{ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ if( pAr->bVerbose ){ - cli_printf(pAr->out, "%s % 10d %s %s\n", + sqlite3_fprintf(pAr->out, "%s % 10d %s %s\n", sqlite3_column_text(pSql, 0), sqlite3_column_int(pSql, 1), sqlite3_column_text(pSql, 2),sqlite3_column_text(pSql, 3)); }else{ - cli_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); + sqlite3_fprintf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); } } } @@ -6638,7 +8006,7 @@ static int arRemoveCommand(ArCommand *pAr){ zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;", pAr->zSrcTable, zWhere); if( pAr->bDryRun ){ - cli_printf(pAr->out, "%s\n", zSql); + sqlite3_fprintf(pAr->out, "%s\n", zSql); }else{ char *zErr = 0; rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0); @@ -6651,7 +8019,7 @@ static int arRemoveCommand(ArCommand *pAr){ } } if( zErr ){ - cli_printf(stdout, "ERROR: %s\n", zErr); /* stdout? */ + sqlite3_fprintf(stdout, "ERROR: %s\n", zErr); /* stdout? */ sqlite3_free(zErr); } } @@ -6666,15 +8034,11 @@ static int arRemoveCommand(ArCommand *pAr){ */ static int arExtractCommand(ArCommand *pAr){ const char *zSql1 = - "WITH dest(dpath,dlen) AS (SELECT realpath($dir),length(realpath($dir)))\n" - "SELECT ($dir || name),\n" - " CASE WHEN $dryrun THEN 0\n" - " ELSE writefile($dir||name, %s, mode, mtime) END\n" - " FROM dest CROSS JOIN %s\n" - " WHERE (%s)\n" - " AND (data IS NULL OR $pass==0)\n" /* Dirs both passes */ - " AND dpath=substr(realpath($dir||name),1,dlen)\n" /* No escapes */ - " AND name NOT GLOB '*..[/\\]*'\n"; /* No /../ in paths */ + "SELECT " + " ($dir || name)," + " writefile(($dir || name), %s, mode, mtime) " + "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" + " AND name NOT GLOB '*..[/\\]*'"; const char *azExtraArg[] = { "sqlar_uncompress(data, sz)", @@ -6709,28 +8073,24 @@ static int arExtractCommand(ArCommand *pAr){ if( rc==SQLITE_OK ){ j = sqlite3_bind_parameter_index(pSql, "$dir"); sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); - j = sqlite3_bind_parameter_index(pSql, "$dryrun"); - sqlite3_bind_int(pSql, j, pAr->bDryRun); - - /* Run the SELECT statement twice - ** (0) writefile() all files and directories - ** (1) writefile() for directory again - ** The second pass is so that the timestamps for extracted directories - ** will be reset to the value in the archive, since populating them - ** in the first pass will have changed the timestamp. */ + + /* Run the SELECT statement twice. The first time, writefile() is called + ** for all archive members that should be extracted. The second time, + ** only for the directories. This is because the timestamps for + ** extracted directories must be reset after they are populated (as + ** populating them changes the timestamp). */ for(i=0; i<2; i++){ - j = sqlite3_bind_parameter_index(pSql, "$pass"); + j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); sqlite3_bind_int(pSql, j, i); if( pAr->bDryRun ){ - cli_printf(pAr->out, "%s\n", sqlite3_sql(pSql)); - if( pAr->bVerbose==0 ) break; - } - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ - if( i==0 && pAr->bVerbose ){ - cli_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); + sqlite3_fprintf(pAr->out, "%s\n", sqlite3_sql(pSql)); + }else{ + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( i==0 && pAr->bVerbose ){ + sqlite3_fprintf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); + } } } - if( pAr->bDryRun ) break; shellReset(&rc, pSql); } shellFinalize(&rc, pSql); @@ -6747,13 +8107,13 @@ static int arExtractCommand(ArCommand *pAr){ static int arExecSql(ArCommand *pAr, const char *zSql){ int rc; if( pAr->bDryRun ){ - cli_printf(pAr->out, "%s\n", zSql); + sqlite3_fprintf(pAr->out, "%s\n", zSql); rc = SQLITE_OK; }else{ char *zErr = 0; rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); if( zErr ){ - cli_printf(stdout, "ERROR: %s\n", zErr); + sqlite3_fprintf(stdout, "ERROR: %s\n", zErr); sqlite3_free(zErr); } } @@ -6929,13 +8289,13 @@ static int arDotCommand( } cmd.db = 0; if( cmd.bDryRun ){ - cli_printf(cmd.out, "-- open database '%s'%s\n", cmd.zFile, + sqlite3_fprintf(cmd.out, "-- open database '%s'%s\n", cmd.zFile, eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); } rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); if( rc!=SQLITE_OK ){ - cli_printf(stderr, "cannot open file: %s (%s)\n", + sqlite3_fprintf(stderr, "cannot open file: %s (%s)\n", cmd.zFile, sqlite3_errmsg(cmd.db)); goto end_ar_command; } @@ -6949,7 +8309,7 @@ static int arDotCommand( if( cmd.eCmd!=AR_CMD_CREATE && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) ){ - cli_printf(stderr, "database does not contain an 'sqlar' table\n"); + sqlite3_fprintf(stderr, "database does not contain an 'sqlar' table\n"); rc = SQLITE_ERROR; goto end_ar_command; } @@ -7007,7 +8367,7 @@ end_ar_command: */ static int recoverSqlCb(void *pCtx, const char *zSql){ ShellState *pState = (ShellState*)pCtx; - cli_printf(pState->out, "%s;\n", zSql); + sqlite3_fprintf(pState->out, "%s;\n", zSql); return SQLITE_OK; } @@ -7050,7 +8410,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ bRowids = 0; } else{ - cli_printf(stderr,"unexpected option: %s\n", azArg[i]); + sqlite3_fprintf(stderr,"unexpected option: %s\n", azArg[i]); showHelp(pState->out, azArg[0]); return 1; } @@ -7060,19 +8420,17 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ pState->db, "main", recoverSqlCb, (void*)pState ); - if( !pState->bSafeMode ){ - sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */ - } + sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */ sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF); sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids); sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist); - cli_printf(pState->out, ".dbconfig defensive off\n"); + sqlite3_fprintf(pState->out, ".dbconfig defensive off\n"); sqlite3_recover_run(p); if( sqlite3_recover_errcode(p)!=SQLITE_OK ){ const char *zErr = sqlite3_recover_errmsg(p); int errCode = sqlite3_recover_errcode(p); - cli_printf(stderr,"sql error: %s (%d)\n", zErr, errCode); + sqlite3_fprintf(stderr,"sql error: %s (%d)\n", zErr, errCode); } rc = sqlite3_recover_finish(p); return rc; @@ -7094,7 +8452,7 @@ static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){ while( SQLITE_OK==sqlite3_intck_step(p) ){ const char *zMsg = sqlite3_intck_message(p); if( zMsg ){ - cli_printf(pState->out, "%s\n", zMsg); + sqlite3_fprintf(pState->out, "%s\n", zMsg); nError++; } nStep++; @@ -7104,11 +8462,11 @@ static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){ } rc = sqlite3_intck_error(p, &zErr); if( zErr ){ - cli_printf(stderr,"%s\n", zErr); + sqlite3_fprintf(stderr,"%s\n", zErr); } sqlite3_intck_close(p); - cli_printf(pState->out, "%lld steps, %lld errors\n", nStep, nError); + sqlite3_fprintf(pState->out, "%lld steps, %lld errors\n", nStep, nError); } return rc; @@ -7131,7 +8489,7 @@ static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){ #define rc_err_oom_die(rc) \ if( rc==SQLITE_NOMEM ) shell_check_oom(0); \ else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \ - cli_printf(stderr,"E:%d\n",rc), assert(0) + sqlite3_fprintf(stderr,"E:%d\n",rc), assert(0) #else static void rc_err_oom_die(int rc){ if( rc==SQLITE_NOMEM ) shell_check_oom(0); @@ -7246,7 +8604,7 @@ SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \ SELECT\ '('||x'0a'\ || group_concat(\ - cname||' ANY',\ + cname||' TEXT',\ ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\ ||')' AS ColsSpec \ FROM (\ @@ -7282,1678 +8640,120 @@ FROM (\ rc_err_oom_die(rc); rc = sqlite3_step(pStmt); rc_err_oom_die(rc); - sqlite3_finalize(pStmt); - return 0; - }else if( *pDb==0 ){ - return 0; - }else{ - /* Formulate the columns spec, close the DB, zero *pDb. */ - char *zColsSpec = 0; - int hasDupes = db_int(*pDb, "%s", zHasDupes); - int nDigits = (hasDupes)? db_int(*pDb, "%s", zColDigits) : 0; - if( hasDupes ){ -#ifdef SHELL_COLUMN_RENAME_CLEAN - rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); - rc_err_oom_die(rc); -#endif - rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0); - rc_err_oom_die(rc); - rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0); - rc_err_oom_die(rc); - sqlite3_bind_int(pStmt, 1, nDigits); - rc = sqlite3_step(pStmt); - sqlite3_finalize(pStmt); - if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM); - } - assert(db_int(*pDb, "%s", zHasDupes)==0); /* Consider: remove this */ - rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); - rc_err_oom_die(rc); - rc = sqlite3_step(pStmt); - if( rc==SQLITE_ROW ){ - zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); - }else{ - zColsSpec = 0; - } - if( pzRenamed!=0 ){ - if( !hasDupes ) *pzRenamed = 0; - else{ - sqlite3_finalize(pStmt); - if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0) - && SQLITE_ROW==sqlite3_step(pStmt) ){ - *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); - }else - *pzRenamed = 0; - } - } - sqlite3_finalize(pStmt); - sqlite3_close(*pDb); - *pDb = 0; - return zColsSpec; - } -} - -/* -** Check if the sqlite_schema table contains one or more virtual tables. If -** parameter zLike is not NULL, then it is an SQL expression that the -** sqlite_schema row must also match. If one or more such rows are found, -** print the following warning to the output: -** -** WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled -*/ -static int outputDumpWarning(ShellState *p, const char *zLike){ - int rc = SQLITE_OK; - sqlite3_stmt *pStmt = 0; - shellPreparePrintf(p->db, &rc, &pStmt, - "SELECT 1 FROM sqlite_schema o WHERE " - "sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true" - ); - if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - cli_puts("/* WARNING: " - "Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n", - p->out - ); - } - shellFinalize(&rc, pStmt); - return rc; -} - -/* -** Fault-Simulator state and logic. -*/ -static struct { - int iId; /* ID that triggers a simulated fault. -1 means "any" */ - int iErr; /* The error code to return on a fault */ - int iCnt; /* Trigger the fault only if iCnt is already zero */ - int iInterval; /* Reset iCnt to this value after each fault */ - int eVerbose; /* When to print output */ - int nHit; /* Number of hits seen so far */ - int nRepeat; /* Turn off after this many hits. 0 for never */ - int nSkip; /* Skip this many before first fault */ -} faultsim_state = {-1, 0, 0, 0, 0, 0, 0, 0}; - -/* -** This is the fault-sim callback -*/ -static int faultsim_callback(int iArg){ - if( faultsim_state.iId>0 && faultsim_state.iId!=iArg ){ - return SQLITE_OK; - } - if( faultsim_state.iCnt ){ - if( faultsim_state.iCnt>0 ) faultsim_state.iCnt--; - if( faultsim_state.eVerbose>=2 ){ - cli_printf(stdout, - "FAULT-SIM id=%d no-fault (cnt=%d)\n", iArg, faultsim_state.iCnt); - } - return SQLITE_OK; - } - if( faultsim_state.eVerbose>=1 ){ - cli_printf(stdout, - "FAULT-SIM id=%d returns %d\n", iArg, faultsim_state.iErr); - } - faultsim_state.iCnt = faultsim_state.iInterval; - faultsim_state.nHit++; - if( faultsim_state.nRepeat>0 && faultsim_state.nRepeat<=faultsim_state.nHit ){ - faultsim_state.iCnt = -1; - } - return faultsim_state.iErr; -} - -/* -** pickStr(zArg, &zErr, zS1, zS2, ..., ""); -** -** Try to match zArg against zS1, zS2, and so forth until the first -** emptry string. Return the index of the match or -1 if none is found. -** If no match is found, and &zErr is not NULL, then write into -** zErr a message describing the valid choices. -*/ -static int pickStr(const char *zArg, char **pzErr, ...){ - int i, n; - const char *z; - sqlite3_str *pMsg; - va_list ap; - va_start(ap, pzErr); - i = 0; - while( (z = va_arg(ap,const char*))!=0 && z[0]!=0 ){ - if( cli_strcmp(zArg, z)==0 ) return i; - i++; - } - va_end(ap); - if( pzErr==0 ) return -1; - n = i; - pMsg = sqlite3_str_new(0); - va_start(ap, pzErr); - sqlite3_str_appendall(pMsg, "should be"); - i = 0; - while( (z = va_arg(ap, const char*))!=0 && z[0]!=0 ){ - if( i==n-1 ){ - sqlite3_str_append(pMsg,", or",4); - }else if( i>0 ){ - sqlite3_str_append(pMsg, ",", 1); - } - sqlite3_str_appendf(pMsg, " %s", z); - i++; - } - va_end(ap); - *pzErr = sqlite3_str_finish(pMsg); - return -1; -} - -/* -** DOT-COMMAND: .import -** -** USAGE: .import [OPTIONS] FILE TABLE -** -** Import CSV or similar text from FILE into TABLE. If TABLE does -** not exist, it is created using the first row of FILE as the column -** names. If FILE begins with "|" then it is a command that is run -** and the output from the command is used as the input data. If -** FILE begins with "<<" followed by a label, then content is read from -** the script until the first line that matches the label. -** -** The content of FILE is interpreted using RFC-4180 ("CSV") quoting -** rules unless the current mode is "ascii" or "tabs" or unless one -** the --ascii option is used. -** -** The column and row separators must be single ASCII characters. If -** multiple characters or a Unicode character are specified for the -** separators, then only the first byte of the separator is used. Except, -** if the row separator is \n and the mode is not --ascii, then \r\n is -** understood as a row separator too. -** -** Options: -** --ascii Do not use RFC-4180 quoting. Use \037 and \036 -** as column and row separators on input, unless other -** delimiters are specified using --colsep and/or --rowsep -** --colsep CHAR Use CHAR as the column separator. -** --csv Input is standard RFC-4180 CSV. -** --esc CHAR Use CHAR as an escape character in unquoted CSV inputs. -** --qesc CHAR Use CHAR as an escape character in quoted CSV inputs. -** --rowsep CHAR Use CHAR as the row separator. -** --schema S When creating TABLE, put it in schema S -** --skip N Ignore the first N rows of input -** -v Verbose mode -*/ -static int dotCmdImport(ShellState *p){ - int nArg = p->dot.nArg; /* Number of arguments */ - char **azArg = p->dot.azArg;/* Argument list */ - char *zTable = 0; /* Insert data into this table */ - char *zSchema = 0; /* Schema of zTable */ - char *zFile = 0; /* Name of file to extra content from */ - sqlite3_stmt *pStmt = NULL; /* A statement */ - int nCol; /* Number of columns in the table */ - i64 nByte; /* Number of bytes in an SQL string */ - int i, j; /* Loop counters */ - int needCommit; /* True to COMMIT or ROLLBACK at end */ - char *zSql = 0; /* An SQL statement */ - ImportCtx sCtx; /* Reader context */ - char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ - int eVerbose = 0; /* Larger for more console output */ - i64 nSkip = 0; /* Initial lines to skip */ - i64 iLineOffset = 0; /* Offset to the first line of input */ - char *zCreate = 0; /* CREATE TABLE statement text */ - int rc; /* Result code */ - - failIfSafeMode(p, "cannot run .import in safe mode"); - memset(&sCtx, 0, sizeof(sCtx)); - if( p->mode.eMode==MODE_Ascii ){ - xRead = ascii_read_one_field; - }else{ - xRead = csv_read_one_field; - } - for(i=1; i<nArg; i++){ - char *z = azArg[i]; - if( z[0]=='-' && z[1]=='-' ) z++; - if( z[0]!='-' ){ - if( zFile==0 ){ - zFile = z; - }else if( zTable==0 ){ - zTable = z; - }else{ - dotCmdError(p, i, "unknown argument", 0); - return 1; - } - }else if( cli_strcmp(z,"-v")==0 ){ - eVerbose++; - }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){ - zSchema = azArg[++i]; - }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){ - nSkip = integerValue(azArg[++i]); - }else if( cli_strcmp(z,"-ascii")==0 ){ - if( sCtx.cColSep==0 ) sCtx.cColSep = SEP_Unit[0]; - if( sCtx.cRowSep==0 ) sCtx.cRowSep = SEP_Record[0]; - xRead = ascii_read_one_field; - }else if( cli_strcmp(z,"-csv")==0 ){ - if( sCtx.cColSep==0 ) sCtx.cColSep = ','; - if( sCtx.cRowSep==0 ) sCtx.cRowSep = '\n'; - xRead = csv_read_one_field; - }else if( cli_strcmp(z,"-esc")==0 ){ - sCtx.cUQEscape = azArg[++i][0]; - }else if( cli_strcmp(z,"-qesc")==0 ){ - sCtx.cQEscape = azArg[++i][0]; - }else if( cli_strcmp(z,"-colsep")==0 ){ - if( i==nArg-1 ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - i++; - sCtx.cColSep = azArg[i][0]; - }else if( cli_strcmp(z,"-rowsep")==0 ){ - if( i==nArg-1 ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - i++; - sCtx.cRowSep = azArg[i][0]; - }else{ - dotCmdError(p, i, "unknown option", 0); - return 1; - } - } - if( zTable==0 ){ - dotCmdError(p, nArg, 0, "Missing %s argument\n", - zFile==0 ? "FILE" : "TABLE"); - return 1; - } - seenInterrupt = 0; - open_db(p, 0); - if( sCtx.cColSep==0 ){ - if( p->mode.spec.zColumnSep && p->mode.spec.zColumnSep[0]!=0 ){ - sCtx.cColSep = p->mode.spec.zColumnSep[0]; - }else{ - sCtx.cColSep = ','; - } - } - if( (sCtx.cColSep & 0x80)!=0 ){ - eputz("Error: .import column separator must be ASCII\n"); - return 1; - } - if( sCtx.cRowSep==0 ){ - if( p->mode.spec.zRowSep && p->mode.spec.zRowSep[0]!=0 ){ - sCtx.cRowSep = p->mode.spec.zRowSep[0]; - }else{ - sCtx.cRowSep = '\n'; - } - } - if( sCtx.cRowSep=='\r' && xRead!=ascii_read_one_field ){ - sCtx.cRowSep = '\n'; - } - if( (sCtx.cRowSep & 0x80)!=0 ){ - eputz("Error: .import row separator must be ASCII\n"); - return 1; - } - sCtx.zFile = zFile; - sCtx.nLine = 1; - if( sCtx.zFile[0]=='|' ){ -#ifdef SQLITE_OMIT_POPEN - eputz("Error: pipes are not supported in this OS\n"); - return 1; -#else - sCtx.in = sqlite3_popen(sCtx.zFile+1, "r"); - sCtx.zFile = "<pipe>"; - sCtx.xCloser = pclose; -#endif - }else if( sCtx.zFile[0]=='<' && sCtx.zFile[1]=='<' && sCtx.zFile[2]!=0 ){ - /* Input text comes from subsequent lines of script until the zFile - ** delimiter */ - int nEndMark = strlen30(zFile)-2; - char *zEndMark = &zFile[2]; - sqlite3_str *pContent = sqlite3_str_new(p->db); - int ckEnd = 1; - i64 iStart = p->lineno; - char zLine[2000]; - sCtx.zFile = p->zInFile; - sCtx.nLine = p->lineno+1; - iLineOffset = p->lineno; - while( sqlite3_fgets(zLine,sizeof(zLine),p->in) ){ - if( ckEnd && cli_strncmp(zLine,zEndMark,nEndMark)==0 ){ - ckEnd = 2; - if( strchr(zLine,'\n') ) p->lineno++; - break; - } - if( strchr(zLine,'\n') ){ - p->lineno++; - ckEnd = 1; - }else{ - ckEnd = 0; - } - sqlite3_str_appendall(pContent, zLine); - } - sCtx.zIn = sqlite3_str_finish(pContent); - if( sCtx.zIn==0 ){ - sCtx.zIn = sqlite3_mprintf(""); - } - if( ckEnd<2 ){ - i64 savedLn = p->lineno; - p->lineno = iStart; - dotCmdError(p, 0, 0,"Content terminator \"%s\" not found.",zEndMark); - p->lineno = savedLn; - import_cleanup(&sCtx); - return 1; - } - }else{ - sCtx.in = sqlite3_fopen(sCtx.zFile, "rb"); - sCtx.xCloser = fclose; - } - if( sCtx.in==0 && sCtx.zIn==0 ){ - dotCmdError(p, 0, 0, "cannot open \"%s\"", zFile); - import_cleanup(&sCtx); - return 1; - } - if( eVerbose>=1 ){ - char zSep[2]; - zSep[1] = 0; - zSep[0] = sCtx.cColSep; - cli_puts("Column separator ", p->out); - output_c_string(p->out, zSep); - cli_puts(", row separator ", p->out); - zSep[0] = sCtx.cRowSep; - output_c_string(p->out, zSep); - cli_puts("\n", p->out); - } - sCtx.z = sqlite3_malloc64(120); - if( sCtx.z==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - /* Below, resources must be freed before exit. */ - while( nSkip>0 ){ - nSkip--; - while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} - } - import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ - if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) - && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema" - " WHERE name=%Q AND type='view'", - zSchema ? zSchema : "main", zTable) - ){ - /* Table does not exist. Create it. */ - sqlite3 *dbCols = 0; - char *zRenames = 0; - char *zColDefs; - zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", - zSchema ? zSchema : "main", zTable); - while( xRead(&sCtx) ){ - zAutoColumn(sCtx.z, &dbCols, 0); - if( sCtx.cTerm!=sCtx.cColSep ) break; - } - zColDefs = zAutoColumn(0, &dbCols, &zRenames); - if( zRenames!=0 ){ - cli_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr, - "Columns renamed during .import %s due to duplicates:\n" - "%s\n", sCtx.zFile, zRenames); - sqlite3_free(zRenames); - } - assert(dbCols==0); - if( zColDefs==0 ){ - cli_printf(stderr,"%s: empty file\n", sCtx.zFile); - import_cleanup(&sCtx); - sqlite3_free(zCreate); - return 1; - } - zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); - if( zCreate==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - if( eVerbose>=1 ){ - cli_printf(p->out, "%s\n", zCreate); - } - rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); - if( rc ){ - cli_printf(stderr, - "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); - } - sqlite3_free(zCreate); - zCreate = 0; - if( rc ){ - import_cleanup(&sCtx); - return 1; - } - } - zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);", - zTable, zSchema); - if( zSql==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - zSql = 0; - if( rc ){ - if (pStmt) sqlite3_finalize(pStmt); - shellDatabaseError(p->db); - import_cleanup(&sCtx); - return 1; - } - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - nCol = sqlite3_column_int(pStmt, 0); - }else{ - nCol = 0; - } - sqlite3_finalize(pStmt); - pStmt = 0; - if( nCol==0 ) return 0; /* no columns, no error */ - - nByte = 64 /* space for "INSERT INTO", "VALUES(", ")\0" */ - + (zSchema ? strlen(zSchema)*2 + 2: 0) /* Quoted schema name */ - + strlen(zTable)*2 + 2 /* Quoted table name */ - + nCol*2; /* Space for ",?" for each column */ - zSql = sqlite3_malloc64( nByte ); - if( zSql==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - if( zSchema ){ - sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", - zSchema, zTable); - }else{ - sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); - } - j = strlen30(zSql); - for(i=1; i<nCol; i++){ - zSql[j++] = ','; - zSql[j++] = '?'; - } - zSql[j++] = ')'; - zSql[j] = 0; - assert( j<nByte ); - if( eVerbose>=2 ){ - cli_printf(p->out, "Insert using: %s\n", zSql); - } - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - zSql = 0; - if( rc ){ - shellDatabaseError(p->db); - if (pStmt) sqlite3_finalize(pStmt); - import_cleanup(&sCtx); - return 1; - } - needCommit = sqlite3_get_autocommit(p->db); - if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); - do{ - int startLine = sCtx.nLine; - for(i=0; i<nCol; i++){ - char *z = xRead(&sCtx); - /* - ** Did we reach end-of-file before finding any columns? - ** If so, stop instead of NULL filling the remaining columns. - */ - if( z==0 && i==0 ) break; - /* - ** Did we reach end-of-file OR end-of-line before finding any - ** columns in ASCII mode? If so, stop instead of NULL filling - ** the remaining columns. - */ - if( p->mode.eMode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; - /* - ** For CSV mode, per RFC 4180, accept EOF in lieu of final - ** record terminator but only for last field of multi-field row. - ** (If there are too few fields, it's not valid CSV anyway.) - */ - if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){ - z = ""; - } - sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); - if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){ - if( i==0 && (strcmp(z,"\n")==0 || strcmp(z,"\r\n")==0) ){ - /* Ignore trailing \n or \r\n when some other row separator */ - break; - } - cli_printf(stderr,"%s:%d: expected %d columns but found %d" - " - filling the rest with NULL\n", - sCtx.zFile, startLine, nCol, i+1); - i += 2; - while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; } - } - } - if( sCtx.cTerm==sCtx.cColSep ){ - do{ - xRead(&sCtx); - i++; - }while( sCtx.cTerm==sCtx.cColSep ); - cli_printf(stderr, - "%s:%d: expected %d columns but found %d - extras ignored\n", - sCtx.zFile, startLine, nCol, i); - } - if( i>=nCol ){ - sqlite3_step(pStmt); - rc = sqlite3_reset(pStmt); - if( rc!=SQLITE_OK ){ - cli_printf(stderr,"%s:%d: INSERT failed: %s\n", - sCtx.zFile, startLine, sqlite3_errmsg(p->db)); - sCtx.nErr++; - if( bail_on_error ) break; - }else{ - sCtx.nRow++; - } - } - }while( sCtx.cTerm!=EOF ); - - import_cleanup(&sCtx); - sqlite3_finalize(pStmt); - if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); - if( eVerbose>0 ){ - cli_printf(p->out, - "Added %d rows with %d errors using %d lines of input\n", - sCtx.nRow, sCtx.nErr, sCtx.nLine-1-iLineOffset); - } - return sCtx.nErr ? 1 : 0; -} - - -/* -** This function computes what to show the user about the configured -** titles (or column-names). Output is an integer between 0 and 3: -** -** 0: The titles do not matter. Never show anything. -** 1: Show "--titles off" -** 2: Show "--titles on" -** 3: Show "--title VALUE" where VALUE is an encoding method -** to use, one of: plain sql csv html tcl json -** -** Inputs are: -** -** spec.bTitles (bT) Whether or not to show the titles -** spec.eTitle (eT) The actual encoding to be used for titles -** ModeInfo.bHdr (bH) Default value for spec.bTitles -** ModeInfo.eHdr (eH) Default value for spec.eTitle -** bAll Whether the -v option is used -*/ -static int modeTitleDsply(ShellState *p, int bAll){ - int eMode = p->mode.eMode; - const ModeInfo *pI = &aModeInfo[eMode]; - int bT = p->mode.spec.bTitles; - int eT = p->mode.spec.eTitle; - int bH = pI->bHdr; - int eH = pI->eHdr; - - /* Variable "v" is the truth table that will determine the answer - ** - ** Actual encoding is different from default - ** vvvvvvvv */ - sqlite3_uint64 v = 0x0133013311220102; - /* ^^^^ ^^^^ - ** Upper 2-byte groups for when ON/OFF disagrees with - ** the default. */ - - if( bH==0 ) return 0; /* Header not appliable. Ex: off, count */ - - if( eT==0 ) eT = eH; /* Fill in missing spec.eTitle */ - if( bT==0 ) bT = bH; /* Fill in missing spec.bTitles */ - - if( eT!=eH ) v >>= 32; /* Encoding disagree in upper 4-bytes */ - if( bT!=bH ) v >>= 16; /* ON/OFF disagree in upper 2-byte pairs */ - if( bT<2 ) v >>= 8; /* ON in even bytes, OFF in odd bytes (1st byte 0) */ - if( !bAll ) v >>= 4; /* bAll values are in the lower half-byte */ - - return v & 3; /* Return the selected truth-table entry */ -} - -/* -** DOT-COMMAND: .mode -** -** USAGE: .mode [MODE] [OPTIONS] -** -** Change the output mode to MODE and/or apply OPTIONS to the output mode. -** Arguments are processed from left to right. If no arguments, show the -** current output mode and relevant options. -** -** Options: -** --align STRING Set the alignment of text in columnar modes -** String consists of characters 'L', 'C', 'R' -** meaning "left", "centered", and "right", with -** one letter per column starting from the left. -** Unspecified alignment defaults to 'L'. -** --blob-quote ARG ARG can be "auto", "text", "sql", "hex", "tcl", -** "json", or "size". Default is "auto". -** --border on|off Show outer border on "box" and "table" modes. -** --charlimit N Set the maximum number of output characters to -** show for any single SQL value to N. Longer values -** truncated. Zero means "no limit". -** --colsep STRING Use STRING as the column separator -** --escape ESC Enable/disable escaping of control characters -** found in the output. ESC can be "off", "ascii", -** or "symbol". -** --linelimit N Set the maximum number of output lines to show for -** any single SQL value to N. Longer values are -** truncated. Zero means "no limit". Only works -** in "line" mode and in columnar modes. -** --limits L,C,T Shorthand for "--linelimit L --charlimit C -** --titlelimit T". The ",T" can be omitted in which -** case the --titlelimit is unchanged. The argument -** can also be "off" to mean "0,0,0" or "on" to -** mean "5,300,20". -** --list List available modes -** --null STRING Render SQL NULL values as the given string -** --once Setting changes to the right are reverted after -** the next SQL command. -** --quote ARG Enable/disable quoting of text. ARG can be -** "off", "on", "sql", "relaxed", "csv", "html", -** "tcl", or "json". "off" means show the text as-is. -** "on" is an alias for "sql". -** --reset Changes all mode settings back to their default. -** --rowsep STRING Use STRING as the row separator -** --sw|--screenwidth N Declare the screen width of the output device -** to be N characters. An attempt may be made to -** wrap output text to fit within this limit. Zero -** means "no limit". Or N can be "auto" to set the -** width automatically. -** --tablename NAME Set the name of the table for "insert" mode. -** --tag NAME Save mode to the left as NAME. -** --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON. -** --title ARG Whether or not to show column headers, and if so -** how to encode them. ARG can be "off", "on", -** "sql", "csv", "html", "tcl", or "json". -** --titlelimit N Limit the length of column titles to N characters. -** -v|--verbose Verbose output -** --widths LIST Set the columns widths for columnar modes. The -** argument is a list of integers, one for each -** column. A "0" width means use a dynamic width -** based on the actual width of data. If there are -** fewer entries in LIST than columns, "0" is used -** for the unspecified widths. -** --wordwrap BOOLEAN Enable/disable word wrapping -** --wrap N Wrap columns wider than N characters -** --ww Shorthand for "--wordwrap on" -*/ -static int dotCmdMode(ShellState *p){ - int nArg = p->dot.nArg; /* Number of arguments */ - char **azArg = p->dot.azArg;/* Argument list */ - int eMode = -1; /* New mode value, or -1 for none */ - int iMode = -1; /* Index of the argument that is the mode name */ - int i; /* Loop counter */ - int k; /* Misc index variable */ - int chng = 0; /* True if anything has changed */ - int bAll = 0; /* Show all details of the mode */ - - for(i=1; i<nArg; i++){ - const char *z = azArg[i]; - if( z[0]=='-' && z[1]=='-' ) z++; - if( z[0]!='-' - && iMode<0 - && (eMode = modeFind(p, azArg[i]))>=0 - && eMode!=MODE_Www - ){ - iMode = i; - modeChange(p, eMode); - /* (Legacy) If the mode is MODE_Insert and the next argument - ** is not an option, then the next argument must be the table - ** name. - */ - if( i+1<nArg && azArg[i+1][0]!='-' ){ - i++; - modeSetStr(&p->mode.spec.zTableName, azArg[i]); - } - chng = 1; - }else if( optionMatch(z,"align") ){ - char *zAlign; - int nAlign; - int nErr = 0; - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - i++; - zAlign = azArg[i]; - nAlign = 0x3fff & strlen(zAlign); - free(p->mode.spec.aAlign); - p->mode.spec.aAlign = malloc(nAlign); - shell_check_oom(p->mode.spec.aAlign); - for(k=0; k<nAlign; k++){ - unsigned char c = 0; - switch( zAlign[k] ){ - case 'l': case 'L': c = QRF_ALIGN_Left; break; - case 'c': case 'C': c = QRF_ALIGN_Center; break; - case 'r': case 'R': c = QRF_ALIGN_Right; break; - default: nErr++; break; - } - p->mode.spec.aAlign[k] = c; - } - p->mode.spec.nAlign = nAlign; - chng = 1; - if( nErr ){ - dotCmdError(p, i, "bad alignment string", - "Should contain only characters L, C, and R."); - return 1; - } - }else if( pickStr(z,0,"-blob","-blob-quote","")>=0 ){ - if( (++i)>=nArg ){ - dotCmdError(p, i-1, "missing argument", 0); - return 1; - } - k = pickStr(azArg[i], 0, - "auto", "text", "sql", "hex", "tcl", "json", "size", ""); - /* 0 1 2 3 4 5 6 - ** Must match QRF_BLOB_xxxx values. See also tag-20251124a */ - if( k>=0 ){ - p->mode.spec.eBlob = k & 0xff; - } - chng = 1; - }else if( optionMatch(z,"border") ){ - if( (++i)>=nArg ){ - dotCmdError(p, i-1, "missing argument", 0); - return 1; - } - k = pickStr(azArg[i], 0, "auto", "off", "on", ""); - if( k>=0 ){ - p->mode.spec.bBorder = k & 0x3; - } - chng = 1; - }else if( 0<=(k=pickStr(z,0,"-charlimit","-linelimit","-titlelimit","")) ){ - int w; /* 0 1 */ - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - w = integerValue(azArg[++i]); - switch( k ){ - case 0: p->mode.spec.nCharLimit = w; break; - case 1: p->mode.spec.nLineLimit = w; break; - default: p->mode.spec.nTitleLimit = w; break; - } - chng = 1; - }else if( 0<=(k=pickStr(z,0,"-tablename","-rowsep","-colsep","-null","")) ){ - /* 0 1 2 3 */ - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - i++; - switch( k ){ - case 0: modeSetStr(&p->mode.spec.zTableName, azArg[i]); break; - case 1: modeSetStr(&p->mode.spec.zRowSep, azArg[i]); break; - case 2: modeSetStr(&p->mode.spec.zColumnSep, azArg[i]); break; - case 3: modeSetStr(&p->mode.spec.zNull, azArg[i]); break; - } - chng = 1; - }else if( optionMatch(z,"escape") ){ - /* See similar code at tag-20250224-1 */ - char *zErr = 0; - if( (++i)>=nArg ){ - dotCmdError(p, i-1, "missing argument", 0); - return 1; - } /* 0 1 2 <-- One less than QRF_ESC_ */ - k = pickStr(azArg[i],&zErr,"off","ascii","symbol",""); - if( k<0 ){ - dotCmdError(p, i, "unknown escape type", "%s", zErr); - sqlite3_free(zErr); - return 1; - } - p->mode.spec.eEsc = k+1; - chng = 1; - }else if( optionMatch(z,"limits") ){ - if( (++i)>=nArg ){ - dotCmdError(p, i-1, "missing argument", 0); - return 1; - } - k = pickStr(azArg[i],0,"on","off",""); - if( k==0 ){ - p->mode.spec.nLineLimit = DFLT_LINE_LIMIT; - p->mode.spec.nCharLimit = DFLT_CHAR_LIMIT; - p->mode.spec.nTitleLimit = DFLT_TITLE_LIMIT; - }else if( k==1 ){ - p->mode.spec.nLineLimit = 0; - p->mode.spec.nCharLimit = 0; - p->mode.spec.nTitleLimit = 0; - }else{ - int L, C, T = 0; - int nNum = sscanf(azArg[i], "%d,%d,%d", &L, &C, &T); - if( nNum<2 || L<0 || C<0 || T<0){ - dotCmdError(p, i, "bad argument", "Should be \"L,C,T\" where L, C" - " and T are unsigned integers"); - return 1; - } - p->mode.spec.nLineLimit = L; - p->mode.spec.nCharLimit = C; - if( nNum==3 ) p->mode.spec.nTitleLimit = T; - } - chng = 1; - }else if( optionMatch(z,"list") ){ - int ii; - cli_puts("available modes:", p->out); - for(ii=0; ii<ArraySize(aModeInfo); ii++){ - if( ii==MODE_Www ) continue; - cli_printf(p->out, " %s", aModeInfo[ii].zName); - } - for(ii=0; ii<p->nSavedModes; ii++){ - cli_printf(p->out, " %s", p->aSavedModes[ii].zTag); - } - cli_puts(" batch tty\n", p->out); - chng = 1; /* Not really a change, but we still want to suppress the - ** "current mode" output */ - }else if( optionMatch(z,"once") ){ - p->nPopMode = 0; - modePush(p); - p->nPopMode = 1; - }else if( optionMatch(z,"noquote") ){ - /* (undocumented legacy) --noquote always turns quoting off */ - p->mode.spec.eText = QRF_TEXT_Plain; - p->mode.spec.eBlob = QRF_BLOB_Auto; - chng = 1; - }else if( optionMatch(z,"quote") ){ - if( i+1<nArg - && azArg[i+1][0]!='-' - && (iMode>0 || strcmp(azArg[i+1],"off")==0 || modeFind(p, azArg[i+1])<0) - ){ - /* --quote is followed by an argument other that is not an option - ** or a mode name. See it must be a boolean or a keyword to describe - ** how to set quoting. */ - i++; - if( (k = pickStr(azArg[i],0,"no","yes","0","1",""))>=0 ){ - k &= 1; /* 0 for "off". 1 for "on". */ - }else{ - char *zErr = 0; - k = pickStr(azArg[i],&zErr, - "off","on","sql","csv","html","tcl","json","relaxed",""); - /* 0 1 2 3 4 5 6 7 */ - if( k<0 ){ - dotCmdError(p, i, "unknown", "%z", zErr); - return 1; - } - } - }else{ - /* (Legacy) no following boolean argument. Turn quoting on */ - k = 1; - } - switch( k ){ - case 1: /* on */ - modeSetStr(&p->mode.spec.zNull, "NULL"); - /* Fall through */ - case 2: /* sql */ - p->mode.spec.eText = QRF_TEXT_Sql; - break; - case 3: /* csv */ - p->mode.spec.eText = QRF_TEXT_Csv; - break; - case 4: /* html */ - p->mode.spec.eText = QRF_TEXT_Html; - break; - case 5: /* tcl */ - p->mode.spec.eText = QRF_TEXT_Tcl; - break; - case 6: /* json */ - p->mode.spec.eText = QRF_TEXT_Json; - break; - case 7: /* relaxed */ - p->mode.spec.eText = QRF_TEXT_Relaxed; - break; - default: /* off */ - p->mode.spec.eText = QRF_TEXT_Plain; - break; - } - chng = 1; - }else if( optionMatch(z,"reset") ){ - int saved_eMode = p->mode.eMode; - modeFree(&p->mode); - modeChange(p, saved_eMode); - }else if( optionMatch(z,"screenwidth") || optionMatch(z,"sw") ){ - if( (++i)>=nArg ){ - dotCmdError(p, i-1, "missing argument", 0); - return 1; - } - k = pickStr(azArg[i],0,"off","auto",""); - if( k==0 ){ - p->mode.bAutoScreenWidth = 0; - p->mode.spec.nScreenWidth = 0; - }else if( k==1 ){ - p->mode.bAutoScreenWidth = 1; - }else{ - i64 w = integerValue(azArg[i]); - p->mode.bAutoScreenWidth = 0; - if( w<0 ) w = 0; - if( w>QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH; - p->mode.spec.nScreenWidth = w; - } - chng = 1; - }else if( optionMatch(z,"tag") ){ - size_t nByte; - int n; - const char *zTag; - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - zTag = azArg[++i]; - if( modeFind(p, zTag)>=0 ){ - dotCmdError(p, i, "mode already exists", 0); - return 1; - } - if( p->nSavedModes > MODE_N_USER ){ - dotCmdError(p, i-1, "cannot add more modes", 0); - return 1; - } - n = p->nSavedModes++; - nByte = sizeof(p->aSavedModes[0]); - nByte *= n+1; - p->aSavedModes = realloc( p->aSavedModes, nByte ); - shell_check_oom(p->aSavedModes); - p->aSavedModes[n].zTag = strdup(zTag); - shell_check_oom(p->aSavedModes[n].zTag); - modeDup(&p->aSavedModes[n].mode, &p->mode); - chng = 1; - }else if( optionMatch(z,"textjsonb") ){ - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - p->mode.spec.bTextJsonb = booleanValue(azArg[++i]) ? QRF_Yes : QRF_No; - chng = 1; - }else if( optionMatch(z,"titles") || optionMatch(z,"title") ){ - char *zErr = 0; - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - k = pickStr(azArg[++i],&zErr, - "off","on","plain","sql","csv","html","tcl","json",""); - /* 0 1 2 3 4 5 6 7 */ - if( k<0 ){ - dotCmdError(p, i, "bad --titles value","%z", zErr); - return 1; - } - p->mode.spec.bTitles = k>=1 ? QRF_Yes : QRF_No; - p->mode.mFlags &= ~MFLG_HDR; - p->mode.spec.eTitle = k>1 ? k-1 : aModeInfo[p->mode.eMode].eHdr; - chng = 1; - }else if( optionMatch(z,"widths") || optionMatch(z,"width") ){ - int nWidth = 0; - short int *aWidth; - const char *zW; - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - zW = azArg[++i]; - /* Every width value takes at least 2 bytes in the input string to - ** specify, so strlen(zW) bytes should be plenty of space to hold the - ** result. */ - aWidth = malloc( strlen(zW) ); - while( IsSpace(zW[0]) ) zW++; - while( zW[0] ){ - int w = 0; - int nDigit = 0; - k = zW[0]=='-' && IsDigit(zW[1]); - while( IsDigit(zW[k]) ){ - w = w*10 + zW[k] - '0'; - if( w>QRF_MAX_WIDTH ){ - dotCmdError(p,i+1,"width too big", - "Maximum column width is %d", QRF_MAX_WIDTH); - free(aWidth); - return 1; - } - nDigit++; - k++; - } - if( nDigit==0 ){ - dotCmdError(p,i+1,"syntax error", - "should be a comma-separated list if integers"); - free(aWidth); - return 1; - } - if( zW[0]=='-' ) w = -w; - aWidth[nWidth++] = w; - zW += k; - if( zW[0]==',' ) zW++; - while( IsSpace(zW[0]) ) zW++; - } - free(p->mode.spec.aWidth); - p->mode.spec.aWidth = aWidth; - p->mode.spec.nWidth = nWidth; - chng = 1; - }else if( optionMatch(z,"wrap") ){ - int w; - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - w = integerValue(azArg[++i]); - if( w<(-QRF_MAX_WIDTH) ) w = -QRF_MAX_WIDTH; - if( w>QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH; - p->mode.spec.nWrap = w; - chng = 1; - }else if( optionMatch(z,"ww") ){ - p->mode.spec.bWordWrap = QRF_Yes; - chng = 1; - }else if( optionMatch(z,"wordwrap") ){ - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - p->mode.spec.bWordWrap = (u8)booleanValue(azArg[++i]) ? QRF_Yes : QRF_No; - chng = 1; - }else if( optionMatch(z,"v") || optionMatch(z,"verbose") ){ - bAll = 1; - }else if( z[0]=='-' ){ - dotCmdError(p, i, "bad option", "Use \".help .mode\" for more info"); - return 1; - }else{ - dotCmdError(p, i, iMode>0?"bad argument":"unknown mode", - "Use \".help .mode\" for more info"); - return 1; - } - } - if( !chng || bAll ){ - const ModeInfo *pI = aModeInfo + p->mode.eMode; - sqlite3_str *pDesc = sqlite3_str_new(p->db); - char *zDesc; - const char *zSetting; - - if( p->nPopMode ) sqlite3_str_appendall(pDesc, "--once "); - sqlite3_str_appendall(pDesc,pI->zName); - if( bAll || (p->mode.spec.nAlign && pI->eCx==2) ){ - int ii; - sqlite3_str_appendall(pDesc, " --align \""); - for(ii=0; ii<p->mode.spec.nAlign; ii++){ - unsigned char a = p->mode.spec.aAlign[ii]; - sqlite3_str_appendchar(pDesc, 1, "LLCR"[a&3]); - } - sqlite3_str_append(pDesc, "\"", 1); - } - if( bAll - || (p->mode.spec.bBorder==QRF_No) != ((pI->mFlg&1)!=0) - ){ - sqlite3_str_appendf(pDesc," --border %s", - p->mode.spec.bBorder==QRF_No ? "off" : "on"); - } - if( bAll || p->mode.spec.eBlob!=QRF_BLOB_Auto ){ - const char *azBQuote[] = - { "auto", "text", "sql", "hex", "tcl", "json", "size" }; - /* 0 1 2 3 4 5 6 - ** Must match QRF_BLOB_xxxx values. See all instances of tag-20251124a */ - u8 e = p->mode.spec.eBlob; - sqlite3_str_appendf(pDesc, " --blob-quote %s", azBQuote[e]); - } - zSetting = aModeStr[pI->eCSep]; - if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zColumnSep)!=0) ){ - sqlite3_str_appendf(pDesc, " --colsep "); - append_c_string(pDesc, p->mode.spec.zColumnSep); - } - if( bAll || p->mode.spec.eEsc!=QRF_Auto ){ - sqlite3_str_appendf(pDesc, " --escape %s",qrfEscNames[p->mode.spec.eEsc]); - } - if( bAll - || (p->mode.spec.nLineLimit>0 && pI->eCx>0) - || p->mode.spec.nCharLimit>0 - || (p->mode.spec.nTitleLimit>0 && pI->eCx>0) - ){ - if( p->mode.spec.nLineLimit==0 - && p->mode.spec.nCharLimit==0 - && p->mode.spec.nTitleLimit==0 - ){ - sqlite3_str_appendf(pDesc, " --limits off"); - }else if( p->mode.spec.nLineLimit==DFLT_LINE_LIMIT - && p->mode.spec.nCharLimit==DFLT_CHAR_LIMIT - && p->mode.spec.nTitleLimit==DFLT_TITLE_LIMIT - ){ - sqlite3_str_appendf(pDesc, " --limits on"); - }else{ - sqlite3_str_appendf(pDesc, " --limits %d,%d,%d", - p->mode.spec.nLineLimit, p->mode.spec.nCharLimit, - p->mode.spec.nTitleLimit); - } - } - zSetting = aModeStr[pI->eNull]; - if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zNull)!=0) ){ - sqlite3_str_appendf(pDesc, " --null "); - append_c_string(pDesc, p->mode.spec.zNull); - } - if( bAll - || (pI->eText!=p->mode.spec.eText && (pI->eText>1 || p->mode.spec.eText>1)) - ){ - sqlite3_str_appendf(pDesc," --quote %s",qrfQuoteNames[p->mode.spec.eText]); - } - zSetting = aModeStr[pI->eRSep]; - if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zRowSep)!=0) ){ - sqlite3_str_appendf(pDesc, " --rowsep "); - append_c_string(pDesc, p->mode.spec.zRowSep); - } - if( bAll - || (pI->eCx && (p->mode.spec.nScreenWidth>0 || p->mode.bAutoScreenWidth)) - ){ - if( p->mode.bAutoScreenWidth ){ - sqlite3_str_appendall(pDesc, " --sw auto"); - }else{ - sqlite3_str_appendf(pDesc," --sw %d", - p->mode.spec.nScreenWidth); - } - } - if( bAll || p->mode.eMode==MODE_Insert ){ - sqlite3_str_appendf(pDesc," --tablename "); - append_c_string(pDesc, p->mode.spec.zTableName); - } - if( bAll || p->mode.spec.bTextJsonb ){ - sqlite3_str_appendf(pDesc," --textjsonb %s", - p->mode.spec.bTextJsonb==QRF_Yes ? "on" : "off"); - } - k = modeTitleDsply(p, bAll); - if( k==1 ){ - sqlite3_str_appendall(pDesc, " --titles off"); - }else if( k==2 ){ - sqlite3_str_appendall(pDesc, " --titles on"); - }else if( k==3 ){ - static const char *azTitle[] = - { "plain", "sql", "csv", "html", "tcl", "json"}; - sqlite3_str_appendf(pDesc, " --titles %s", - azTitle[p->mode.spec.eTitle-1]); - } - if( p->mode.spec.nWidth>0 && (bAll || pI->eCx==2) ){ - int ii; - const char *zSep = " --widths "; - for(ii=0; ii<p->mode.spec.nWidth; ii++){ - sqlite3_str_appendf(pDesc, "%s%d", zSep, (int)p->mode.spec.aWidth[ii]); - zSep = ","; - } - }else if( bAll ){ - sqlite3_str_appendall(pDesc, " --widths \"\""); - } - if( bAll || (pI->eCx>0 && p->mode.spec.bWordWrap) ){ - if( bAll ){ - sqlite3_str_appendf(pDesc, " --wordwrap %s", - p->mode.spec.bWordWrap==QRF_Yes ? "on" : "off"); - } - if( p->mode.spec.nWrap ){ - sqlite3_str_appendf(pDesc, " --wrap %d", p->mode.spec.nWrap); - } - if( !bAll ) sqlite3_str_append(pDesc, " --ww", 5); - } - zDesc = sqlite3_str_finish(pDesc); - cli_printf(p->out, ".mode %s\n", zDesc); - fflush(p->out); - sqlite3_free(zDesc); - } - return 0; -} - -/* -** DOT-COMMAND: .output -** USAGE: .output [OPTIONS] [FILE] -** -** Begin redirecting output to FILE. Or if FILE is omitted, revert -** to sending output to the console. If FILE begins with "|" then -** the remainder of file is taken as a pipe and output is directed -** into that pipe. If FILE is "memory" then output is captured in an -** internal memory buffer. If FILE is "off" then output is redirected -** into /dev/null or the equivalent. -** -** Options: -** --bom Prepend a byte-order mark to the output -** -e Accumulate output in a temporary text file then -** launch a text editor when the redirection ends. -** --error-prefix X Use X as the left-margin prefix for error messages. -** Set to an empty string to restore the default. -** --keep Keep redirecting output to its current destination. -** Use this option in combination with --show or -** with --error-prefix when you do not want to stop -** a current redirection. -** --plain Use plain text rather than HTML tables with -w -** --show Show output text captured by .testcase or by -** redirecting to "memory". -** -w Show the output in a web browser. Output is -** written into a temporary HTML file until the -** redirect ends, then the web browser is launched. -** Query results are shown as HTML tables, unless -** the --plain is used too. -** -x Show the output in a spreadsheet. Output is -** written to a temp file as CSV then the spreadsheet -** is launched when -** -** DOT-COMMAND: .once -** USAGE: .once [OPTIONS] FILE ... -** -** Write the output for the next line of SQL or the next dot-command into -** FILE. If FILE begins with "|" then it is a program into which output -** is written. The FILE argument should be omitted if one of the -e, -w, -** or -x options is used. -** -** Options: -** -e Capture output into a temporary file then bring up -** a text editor on that temporary file. -** --plain Use plain text rather than HTML tables with -w -** -w Capture output into an HTML file then bring up that -** file in a web browser -** -x Show the output in a spreadsheet. Output is -** written to a temp file as CSV then the spreadsheet -** is launched when -** -** DOT-COMMAND: .excel -** Shorthand for ".once -x" -** -** DOT-COMMAND: .www [--plain] -** Shorthand for ".once -w" or ".once --plain -w" -*/ -static int dotCmdOutput(ShellState *p){ - int nArg = p->dot.nArg; /* Number of arguments */ - char **azArg = p->dot.azArg; /* Text of the arguments */ - char *zFile = 0; /* The FILE argument */ - int i; /* Loop counter */ - int eMode = 0; /* 0: .outout/.once, 'x'=.excel, 'w'=.www */ - int bOnce = 0; /* 0: .output, 1: .once, 2: .excel/.www */ - int bPlain = 0; /* --plain option */ - int bKeep = 0; /* Keep redirecting */ - static const char *zBomUtf8 = "\357\273\277"; - const char *zBom = 0; - char c = azArg[0][0]; - int n = strlen30(azArg[0]); - - failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); - if( c=='e' ){ - eMode = 'x'; - bOnce = 2; - }else if( c=='w' ){ - eMode = 'w'; - bOnce = 2; - }else if( n>=2 && cli_strncmp(azArg[0],"once",n)==0 ){ - bOnce = 1; - } - for(i=1; i<nArg; i++){ - char *z = azArg[i]; - if( z[0]=='-' ){ - if( z[1]=='-' ) z++; - if( cli_strcmp(z,"-bom")==0 ){ - zBom = zBomUtf8; - }else if( cli_strcmp(z,"-plain")==0 ){ - bPlain = 1; - }else if( c=='o' && z[0]=='1' && z[1]!=0 && z[2]==0 - && (z[1]=='x' || z[1]=='e' || z[1]=='w') ){ - if( bKeep || eMode ){ - dotCmdError(p, i, "incompatible with prior options",0); - goto dotCmdOutput_error; - } - eMode = z[1]; - }else if( cli_strcmp(z,"-show")==0 ){ - if( cli_output_capture ){ - sqlite3_fprintf(stdout, "%s", sqlite3_str_value(cli_output_capture)); - } - }else if( cli_strcmp(z,"-keep")==0 ){ - bKeep = 1; - }else if( optionMatch(z,"error-prefix") ){ - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - free(p->zErrPrefix); - i++; - p->zErrPrefix = azArg[i][0]==0 ? 0 : strdup(azArg[i]); - }else{ - dotCmdError(p, i, "unknown option", 0); - sqlite3_free(zFile); - return 1; - } - }else if( zFile==0 && eMode==0 ){ - if( bKeep ){ - dotCmdError(p, i, "incompatible with prior options",0); - goto dotCmdOutput_error; - } - if( cli_strcmp(z, "memory")==0 && bOnce ){ - dotCmdError(p, 0, "cannot redirect to \"memory\"", 0); - goto dotCmdOutput_error; - } - if( cli_strcmp(z, "off")==0 ){ -#ifdef _WIN32 - zFile = sqlite3_mprintf("nul"); -#else - zFile = sqlite3_mprintf("/dev/null"); -#endif - }else{ - zFile = sqlite3_mprintf("%s", z); - } - if( zFile && zFile[0]=='|' ){ - while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]); - break; - } - }else{ - dotCmdError(p, i, "surplus argument", 0); - sqlite3_free(zFile); - return 1; - } - } - if( zFile==0 && !bKeep ){ - zFile = sqlite3_mprintf("stdout"); - shell_check_oom(zFile); - } - if( bOnce ){ - p->nPopOutput = 2; - }else{ - p->nPopOutput = 0; - } - if( !bKeep ) output_reset(p); -#ifndef SQLITE_NOHAVE_SYSTEM - if( eMode=='e' || eMode=='x' || eMode=='w' ){ - p->doXdgOpen = 1; - modePush(p); - if( eMode=='x' ){ - /* spreadsheet mode. Output as CSV. */ - newTempFile(p, "csv"); - p->mode.mFlags &= ~MFLG_ECHO; - p->mode.eMode = MODE_Csv; - modeSetStr(&p->mode.spec.zColumnSep, SEP_Comma); - modeSetStr(&p->mode.spec.zRowSep, SEP_CrLf); -#ifdef _WIN32 - zBom = zBomUtf8; /* Always include the BOM on Windows, as Excel does - ** not work without it. */ -#endif - }else if( eMode=='w' ){ - /* web-browser mode. */ - newTempFile(p, "html"); - if( !bPlain ) p->mode.eMode = MODE_Www; - }else{ - /* text editor mode */ - newTempFile(p, "txt"); - } - sqlite3_free(zFile); - zFile = sqlite3_mprintf("%s", p->zTempFile); - } -#endif /* SQLITE_NOHAVE_SYSTEM */ - if( !bKeep ) shell_check_oom(zFile); - if( bKeep ){ - /* no-op */ - }else if( cli_strcmp(zFile,"memory")==0 ){ - if( cli_output_capture ){ - sqlite3_str_free(cli_output_capture); - } - cli_output_capture = sqlite3_str_new(0); - }else if( zFile[0]=='|' ){ -#ifdef SQLITE_OMIT_POPEN - eputz("Error: pipes are not supported in this OS\n"); - output_redir(p, stdout); - goto dotCmdOutput_error; -#else - FILE *pfPipe = sqlite3_popen(zFile + 1, "w"); - if( pfPipe==0 ){ - assert( stderr!=NULL ); - cli_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); - goto dotCmdOutput_error; - }else{ - output_redir(p, pfPipe); - if( zBom ) cli_puts(zBom, pfPipe); - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); - } -#endif - }else{ - FILE *pfFile = output_file_open(p, zFile); - if( pfFile==0 ){ - if( cli_strcmp(zFile,"off")!=0 ){ - assert( stderr!=NULL ); - cli_printf(stderr,"Error: cannot write to \"%s\"\n", zFile); - } - goto dotCmdOutput_error; - } else { - output_redir(p, pfFile); - if( zBom ) cli_puts(zBom, pfFile); - if( bPlain && eMode=='w' ){ - cli_puts( - "<!DOCTYPE html>\n<BODY>\n<PLAINTEXT>\n", - pfFile - ); - } - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); - } - } - sqlite3_free(zFile); - return 0; - -dotCmdOutput_error: - sqlite3_free(zFile); - return 1; -} - -/* -** DOT-COMMAND: .check -** USAGE: .check [OPTIONS] PATTERN -** -** Verify results of commands since the most recent .testcase command. -** Restore output to the console, unless --keep is used. -** -** If PATTERN starts with "<<ENDMARK" then the actual pattern is taken from -** subsequent lines of text up to the first line that begins with ENDMARK. -** All pattern lines and the ENDMARK are discarded. -** -** Options: -** --exact Do an exact comparison including leading and -** trailing whitespace. -** --glob Treat PATTERN as a GLOB -** --keep Do not reset the testcase. More .check commands -** will follow. -** --notglob Output should not match PATTERN -** --show Write testcase output to the screen, for debugging. -*/ -static int dotCmdCheck(ShellState *p){ - int nArg = p->dot.nArg; /* Number of arguments */ - char **azArg = p->dot.azArg; /* Text of the arguments */ - int i; /* Loop counter */ - int k; /* Result of pickStr() */ - char *zTest; /* Textcase result */ - int bKeep = 0; /* --keep option */ - char *zCheck = 0; /* PATTERN argument */ - char *zPattern = 0; /* Actual test pattern */ - int eCheck = 0; /* 1: --glob, 2: --notglob, 3: --exact */ - int isOk; /* True if results are OK */ - sqlite3_int64 iStart = p->lineno; /* Line number of .check statement */ - - if( p->zTestcase[0]==0 ){ - dotCmdError(p, 0, "no .testcase is active", 0); - return 1; - } - for(i=1; i<nArg; i++){ - char *z = azArg[i]; - if( z[0]=='-' && z[1]=='-' && z[2]!=0 ) z++; - if( cli_strcmp(z,"-keep")==0 ){ - bKeep = 1; - }else if( cli_strcmp(z,"-show")==0 ){ - if( cli_output_capture ){ - sqlite3_fprintf(stdout, "%s", sqlite3_str_value(cli_output_capture)); - } - bKeep = 1; - }else if( z[0]=='-' - && (k = pickStr(&z[1],0,"glob","notglob","exact",""))>=0 - ){ - if( eCheck && eCheck!=k+1 ){ - dotCmdError(p, i, "incompatible with prior options",0); - return 1; - } - eCheck = k+1; - }else if( zCheck ){ - dotCmdError(p, i, "unknown option", 0); - return 1; - }else{ - zCheck = azArg[i]; - } - } - if( zCheck==0 ){ - dotCmdError(p, 0, "no PATTERN specified", 0); - return 1; - } - if( cli_output_capture && sqlite3_str_length(cli_output_capture) ){ - zTest = sqlite3_str_value(cli_output_capture); - shell_check_oom(zTest); - }else{ - zTest = ""; - } - p->nTestRun++; - if( zCheck[0]=='<' && zCheck[1]=='<' && zCheck[2]!=0 ){ - int nCheck = strlen30(zCheck); - sqlite3_str *pPattern = sqlite3_str_new(p->db); - char zLine[2000]; - while( sqlite3_fgets(zLine,sizeof(zLine),p->in) ){ - if( strchr(zLine,'\n') ) p->lineno++; - if( cli_strncmp(&zCheck[2],zLine,nCheck-2)==0 ) break; - sqlite3_str_appendall(pPattern, zLine); - } - zPattern = sqlite3_str_finish(pPattern); - if( zPattern==0 ){ - zPattern = sqlite3_mprintf(""); - } + sqlite3_finalize(pStmt); + return 0; + }else if( *pDb==0 ){ + return 0; }else{ - zPattern = zCheck; - } - shell_check_oom(zPattern); - switch( eCheck ){ - case 1: { - char *zGlob = sqlite3_mprintf("*%s*", zPattern); - isOk = testcase_glob(zGlob, zTest)!=0; - sqlite3_free(zGlob); - break; - } - case 2: { - char *zGlob = sqlite3_mprintf("*%s*", zPattern); - isOk = testcase_glob(zGlob, zTest)==0; - sqlite3_free(zGlob); - break; + /* Formulate the columns spec, close the DB, zero *pDb. */ + char *zColsSpec = 0; + int hasDupes = db_int(*pDb, "%s", zHasDupes); + int nDigits = (hasDupes)? db_int(*pDb, "%s", zColDigits) : 0; + if( hasDupes ){ +#ifdef SHELL_COLUMN_RENAME_CLEAN + rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); + rc_err_oom_die(rc); +#endif + rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0); + rc_err_oom_die(rc); + rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0); + rc_err_oom_die(rc); + sqlite3_bind_int(pStmt, 1, nDigits); + rc = sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM); } - case 3: { - isOk = cli_strcmp(zTest,zPattern)==0; - break; + assert(db_int(*pDb, "%s", zHasDupes)==0); /* Consider: remove this */ + rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); + rc_err_oom_die(rc); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + }else{ + zColsSpec = 0; } - default: { - /* Skip leading and trailing \n and \r on both pattern and test output */ - const char *z1 = zPattern; - const char *z2 = zTest; - size_t n1, n2; - while( z1[0]=='\n' || z1[0]=='\r' ) z1++; - n1 = strlen(z1); - while( n1>0 && (z1[n1-1]=='\n' || z1[n1-1]=='\r') ) n1--; - while( z2[0]=='\n' || z2[0]=='\r' ) z2++; - n2 = strlen(z2); - while( n2>0 && (z2[n2-1]=='\n' || z2[n2-1]=='\r') ) n2--; - isOk = n1==n2 && memcmp(z1,z2,n1)==0; - break; + if( pzRenamed!=0 ){ + if( !hasDupes ) *pzRenamed = 0; + else{ + sqlite3_finalize(pStmt); + if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0) + && SQLITE_ROW==sqlite3_step(pStmt) ){ + *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + }else + *pzRenamed = 0; + } } + sqlite3_finalize(pStmt); + sqlite3_close(*pDb); + *pDb = 0; + return zColsSpec; } - if( !isOk ){ - sqlite3_fprintf(stderr, - "%s:%lld: .check failed for testcase %s\n", - p->zInFile, iStart, p->zTestcase); - p->nTestErr++; - sqlite3_fprintf(stderr, "Expected: [%s]\n", zPattern); - sqlite3_fprintf(stderr, "Got: [%s]\n", zTest); - } - if( zPattern!=zCheck ){ - sqlite3_free(zPattern); - } - if( !bKeep ){ - output_reset(p); - p->zTestcase[0] = 0; - } - return 0; } /* -** DOT-COMMAND: .testcase -** USAGE: .testcase [OPTIONS] NAME -** -** Start a new test case identified by NAME. All output -** through the next ".check" command is captured for comparison. See the -** ".check" commandn for additional informatioon. +** Check if the sqlite_schema table contains one or more virtual tables. If +** parameter zLike is not NULL, then it is an SQL expression that the +** sqlite_schema row must also match. If one or more such rows are found, +** print the following warning to the output: ** -** Options: -** --error-prefix TEXT Change error message prefix text to TEXT +** WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */ -static int dotCmdTestcase(ShellState *p){ - int nArg = p->dot.nArg; /* Number of arguments */ - char **azArg = p->dot.azArg; /* Text of the arguments */ - int i; /* Loop counter */ - const char *zName = 0; /* Testcase name */ - - for(i=1; i<nArg; i++){ - char *z = azArg[i]; - if( z[0]=='-' && z[1]=='-' && z[2]!=0 ) z++; - if( optionMatch(z,"error-prefix") ){ - if( i+1>=nArg ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - free(p->zErrPrefix); - i++; - p->zErrPrefix = azArg[i][0]==0 ? 0 : strdup(azArg[i]); - }else if( zName ){ - dotCmdError(p, i, "unknown option", 0); - return 1; - }else{ - zName = azArg[i]; - } - } - output_reset(p); - if( cli_output_capture ){ - sqlite3_str_free(cli_output_capture); - } - cli_output_capture = sqlite3_str_new(0); - if( zName ){ - sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", zName); - }else{ - sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s:%lld", - p->zInFile, p->lineno); +static int outputDumpWarning(ShellState *p, const char *zLike){ + int rc = SQLITE_OK; + sqlite3_stmt *pStmt = 0; + shellPreparePrintf(p->db, &rc, &pStmt, + "SELECT 1 FROM sqlite_schema o WHERE " + "sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true" + ); + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + sqlite3_fputs("/* WARNING: " + "Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n", + p->out + ); } - return 0; + shellFinalize(&rc, pStmt); + return rc; } /* -** Enlarge the space allocated in p->dot so that it can hold more -** than nArg parsed command-line arguments. +** Fault-Simulator state and logic. */ -static void parseDotRealloc(ShellState *p, int nArg){ - p->dot.nAlloc = nArg+22; - p->dot.azArg = realloc(p->dot.azArg,p->dot.nAlloc*sizeof(char*)); - shell_check_oom(p->dot.azArg); - p->dot.aiOfst = realloc(p->dot.aiOfst,p->dot.nAlloc*sizeof(int)); - shell_check_oom(p->dot.aiOfst); - p->dot.abQuot = realloc(p->dot.abQuot,p->dot.nAlloc); - shell_check_oom(p->dot.abQuot); -} - +static struct { + int iId; /* ID that triggers a simulated fault. -1 means "any" */ + int iErr; /* The error code to return on a fault */ + int iCnt; /* Trigger the fault only if iCnt is already zero */ + int iInterval; /* Reset iCnt to this value after each fault */ + int eVerbose; /* When to print output */ + int nHit; /* Number of hits seen so far */ + int nRepeat; /* Turn off after this many hits. 0 for never */ + int nSkip; /* Skip this many before first fault */ +} faultsim_state = {-1, 0, 0, 0, 0, 0, 0, 0}; /* -** Parse input line zLine up into individual arguments. Retain the -** parse in the p->dot substructure. +** This is the fault-sim callback */ -static void parseDotCmdArgs(const char *zLine, ShellState *p){ - char *z; - int h = 1; - int nArg = 0; - size_t szLine; - - p->dot.zOrig = zLine; - free(p->dot.zCopy); - z = p->dot.zCopy = strdup(zLine); - shell_check_oom(z); - szLine = strlen(z); - while( szLine>0 && IsSpace(z[szLine-1]) ) szLine--; - if( szLine>0 && z[szLine-1]==';' ){ - szLine--; - while( szLine>0 && IsSpace(z[szLine-1]) ) szLine--; - } - z[szLine] = 0; - parseDotRealloc(p, 2); - while( z[h] ){ - while( IsSpace(z[h]) ){ h++; } - if( z[h]==0 ) break; - if( nArg+2>p->dot.nAlloc ){ - parseDotRealloc(p, nArg); - } - if( z[h]=='\'' || z[h]=='"' ){ - int delim = z[h++]; - p->dot.abQuot[nArg] = 1; - p->dot.azArg[nArg] = &z[h]; - p->dot.aiOfst[nArg] = h; - while( z[h] && z[h]!=delim ){ - if( z[h]=='\\' && delim=='"' && z[h+1]!=0 ) h++; - h++; - } - if( z[h]==delim ){ - z[h++] = 0; - } - if( delim=='"' ) resolve_backslashes(p->dot.azArg[nArg]); - }else{ - p->dot.abQuot[nArg] = 0; - p->dot.azArg[nArg] = &z[h]; - p->dot.aiOfst[nArg] = h; - while( z[h] && !IsSpace(z[h]) ){ h++; } - if( z[h] ) z[h++] = 0; +static int faultsim_callback(int iArg){ + if( faultsim_state.iId>0 && faultsim_state.iId!=iArg ){ + return SQLITE_OK; + } + if( faultsim_state.iCnt ){ + if( faultsim_state.iCnt>0 ) faultsim_state.iCnt--; + if( faultsim_state.eVerbose>=2 ){ + sqlite3_fprintf(stdout, + "FAULT-SIM id=%d no-fault (cnt=%d)\n", iArg, faultsim_state.iCnt); } - nArg++; + return SQLITE_OK; + } + if( faultsim_state.eVerbose>=1 ){ + sqlite3_fprintf(stdout, + "FAULT-SIM id=%d returns %d\n", iArg, faultsim_state.iErr); + } + faultsim_state.iCnt = faultsim_state.iInterval; + faultsim_state.nHit++; + if( faultsim_state.nRepeat>0 && faultsim_state.nRepeat<=faultsim_state.nHit ){ + faultsim_state.iCnt = -1; } - p->dot.nArg = nArg; - p->dot.azArg[nArg] = 0; + return faultsim_state.iErr; } /* @@ -8962,11 +8762,12 @@ static void parseDotCmdArgs(const char *zLine, ShellState *p){ ** ** Return 1 on error, 2 to exit, and 0 otherwise. */ -static int do_meta_command(const char *zLine, ShellState *p){ - int nArg; +static int do_meta_command(char *zLine, ShellState *p){ + int h = 1; + int nArg = 0; int n, c; int rc = 0; - char **azArg; + char *azArg[52]; #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) if( p->expert.pExpert ){ @@ -8974,11 +8775,29 @@ static int do_meta_command(const char *zLine, ShellState *p){ } #endif - /* Parse the input line into tokens stored in p->dot. + /* Parse the input line into tokens. */ - parseDotCmdArgs(zLine, p); - nArg = p->dot.nArg; - azArg = p->dot.azArg; + while( zLine[h] && nArg<ArraySize(azArg)-1 ){ + while( IsSpace(zLine[h]) ){ h++; } + if( zLine[h]==0 ) break; + if( zLine[h]=='\'' || zLine[h]=='"' ){ + int delim = zLine[h++]; + azArg[nArg++] = &zLine[h]; + while( zLine[h] && zLine[h]!=delim ){ + if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++; + h++; + } + if( zLine[h]==delim ){ + zLine[h++] = 0; + } + if( delim=='"' ) resolve_backslashes(azArg[nArg-1]); + }else{ + azArg[nArg++] = &zLine[h]; + while( zLine[h] && !IsSpace(zLine[h]) ){ h++; } + if( zLine[h] ) zLine[h++] = 0; + } + } + azArg[nArg] = 0; /* Process the input line. */ @@ -8990,7 +8809,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ #ifndef SQLITE_OMIT_AUTHORIZATION if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){ if( nArg!=2 ){ - cli_printf(stderr, "Usage: .auth ON|OFF\n"); + sqlite3_fprintf(stderr, "Usage: .auth ON|OFF\n"); rc = 1; goto meta_command_exit; } @@ -9037,7 +8856,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ bAsync = 1; }else { - dotCmdError(p, j, "unknown option", "should be -append or -async"); + sqlite3_fprintf(stderr,"unknown option: %s\n", azArg[j]); return 1; } }else if( zDestFile==0 ){ @@ -9046,19 +8865,19 @@ static int do_meta_command(const char *zLine, ShellState *p){ zDb = zDestFile; zDestFile = azArg[j]; }else{ - cli_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); + sqlite3_fprintf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); return 1; } } if( zDestFile==0 ){ - cli_printf(stderr, "missing FILENAME argument on .backup\n"); + sqlite3_fprintf(stderr, "missing FILENAME argument on .backup\n"); return 1; } if( zDb==0 ) zDb = "main"; rc = sqlite3_open_v2(zDestFile, &pDest, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); if( rc!=SQLITE_OK ){ - cli_printf(stderr,"Error: cannot open \"%s\"\n", zDestFile); + sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zDestFile); close_db(pDest); return 1; } @@ -9119,7 +8938,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ rc = chdir(azArg[1]); #endif if( rc ){ - cli_printf(stderr,"Cannot change to directory \"%s\"\n", azArg[1]); + sqlite3_fprintf(stderr,"Cannot change to directory \"%s\"\n", azArg[1]); rc = 1; } }else{ @@ -9138,13 +8957,31 @@ static int do_meta_command(const char *zLine, ShellState *p){ } }else +#ifndef SQLITE_SHELL_FIDDLE /* Cancel output redirection, if it is currently set (by .testcase) ** Then read the content of the testcase-out.txt file and compare against ** azArg[1]. If there are differences, report an error and exit. */ if( c=='c' && n>=3 && cli_strncmp(azArg[0], "check", n)==0 ){ - rc = dotCmdCheck(p); + char *zRes = 0; + output_reset(p); + if( nArg!=2 ){ + eputz("Usage: .check GLOB-PATTERN\n"); + rc = 2; + }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ + rc = 2; + }else if( testcase_glob(azArg[1],zRes)==0 ){ + sqlite3_fprintf(stderr, + "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", + p->zTestcase, azArg[1], zRes); + rc = 1; + }else{ + sqlite3_fprintf(p->out, "testcase-%s ok\n", p->zTestcase); + p->nCheck++; + } + sqlite3_free(zRes); }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ #ifndef SQLITE_SHELL_FIDDLE if( c=='c' && cli_strncmp(azArg[0], "clone", n)==0 ){ @@ -9172,9 +9009,9 @@ static int do_meta_command(const char *zLine, ShellState *p){ zFile = "(temporary-file)"; } if( p->pAuxDb == &p->aAuxDb[i] ){ - cli_printf(stdout, "ACTIVE %d: %s\n", i, zFile); + sqlite3_fprintf(stdout, "ACTIVE %d: %s\n", i, zFile); }else if( p->aAuxDb[i].db!=0 ){ - cli_printf(stdout, " %d: %s\n", i, zFile); + sqlite3_fprintf(stdout, " %d: %s\n", i, zFile); } } }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){ @@ -9210,17 +9047,12 @@ static int do_meta_command(const char *zLine, ShellState *p){ ){ if( nArg==2 ){ #ifdef _WIN32 - if( booleanValue(azArg[1]) ){ - p->mode.mFlags |= MFLG_CRLF; - }else{ - p->mode.mFlags &= ~MFLG_CRLF; - } + p->crlfMode = booleanValue(azArg[1]); #else - p->mode.mFlags &= ~MFLG_CRLF; + p->crlfMode = 0; #endif } - cli_printf(stderr, "crlf is %s\n", - (p->mode.mFlags & MFLG_CRLF)!=0 ? "ON" : "OFF"); + sqlite3_fprintf(stderr, "crlf is %s\n", p->crlfMode ? "ON" : "OFF"); }else if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){ @@ -9250,7 +9082,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ int eTxn = sqlite3_txn_state(p->db, azName[i*2]); int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); const char *z = azName[i*2+1]; - cli_printf(p->out, "%s: %s %s%s\n", + sqlite3_fprintf(p->out, "%s: %s %s%s\n", azName[i*2], z && z[0] ? z : "\"\"", bRdonly ? "r/o" : "r/w", eTxn==SQLITE_TXN_NONE ? "" : eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); @@ -9276,7 +9108,6 @@ static int do_meta_command(const char *zLine, ShellState *p){ { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, - { "fp_digits", SQLITE_DBCONFIG_FP_DIGITS }, { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, @@ -9293,24 +9124,16 @@ static int do_meta_command(const char *zLine, ShellState *p){ for(ii=0; ii<ArraySize(aDbConfig); ii++){ if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; if( nArg>=3 ){ - if( aDbConfig[ii].op==SQLITE_DBCONFIG_FP_DIGITS ){ - sqlite3_db_config(p->db, aDbConfig[ii].op, atoi(azArg[2]), 0); - }else{ - sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); - } + sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); } sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); - if( aDbConfig[ii].op==SQLITE_DBCONFIG_FP_DIGITS ){ - cli_printf(p->out, "%19s %d\n", aDbConfig[ii].zName, v); - }else{ - cli_printf(p->out, "%19s %s\n", - aDbConfig[ii].zName, v ? "on" : "off"); - } + sqlite3_fprintf(p->out, "%19s %s\n", + aDbConfig[ii].zName, v ? "on" : "off"); if( nArg>1 ) break; } if( nArg>1 && ii==ArraySize(aDbConfig) ){ - dotCmdError(p, 1, "unknown dbconfig", - "Enter \".dbconfig\" with no arguments for a list"); + sqlite3_fprintf(stderr,"Error: unknown dbconfig \"%s\"\n", azArg[1]); + eputz("Enter \".dbconfig\" with no arguments for a list\n"); } }else @@ -9329,19 +9152,19 @@ static int do_meta_command(const char *zLine, ShellState *p){ char *zLike = 0; char *zSql; int i; + int savedShowHeader = p->showHeader; int savedShellFlags = p->shellFlgs; - Mode saved_mode; ShellClearFlag(p, - SHFLG_PreserveRowid|SHFLG_DumpDataOnly|SHFLG_DumpNoSys); + SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo + |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); for(i=1; i<nArg; i++){ if( azArg[i][0]=='-' ){ const char *z = azArg[i]+1; if( z[0]=='-' ) z++; if( cli_strcmp(z,"preserve-rowids")==0 ){ #ifdef SQLITE_OMIT_VIRTUALTABLE - dotCmdError(p, i, "unable", - "The --preserve-rowids option is not compatible" - " with SQLITE_OMIT_VIRTUALTABLE"); + eputz("The --preserve-rowids option is not compatible" + " with SQLITE_OMIT_VIRTUALTABLE\n"); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -9350,7 +9173,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ #endif }else if( cli_strcmp(z,"newlines")==0 ){ - /*ShellSetFlag(p, SHFLG_Newlines);*/ + ShellSetFlag(p, SHFLG_Newlines); }else if( cli_strcmp(z,"data-only")==0 ){ ShellSetFlag(p, SHFLG_DumpDataOnly); @@ -9359,7 +9182,8 @@ static int do_meta_command(const char *zLine, ShellState *p){ ShellSetFlag(p, SHFLG_DumpNoSys); }else { - dotCmdError(p, i, "unknown option", 0); + sqlite3_fprintf(stderr, + "Unknown option \"%s\" on \".dump\"\n", azArg[i]); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -9389,17 +9213,16 @@ static int do_meta_command(const char *zLine, ShellState *p){ open_db(p, 0); - modeDup(&saved_mode, &p->mode); outputDumpWarning(p, zLike); if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. ** So disable foreign-key constraint enforcement to prevent problems. */ - cli_puts("PRAGMA foreign_keys=OFF;\n", p->out); - cli_puts("BEGIN TRANSACTION;\n", p->out); + sqlite3_fputs("PRAGMA foreign_keys=OFF;\n", p->out); + sqlite3_fputs("BEGIN TRANSACTION;\n", p->out); } p->writableSchema = 0; - p->mode.spec.bTitles = QRF_No; + p->showHeader = 0; /* Set writable_schema=ON since doing so forces SQLite to initialize ** as much of the schema as it can even if the sqlite_schema table is ** corrupt. */ @@ -9428,27 +9251,22 @@ static int do_meta_command(const char *zLine, ShellState *p){ } sqlite3_free(zLike); if( p->writableSchema ){ - cli_puts("PRAGMA writable_schema=OFF;\n", p->out); + sqlite3_fputs("PRAGMA writable_schema=OFF;\n", p->out); p->writableSchema = 0; } sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - cli_puts(p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n", p->out); + sqlite3_fputs(p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n", p->out); } + p->showHeader = savedShowHeader; p->shellFlgs = savedShellFlags; - modeFree(&p->mode); - p->mode = saved_mode; rc = p->nErr>0; }else if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){ if( nArg==2 ){ - if( booleanValue(azArg[1]) ){ - p->mode.mFlags |= MFLG_ECHO; - }else{ - p->mode.mFlags &= ~MFLG_ECHO; - } + setOrClearFlag(p, SHFLG_Echo, azArg[1]); }else{ eputz("Usage: .echo on|off\n"); rc = 1; @@ -9462,24 +9280,28 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ if( nArg==2 ){ - if( p->mode.autoEQPtrace ){ + p->autoEQPtest = 0; + if( p->autoEQPtrace ){ if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0); - p->mode.autoEQPtrace = 0; + p->autoEQPtrace = 0; } if( cli_strcmp(azArg[1],"full")==0 ){ - p->mode.autoEQP = AUTOEQP_full; + p->autoEQP = AUTOEQP_full; }else if( cli_strcmp(azArg[1],"trigger")==0 ){ - p->mode.autoEQP = AUTOEQP_trigger; + p->autoEQP = AUTOEQP_trigger; #ifdef SQLITE_DEBUG + }else if( cli_strcmp(azArg[1],"test")==0 ){ + p->autoEQP = AUTOEQP_on; + p->autoEQPtest = 1; }else if( cli_strcmp(azArg[1],"trace")==0 ){ - p->mode.autoEQP = AUTOEQP_full; - p->mode.autoEQPtrace = 1; + p->autoEQP = AUTOEQP_full; + p->autoEQPtrace = 1; open_db(p, 0); sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0); sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0); #endif }else{ - p->mode.autoEQP = (u8)booleanValue(azArg[1]); + p->autoEQP = (u8)booleanValue(azArg[1]); } }else{ eputz("Usage: .eqp off|on|trace|trigger|full\n"); @@ -9489,7 +9311,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ #ifndef SQLITE_SHELL_FIDDLE if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){ - if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) cli_exit(rc); + if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); rc = 2; }else #endif @@ -9497,19 +9319,31 @@ static int do_meta_command(const char *zLine, ShellState *p){ /* The ".explain" command is automatic now. It is largely pointless. It ** retained purely for backwards compatibility */ if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){ + int val = 1; if( nArg>=2 ){ if( cli_strcmp(azArg[1],"auto")==0 ){ - p->mode.autoExplain = 1; + val = 99; }else{ - p->mode.autoExplain = booleanValue(azArg[1]); + val = booleanValue(azArg[1]); } } + if( val==1 && p->mode!=MODE_Explain ){ + p->normalMode = p->mode; + p->mode = MODE_Explain; + p->autoExplain = 0; + }else if( val==0 ){ + if( p->mode==MODE_Explain ) p->mode = p->normalMode; + p->autoExplain = 0; + }else if( val==99 ){ + if( p->mode==MODE_Explain ) p->mode = p->normalMode; + p->autoExplain = 1; + } }else #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ if( p->bSafeMode ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Cannot run experimental commands such as \"%s\" in safe mode\n", azArg[0]); rc = 1; @@ -9567,9 +9401,9 @@ static int do_meta_command(const char *zLine, ShellState *p){ /* --help lists all file-controls */ if( cli_strcmp(zCmd,"help")==0 ){ - cli_puts("Available file-controls:\n", p->out); + sqlite3_fputs("Available file-controls:\n", p->out); for(i=0; i<ArraySize(aCtrl); i++){ - cli_printf(p->out, + sqlite3_fprintf(p->out, " .filectrl %s %s\n", aCtrl[i].zCtrlName, aCtrl[i].zUsage); } rc = 1; @@ -9585,7 +9419,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ filectrl = aCtrl[i].ctrlCode; iCtrl = i; }else{ - cli_printf(stderr,"Error: ambiguous file-control: \"%s\"\n" + sqlite3_fprintf(stderr,"Error: ambiguous file-control: \"%s\"\n" "Use \".filectrl --help\" for help\n", zCmd); rc = 1; goto meta_command_exit; @@ -9593,7 +9427,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ } } if( filectrl<0 ){ - cli_printf(stderr,"Error: unknown file-control: %s\n" + sqlite3_fprintf(stderr,"Error: unknown file-control: %s\n" "Use \".filectrl --help\" for help\n", zCmd); }else{ switch(filectrl){ @@ -9637,7 +9471,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( nArg!=2 ) break; sqlite3_file_control(p->db, zSchema, filectrl, &z); if( z ){ - cli_printf(p->out, "%s\n", z); + sqlite3_fprintf(p->out, "%s\n", z); sqlite3_free(z); } isOk = 2; @@ -9651,30 +9485,31 @@ static int do_meta_command(const char *zLine, ShellState *p){ } x = -1; sqlite3_file_control(p->db, zSchema, filectrl, &x); - cli_printf(p->out, "%d\n", x); + sqlite3_fprintf(p->out, "%d\n", x); isOk = 2; break; } } } if( isOk==0 && iCtrl>=0 ){ - cli_printf(p->out, "Usage: .filectrl %s %s\n", + sqlite3_fprintf(p->out, "Usage: .filectrl %s %s\n", zCmd, aCtrl[iCtrl].zUsage); rc = 1; }else if( isOk==1 ){ char zBuf[100]; sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); - cli_printf(p->out, "%s\n", zBuf); + sqlite3_fprintf(p->out, "%s\n", zBuf); } }else if( c=='f' && cli_strncmp(azArg[0], "fullschema", n)==0 ){ ShellState data; int doStats = 0; - int hasStat[5]; - int flgs = 0; - char *zSql; + memcpy(&data, p, sizeof(data)); + data.showHeader = 0; + data.cMode = data.mode = MODE_Semi; if( nArg==2 && optionMatch(azArg[1], "indent") ){ + data.cMode = data.mode = MODE_Pretty; nArg = 1; } if( nArg!=1 ){ @@ -9683,81 +9518,384 @@ static int do_meta_command(const char *zLine, ShellState *p){ goto meta_command_exit; } open_db(p, 0); - zSql = sqlite3_mprintf( - "SELECT shell_format_schema(sql,%d) FROM" + rc = sqlite3_exec(p->db, + "SELECT sql FROM" " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" " FROM sqlite_schema UNION ALL" " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) " "WHERE type!='meta' AND sql NOTNULL" - " AND name NOT LIKE 'sqlite__%%' ESCAPE '_' " - "ORDER BY x", flgs); - memcpy(&data, p, sizeof(data)); - data.mode.spec.bTitles = QRF_No; - data.mode.eMode = MODE_List; - data.mode.spec.eText = QRF_TEXT_Plain; - data.mode.spec.nCharLimit = 0; - data.mode.spec.zRowSep = "\n"; - rc = shell_exec(&data,zSql,0); - sqlite3_free(zSql); + " AND name NOT LIKE 'sqlite__%' ESCAPE '_' " + "ORDER BY x", + callback, &data, 0 + ); if( rc==SQLITE_OK ){ sqlite3_stmt *pStmt; - memset(hasStat, 0, sizeof(hasStat)); rc = sqlite3_prepare_v2(p->db, - "SELECT substr(name,12,1) FROM sqlite_schema" + "SELECT rowid FROM sqlite_schema" " WHERE name GLOB 'sqlite_stat[134]'", -1, &pStmt, 0); if( rc==SQLITE_OK ){ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - int k = sqlite3_column_int(pStmt,0); - assert( k==1 || k==3 || k==4 ); - hasStat[k] = 1; - doStats = 1; - } + doStats = sqlite3_step(pStmt)==SQLITE_ROW; + sqlite3_finalize(pStmt); } - sqlite3_finalize(pStmt); } if( doStats==0 ){ - cli_puts("/* No STAT tables available */\n", p->out); + sqlite3_fputs("/* No STAT tables available */\n", p->out); }else{ - cli_puts("ANALYZE sqlite_schema;\n", p->out); - data.mode.eMode = MODE_Insert; - if( hasStat[1] ){ - data.mode.spec.zTableName = "sqlite_stat1"; - shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); - } - if( hasStat[4] ){ - data.mode.spec.zTableName = "sqlite_stat4"; - shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); - } - cli_puts("ANALYZE sqlite_schema;\n", p->out); + sqlite3_fputs("ANALYZE sqlite_schema;\n", p->out); + data.cMode = data.mode = MODE_Insert; + data.zDestTable = "sqlite_stat1"; + shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); + data.zDestTable = "sqlite_stat4"; + shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); + sqlite3_fputs("ANALYZE sqlite_schema;\n", p->out); } }else if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){ if( nArg==2 ){ - p->mode.spec.bTitles = booleanValue(azArg[1]) ? QRF_Yes : QRF_No; - p->mode.mFlags |= MFLG_HDR; - p->mode.spec.eTitle = aModeInfo[p->mode.eMode].eHdr; + p->showHeader = booleanValue(azArg[1]); + p->shellFlgs |= SHFLG_HeaderSet; }else{ eputz("Usage: .headers on|off\n"); rc = 1; } - }else - - if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){ - if( nArg>=2 ){ - n = showHelp(p->out, azArg[1]); - if( n==0 ){ - cli_printf(p->out, "Nothing matches '%s'\n", azArg[1]); + }else + + if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){ + if( nArg>=2 ){ + n = showHelp(p->out, azArg[1]); + if( n==0 ){ + sqlite3_fprintf(p->out, "Nothing matches '%s'\n", azArg[1]); + } + }else{ + showHelp(p->out, 0); + } + }else + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ + char *zTable = 0; /* Insert data into this table */ + char *zSchema = 0; /* Schema of zTable */ + char *zFile = 0; /* Name of file to extra content from */ + sqlite3_stmt *pStmt = NULL; /* A statement */ + int nCol; /* Number of columns in the table */ + i64 nByte; /* Number of bytes in an SQL string */ + int i, j; /* Loop counters */ + int needCommit; /* True to COMMIT or ROLLBACK at end */ + int nSep; /* Number of bytes in p->colSeparator[] */ + char *zSql = 0; /* An SQL statement */ + ImportCtx sCtx; /* Reader context */ + char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ + int eVerbose = 0; /* Larger for more console output */ + i64 nSkip = 0; /* Initial lines to skip */ + int useOutputMode = 1; /* Use output mode to determine separators */ + char *zCreate = 0; /* CREATE TABLE statement text */ + + failIfSafeMode(p, "cannot run .import in safe mode"); + memset(&sCtx, 0, sizeof(sCtx)); + if( p->mode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + rc = 1; + for(i=1; i<nArg; i++){ + char *z = azArg[i]; + if( z[0]=='-' && z[1]=='-' ) z++; + if( z[0]!='-' ){ + if( zFile==0 ){ + zFile = z; + }else if( zTable==0 ){ + zTable = z; + }else{ + sqlite3_fprintf(p->out, "ERROR: extra argument: \"%s\". Usage:\n",z); + showHelp(p->out, "import"); + goto meta_command_exit; + } + }else if( cli_strcmp(z,"-v")==0 ){ + eVerbose++; + }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){ + zSchema = azArg[++i]; + }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){ + nSkip = integerValue(azArg[++i]); + }else if( cli_strcmp(z,"-ascii")==0 ){ + sCtx.cColSep = SEP_Unit[0]; + sCtx.cRowSep = SEP_Record[0]; + xRead = ascii_read_one_field; + useOutputMode = 0; + }else if( cli_strcmp(z,"-csv")==0 ){ + sCtx.cColSep = ','; + sCtx.cRowSep = '\n'; + xRead = csv_read_one_field; + useOutputMode = 0; + }else{ + sqlite3_fprintf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z); + showHelp(p->out, "import"); + goto meta_command_exit; + } + } + if( zTable==0 ){ + sqlite3_fprintf(p->out, "ERROR: missing %s argument. Usage:\n", + zFile==0 ? "FILE" : "TABLE"); + showHelp(p->out, "import"); + goto meta_command_exit; + } + seenInterrupt = 0; + open_db(p, 0); + if( useOutputMode ){ + /* If neither the --csv or --ascii options are specified, then set + ** the column and row separator characters from the output mode. */ + nSep = strlen30(p->colSeparator); + if( nSep==0 ){ + eputz("Error: non-null column separator required for import\n"); + goto meta_command_exit; + } + if( nSep>1 ){ + eputz("Error: multi-character column separators not allowed" + " for import\n"); + goto meta_command_exit; + } + nSep = strlen30(p->rowSeparator); + if( nSep==0 ){ + eputz("Error: non-null row separator required for import\n"); + goto meta_command_exit; + } + if( nSep==2 && p->mode==MODE_Csv + && cli_strcmp(p->rowSeparator,SEP_CrLf)==0 + ){ + /* When importing CSV (only), if the row separator is set to the + ** default output row separator, change it to the default input + ** row separator. This avoids having to maintain different input + ** and output row separators. */ + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + nSep = strlen30(p->rowSeparator); + } + if( nSep>1 ){ + eputz("Error: multi-character row separators not allowed" + " for import\n"); + goto meta_command_exit; + } + sCtx.cColSep = (u8)p->colSeparator[0]; + sCtx.cRowSep = (u8)p->rowSeparator[0]; + } + sCtx.zFile = zFile; + sCtx.nLine = 1; + if( sCtx.zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + eputz("Error: pipes are not supported in this OS\n"); + goto meta_command_exit; +#else + sCtx.in = sqlite3_popen(sCtx.zFile+1, "r"); + sCtx.zFile = "<pipe>"; + sCtx.xCloser = pclose; +#endif + }else{ + sCtx.in = sqlite3_fopen(sCtx.zFile, "rb"); + sCtx.xCloser = fclose; + } + if( sCtx.in==0 ){ + sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zFile); + goto meta_command_exit; + } + if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ + char zSep[2]; + zSep[1] = 0; + zSep[0] = sCtx.cColSep; + sqlite3_fputs("Column separator ", p->out); + output_c_string(p->out, zSep); + sqlite3_fputs(", row separator ", p->out); + zSep[0] = sCtx.cRowSep; + output_c_string(p->out, zSep); + sqlite3_fputs("\n", p->out); + } + sCtx.z = sqlite3_malloc64(120); + if( sCtx.z==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + /* Below, resources must be freed before exit. */ + while( nSkip>0 ){ + nSkip--; + while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} + } + import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ + if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) + && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema" + " WHERE name=%Q AND type='view'", + zSchema ? zSchema : "main", zTable) + ){ + /* Table does not exist. Create it. */ + sqlite3 *dbCols = 0; + char *zRenames = 0; + char *zColDefs; + zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", + zSchema ? zSchema : "main", zTable); + while( xRead(&sCtx) ){ + zAutoColumn(sCtx.z, &dbCols, 0); + if( sCtx.cTerm!=sCtx.cColSep ) break; + } + zColDefs = zAutoColumn(0, &dbCols, &zRenames); + if( zRenames!=0 ){ + sqlite3_fprintf((stdin_is_interactive && p->in==stdin)? p->out : stderr, + "Columns renamed during .import %s due to duplicates:\n" + "%s\n", sCtx.zFile, zRenames); + sqlite3_free(zRenames); + } + assert(dbCols==0); + if( zColDefs==0 ){ + sqlite3_fprintf(stderr,"%s: empty file\n", sCtx.zFile); + import_cleanup(&sCtx); + rc = 1; + sqlite3_free(zCreate); + goto meta_command_exit; + } + zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); + if( zCreate==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + if( eVerbose>=1 ){ + sqlite3_fprintf(p->out, "%s\n", zCreate); + } + rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); + if( rc ){ + sqlite3_fprintf(stderr, + "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); + } + sqlite3_free(zCreate); + zCreate = 0; + if( rc ){ + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; + } + } + zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);", + zTable, zSchema); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; + if( rc ){ + if (pStmt) sqlite3_finalize(pStmt); + shellDatabaseError(p->db); + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; + } + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + nCol = sqlite3_column_int(pStmt, 0); + }else{ + nCol = 0; + } + sqlite3_finalize(pStmt); + pStmt = 0; + if( nCol==0 ) return 0; /* no columns, no error */ + + nByte = 64 /* space for "INSERT INTO", "VALUES(", ")\0" */ + + (zSchema ? strlen(zSchema)*2 + 2: 0) /* Quoted schema name */ + + strlen(zTable)*2 + 2 /* Quoted table name */ + + nCol*2; /* Space for ",?" for each column */ + zSql = sqlite3_malloc64( nByte ); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + if( zSchema ){ + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", + zSchema, zTable); + }else{ + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); + } + j = strlen30(zSql); + for(i=1; i<nCol; i++){ + zSql[j++] = ','; + zSql[j++] = '?'; + } + zSql[j++] = ')'; + zSql[j] = 0; + assert( j<nByte ); + if( eVerbose>=2 ){ + sqlite3_fprintf(p->out, "Insert using: %s\n", zSql); + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; + if( rc ){ + shellDatabaseError(p->db); + if (pStmt) sqlite3_finalize(pStmt); + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; + } + needCommit = sqlite3_get_autocommit(p->db); + if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); + do{ + int startLine = sCtx.nLine; + for(i=0; i<nCol; i++){ + char *z = xRead(&sCtx); + /* + ** Did we reach end-of-file before finding any columns? + ** If so, stop instead of NULL filling the remaining columns. + */ + if( z==0 && i==0 ) break; + /* + ** Did we reach end-of-file OR end-of-line before finding any + ** columns in ASCII mode? If so, stop instead of NULL filling + ** the remaining columns. + */ + if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; + /* + ** For CSV mode, per RFC 4180, accept EOF in lieu of final + ** record terminator but only for last field of multi-field row. + ** (If there are too few fields, it's not valid CSV anyway.) + */ + if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){ + z = ""; + } + sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); + if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){ + sqlite3_fprintf(stderr,"%s:%d: expected %d columns but found %d" + " - filling the rest with NULL\n", + sCtx.zFile, startLine, nCol, i+1); + i += 2; + while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; } + } } - }else{ - showHelp(p->out, 0); - } - }else + if( sCtx.cTerm==sCtx.cColSep ){ + do{ + xRead(&sCtx); + i++; + }while( sCtx.cTerm==sCtx.cColSep ); + sqlite3_fprintf(stderr, + "%s:%d: expected %d columns but found %d - extras ignored\n", + sCtx.zFile, startLine, nCol, i); + } + if( i>=nCol ){ + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ){ + sqlite3_fprintf(stderr,"%s:%d: INSERT failed: %s\n", + sCtx.zFile, startLine, sqlite3_errmsg(p->db)); + sCtx.nErr++; + }else{ + sCtx.nRow++; + } + } + }while( sCtx.cTerm!=EOF ); -#ifndef SQLITE_SHELL_FIDDLE - if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ - rc = dotCmdImport(p); + import_cleanup(&sCtx); + sqlite3_finalize(pStmt); + if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); + if( eVerbose>0 ){ + sqlite3_fprintf(p->out, + "Added %d rows with %d errors using %d lines of input\n", + sCtx.nRow, sCtx.nErr, sCtx.nLine-1); + } }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ @@ -9790,10 +9928,10 @@ static int do_meta_command(const char *zLine, ShellState *p){ } zSql = sqlite3_mprintf( "SELECT rootpage, 0 FROM sqlite_schema" - " WHERE type='index' AND lower(name)=lower('%q')" + " WHERE name='%q' AND type='index'" "UNION ALL " "SELECT rootpage, 1 FROM sqlite_schema" - " WHERE type='table' AND lower(name)=lower('%q')" + " WHERE name='%q' AND type='table'" " AND sql LIKE '%%without%%rowid%%'", azArg[1], azArg[1] ); @@ -9831,7 +9969,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ } sqlite3_finalize(pStmt); if( i==0 || tnum==0 ){ - cli_printf(stderr,"no such index: \"%s\"\n", azArg[1]); + sqlite3_fprintf(stderr,"no such index: \"%s\"\n", azArg[1]); rc = 1; sqlite3_free(zCollist); goto meta_command_exit; @@ -9846,13 +9984,13 @@ static int do_meta_command(const char *zLine, ShellState *p){ rc = sqlite3_exec(p->db, zSql, 0, 0, 0); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); if( rc ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); }else{ - cli_printf(stdout, "%s;\n", zSql); + sqlite3_fprintf(stdout, "%s;\n", zSql); } }else{ - cli_printf(stderr,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); + sqlite3_fprintf(stderr,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); rc = 1; } sqlite3_free(zSql); @@ -9866,7 +10004,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( iArg==0 ) iArg = -1; } if( (nArg!=1 && nArg!=2) || iArg<0 ){ - cli_printf(stderr,"%s","Usage: .intck STEPS_PER_UNLOCK\n"); + sqlite3_fprintf(stderr,"%s","Usage: .intck STEPS_PER_UNLOCK\n"); rc = 1; goto meta_command_exit; } @@ -9887,7 +10025,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ }else{ iotrace = sqlite3_fopen(azArg[1], "w"); if( iotrace==0 ){ - cli_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); + sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); sqlite3IoTrace = 0; rc = 1; }else{ @@ -9906,7 +10044,6 @@ static int do_meta_command(const char *zLine, ShellState *p){ { "sql_length", SQLITE_LIMIT_SQL_LENGTH }, { "column", SQLITE_LIMIT_COLUMN }, { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH }, - { "parser_depth", SQLITE_LIMIT_PARSER_DEPTH }, { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT }, { "vdbe_op", SQLITE_LIMIT_VDBE_OP }, { "function_arg", SQLITE_LIMIT_FUNCTION_ARG }, @@ -9920,7 +10057,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ open_db(p, 0); if( nArg==1 ){ for(i=0; i<ArraySize(aLimit); i++){ - cli_printf(stdout, "%20s %d\n", aLimit[i].zLimitName, + sqlite3_fprintf(stdout, "%20s %d\n", aLimit[i].zLimitName, sqlite3_limit(p->db, aLimit[i].limitCode, -1)); } }else if( nArg>3 ){ @@ -9935,14 +10072,14 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( iLimit<0 ){ iLimit = i; }else{ - cli_printf(stderr,"ambiguous limit: \"%s\"\n", azArg[1]); + sqlite3_fprintf(stderr,"ambiguous limit: \"%s\"\n", azArg[1]); rc = 1; goto meta_command_exit; } } } if( iLimit<0 ){ - cli_printf(stderr,"unknown limit: \"%s\"\n" + sqlite3_fprintf(stderr,"unknown limit: \"%s\"\n" "enter \".limits\" with no arguments for a list.\n", azArg[1]); rc = 1; @@ -9951,10 +10088,9 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( nArg==3 ){ sqlite3_limit(p->db, aLimit[iLimit].limitCode, (int)integerValue(azArg[2])); - }else{ - cli_printf(stdout, "%20s %d\n", aLimit[iLimit].zLimitName, - sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); } + sqlite3_fprintf(stdout, "%20s %d\n", aLimit[iLimit].zLimitName, + sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); } }else @@ -10002,12 +10138,174 @@ static int do_meta_command(const char *zLine, ShellState *p){ } output_file_close(p->pLog); if( cli_strcmp(zFile,"on")==0 ) zFile = "stdout"; - p->pLog = output_file_open(p, zFile); + p->pLog = output_file_open(zFile); } }else if( c=='m' && cli_strncmp(azArg[0], "mode", n)==0 ){ - rc = dotCmdMode(p); + const char *zMode = 0; + const char *zTabname = 0; + int i, n2; + int chng = 0; /* 0x01: change to cmopts. 0x02: Any other change */ + ColModeOpts cmOpts = ColModeOpts_default; + for(i=1; i<nArg; i++){ + const char *z = azArg[i]; + if( optionMatch(z,"wrap") && i+1<nArg ){ + cmOpts.iWrap = integerValue(azArg[++i]); + chng |= 1; + }else if( optionMatch(z,"ww") ){ + cmOpts.bWordWrap = 1; + chng |= 1; + }else if( optionMatch(z,"wordwrap") && i+1<nArg ){ + cmOpts.bWordWrap = (u8)booleanValue(azArg[++i]); + chng |= 1; + }else if( optionMatch(z,"quote") ){ + cmOpts.bQuote = 1; + chng |= 1; + }else if( optionMatch(z,"noquote") ){ + cmOpts.bQuote = 0; + chng |= 1; + }else if( optionMatch(z,"escape") && i+1<nArg ){ + /* See similar code at tag-20250224-1 */ + const char *zEsc = azArg[++i]; + int k; + for(k=0; k<ArraySize(shell_EscModeNames); k++){ + if( sqlite3_stricmp(zEsc,shell_EscModeNames[k])==0 ){ + p->eEscMode = k; + chng |= 2; + break; + } + } + if( k>=ArraySize(shell_EscModeNames) ){ + sqlite3_fprintf(stderr, "unknown control character escape mode \"%s\"" + " - choices:", zEsc); + for(k=0; k<ArraySize(shell_EscModeNames); k++){ + sqlite3_fprintf(stderr, " %s", shell_EscModeNames[k]); + } + sqlite3_fprintf(stderr, "\n"); + rc = 1; + goto meta_command_exit; + } + }else if( zMode==0 ){ + zMode = z; + /* Apply defaults for qbox pseudo-mode. If that + * overwrites already-set values, user was informed of this. + */ + chng |= 1; + if( cli_strcmp(z, "qbox")==0 ){ + ColModeOpts cmo = ColModeOpts_default_qbox; + zMode = "box"; + cmOpts = cmo; + } + }else if( zTabname==0 ){ + zTabname = z; + }else if( z[0]=='-' ){ + sqlite3_fprintf(stderr,"unknown option: %s\n", z); + eputz("options:\n" + " --escape MODE\n" + " --noquote\n" + " --quote\n" + " --wordwrap on/off\n" + " --wrap N\n" + " --ww\n"); + rc = 1; + goto meta_command_exit; + }else{ + sqlite3_fprintf(stderr,"extra argument: \"%s\"\n", z); + rc = 1; + goto meta_command_exit; + } + } + if( !chng ){ + if( p->mode==MODE_Column + || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) + ){ + sqlite3_fprintf(p->out, + "current output mode: %s --wrap %d --wordwrap %s " + "--%squote --escape %s\n", + modeDescr[p->mode], p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no", + shell_EscModeNames[p->eEscMode] + ); + }else{ + sqlite3_fprintf(p->out, + "current output mode: %s --escape %s\n", + modeDescr[p->mode], + shell_EscModeNames[p->eEscMode] + ); + } + } + if( zMode==0 ){ + zMode = modeDescr[p->mode]; + if( (chng&1)==0 ) cmOpts = p->cmOpts; + } + n2 = strlen30(zMode); + if( cli_strncmp(zMode,"lines",n2)==0 ){ + p->mode = MODE_Line; + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + }else if( cli_strncmp(zMode,"columns",n2)==0 ){ + p->mode = MODE_Column; + if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){ + p->showHeader = 1; + } + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + p->cmOpts = cmOpts; + }else if( cli_strncmp(zMode,"list",n2)==0 ){ + p->mode = MODE_List; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + }else if( cli_strncmp(zMode,"html",n2)==0 ){ + p->mode = MODE_Html; + }else if( cli_strncmp(zMode,"tcl",n2)==0 ){ + p->mode = MODE_Tcl; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + }else if( cli_strncmp(zMode,"csv",n2)==0 ){ + p->mode = MODE_Csv; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); + }else if( cli_strncmp(zMode,"tabs",n2)==0 ){ + p->mode = MODE_List; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab); + }else if( cli_strncmp(zMode,"insert",n2)==0 ){ + p->mode = MODE_Insert; + set_table_name(p, zTabname ? zTabname : "table"); + if( p->eEscMode==SHELL_ESC_OFF ){ + ShellSetFlag(p, SHFLG_Newlines); + }else{ + ShellClearFlag(p, SHFLG_Newlines); + } + }else if( cli_strncmp(zMode,"quote",n2)==0 ){ + p->mode = MODE_Quote; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + }else if( cli_strncmp(zMode,"ascii",n2)==0 ){ + p->mode = MODE_Ascii; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); + }else if( cli_strncmp(zMode,"markdown",n2)==0 ){ + p->mode = MODE_Markdown; + p->cmOpts = cmOpts; + }else if( cli_strncmp(zMode,"table",n2)==0 ){ + p->mode = MODE_Table; + p->cmOpts = cmOpts; + }else if( cli_strncmp(zMode,"box",n2)==0 ){ + p->mode = MODE_Box; + p->cmOpts = cmOpts; + }else if( cli_strncmp(zMode,"count",n2)==0 ){ + p->mode = MODE_Count; + }else if( cli_strncmp(zMode,"off",n2)==0 ){ + p->mode = MODE_Off; + }else if( cli_strncmp(zMode,"json",n2)==0 ){ + p->mode = MODE_Json; + }else{ + eputz("Error: mode should be one of: " + "ascii box column csv html insert json line list markdown " + "qbox quote table tabs tcl\n"); + rc = 1; + } + p->cMode = p->mode; }else #ifndef SQLITE_SHELL_FIDDLE @@ -10016,9 +10314,9 @@ static int do_meta_command(const char *zLine, ShellState *p){ eputz("Usage: .nonce NONCE\n"); rc = 1; }else if( p->zNonce==0 || cli_strcmp(azArg[1],p->zNonce)!=0 ){ - cli_printf(stderr,"line %lld: incorrect nonce: \"%s\"\n", + sqlite3_fprintf(stderr,"line %lld: incorrect nonce: \"%s\"\n", p->lineno, azArg[1]); - cli_exit(1); + exit(1); }else{ p->bSafeMode = 0; return 0; /* Return immediately to bypass the safe mode reset @@ -10029,7 +10327,8 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( c=='n' && cli_strncmp(azArg[0], "nullvalue", n)==0 ){ if( nArg==2 ){ - modeSetStr(&p->mode.spec.zNull, azArg[1]); + sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, + "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); }else{ eputz("Usage: .nullvalue STRING\n"); rc = 1; @@ -10044,8 +10343,6 @@ static int do_meta_command(const char *zLine, ShellState *p){ int openMode = SHELL_OPEN_UNSPEC; int openFlags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; - if( p->bSafeMode ) openFlags = SQLITE_OPEN_READONLY; - /* Check for command-line arguments */ for(iName=1; iName<nArg; iName++){ const char *z = azArg[iName]; @@ -10053,10 +10350,10 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( optionMatch(z,"new") ){ newFlag = 1; #ifdef SQLITE_HAVE_ZLIB - }else if( optionMatch(z, "zip") && !p->bSafeMode ){ + }else if( optionMatch(z, "zip") ){ openMode = SHELL_OPEN_ZIPFILE; #endif - }else if( optionMatch(z, "append") && !p->bSafeMode ){ + }else if( optionMatch(z, "append") ){ openMode = SHELL_OPEN_APPENDVFS; }else if( optionMatch(z, "readonly") ){ openFlags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); @@ -10080,11 +10377,11 @@ static int do_meta_command(const char *zLine, ShellState *p){ }else #endif /* !SQLITE_SHELL_FIDDLE */ if( z[0]=='-' ){ - cli_printf(stderr,"unknown option: %s\n", z); + sqlite3_fprintf(stderr,"unknown option: %s\n", z); rc = 1; goto meta_command_exit; }else if( zFN ){ - cli_printf(stderr,"extra argument: \"%s\"\n", z); + sqlite3_fprintf(stderr,"extra argument: \"%s\"\n", z); rc = 1; goto meta_command_exit; }else{ @@ -10135,7 +10432,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ p->pAuxDb->zDbFilename = zNewFilename; open_db(p, OPEN_DB_KEEPALIVE); if( p->db==0 ){ - cli_printf(stderr,"Error: cannot open '%s'\n", zNewFilename); + sqlite3_fprintf(stderr,"Error: cannot open '%s'\n", zNewFilename); sqlite3_free(zNewFilename); }else{ p->pAuxDb->zFreeOnClose = zNewFilename; @@ -10155,7 +10452,145 @@ static int do_meta_command(const char *zLine, ShellState *p){ || (c=='e' && n==5 && cli_strcmp(azArg[0],"excel")==0) || (c=='w' && n==3 && cli_strcmp(azArg[0],"www")==0) ){ - rc = dotCmdOutput(p); + char *zFile = 0; + int i; + int eMode = 0; /* 0: .outout/.once, 'x'=.excel, 'w'=.www */ + int bOnce = 0; /* 0: .output, 1: .once, 2: .excel/.www */ + int bPlain = 0; /* --plain option */ + static const char *zBomUtf8 = "\357\273\277"; + const char *zBom = 0; + + failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); + if( c=='e' ){ + eMode = 'x'; + bOnce = 2; + }else if( c=='w' ){ + eMode = 'w'; + bOnce = 2; + }else if( cli_strncmp(azArg[0],"once",n)==0 ){ + bOnce = 1; + } + for(i=1; i<nArg; i++){ + char *z = azArg[i]; + if( z[0]=='-' ){ + if( z[1]=='-' ) z++; + if( cli_strcmp(z,"-bom")==0 ){ + zBom = zBomUtf8; + }else if( cli_strcmp(z,"-plain")==0 ){ + bPlain = 1; + }else if( c=='o' && cli_strcmp(z,"-x")==0 ){ + eMode = 'x'; /* spreadsheet */ + }else if( c=='o' && cli_strcmp(z,"-e")==0 ){ + eMode = 'e'; /* text editor */ + }else if( c=='o' && cli_strcmp(z,"-w")==0 ){ + eMode = 'w'; /* Web browser */ + }else{ + sqlite3_fprintf(p->out, + "ERROR: unknown option: \"%s\". Usage:\n", azArg[i]); + showHelp(p->out, azArg[0]); + rc = 1; + sqlite3_free(zFile); + goto meta_command_exit; + } + }else if( zFile==0 && eMode==0 ){ + if( cli_strcmp(z, "off")==0 ){ +#ifdef _WIN32 + zFile = sqlite3_mprintf("nul"); +#else + zFile = sqlite3_mprintf("/dev/null"); +#endif + }else{ + zFile = sqlite3_mprintf("%s", z); + } + if( zFile && zFile[0]=='|' ){ + while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]); + break; + } + }else{ + sqlite3_fprintf(p->out, + "ERROR: extra parameter: \"%s\". Usage:\n", azArg[i]); + showHelp(p->out, azArg[0]); + rc = 1; + sqlite3_free(zFile); + goto meta_command_exit; + } + } + if( zFile==0 ){ + zFile = sqlite3_mprintf("stdout"); + } + shell_check_oom(zFile); + if( bOnce ){ + p->outCount = 2; + }else{ + p->outCount = 0; + } + output_reset(p); +#ifndef SQLITE_NOHAVE_SYSTEM + if( eMode=='e' || eMode=='x' || eMode=='w' ){ + p->doXdgOpen = 1; + outputModePush(p); + if( eMode=='x' ){ + /* spreadsheet mode. Output as CSV. */ + newTempFile(p, "csv"); + ShellClearFlag(p, SHFLG_Echo); + p->mode = MODE_Csv; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); +#ifdef _WIN32 + zBom = zBomUtf8; /* Always include the BOM on Windows, as Excel does + ** not work without it. */ +#endif + }else if( eMode=='w' ){ + /* web-browser mode. */ + newTempFile(p, "html"); + if( !bPlain ) p->mode = MODE_Www; + }else{ + /* text editor mode */ + newTempFile(p, "txt"); + } + sqlite3_free(zFile); + zFile = sqlite3_mprintf("%s", p->zTempFile); + } +#endif /* SQLITE_NOHAVE_SYSTEM */ + shell_check_oom(zFile); + if( zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + eputz("Error: pipes are not supported in this OS\n"); + rc = 1; + output_redir(p, stdout); +#else + FILE *pfPipe = sqlite3_popen(zFile + 1, "w"); + if( pfPipe==0 ){ + assert( stderr!=NULL ); + sqlite3_fprintf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); + rc = 1; + }else{ + output_redir(p, pfPipe); + if( zBom ) sqlite3_fputs(zBom, pfPipe); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); + } +#endif + }else{ + FILE *pfFile = output_file_open(zFile); + if( pfFile==0 ){ + if( cli_strcmp(zFile,"off")!=0 ){ + assert( stderr!=NULL ); + sqlite3_fprintf(stderr,"Error: cannot write to \"%s\"\n", zFile); + } + rc = 1; + } else { + output_redir(p, pfFile); + if( zBom ) sqlite3_fputs(zBom, pfFile); + if( bPlain && eMode=='w' ){ + sqlite3_fputs( + "<!DOCTYPE html>\n<BODY>\n<PLAINTEXT>\n", + pfFile + ); + } + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); + } + } + sqlite3_free(zFile); }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ @@ -10192,7 +10627,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ "SELECT key, quote(value) " "FROM temp.sqlite_parameters;", -1, &pStmt, 0); while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - cli_printf(p->out, + sqlite3_fprintf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), sqlite3_column_text(pStmt,1)); } @@ -10238,7 +10673,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rx!=SQLITE_OK ){ - cli_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); + sqlite3_fprintf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); pStmt = 0; rc = 1; @@ -10268,10 +10703,10 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( c=='p' && n>=3 && cli_strncmp(azArg[0], "print", n)==0 ){ int i; for(i=1; i<nArg; i++){ - if( i>1 ) cli_puts(" ", p->out); - cli_puts(azArg[i], p->out); + if( i>1 ) sqlite3_fputs(" ", p->out); + sqlite3_fputs(azArg[i], p->out); } - cli_puts("\n", p->out); + sqlite3_fputs("\n", p->out); }else #ifndef SQLITE_OMIT_PROGRESS_CALLBACK @@ -10298,19 +10733,6 @@ static int do_meta_command(const char *zLine, ShellState *p){ p->flgProgress |= SHELL_PROGRESS_ONCE; continue; } - if( cli_strcmp(z,"timeout")==0 ){ - if( i==nArg-1 ){ - dotCmdError(p, i, "missing argument", 0); - return 1; - } - i++; - p->tmProgress = atof(azArg[i]); - if( p->tmProgress>0.0 ){ - p->flgProgress = SHELL_PROGRESS_QUIET|SHELL_PROGRESS_TMOUT; - if( nn==0 ) nn = 100; - } - continue; - } if( cli_strcmp(z,"limit")==0 ){ if( i+1>=nArg ){ eputz("Error: missing argument on --limit\n"); @@ -10321,7 +10743,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ } continue; } - cli_printf(stderr,"Error: unknown option: \"%s\"\n", azArg[i]); + sqlite3_fprintf(stderr,"Error: unknown option: \"%s\"\n", azArg[i]); rc = 1; goto meta_command_exit; }else{ @@ -10365,7 +10787,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ #else p->in = sqlite3_popen(azArg[1]+1, "r"); if( p->in==0 ){ - cli_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); + sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ rc = process_input(p, "<pipe>"); @@ -10373,12 +10795,10 @@ static int do_meta_command(const char *zLine, ShellState *p){ } #endif }else if( (p->in = openChrSource(azArg[1]))==0 ){ - cli_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); + sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ - char *zFilename = strdup(azArg[1]); - rc = process_input(p, zFilename); - free(zFilename); + rc = process_input(p, azArg[1]); fclose(p->in); } p->in = inSaved; @@ -10408,7 +10828,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ } rc = sqlite3_open(zSrcFile, &pSrc); if( rc!=SQLITE_OK ){ - cli_printf(stderr,"Error: cannot open \"%s\"\n", zSrcFile); + sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zSrcFile); close_db(pSrc); return 1; } @@ -10446,21 +10866,21 @@ static int do_meta_command(const char *zLine, ShellState *p){ ){ if( nArg==2 ){ if( cli_strcmp(azArg[1], "vm")==0 ){ - p->mode.scanstatsOn = 3; + p->scanstatsOn = 3; }else if( cli_strcmp(azArg[1], "est")==0 ){ - p->mode.scanstatsOn = 2; + p->scanstatsOn = 2; }else{ - p->mode.scanstatsOn = (u8)booleanValue(azArg[1]); + p->scanstatsOn = (u8)booleanValue(azArg[1]); } open_db(p, 0); sqlite3_db_config( - p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->mode.scanstatsOn, (int*)0 + p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 ); #if !defined(SQLITE_ENABLE_STMT_SCANSTATUS) eputz("Warning: .scanstats not available in this build.\n"); #elif !defined(SQLITE_ENABLE_BYTECODE_VTAB) - if( p->mode.scanstatsOn==3 ){ + if( p->scanstatsOn==3 ){ eputz("Warning: \".scanstats vm\" not available in this build.\n"); } #endif @@ -10471,6 +10891,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ }else if( c=='s' && cli_strncmp(azArg[0], "schema", n)==0 ){ + ShellText sSelect; ShellState data; char *zErrMsg = 0; const char *zDiv = "("; @@ -10478,27 +10899,22 @@ static int do_meta_command(const char *zLine, ShellState *p){ int iSchema = 0; int bDebug = 0; int bNoSystemTabs = 0; - int bIndent = 0; int ii; - sqlite3_str *pSql; - sqlite3_stmt *pStmt = 0; - + open_db(p, 0); memcpy(&data, p, sizeof(data)); - data.mode.spec.bTitles = QRF_No; - data.mode.eMode = MODE_List; - data.mode.spec.eText = QRF_TEXT_Plain; - data.mode.spec.nCharLimit = 0; - data.mode.spec.zRowSep = "\n"; + data.showHeader = 0; + data.cMode = data.mode = MODE_Semi; + initText(&sSelect); for(ii=1; ii<nArg; ii++){ if( optionMatch(azArg[ii],"indent") ){ - bIndent = 1; + data.cMode = data.mode = MODE_Pretty; }else if( optionMatch(azArg[ii],"debug") ){ bDebug = 1; }else if( optionMatch(azArg[ii],"nosys") ){ bNoSystemTabs = 1; }else if( azArg[ii][0]=='-' ){ - cli_printf(stderr,"Unknown option: \"%s\"\n", azArg[ii]); + sqlite3_fprintf(stderr,"Unknown option: \"%s\"\n", azArg[ii]); rc = 1; goto meta_command_exit; }else if( zName==0 ){ @@ -10515,85 +10931,96 @@ static int do_meta_command(const char *zLine, ShellState *p){ || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0 || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0; if( isSchema ){ - cli_printf(p->out, - "CREATE TABLE %ssqlite_schema (\n" + char *new_argv[2], *new_colv[2]; + new_argv[0] = sqlite3_mprintf( + "CREATE TABLE %s (\n" " type text,\n" " name text,\n" " tbl_name text,\n" " rootpage integer,\n" " sql text\n" - ");\n", - sqlite3_strlike("sqlite_t%",zName,0)==0 ? "temp." : "" - ); + ")", zName); + shell_check_oom(new_argv[0]); + new_argv[1] = 0; + new_colv[0] = "sql"; + new_colv[1] = 0; + callback(&data, 1, new_argv, new_colv); + sqlite3_free(new_argv[0]); } } - rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); - if( rc ){ - shellDatabaseError(p->db); + if( zDiv ){ + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list", + -1, &pStmt, 0); + if( rc ){ + shellDatabaseError(p->db); + sqlite3_finalize(pStmt); + rc = 1; + goto meta_command_exit; + } + appendText(&sSelect, "SELECT sql FROM", 0); + iSchema = 0; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pStmt, 0); + char zScNum[30]; + sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema); + appendText(&sSelect, zDiv, 0); + zDiv = " UNION ALL "; + appendText(&sSelect, "SELECT shell_add_schema(sql,", 0); + if( sqlite3_stricmp(zDb, "main")!=0 ){ + appendText(&sSelect, zDb, '\''); + }else{ + appendText(&sSelect, "NULL", 0); + } + appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0); + appendText(&sSelect, zScNum, 0); + appendText(&sSelect, " AS snum, ", 0); + appendText(&sSelect, zDb, '\''); + appendText(&sSelect, " AS sname FROM ", 0); + appendText(&sSelect, zDb, quoteChar(zDb)); + appendText(&sSelect, ".sqlite_schema", 0); + } sqlite3_finalize(pStmt); - - rc = 1; - goto meta_command_exit; - } - pSql = sqlite3_str_new(p->db); - sqlite3_str_appendf(pSql, "SELECT sql FROM", 0); - iSchema = 0; - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zDb = (const char*)sqlite3_column_text(pStmt, 1); - char zScNum[30]; - sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema); - sqlite3_str_appendall(pSql, zDiv); - zDiv = " UNION ALL "; - if( sqlite3_stricmp(zDb, "main")==0 ){ - sqlite3_str_appendf(pSql, - "SELECT shell_format_schema(shell_add_schema(sql,NULL,name),%d)", - bIndent); - }else{ - sqlite3_str_appendf(pSql, - "SELECT shell_format_schema(shell_add_schema(sql,%Q,name),%d)", - zDb, bIndent); +#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS + if( zName ){ + appendText(&sSelect, + " UNION ALL SELECT shell_module_schema(name)," + " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list", + 0); + } +#endif + appendText(&sSelect, ") WHERE ", 0); + if( zName ){ + char *zQarg = sqlite3_mprintf("%Q", zName); + int bGlob; + shell_check_oom(zQarg); + bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 || + strchr(zName, '[') != 0; + if( strchr(zName, '.') ){ + appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0); + }else{ + appendText(&sSelect, "lower(tbl_name)", 0); + } + appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0); + appendText(&sSelect, zQarg, 0); + if( !bGlob ){ + appendText(&sSelect, " ESCAPE '\\' ", 0); + } + appendText(&sSelect, " AND ", 0); + sqlite3_free(zQarg); } - sqlite3_str_appendf(pSql, - " AS sql, type, tbl_name, name, rowid, %d AS snum, %Q as sname", - ++iSchema, zDb); - sqlite3_str_appendf(pSql," FROM \"%w\".sqlite_schema", zDb); - } - sqlite3_finalize(pStmt); -#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) \ - && !defined(SQLITE_OMIT_VIRTUALTABLE) - if( zName ){ - sqlite3_str_appendall(pSql, - " UNION ALL SELECT shell_module_schema(name)," - " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list"); - } -#endif - sqlite3_str_appendf(pSql, ") WHERE ", 0); - if( zName ){ - int bGlob; - bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 || - strchr(zName, '[') != 0; - if( strchr(zName, '.') ){ - sqlite3_str_appendall(pSql, "lower(format('%%s.%%s',sname,tbl_name))"); - }else{ - sqlite3_str_appendall(pSql, "lower(tbl_name)"); + if( bNoSystemTabs ){ + appendText(&sSelect, "name NOT LIKE 'sqlite__%%' ESCAPE '_' AND ", 0); } - if( bGlob ){ - sqlite3_str_appendf(pSql, " GLOB %Q AND ", zName); + appendText(&sSelect, "sql IS NOT NULL" + " ORDER BY snum, rowid", 0); + if( bDebug ){ + sqlite3_fprintf(p->out, "SQL: %s;\n", sSelect.zTxt); }else{ - sqlite3_str_appendf(pSql, " LIKE %Q ESCAPE '\\' AND ", zName); + rc = sqlite3_exec(p->db, sSelect.zTxt, callback, &data, &zErrMsg); } + freeText(&sSelect); } - if( bNoSystemTabs ){ - sqlite3_str_appendf(pSql, " name NOT LIKE 'sqlite__%%' ESCAPE '_' AND "); - } - sqlite3_str_appendf(pSql, "sql IS NOT NULL ORDER BY snum, rowid"); - if( bDebug ){ - cli_printf(p->out, "SQL: %s;\n", sqlite3_str_value(pSql)); - }else{ - rc = shell_exec(&data, sqlite3_str_value(pSql), &zErrMsg); - } - sqlite3_str_free(pSql); - if( zErrMsg ){ shellEmitError(zErrMsg); sqlite3_free(zErrMsg); @@ -10649,7 +11076,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ }else{ rc = sqlite3session_attach(pSession->p, azCmd[1]); if( rc ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "ERROR: sqlite3session_attach() returns %d\n",rc); rc = 0; } @@ -10669,7 +11096,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( pSession->p==0 ) goto session_not_open; out = sqlite3_fopen(azCmd[1], "wb"); if( out==0 ){ - cli_printf(stderr,"ERROR: cannot open \"%s\" for writing\n", + sqlite3_fprintf(stderr,"ERROR: cannot open \"%s\" for writing\n", azCmd[1]); }else{ int szChng; @@ -10680,12 +11107,12 @@ static int do_meta_command(const char *zLine, ShellState *p){ rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); } if( rc ){ - cli_printf(stdout, "Error: error code %d\n", rc); + sqlite3_fprintf(stdout, "Error: error code %d\n", rc); rc = 0; } if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "ERROR: Failed to write entire %d-byte output\n", szChng); } sqlite3_free(pChng); @@ -10713,7 +11140,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); if( pAuxDb->nSession ){ ii = sqlite3session_enable(pSession->p, ii); - cli_printf(p->out, + sqlite3_fprintf(p->out, "session %s enable flag = %d\n", pSession->zName, ii); } }else @@ -10750,7 +11177,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); if( pAuxDb->nSession ){ ii = sqlite3session_indirect(pSession->p, ii); - cli_printf(p->out, + sqlite3_fprintf(p->out, "session %s indirect flag = %d\n", pSession->zName, ii); } }else @@ -10763,7 +11190,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( nCmd!=1 ) goto session_syntax_error; if( pAuxDb->nSession ){ ii = sqlite3session_isempty(pSession->p); - cli_printf(p->out, + sqlite3_fprintf(p->out, "session %s isempty flag = %d\n", pSession->zName, ii); } }else @@ -10773,7 +11200,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ */ if( cli_strcmp(azCmd[0],"list")==0 ){ for(i=0; i<pAuxDb->nSession; i++){ - cli_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); + sqlite3_fprintf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); } }else @@ -10788,19 +11215,19 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( zName[0]==0 ) goto session_syntax_error; for(i=0; i<pAuxDb->nSession; i++){ if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ - cli_printf(stderr,"Session \"%s\" already exists\n", zName); + sqlite3_fprintf(stderr,"Session \"%s\" already exists\n", zName); goto meta_command_exit; } } if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); goto meta_command_exit; } pSession = &pAuxDb->aSession[pAuxDb->nSession]; rc = sqlite3session_create(p->db, azCmd[1], &pSession->p); if( rc ){ - cli_printf(stderr,"Cannot open session: error code=%d\n", rc); + sqlite3_fprintf(stderr,"Cannot open session: error code=%d\n", rc); rc = 0; goto meta_command_exit; } @@ -10824,7 +11251,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ int i, v; for(i=1; i<nArg; i++){ v = booleanValue(azArg[i]); - cli_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v); + sqlite3_fprintf(p->out, "%s: %d 0x%x\n", azArg[i], v, v); } } if( cli_strncmp(azArg[0]+9, "integer", n-9)==0 ){ @@ -10833,7 +11260,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ char zBuf[200]; v = integerValue(azArg[i]); sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v); - cli_puts(zBuf, p->out); + sqlite3_fputs(zBuf, p->out); } } }else @@ -10860,9 +11287,9 @@ static int do_meta_command(const char *zLine, ShellState *p){ bVerbose++; }else { - cli_printf(stderr, + sqlite3_fprintf(stderr, "Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]); - cli_puts("Should be one of: --init -v\n", stderr); + sqlite3_fputs("Should be one of: --init -v\n", stderr); rc = 1; goto meta_command_exit; } @@ -10907,10 +11334,10 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( zAns==0 ) continue; k = 0; if( bVerbose>0 ){ - cli_printf(stdout, "%d: %s %s\n", tno, zOp, zSql); + sqlite3_fprintf(stdout, "%d: %s %s\n", tno, zOp, zSql); } if( cli_strcmp(zOp,"memo")==0 ){ - cli_printf(p->out, "%s\n", zSql); + sqlite3_fprintf(p->out, "%s\n", zSql); }else if( cli_strcmp(zOp,"run")==0 ){ char *zErrMsg = 0; @@ -10919,22 +11346,22 @@ static int do_meta_command(const char *zLine, ShellState *p){ rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg); nTest++; if( bVerbose ){ - cli_printf(p->out, "Result: %s\n", str.zTxt); + sqlite3_fprintf(p->out, "Result: %s\n", str.zTxt); } if( rc || zErrMsg ){ nErr++; rc = 1; - cli_printf(p->out, "%d: error-code-%d: %s\n", tno, rc,zErrMsg); + sqlite3_fprintf(p->out, "%d: error-code-%d: %s\n", tno, rc,zErrMsg); sqlite3_free(zErrMsg); }else if( cli_strcmp(zAns,str.zTxt)!=0 ){ nErr++; rc = 1; - cli_printf(p->out, "%d: Expected: [%s]\n", tno, zAns); - cli_printf(p->out, "%d: Got: [%s]\n", tno, str.zTxt); + sqlite3_fprintf(p->out, "%d: Expected: [%s]\n", tno, zAns); + sqlite3_fprintf(p->out, "%d: Got: [%s]\n", tno, str.zTxt); } } else{ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Unknown operation \"%s\" on selftest line %d\n", zOp, tno); rc = 1; break; @@ -10943,7 +11370,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ sqlite3_finalize(pStmt); } /* End loop over k */ freeText(&str); - cli_printf(p->out, "%d errors out of %d tests\n", nErr, nTest); + sqlite3_fprintf(p->out, "%d errors out of %d tests\n", nErr, nTest); }else if( c=='s' && cli_strncmp(azArg[0], "separator", n)==0 ){ @@ -10952,10 +11379,12 @@ static int do_meta_command(const char *zLine, ShellState *p){ rc = 1; } if( nArg>=2 ){ - modeSetStr(&p->mode.spec.zColumnSep, azArg[1]); + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, + "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]); } if( nArg>=3 ){ - modeSetStr(&p->mode.spec.zRowSep,azArg[2]); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, + "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]); } }else @@ -10989,7 +11418,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ bDebug = 1; }else { - cli_printf(stderr, + sqlite3_fprintf(stderr, "Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]); showHelp(p->out, azArg[0]); rc = 1; @@ -11068,7 +11497,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ freeText(&sQuery); freeText(&sSql); if( bDebug ){ - cli_printf(p->out, "%s\n", zSql); + sqlite3_fprintf(p->out, "%s\n", zSql); }else{ shell_exec(p, zSql, 0); } @@ -11098,7 +11527,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ "' OR ') as query, tname from tabcols group by tname)" , zRevText); shell_check_oom(zRevText); - if( bDebug ) cli_printf(p->out, "%s\n", zRevText); + if( bDebug ) sqlite3_fprintf(p->out, "%s\n", zRevText); lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0); if( lrc!=SQLITE_OK ){ /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the @@ -11111,7 +11540,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0); sqlite3_stmt *pCheckStmt; lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0); - if( bDebug ) cli_printf(p->out, "%s\n", zGenQuery); + if( bDebug ) sqlite3_fprintf(p->out, "%s\n", zGenQuery); if( lrc!=SQLITE_OK ){ rc = 1; }else{ @@ -11119,7 +11548,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ double countIrreversible = sqlite3_column_double(pCheckStmt, 0); if( countIrreversible>0 ){ int sz = (int)(countIrreversible + 0.5); - cli_printf(stderr, + sqlite3_fprintf(stderr, "Digest includes %d invalidly encoded text field%s.\n", sz, (sz>1)? "s": ""); } @@ -11158,7 +11587,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ x = zCmd!=0 ? system(zCmd) : 1; /*consoleRenewSetup();*/ sqlite3_free(zCmd); - if( x ) cli_printf(stderr,"System command returns %d\n", x); + if( x ) sqlite3_fprintf(stderr,"System command returns %d\n", x); }else #endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */ @@ -11171,51 +11600,48 @@ static int do_meta_command(const char *zLine, ShellState *p){ rc = 1; goto meta_command_exit; } - cli_printf(p->out, "%12.12s: %s\n","echo", - azBool[(p->mode.mFlags & MFLG_ECHO)!=0]); - cli_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->mode.autoEQP&3]); - cli_printf(p->out, "%12.12s: %s\n","explain", - p->mode.autoExplain ? "auto" : "off"); - cli_printf(p->out, "%12.12s: %s\n","headers", - azBool[p->mode.spec.bTitles==QRF_Yes]); - if( p->mode.spec.eStyle==QRF_STYLE_Column - || p->mode.spec.eStyle==QRF_STYLE_Box - || p->mode.spec.eStyle==QRF_STYLE_Table - || p->mode.spec.eStyle==QRF_STYLE_Markdown + sqlite3_fprintf(p->out, "%12.12s: %s\n","echo", + azBool[ShellHasFlag(p, SHFLG_Echo)]); + sqlite3_fprintf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); + sqlite3_fprintf(p->out, "%12.12s: %s\n","explain", + p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); + sqlite3_fprintf(p->out, "%12.12s: %s\n","headers", + azBool[p->showHeader!=0]); + if( p->mode==MODE_Column + || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) ){ - cli_printf(p->out, + sqlite3_fprintf(p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode", - aModeInfo[p->mode.eMode].zName, p->mode.spec.nWrap, - p->mode.spec.bWordWrap==QRF_Yes ? "on" : "off", - p->mode.spec.eText==QRF_TEXT_Sql ? "" : "no"); + modeDescr[p->mode], p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no"); }else{ - cli_printf(p->out, "%12.12s: %s\n","mode", - aModeInfo[p->mode.eMode].zName); + sqlite3_fprintf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]); } - cli_printf(p->out, "%12.12s: ", "nullvalue"); - output_c_string(p->out, p->mode.spec.zNull); - cli_puts("\n", p->out); - cli_printf(p->out, "%12.12s: %s\n","output", + sqlite3_fprintf(p->out, "%12.12s: ", "nullvalue"); + output_c_string(p->out, p->nullValue); + sqlite3_fputs("\n", p->out); + sqlite3_fprintf(p->out, "%12.12s: %s\n","output", strlen30(p->outfile) ? p->outfile : "stdout"); - cli_printf(p->out, "%12.12s: ", "colseparator"); - output_c_string(p->out, p->mode.spec.zColumnSep); - cli_puts("\n", p->out); - cli_printf(p->out, "%12.12s: ", "rowseparator"); - output_c_string(p->out, p->mode.spec.zRowSep); - cli_puts("\n", p->out); + sqlite3_fprintf(p->out, "%12.12s: ", "colseparator"); + output_c_string(p->out, p->colSeparator); + sqlite3_fputs("\n", p->out); + sqlite3_fprintf(p->out, "%12.12s: ", "rowseparator"); + output_c_string(p->out, p->rowSeparator); + sqlite3_fputs("\n", p->out); switch( p->statsOn ){ case 0: zOut = "off"; break; default: zOut = "on"; break; case 2: zOut = "stmt"; break; case 3: zOut = "vmstep"; break; } - cli_printf(p->out, "%12.12s: %s\n","stats", zOut); - cli_printf(p->out, "%12.12s: ", "width"); - for(i=0; i<p->mode.spec.nWidth; i++){ - cli_printf(p->out, "%d ", (int)p->mode.spec.aWidth[i]); + sqlite3_fprintf(p->out, "%12.12s: %s\n","stats", zOut); + sqlite3_fprintf(p->out, "%12.12s: ", "width"); + for (i=0;i<p->nWidth;i++) { + sqlite3_fprintf(p->out, "%d ", p->colWidth[i]); } - cli_puts("\n", p->out); - cli_printf(p->out, "%12.12s: %s\n", "filename", + sqlite3_fputs("\n", p->out); + sqlite3_fprintf(p->out, "%12.12s: %s\n", "filename", p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); }else @@ -11241,9 +11667,11 @@ static int do_meta_command(const char *zLine, ShellState *p){ || cli_strncmp(azArg[0], "indexes", n)==0) ) ){ sqlite3_stmt *pStmt; - sqlite3_str *pSql; - const char *zPattern = nArg>1 ? azArg[1] : 0; - + char **azResult; + int nRow, nAlloc; + int ii; + ShellText s; + initText(&s); open_db(p, 0); rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); if( rc ){ @@ -11260,53 +11688,103 @@ static int do_meta_command(const char *zLine, ShellState *p){ sqlite3_finalize(pStmt); goto meta_command_exit; } - pSql = sqlite3_str_new(p->db); - while( sqlite3_step(pStmt)==SQLITE_ROW ){ + for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){ const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); if( zDbName==0 ) continue; - if( sqlite3_str_length(pSql) ){ - sqlite3_str_appendall(pSql, " UNION ALL "); - } + if( s.zTxt && s.zTxt[0] ) appendText(&s, " UNION ALL ", 0); if( sqlite3_stricmp(zDbName, "main")==0 ){ - sqlite3_str_appendall(pSql, "SELECT name FROM "); + appendText(&s, "SELECT name FROM ", 0); }else{ - sqlite3_str_appendf(pSql, "SELECT %Q||'.'||name FROM ", zDbName); + appendText(&s, "SELECT ", 0); + appendText(&s, zDbName, '\''); + appendText(&s, "||'.'||name FROM ", 0); } - sqlite3_str_appendf(pSql, "\"%w\".sqlite_schema", zDbName); + appendText(&s, zDbName, '"'); + appendText(&s, ".sqlite_schema ", 0); if( c=='t' ){ - sqlite3_str_appendf(pSql, - " WHERE type IN ('table','view')" - " AND name NOT LIKE 'sqlite__%%' ESCAPE '_'" - ); - if( zPattern ){ - sqlite3_str_appendf(pSql," AND name LIKE %Q", zPattern); - } + appendText(&s," WHERE type IN ('table','view')" + " AND name NOT LIKE 'sqlite__%' ESCAPE '_'" + " AND name LIKE ?1", 0); }else{ - sqlite3_str_appendf(pSql, " WHERE type='index'"); - if( zPattern ){ - sqlite3_str_appendf(pSql," AND tbl_name LIKE %Q", zPattern); - } + appendText(&s," WHERE type='index'" + " AND tbl_name LIKE ?1", 0); } } rc = sqlite3_finalize(pStmt); if( rc==SQLITE_OK ){ - sqlite3_str_appendall(pSql, " ORDER BY 1"); + appendText(&s, " ORDER BY 1", 0); + rc = sqlite3_prepare_v2(p->db, s.zTxt, -1, &pStmt, 0); } - - /* Run the SQL statement in "split" mode. */ - modePush(p); - modeChange(p, MODE_Split); - shell_exec(p, sqlite3_str_value(pSql), 0); - sqlite3_str_free(pSql); - modePop(p); + freeText(&s); if( rc ) return shellDatabaseError(p->db); + + /* Run the SQL statement prepared by the above block. Store the results + ** as an array of nul-terminated strings in azResult[]. */ + nRow = nAlloc = 0; + azResult = 0; + if( nArg>1 ){ + sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT); + }else{ + sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); + } + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + if( nRow>=nAlloc ){ + char **azNew; + sqlite3_int64 n2 = 2*(sqlite3_int64)nAlloc + 10; + azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); + shell_check_oom(azNew); + nAlloc = (int)n2; + azResult = azNew; + } + azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + shell_check_oom(azResult[nRow]); + nRow++; + } + if( sqlite3_finalize(pStmt)!=SQLITE_OK ){ + rc = shellDatabaseError(p->db); + } + + /* Pretty-print the contents of array azResult[] to the output */ + if( rc==0 && nRow>0 ){ + int len, maxlen = 0; + int i, j; + int nPrintCol, nPrintRow; + for(i=0; i<nRow; i++){ + len = strlen30(azResult[i]); + if( len>maxlen ) maxlen = len; + } + nPrintCol = 80/(maxlen+2); + if( nPrintCol<1 ) nPrintCol = 1; + nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; + for(i=0; i<nPrintRow; i++){ + for(j=i; j<nRow; j+=nPrintRow){ + char *zSp = j<nPrintRow ? "" : " "; + sqlite3_fprintf(p->out, + "%s%-*s", zSp, maxlen, azResult[j] ? azResult[j]:""); + } + sqlite3_fputs("\n", p->out); + } + } + + for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]); + sqlite3_free(azResult); }else - /* Set the p->zTestcase name and begin redirecting output into - ** the cli_output_capture sqlite3_str */ +#ifndef SQLITE_SHELL_FIDDLE + /* Begin redirecting output to the file "testcase-out.txt" */ if( c=='t' && cli_strcmp(azArg[0],"testcase")==0 ){ - rc = dotCmdTestcase(p); + output_reset(p); + p->out = output_file_open("testcase-out.txt"); + if( p->out==0 ){ + eputz("Error: cannot open 'testcase-out.txt'\n"); + } + if( nArg>=2 ){ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); + }else{ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); + } }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ #ifndef SQLITE_UNTESTABLE if( c=='t' && n>=8 && cli_strncmp(azArg[0], "testctrl", n)==0 ){ @@ -11359,10 +11837,10 @@ static int do_meta_command(const char *zLine, ShellState *p){ /* --help lists all test-controls */ if( cli_strcmp(zCmd,"help")==0 ){ - cli_puts("Available test-controls:\n", p->out); + sqlite3_fputs("Available test-controls:\n", p->out); for(i=0; i<ArraySize(aCtrl); i++){ if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue; - cli_printf(p->out, " .testctrl %s %s\n", + sqlite3_fprintf(p->out, " .testctrl %s %s\n", aCtrl[i].zCtrlName, aCtrl[i].zUsage); } rc = 1; @@ -11379,7 +11857,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ testctrl = aCtrl[i].ctrlCode; iCtrl = i; }else{ - cli_printf(stderr,"Error: ambiguous test-control: \"%s\"\n" + sqlite3_fprintf(stderr,"Error: ambiguous test-control: \"%s\"\n" "Use \".testctrl --help\" for help\n", zCmd); rc = 1; goto meta_command_exit; @@ -11387,7 +11865,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ } } if( testctrl<0 ){ - cli_printf(stderr,"Error: unknown test-control: %s\n" + sqlite3_fprintf(stderr,"Error: unknown test-control: %s\n" "Use \".testctrl --help\" for help\n", zCmd); }else{ switch(testctrl){ @@ -11467,13 +11945,13 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( sqlite3_stricmp(zLabel, aLabel[jj].zLabel)==0 ) break; } if( jj>=ArraySize(aLabel) ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Error: no such optimization: \"%s\"\n", zLabel); - cli_puts("Should be one of:", stderr); + sqlite3_fputs("Should be one of:", stderr); for(jj=0; jj<ArraySize(aLabel); jj++){ - cli_printf(stderr," %s", aLabel[jj].zLabel); + sqlite3_fprintf(stderr," %s", aLabel[jj].zLabel); } - cli_puts("\n", stderr); + sqlite3_fputs("\n", stderr); rc = 1; goto meta_command_exit; } @@ -11491,23 +11969,23 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( m & newOpt ) nOff++; } if( nOff<12 ){ - cli_puts("+All", p->out); + sqlite3_fputs("+All", p->out); for(ii=0; ii<ArraySize(aLabel); ii++){ if( !aLabel[ii].bDsply ) continue; if( (newOpt & aLabel[ii].mask)!=0 ){ - cli_printf(p->out, " -%s", aLabel[ii].zLabel); + sqlite3_fprintf(p->out, " -%s", aLabel[ii].zLabel); } } }else{ - cli_puts("-All", p->out); + sqlite3_fputs("-All", p->out); for(ii=0; ii<ArraySize(aLabel); ii++){ if( !aLabel[ii].bDsply ) continue; if( (newOpt & aLabel[ii].mask)==0 ){ - cli_printf(p->out, " +%s", aLabel[ii].zLabel); + sqlite3_fprintf(p->out, " +%s", aLabel[ii].zLabel); } } } - cli_puts("\n", p->out); + sqlite3_fputs("\n", p->out); rc2 = isOk = 3; break; } @@ -11547,7 +12025,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ sqlite3 *db; if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){ sqlite3_randomness(sizeof(ii),&ii); - cli_printf(stdout, "-- random seed: %d\n", ii); + sqlite3_fprintf(stdout, "-- random seed: %d\n", ii); } if( nArg==3 ){ db = 0; @@ -11600,7 +12078,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ case SQLITE_TESTCTRL_SEEK_COUNT: { u64 x = 0; rc2 = sqlite3_test_control(testctrl, p->db, &x); - cli_printf(p->out, "%llu\n", x); + sqlite3_fprintf(p->out, "%llu\n", x); isOk = 3; break; } @@ -11631,11 +12109,11 @@ static int do_meta_command(const char *zLine, ShellState *p){ int val = 0; rc2 = sqlite3_test_control(testctrl, -id, &val); if( rc2!=SQLITE_OK ) break; - if( id>1 ) cli_puts(" ", p->out); - cli_printf(p->out, "%d: %d", id, val); + if( id>1 ) sqlite3_fputs(" ", p->out); + sqlite3_fprintf(p->out, "%d: %d", id, val); id++; } - if( id>1 ) cli_puts("\n", p->out); + if( id>1 ) sqlite3_fputs("\n", p->out); isOk = 3; } break; @@ -11674,7 +12152,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ int ii, jj, x; int *aOp; if( nArg!=4 ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "ERROR - should be: \".testctrl bitvec_test SIZE INT-ARRAY\"\n" ); rc = 1; @@ -11697,7 +12175,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ } aOp[jj] = x; x = sqlite3_test_control(testctrl, iSize, aOp); - cli_printf(p->out, "result: %d\n", x); + sqlite3_fprintf(p->out, "result: %d\n", x); free(aOp); break; } @@ -11720,21 +12198,21 @@ static int do_meta_command(const char *zLine, ShellState *p){ faultsim_state.nHit = 0; sqlite3_test_control(testctrl, faultsim_callback); }else if( cli_strcmp(z,"status")==0 ){ - cli_printf(p->out, "faultsim.iId: %d\n", + sqlite3_fprintf(p->out, "faultsim.iId: %d\n", faultsim_state.iId); - cli_printf(p->out, "faultsim.iErr: %d\n", + sqlite3_fprintf(p->out, "faultsim.iErr: %d\n", faultsim_state.iErr); - cli_printf(p->out, "faultsim.iCnt: %d\n", + sqlite3_fprintf(p->out, "faultsim.iCnt: %d\n", faultsim_state.iCnt); - cli_printf(p->out, "faultsim.nHit: %d\n", + sqlite3_fprintf(p->out, "faultsim.nHit: %d\n", faultsim_state.nHit); - cli_printf(p->out, "faultsim.iInterval: %d\n", + sqlite3_fprintf(p->out, "faultsim.iInterval: %d\n", faultsim_state.iInterval); - cli_printf(p->out, "faultsim.eVerbose: %d\n", + sqlite3_fprintf(p->out, "faultsim.eVerbose: %d\n", faultsim_state.eVerbose); - cli_printf(p->out, "faultsim.nRepeat: %d\n", + sqlite3_fprintf(p->out, "faultsim.nRepeat: %d\n", faultsim_state.nRepeat); - cli_printf(p->out, "faultsim.nSkip: %d\n", + sqlite3_fprintf(p->out, "faultsim.nSkip: %d\n", faultsim_state.nSkip); }else if( cli_strcmp(z,"-v")==0 ){ if( faultsim_state.eVerbose<2 ) faultsim_state.eVerbose++; @@ -11753,7 +12231,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ }else if( cli_strcmp(z,"-?")==0 || sqlite3_strglob("*help*",z)==0){ bShowHelp = 1; }else{ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Unrecognized fault_install argument: \"%s\"\n", azArg[kk]); rc = 1; @@ -11762,7 +12240,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ } } if( bShowHelp ){ - cli_puts( + sqlite3_fputs( "Usage: .testctrl fault_install ARGS\n" "Possible arguments:\n" " off Disable faultsim\n" @@ -11784,13 +12262,13 @@ static int do_meta_command(const char *zLine, ShellState *p){ } } if( isOk==0 && iCtrl>=0 ){ - cli_printf(p->out, + sqlite3_fprintf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); rc = 1; }else if( isOk==1 ){ - cli_printf(p->out, "%d\n", rc2); + sqlite3_fprintf(p->out, "%d\n", rc2); }else if( isOk==2 ){ - cli_printf(p->out, "0x%08x\n", rc2); + sqlite3_fprintf(p->out, "0x%08x\n", rc2); } }else #endif /* !defined(SQLITE_UNTESTABLE) */ @@ -11802,17 +12280,13 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( c=='t' && n>=5 && cli_strncmp(azArg[0], "timer", n)==0 ){ if( nArg==2 ){ - if( cli_strcmp(azArg[1],"once")==0 ){ - p->enableTimer = 1; - }else{ - p->enableTimer = 2*booleanValue(azArg[1]); - } - if( p->enableTimer && !HAS_TIMER ){ + enableTimer = booleanValue(azArg[1]); + if( enableTimer && !HAS_TIMER ){ eputz("Error: timer not available on this system.\n"); - p->enableTimer = 0; + enableTimer = 0; } }else{ - eputz("Usage: .timer on|off|once\n"); + eputz("Usage: .timer on|off\n"); rc = 1; } }else @@ -11849,13 +12323,13 @@ static int do_meta_command(const char *zLine, ShellState *p){ mType |= SQLITE_TRACE_CLOSE; } else { - cli_printf(stderr,"Unknown option \"%s\" on \".trace\"\n", z); + sqlite3_fprintf(stderr,"Unknown option \"%s\" on \".trace\"\n", z); rc = 1; goto meta_command_exit; } }else{ output_file_close(p->traceOut); - p->traceOut = output_file_open(p, z); + p->traceOut = output_file_open(z); } } if( p->traceOut==0 ){ @@ -11894,21 +12368,21 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; - cli_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, + sqlite3_fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); #if SQLITE_HAVE_ZLIB - cli_printf(p->out, "zlib version %s\n", zlibVersion()); + sqlite3_fprintf(p->out, "zlib version %s\n", zlibVersion()); #endif #define CTIMEOPT_VAL_(opt) #opt #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) #if defined(__clang__) && defined(__clang_major__) - cli_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." + sqlite3_fprintf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." CTIMEOPT_VAL(__clang_minor__) "." CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); #elif defined(_MSC_VER) - cli_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); + sqlite3_fprintf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); #elif defined(__GNUC__) && defined(__VERSION__) - cli_printf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); + sqlite3_fprintf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); #endif }else @@ -11918,10 +12392,10 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( p->db ){ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs); if( pVfs ){ - cli_printf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName); - cli_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - cli_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - cli_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + sqlite3_fprintf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName); + sqlite3_fprintf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); + sqlite3_fprintf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); + sqlite3_fprintf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); } } }else @@ -11933,13 +12407,13 @@ static int do_meta_command(const char *zLine, ShellState *p){ sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); } for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){ - cli_printf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName, + sqlite3_fprintf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName, pVfs==pCurrent ? " <--- CURRENT" : ""); - cli_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - cli_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - cli_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + sqlite3_fprintf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); + sqlite3_fprintf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); + sqlite3_fprintf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); if( pVfs->pNext ){ - cli_puts("-----------------------------------\n", p->out); + sqlite3_fputs("-----------------------------------\n", p->out); } } }else @@ -11950,7 +12424,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( p->db ){ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName); if( zVfsName ){ - cli_printf(p->out, "%s\n", zVfsName); + sqlite3_fprintf(p->out, "%s\n", zVfsName); sqlite3_free(zVfsName); } } @@ -11963,31 +12437,31 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( c=='w' && cli_strncmp(azArg[0], "width", n)==0 ){ int j; - p->mode.spec.nWidth = nArg-1; - p->mode.spec.aWidth = realloc(p->mode.spec.aWidth, - (p->mode.spec.nWidth+1)*sizeof(short int)); - shell_check_oom(p->mode.spec.aWidth); + assert( nArg<=ArraySize(azArg) ); + p->nWidth = nArg-1; + p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2); + if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory(); + if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth]; for(j=1; j<nArg; j++){ i64 w = integerValue(azArg[j]); - if( w < -QRF_MAX_WIDTH ) w = -QRF_MAX_WIDTH; - if( w > QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH; - p->mode.spec.aWidth[j-1] = (short int)w; + if( w < -30000 ) w = -30000; + if( w > +30000 ) w = +30000; + p->colWidth[j-1] = (int)w; } }else { - cli_printf(stderr,"Error: unknown command or invalid arguments: " + sqlite3_fprintf(stderr,"Error: unknown command or invalid arguments: " " \"%s\". Enter \".help\" for help\n", azArg[0]); rc = 1; } meta_command_exit: - if( p->nPopOutput ){ - p->nPopOutput--; - if( p->nPopOutput==0 ) output_reset(p); + if( p->outCount ){ + p->outCount--; + if( p->outCount==0 ) output_reset(p); } p->bSafeMode = p->bSafeModePersist; - p->dot.nArg = 0; return rc; } @@ -12224,9 +12698,9 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ open_db(p, 0); if( ShellHasFlag(p,SHFLG_Backslash) ) resolve_backslashes(zSql); if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; - BEGIN_TIMER(p); + BEGIN_TIMER; rc = shell_exec(p, zSql, &zErrMsg); - END_TIMER(p); + END_TIMER(p->out); if( rc || zErrMsg ){ char zPrefix[100]; const char *zErrorTail; @@ -12250,7 +12724,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ }else{ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); } - cli_printf(stderr,"%s %s\n", zPrefix, zErrorTail); + sqlite3_fprintf(stderr,"%s %s\n", zPrefix, zErrorTail); sqlite3_free(zErrMsg); zErrMsg = 0; return 1; @@ -12259,7 +12733,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ sqlite3_snprintf(sizeof(zLineBuf), zLineBuf, "changes: %lld total_changes: %lld", sqlite3_changes64(p->db), sqlite3_total_changes64(p->db)); - cli_printf(p->out, "%s\n", zLineBuf); + sqlite3_fprintf(p->out, "%s\n", zLineBuf); } if( doAutoDetectRestore(p, zSql) ) return 1; @@ -12267,8 +12741,8 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ } static void echo_group_input(ShellState *p, const char *zDo){ - if( p->mode.mFlags & MFLG_ECHO ){ - cli_printf(p->out, "%s\n", zDo); + if( ShellHasFlag(p, SHFLG_Echo) ){ + sqlite3_fprintf(p->out, "%s\n", zDo); fflush(p->out); } } @@ -12325,19 +12799,14 @@ static int process_input(ShellState *p, const char *zSrc){ int errCnt = 0; /* Number of errors seen */ i64 startline = 0; /* Line number for start of current input */ QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ - const char *saved_zInFile; /* Prior value of p->zInFile */ - i64 saved_lineno; /* Prior value of p->lineno */ if( p->inputNesting==MAX_INPUT_NESTING ){ /* This will be more informative in a later version. */ - cli_printf(stderr,"%s: Input nesting limit (%d) reached at line %lld." + sqlite3_fprintf(stderr,"%s: Input nesting limit (%d) reached at line %lld." " Check recursion.\n", zSrc, MAX_INPUT_NESTING, p->lineno); return 1; } ++p->inputNesting; - saved_zInFile = p->zInFile; - p->zInFile = zSrc; - saved_lineno = p->lineno; p->lineno = 0; CONTINUE_PROMPT_RESET; while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ @@ -12345,7 +12814,7 @@ static int process_input(ShellState *p, const char *zSrc){ zLine = one_input_line(p->in, zLine, nSql>0); if( zLine==0 ){ /* End of input */ - if( p->in==0 && stdin_is_interactive ) cli_puts("\n", p->out); + if( p->in==0 && stdin_is_interactive ) sqlite3_fputs("\n", p->out); break; } if( seenInterrupt ){ @@ -12402,7 +12871,7 @@ static int process_input(ShellState *p, const char *zSrc){ if( nSql>0x7fff0000 ){ char zSize[100]; sqlite3_snprintf(sizeof(zSize),zSize,"%,lld",nSql); - cli_printf(stderr, "%s:%lld: Input SQL is too big: %s bytes\n", + sqlite3_fprintf(stderr, "%s:%lld: Input SQL is too big: %s bytes\n", zSrc, startline, zSize); nSql = 0; errCnt++; @@ -12412,16 +12881,12 @@ static int process_input(ShellState *p, const char *zSrc){ errCnt += runOneSqlLine(p, zSql, p->in, startline); CONTINUE_PROMPT_RESET; nSql = 0; - if( p->nPopOutput ){ + if( p->outCount ){ output_reset(p); - p->nPopOutput = 0; + p->outCount = 0; }else{ clearTempFile(p); } - if( p->nPopMode ){ - modePop(p); - p->nPopMode = 0; - } p->bSafeMode = p->bSafeModePersist; qss = QSS_Start; }else if( nSql && QSS_PLAINWHITE(qss) ){ @@ -12439,8 +12904,6 @@ static int process_input(ShellState *p, const char *zSrc){ free(zSql); free(zLine); --p->inputNesting; - p->zInFile = saved_zInFile; - p->lineno = saved_lineno; return errCnt>0; } @@ -12601,13 +13064,13 @@ static void process_sqliterc( p->in = sqliterc ? sqlite3_fopen(sqliterc,"rb") : 0; if( p->in ){ if( stdin_is_interactive ){ - cli_printf(stderr,"-- Loading resources from %s\n", sqliterc); + sqlite3_fprintf(stderr,"-- Loading resources from %s\n", sqliterc); } - if( process_input(p, sqliterc) && bail_on_error ) cli_exit(1); + if( process_input(p, sqliterc) && bail_on_error ) exit(1); fclose(p->in); }else if( sqliterc_override!=0 ){ - cli_printf(stderr,"cannot open: \"%s\"\n", sqliterc); - if( bail_on_error ) cli_exit(1); + sqlite3_fprintf(stderr,"cannot open: \"%s\"\n", sqliterc); + if( bail_on_error ) exit(1); } p->in = inSaved; p->lineno = savedLineno; @@ -12629,8 +13092,8 @@ static const char zOptions[] = " -bail stop after hitting an error\n" " -batch force batch I/O\n" " -box set output mode to 'box'\n" - " -cmd COMMAND run \"COMMAND\" before reading stdin\n" " -column set output mode to 'column'\n" + " -cmd COMMAND run \"COMMAND\" before reading stdin\n" " -csv set output mode to 'csv'\n" #if !defined(SQLITE_OMIT_DESERIALIZE) " -deserialize open the database using sqlite3_deserialize()\n" @@ -12661,7 +13124,6 @@ static const char zOptions[] = #endif " -newline SEP set output row separator. Default: '\\n'\n" " -nofollow refuse to open symbolic links to database files\n" - " -noinit Do not read the ~/.sqliterc file at startup\n" " -nonce STRING set the safe-mode escape nonce\n" " -no-rowid-in-view Disable rowid-in-view using sqlite3_config()\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" @@ -12670,7 +13132,6 @@ static const char zOptions[] = " -quote set output mode to 'quote'\n" " -readonly open the database read-only\n" " -safe enable safe-mode\n" - " -screenwidth N use N as the default screenwidth \n" " -separator SEP set output column separator. Default: '|'\n" #ifdef SQLITE_ENABLE_SORTER_REFERENCES " -sorterref SIZE sorter references threshold size\n" @@ -12687,11 +13148,11 @@ static const char zOptions[] = #endif ; static void usage(int showDetail){ - cli_printf(stderr,"Usage: %s [OPTIONS] [FILENAME [SQL...]]\n" + sqlite3_fprintf(stderr,"Usage: %s [OPTIONS] [FILENAME [SQL...]]\n" "FILENAME is the name of an SQLite database. A new database is created\n" "if the file does not previously exist. Defaults to :memory:.\n", Argv0); if( showDetail ){ - cli_printf(stderr,"OPTIONS include:\n%s", zOptions); + sqlite3_fprintf(stderr,"OPTIONS include:\n%s", zOptions); }else{ eputz("Use the -help option for additional information\n"); } @@ -12712,11 +13173,19 @@ static void verify_uninitialized(void){ /* ** Initialize the state information in data */ -static void main_init(ShellState *p) { - memset(p, 0, sizeof(*p)); - p->pAuxDb = &p->aAuxDb[0]; - p->shellFlgs = SHFLG_Lookaside; - sqlite3_config(SQLITE_CONFIG_LOG, shellLog, p); +static void main_init(ShellState *data) { + memset(data, 0, sizeof(*data)); + data->normalMode = data->cMode = data->mode = MODE_List; + data->autoExplain = 1; +#ifdef _WIN32 + data->crlfMode = 1; +#endif + data->pAuxDb = &data->aAuxDb[0]; + memcpy(data->colSeparator,SEP_Column, 2); + memcpy(data->rowSeparator,SEP_Row, 2); + data->showHeader = 0; + data->shellFlgs = SHFLG_Lookaside; + sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); #if !defined(SQLITE_SHELL_FIDDLE) verify_uninitialized(); #endif @@ -12731,18 +13200,22 @@ static void main_init(ShellState *p) { */ #if defined(_WIN32) || defined(WIN32) static void printBold(const char *zText){ +#if !SQLITE_OS_WINRT HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO defaultScreenInfo; GetConsoleScreenBufferInfo(out, &defaultScreenInfo); SetConsoleTextAttribute(out, FOREGROUND_RED|FOREGROUND_INTENSITY ); +#endif sputz(stdout, zText); +#if !SQLITE_OS_WINRT SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); +#endif } #else static void printBold(const char *zText){ - cli_printf(stdout, "\033[1m%s\033[0m", zText); + sqlite3_fprintf(stdout, "\033[1m%s\033[0m", zText); } #endif @@ -12752,9 +13225,9 @@ static void printBold(const char *zText){ */ static char *cmdline_option_value(int argc, char **argv, int i){ if( i==argc ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "%s: Error: missing argument to %s\n", argv[0], argv[argc-1]); - cli_exit(1); + exit(1); } return argv[i]; } @@ -12767,66 +13240,30 @@ static void sayAbnormalExit(void){ */ static int vfstraceOut(const char *z, void *pArg){ ShellState *p = (ShellState*)pArg; - cli_puts(z, p->out); + sqlite3_fputs(z, p->out); fflush(p->out); return 1; } -/* Alternative name to the entry point for Fiddle */ +#ifndef SQLITE_SHELL_IS_UTF8 +# if (defined(_WIN32) || defined(WIN32)) \ + && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__))) +# define SQLITE_SHELL_IS_UTF8 (0) +# else +# define SQLITE_SHELL_IS_UTF8 (1) +# endif +#endif + #ifdef SQLITE_SHELL_FIDDLE # define main fiddle_main #endif -/* Use the wmain() entry point on Windows. Translate arguments to -** UTF8, then invoke the traditional main() entry point which is -** renamed using a #define to utf8_main() . -*/ -#if defined(_WIN32) && !defined(main) -# define main utf8_main /* Rename entry point to utf_main() */ -int SQLITE_CDECL utf8_main(int,char**); /* Forward declaration */ -int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ - int rc, i; - char **argv = malloc( sizeof(char*) * (argc+1) ); - char **orig = argv; - if( argv==0 ){ - fprintf(stderr, "malloc failed\n"); - exit(1); - } - for(i=0; i<argc; i++){ - int nByte = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, 0, 0, 0, 0); - if( nByte==0 ){ - argv[i] = 0; - }else{ - argv[i] = malloc( nByte ); - if( argv[i]==0 ){ - fprintf(stderr, "malloc failed\n"); - exit(1); - } - nByte = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i],nByte,0,0); - if( nByte==0 ){ - free(argv[i]); - argv[i] = 0; - } - } - } - argv[argc] = 0; - rc = utf8_main(argc, argv); - for(i=0; i<argc; i++) free(orig[i]); - free(argv); - return rc; -} -#endif /* WIN32 */ - -/* -** This is the main entry point for the process. Everything starts here. -** -** The "main" identifier may have been #defined to something else: -** -** utf8_main On Windows -** fiddle_main In Fiddle -** sqlite3_shell Other projects that use shell.c as a subroutine -*/ +#if SQLITE_SHELL_IS_UTF8 int SQLITE_CDECL main(int argc, char **argv){ +#else +int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ + char **argv; +#endif #ifdef SQLITE_DEBUG sqlite3_int64 mem_main_enter = 0; #endif @@ -12841,13 +13278,15 @@ int SQLITE_CDECL main(int argc, char **argv){ int rc = 0; int warnInmemoryDb = 0; int readStdin = 1; - int noInit = 0; /* Do not read ~/.sqliterc if true */ int nCmd = 0; int nOptsEnd = argc; int bEnableVfstrace = 0; char **azCmd = 0; - int *aiCmd = 0; const char *zVfs = 0; /* Value of -vfs command-line option */ +#if !SQLITE_SHELL_IS_UTF8 + char **argvToFree = 0; + int argcToFree = 0; +#endif setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ #ifdef SQLITE_SHELL_FIDDLE @@ -12866,7 +13305,7 @@ int SQLITE_CDECL main(int argc, char **argv){ if( getenv("SQLITE_DEBUG_BREAK") ){ if( isatty(0) && isatty(2) ){ char zLine[100]; - cli_printf(stderr, + sqlite3_fprintf(stderr, "attach debugger to process %d and press ENTER to continue...", GETPID()); if( sqlite3_fgets(zLine, sizeof(zLine), stdin)!=0 @@ -12876,7 +13315,11 @@ int SQLITE_CDECL main(int argc, char **argv){ } }else{ #if defined(_WIN32) || defined(WIN32) +#if SQLITE_OS_WINRT + __debugbreak(); +#else DebugBreak(); +#endif #elif defined(SIGTRAP) raise(SIGTRAP); #endif @@ -12894,7 +13337,7 @@ int SQLITE_CDECL main(int argc, char **argv){ #if USE_SYSTEM_SQLITE+0!=1 if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){ - cli_printf(stderr, + sqlite3_fprintf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", sqlite3_sourceid(), SQLITE_SOURCE_ID); exit(1); @@ -12902,6 +13345,32 @@ int SQLITE_CDECL main(int argc, char **argv){ #endif main_init(&data); + /* On Windows, we must translate command-line arguments into UTF-8. + ** The SQLite memory allocator subsystem has to be enabled in order to + ** do this. But we want to run an sqlite3_shutdown() afterwards so that + ** subsequent sqlite3_config() calls will work. So copy all results into + ** memory that does not come from the SQLite memory allocator. + */ +#if !SQLITE_SHELL_IS_UTF8 + sqlite3_initialize(); + argvToFree = malloc(sizeof(argv[0])*argc*2); + shell_check_oom(argvToFree); + argcToFree = argc; + argv = argvToFree + argc; + for(i=0; i<argc; i++){ + char *z = sqlite3_win32_unicode_to_utf8(wargv[i]); + i64 n; + shell_check_oom(z); + n = strlen(z); + argv[i] = malloc( n+1 ); + shell_check_oom(argv[i]); + memcpy(argv[i], z, n+1); + argvToFree[i] = argv[i]; + sqlite3_free(z); + } + sqlite3_shutdown(); +#endif + assert( argc>=1 && argv && argv[0] ); Argv0 = argv[0]; @@ -12917,7 +13386,7 @@ int SQLITE_CDECL main(int argc, char **argv){ } #endif - /* Do an initial pass through the command-line arguments to locate + /* Do an initial pass through the command-line argument to locate ** the name of the database file, the name of the initialization file, ** the size of the alternative malloc heap, options affecting commands ** or SQL run from the command line, and the first command to execute. @@ -12929,20 +13398,16 @@ int SQLITE_CDECL main(int argc, char **argv){ char *z; z = argv[i]; if( z[0]!='-' || i>nOptsEnd ){ - if( data.aAuxDb->zDbFilename==0 && !isScriptFile(z,1) ){ + if( data.aAuxDb->zDbFilename==0 ){ data.aAuxDb->zDbFilename = z; }else{ /* Excess arguments are interpreted as SQL (or dot-commands) and ** mean that nothing is read from stdin */ readStdin = 0; - stdin_is_interactive = 0; nCmd++; azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd); shell_check_oom(azCmd); - aiCmd = realloc(aiCmd, sizeof(aiCmd[0])*nCmd); - shell_check_oom(azCmd); azCmd[nCmd-1] = z; - aiCmd[nCmd-1] = i; } continue; } @@ -12965,15 +13430,6 @@ int SQLITE_CDECL main(int argc, char **argv){ ** we do the actual processing of arguments later in a second pass. */ stdin_is_interactive = 0; - stdout_is_console = 0; - modeChange(&data, MODE_BATCH); - }else if( cli_strcmp(z,"-screenwidth")==0 ){ - int n = atoi(cmdline_option_value(argc, argv, ++i)); - if( n<2 ){ - sqlite3_fprintf(stderr,"minimum --screenwidth is 2\n"); - exit(1); - } - stdout_tty_width = n; }else if( cli_strcmp(z,"-utf8")==0 ){ }else if( cli_strcmp(z,"-no-utf8")==0 ){ }else if( cli_strcmp(z,"-no-rowid-in-view")==0 ){ @@ -13007,7 +13463,7 @@ int SQLITE_CDECL main(int argc, char **argv){ int szHdr = 0; sqlite3_config(SQLITE_CONFIG_PCACHE_HDRSZ, &szHdr); sz += szHdr; - cli_printf(stdout, "Page cache size increased to %d to accommodate" + sqlite3_fprintf(stdout, "Page cache size increased to %d to accommodate" " the %d-byte headers\n", (int)sz, szHdr); } verify_uninitialized(); @@ -13068,8 +13524,6 @@ int SQLITE_CDECL main(int argc, char **argv){ data.openFlags |= SQLITE_OPEN_READONLY; }else if( cli_strcmp(z,"-nofollow")==0 ){ data.openFlags |= SQLITE_OPEN_NOFOLLOW; - }else if( cli_strcmp(z,"-noinit")==0 ){ - noInit = 1; }else if( cli_strcmp(z,"-exclusive")==0 ){ /* UNDOCUMENTED */ data.openFlags |= SQLITE_OPEN_EXCLUSIVE; }else if( cli_strcmp(z,"-ifexists")==0 ){ @@ -13097,16 +13551,6 @@ int SQLITE_CDECL main(int argc, char **argv){ }else if( cli_strcmp(z,"-escape")==0 && i+1<argc ){ /* skip over the argument */ i++; - }else if( cli_strcmp(z,"-test-argv")==0 ){ - /* Undocumented test option. Print the values in argv[] and exit. - ** Use this to verify that any translation of the argv[], for example - ** on Windows that receives wargv[] from the OS and must convert - ** to UTF8 prior to calling this routine. */ - int kk; - for(kk=0; kk<argc; kk++){ - sqlite3_fprintf(stdout,"argv[%d] = \"%s\"\n", kk, argv[kk]); - } - return 0; } } #ifndef SQLITE_SHELL_FIDDLE @@ -13133,27 +13577,8 @@ int SQLITE_CDECL main(int argc, char **argv){ sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs); if( pVfs ){ sqlite3_vfs_register(pVfs, 1); - } -#if !defined(SQLITE_OMIT_LOAD_EXTENSION) - else if( access(zVfs,0)==0 ){ - /* If the VFS name is not the name of an existing VFS, but it is - ** the name of a file, then try to load that file as an extension. - ** Presumably the extension implements the desired VFS. */ - sqlite3 *db = 0; - char *zErr = 0; - sqlite3_open(":memory:", &db); - sqlite3_enable_load_extension(db, 1); - rc = sqlite3_load_extension(db, zVfs, 0, &zErr); - sqlite3_close(db); - if( (rc&0xff)!=SQLITE_OK ){ - cli_printf(stderr, "could not load extension VFS \"%s\": %s\n", - zVfs, zErr); - exit(1); - } - } -#endif - else{ - cli_printf(stderr,"no such VFS: \"%s\"\n", zVfs); + }else{ + sqlite3_fprintf(stderr,"no such VFS: \"%s\"\n", zVfs); exit(1); } } @@ -13163,10 +13588,9 @@ int SQLITE_CDECL main(int argc, char **argv){ data.pAuxDb->zDbFilename = ":memory:"; warnInmemoryDb = argc==1; #else - cli_printf(stderr, + sqlite3_fprintf(stderr, "%s: Error: no database filename specified\n", Argv0); - rc = 1; - goto shell_main_exit; + return 1; #endif } data.out = stdout; @@ -13176,7 +13600,6 @@ int SQLITE_CDECL main(int argc, char **argv){ #ifndef SQLITE_SHELL_FIDDLE sqlite3_appendvfs_init(0,0,0); #endif - modeDefault(&data); /* Go ahead and open the database file if it already exists. If the ** file does not exist, delay opening it. This prevents empty database @@ -13191,9 +13614,9 @@ int SQLITE_CDECL main(int argc, char **argv){ ** is given on the command line, look for a file named ~/.sqliterc and ** try to process it. */ - if( !noInit ) process_sqliterc(&data,zInitFile); + process_sqliterc(&data,zInitFile); - /* Make a second pass through the command-line arguments and set + /* Make a second pass through the command-line argument and set ** options. This second pass is delayed until after the initialization ** file is processed so that the command-line arguments will override ** settings in the initialization file. @@ -13205,44 +13628,45 @@ int SQLITE_CDECL main(int argc, char **argv){ if( cli_strcmp(z,"-init")==0 ){ i++; }else if( cli_strcmp(z,"-html")==0 ){ - modeChange(&data, MODE_Html); + data.mode = MODE_Html; }else if( cli_strcmp(z,"-list")==0 ){ - modeChange(&data, MODE_List); + data.mode = MODE_List; }else if( cli_strcmp(z,"-quote")==0 ){ - modeChange(&data, MODE_Quote); + data.mode = MODE_Quote; + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row); }else if( cli_strcmp(z,"-line")==0 ){ - modeChange(&data, MODE_Line); + data.mode = MODE_Line; }else if( cli_strcmp(z,"-column")==0 ){ - modeChange(&data, MODE_Column); + data.mode = MODE_Column; }else if( cli_strcmp(z,"-json")==0 ){ - modeChange(&data, MODE_Json); + data.mode = MODE_Json; }else if( cli_strcmp(z,"-markdown")==0 ){ - modeChange(&data, MODE_Markdown); + data.mode = MODE_Markdown; }else if( cli_strcmp(z,"-table")==0 ){ - modeChange(&data, MODE_Table); - }else if( cli_strcmp(z,"-psql")==0 ){ - modeChange(&data, MODE_Psql); + data.mode = MODE_Table; }else if( cli_strcmp(z,"-box")==0 ){ - modeChange(&data, MODE_Box); + data.mode = MODE_Box; }else if( cli_strcmp(z,"-csv")==0 ){ - modeChange(&data, MODE_Csv); + data.mode = MODE_Csv; + memcpy(data.colSeparator,",",2); }else if( cli_strcmp(z,"-escape")==0 && i+1<argc ){ /* See similar code at tag-20250224-1 */ const char *zEsc = argv[++i]; int k; - for(k=0; k<ArraySize(qrfEscNames); k++){ - if( sqlite3_stricmp(zEsc,qrfEscNames[k])==0 ){ - data.mode.spec.eEsc = k; + for(k=0; k<ArraySize(shell_EscModeNames); k++){ + if( sqlite3_stricmp(zEsc,shell_EscModeNames[k])==0 ){ + data.eEscMode = k; break; } } - if( k>=ArraySize(qrfEscNames) ){ - cli_printf(stderr, "unknown control character escape mode \"%s\"" + if( k>=ArraySize(shell_EscModeNames) ){ + sqlite3_fprintf(stderr, "unknown control character escape mode \"%s\"" " - choices:", zEsc); - for(k=0; k<ArraySize(qrfEscNames); k++){ - cli_printf(stderr, " %s", qrfEscNames[k]); + for(k=0; k<ArraySize(shell_EscModeNames); k++){ + sqlite3_fprintf(stderr, " %s", shell_EscModeNames[k]); } - cli_printf(stderr, "\n"); + sqlite3_fprintf(stderr, "\n"); exit(1); } #ifdef SQLITE_HAVE_ZLIB @@ -13262,40 +13686,44 @@ int SQLITE_CDECL main(int argc, char **argv){ data.openFlags |= SQLITE_OPEN_READONLY; }else if( cli_strcmp(z,"-nofollow")==0 ){ data.openFlags |= SQLITE_OPEN_NOFOLLOW; - }else if( cli_strcmp(z,"-noinit")==0 ){ - /* No-op */ }else if( cli_strcmp(z,"-exclusive")==0 ){ /* UNDOCUMENTED */ data.openFlags |= SQLITE_OPEN_EXCLUSIVE; }else if( cli_strcmp(z,"-ifexists")==0 ){ data.openFlags &= ~(SQLITE_OPEN_CREATE); if( data.openFlags==0 ) data.openFlags = SQLITE_OPEN_READWRITE; }else if( cli_strcmp(z,"-ascii")==0 ){ - modeChange(&data, MODE_Ascii); + data.mode = MODE_Ascii; + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record); }else if( cli_strcmp(z,"-tabs")==0 ){ - modeChange(&data, MODE_Tabs); + data.mode = MODE_List; + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row); }else if( cli_strcmp(z,"-separator")==0 ){ - modeSetStr(&data.mode.spec.zColumnSep, - cmdline_option_value(argc,argv,++i)); + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, + "%s",cmdline_option_value(argc,argv,++i)); }else if( cli_strcmp(z,"-newline")==0 ){ - modeSetStr(&data.mode.spec.zRowSep, - cmdline_option_value(argc,argv,++i)); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, + "%s",cmdline_option_value(argc,argv,++i)); }else if( cli_strcmp(z,"-nullvalue")==0 ){ - modeSetStr(&data.mode.spec.zNull, - cmdline_option_value(argc,argv,++i)); + sqlite3_snprintf(sizeof(data.nullValue), data.nullValue, + "%s",cmdline_option_value(argc,argv,++i)); }else if( cli_strcmp(z,"-header")==0 ){ - data.mode.spec.bTitles = QRF_Yes; + data.showHeader = 1; + ShellSetFlag(&data, SHFLG_HeaderSet); }else if( cli_strcmp(z,"-noheader")==0 ){ - data.mode.spec.bTitles = QRF_No; + data.showHeader = 0; + ShellSetFlag(&data, SHFLG_HeaderSet); }else if( cli_strcmp(z,"-echo")==0 ){ - data.mode.mFlags |= MFLG_ECHO; + ShellSetFlag(&data, SHFLG_Echo); }else if( cli_strcmp(z,"-eqp")==0 ){ - data.mode.autoEQP = AUTOEQP_on; + data.autoEQP = AUTOEQP_on; }else if( cli_strcmp(z,"-eqpfull")==0 ){ - data.mode.autoEQP = AUTOEQP_full; + data.autoEQP = AUTOEQP_full; }else if( cli_strcmp(z,"-stats")==0 ){ data.statsOn = 1; }else if( cli_strcmp(z,"-scanstats")==0 ){ - data.mode.scanstatsOn = 1; + data.scanstatsOn = 1; }else if( cli_strcmp(z,"-backslash")==0 ){ /* Undocumented command-line option: -backslash ** Causes C-style backslash escapes to be evaluated in SQL statements @@ -13306,10 +13734,9 @@ int SQLITE_CDECL main(int argc, char **argv){ }else if( cli_strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ }else if( cli_strcmp(z,"-version")==0 ){ - cli_printf(stdout, "%s %s (%d-bit)\n", + sqlite3_fprintf(stdout, "%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), 8*(int)sizeof(char*)); - rc = 0; - goto shell_main_exit; + return 0; }else if( cli_strcmp(z,"-interactive")==0 ){ /* Need to check for interactive override here to so that it can ** affect console setup (for Windows only) and testing thereof. @@ -13317,8 +13744,6 @@ int SQLITE_CDECL main(int argc, char **argv){ stdin_is_interactive = 1; }else if( cli_strcmp(z,"-batch")==0 ){ /* already handled */ - }else if( cli_strcmp(z,"-screenwidth")==0 ){ - i++; }else if( cli_strcmp(z,"-utf8")==0 ){ /* already handled */ }else if( cli_strcmp(z,"-no-utf8")==0 ){ @@ -13364,26 +13789,23 @@ int SQLITE_CDECL main(int argc, char **argv){ z = cmdline_option_value(argc,argv,++i); if( z[0]=='.' ){ rc = do_meta_command(z, &data); - if( rc && (bail_on_error || rc==2) ){ - if( rc==2 ) rc = 0; - goto shell_main_exit; - } + if( rc && bail_on_error ) return rc==2 ? 0 : rc; }else{ open_db(&data, 0); rc = shell_exec(&data, z, &zErrMsg); if( zErrMsg!=0 ){ shellEmitError(zErrMsg); sqlite3_free(zErrMsg); - if( !rc ) rc = 1; + if( bail_on_error ) return rc!=0 ? rc : 1; }else if( rc!=0 ){ - cli_printf(stderr,"Error: unable to process SQL \"%s\"\n", z); + sqlite3_fprintf(stderr,"Error: unable to process SQL \"%s\"\n", z); + if( bail_on_error ) return rc; } - if( bail_on_error ) goto shell_main_exit; } #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) }else if( cli_strncmp(z, "-A", 2)==0 ){ if( nCmd>0 ){ - cli_printf(stderr,"Error: cannot mix regular SQL or dot-commands" + sqlite3_fprintf(stderr,"Error: cannot mix regular SQL or dot-commands" " with \"%s\"\n", z); rc = 1; goto shell_main_exit; @@ -13396,7 +13818,6 @@ int SQLITE_CDECL main(int argc, char **argv){ arDotCommand(&data, 1, argv+i, argc-i); } readStdin = 0; - stdin_is_interactive = 0; break; #endif }else if( cli_strcmp(z,"-safe")==0 ){ @@ -13404,11 +13825,11 @@ int SQLITE_CDECL main(int argc, char **argv){ }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ /* Acted upon in first pass. */ }else{ - cli_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); + sqlite3_fprintf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); eputz("Use -help for a list of options.\n"); - rc = 1; - goto shell_main_exit; + return 1; } + data.cMode = data.mode; } if( !readStdin ){ @@ -13418,26 +13839,8 @@ int SQLITE_CDECL main(int argc, char **argv){ */ for(i=0; i<nCmd; i++){ echo_group_input(&data, azCmd[i]); - if( isScriptFile(azCmd[i],0) ){ - FILE *inSaved = data.in; - i64 savedLineno = data.lineno; - int res = 1; - if( (data.in = openChrSource(azCmd[i]))!=0 ){ - res = process_input(&data, azCmd[i]); - fclose(data.in); - } - data.in = inSaved; - data.lineno = savedLineno; - if( res ) i = nCmd; - }else if( azCmd[i][0]=='.' ){ - char *zErrCtx = malloc( 64 ); - shell_check_oom(zErrCtx); - sqlite3_snprintf(64,zErrCtx,"argv[%i]:",aiCmd[i]); - data.zInFile = "<cmdline>"; - data.zErrPrefix = zErrCtx; + if( azCmd[i][0]=='.' ){ rc = do_meta_command(azCmd[i], &data); - free(data.zErrPrefix); - data.zErrPrefix = 0; if( rc ){ if( rc==2 ) rc = 0; goto shell_main_exit; @@ -13449,23 +13852,13 @@ int SQLITE_CDECL main(int argc, char **argv){ if( zErrMsg!=0 ){ shellEmitError(zErrMsg); }else{ - cli_printf(stderr, + sqlite3_fprintf(stderr, "Error: unable to process SQL: %s\n", azCmd[i]); } sqlite3_free(zErrMsg); if( rc==0 ) rc = 1; goto shell_main_exit; } - if( data.nPopMode ){ - modePop(&data); - data.nPopMode = 0; - } - } - if( data.nPopOutput ){ - output_reset(&data); - data.nPopOutput = 0; - }else{ - clearTempFile(&data); } } }else{ @@ -13474,7 +13867,7 @@ int SQLITE_CDECL main(int argc, char **argv){ if( stdin_is_interactive ){ char *zHome; char *zHistory; - cli_printf(stdout, + sqlite3_fprintf(stdout, "SQLite version %s %.19s\n" /*extra-version-info*/ "Enter \".help\" for usage hints.\n", sqlite3_libversion(), sqlite3_sourceid()); @@ -13527,7 +13920,6 @@ int SQLITE_CDECL main(int argc, char **argv){ #endif shell_main_exit: free(azCmd); - free(aiCmd); set_table_name(&data, 0); if( data.db ){ session_close_all(&data, -1); @@ -13544,28 +13936,12 @@ int SQLITE_CDECL main(int argc, char **argv){ output_reset(&data); data.doXdgOpen = 0; clearTempFile(&data); - modeFree(&data.mode); - if( data.nSavedModes ){ - int ii; - for(ii=0; ii<data.nSavedModes; ii++){ - modeFree(&data.aSavedModes[ii].mode); - free(data.aSavedModes[ii].zTag); - } - free(data.aSavedModes); - } - free(data.zErrPrefix); +#if !SQLITE_SHELL_IS_UTF8 + for(i=0; i<argcToFree; i++) free(argvToFree[i]); + free(argvToFree); +#endif + free(data.colWidth); free(data.zNonce); - free(data.dot.zCopy); - free(data.dot.azArg); - free(data.dot.aiOfst); - free(data.dot.abQuot); - if( data.nTestRun ){ - sqlite3_fprintf(stdout, "%d test%s run with %d error%s\n", - data.nTestRun, data.nTestRun==1 ? "" : "s", - data.nTestErr, data.nTestErr==1 ? "" : "s"); - fflush(stdout); - rc = data.nTestErr>0; - } /* Clear the global data structure so that valgrind will detect memory ** leaks */ memset(&data, 0, sizeof(data)); @@ -13574,7 +13950,7 @@ int SQLITE_CDECL main(int argc, char **argv){ } #ifdef SQLITE_DEBUG if( sqlite3_memory_used()>mem_main_enter ){ - cli_printf(stderr,"Memory leaked: %u bytes\n", + sqlite3_fprintf(stderr,"Memory leaked: %u bytes\n", (unsigned int)(sqlite3_memory_used()-mem_main_enter)); } #endif @@ -13614,7 +13990,7 @@ sqlite3_vfs * fiddle_db_vfs(const char *zDbName){ /* Only for emcc experimentation purposes. */ sqlite3 * fiddle_db_arg(sqlite3 *arg){ - cli_printf(stdout, "fiddle_db_arg(%p)\n", (const void*)arg); + sqlite3_fprintf(stdout, "fiddle_db_arg(%p)\n", (const void*)arg); return arg; } @@ -13651,7 +14027,7 @@ void fiddle_reset_db(void){ ** Resolve problem reported in ** https://sqlite.org/forum/forumpost/0b41a25d65 */ - cli_puts("Rolling back in-progress transaction.\n", stdout); + sqlite3_fputs("Rolling back in-progress transaction.\n", stdout); sqlite3_exec(globalDb,"ROLLBACK", 0, 0, 0); } rc = sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index b6773e1d9..f6ed48c20 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -1490,7 +1490,7 @@ typedef const char *sqlite3_filename; ** greater and the function pointer is not NULL) and will fall back ** to xCurrentTime() if xCurrentTimeInt64() is unavailable. ** -** ^The xSetSystemCall(), xGetSystemCall(), and xNextSystemCall() interfaces +** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces ** are not used by the SQLite core. These optional interfaces are provided ** by some VFSes to facilitate testing of the VFS code. By overriding ** system calls with functions under its control, a test program can @@ -2567,15 +2567,12 @@ struct sqlite3_mem_methods { ** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] ** <dt>SQLITE_DBCONFIG_STMT_SCANSTATUS</dt> ** <dd>The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in -** [SQLITE_ENABLE_STMT_SCANSTATUS] builds. In this case, it sets or clears -** a flag that enables collection of run-time performance statistics -** used by [sqlite3_stmt_scanstatus_v2()] and the [nexec and ncycle] -** columns of the [bytecode virtual table]. -** For statistics to be collected, the flag must be set on -** the database handle both when the SQL statement is -** [sqlite3_prepare|prepared] and when it is [sqlite3_step|stepped]. -** The flag is set (collection of statistics is enabled) by default. -** <p>This option takes two arguments: an integer and a pointer to +** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears +** a flag that enables collection of the sqlite3_stmt_scanstatus_v2() +** statistics. For statistics to be collected, the flag must be set on +** the database handle both when the SQL statement is prepared and when it +** is stepped. The flag is set (collection of statistics is enabled) +** by default. <p>This option takes two arguments: an integer and a pointer to ** an integer. The first argument is 1, 0, or -1 to enable, disable, or ** leave unchanged the statement scanstatus option. If the second argument ** is not NULL, then the value of the statement scanstatus setting after @@ -2648,22 +2645,6 @@ struct sqlite3_mem_methods { ** comments are allowed in SQL text after processing the first argument. ** </dd> ** -** [[SQLITE_DBCONFIG_FP_DIGITS]] -** <dt>SQLITE_DBCONFIG_FP_DIGITS</dt> -** <dd>The SQLITE_DBCONFIG_FP_DIGITS setting is a small integer that determines -** the number of significant digits that SQLite will attempt to preserve when -** converting floating point numbers (IEEE 754 "doubles") into text. The -** default value 17, as of SQLite version 3.52.0. The value was 15 in all -** prior versions.<p> -** This option takes two arguments which are an integer and a pointer -** to an integer. The first argument is a small integer, between 3 and 23, or -** zero. The FP_DIGITS setting is changed to that small integer, or left -** altered if the first argument is zero or out of range. The second argument -** is a pointer to an integer. If the pointer is not NULL, then the value of -** the FP_DIGITS setting, after possibly being modified by the first -** arguments, is written into the integer to which the second argument points. -** </dd> -** ** </dl> ** ** [[DBCONFIG arguments]] <h3>Arguments To SQLITE_DBCONFIG Options</h3> @@ -2681,10 +2662,9 @@ struct sqlite3_mem_methods { ** the first argument. ** ** <p>While most SQLITE_DBCONFIG options use the argument format -** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME], -** [SQLITE_DBCONFIG_LOOKASIDE], and [SQLITE_DBCONFIG_FP_DIGITS] options -** are different. See the documentation of those exceptional options for -** details. +** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME] +** and [SQLITE_DBCONFIG_LOOKASIDE] options are different. See the +** documentation of those exceptional options for details. */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ @@ -2709,8 +2689,7 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */ -#define SQLITE_DBCONFIG_FP_DIGITS 1023 /* int int* */ -#define SQLITE_DBCONFIG_MAX 1023 /* Largest DBCONFIG */ +#define SQLITE_DBCONFIG_MAX 1022 /* Largest DBCONFIG */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes @@ -4192,7 +4171,6 @@ void sqlite3_free_filename(sqlite3_filename); ** <li> sqlite3_errmsg() ** <li> sqlite3_errmsg16() ** <li> sqlite3_error_offset() -** <li> sqlite3_db_handle() ** </ul> ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language @@ -4239,7 +4217,7 @@ const char *sqlite3_errstr(int); int sqlite3_error_offset(sqlite3 *db); /* -** CAPI3REF: Set Error Code And Message +** CAPI3REF: Set Error Codes And Message ** METHOD: sqlite3 ** ** Set the error code of the database handle passed as the first argument @@ -4358,10 +4336,6 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(<dt>SQLITE_LIMIT_EXPR_DEPTH</dt> ** <dd>The maximum depth of the parse tree on any expression.</dd>)^ ** -** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(<dt>SQLITE_LIMIT_PARSER_DEPTH</dt> -** <dd>The maximum depth of the LALR(1) parser stack used to analyze -** input SQL statements.</dd>)^ -** ** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(<dt>SQLITE_LIMIT_COMPOUND_SELECT</dt> ** <dd>The maximum number of terms in a compound SELECT statement.</dd>)^ ** @@ -4406,7 +4380,6 @@ int sqlite3_limit(sqlite3*, int id, int newVal); #define SQLITE_LIMIT_VARIABLE_NUMBER 9 #define SQLITE_LIMIT_TRIGGER_DEPTH 10 #define SQLITE_LIMIT_WORKER_THREADS 11 -#define SQLITE_LIMIT_PARSER_DEPTH 12 /* ** CAPI3REF: Prepare Flags @@ -4451,29 +4424,12 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** fails, the sqlite3_prepare_v3() call returns the same error indications ** with or without this flag; it just omits the call to [sqlite3_log()] that ** logs the error. -** -** [[SQLITE_PREPARE_FROM_DDL]] <dt>SQLITE_PREPARE_FROM_DDL</dt> -** <dd>The SQLITE_PREPARE_FROM_DDL flag causes the SQL compiler to enforce -** security constraints that would otherwise only be enforced when parsing -** the database schema. In other words, the SQLITE_PREPARE_FROM_DDL flag -** causes the SQL compiler to treat the SQL statement being prepared as if -** it had come from an attacker. When SQLITE_PREPARE_FROM_DDL is used and -** [SQLITE_DBCONFIG_TRUSTED_SCHEMA] is off, SQL functions may only be called -** if they are tagged with [SQLITE_INNOCUOUS] and virtual tables may only -** be used if they are tagged with [SQLITE_VTAB_INNOCUOUS]. Best practice -** is to use the SQLITE_PREPARE_FROM_DDL option when preparing any SQL that -** is derived from parts of the database schema. In particular, virtual -** table implementations that run SQL statements that are derived from -** arguments to their CREATE VIRTUAL TABLE statement should always use -** [sqlite3_prepare_v3()] and set the SQLITE_PREPARE_FROM_DDL flag to -** prevent bypass of the [SQLITE_DBCONFIG_TRUSTED_SCHEMA] security checks. ** </dl> */ #define SQLITE_PREPARE_PERSISTENT 0x01 #define SQLITE_PREPARE_NORMALIZE 0x02 #define SQLITE_PREPARE_NO_VTAB 0x04 #define SQLITE_PREPARE_DONT_LOG 0x10 -#define SQLITE_PREPARE_FROM_DDL 0x20 /* ** CAPI3REF: Compiling An SQL Statement @@ -4487,9 +4443,8 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** ** The preferred routine to use is [sqlite3_prepare_v2()]. The ** [sqlite3_prepare()] interface is legacy and should be avoided. -** [sqlite3_prepare_v3()] has an extra -** [SQLITE_PREPARE_FROM_DDL|"prepFlags" option] that is some times -** needed for special purpose or to pass along security restrictions. +** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used +** for special purposes. ** ** The use of the UTF-8 interfaces is preferred, as SQLite currently ** does all parsing using UTF-8. The UTF-16 interfaces are provided @@ -4894,8 +4849,8 @@ typedef struct sqlite3_context sqlite3_context; ** it should be a pointer to well-formed UTF16 text. ** ^If the third parameter to sqlite3_bind_text64() is not NULL, then ** it should be a pointer to a well-formed unicode string that is -** either UTF8 if the sixth parameter is SQLITE_UTF8 or SQLITE_UTF8_ZT, -** or UTF16 otherwise. +** either UTF8 if the sixth parameter is SQLITE_UTF8, or UTF16 +** otherwise. ** ** [[byte-order determination rules]] ^The byte-order of ** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) @@ -4941,15 +4896,10 @@ typedef struct sqlite3_context sqlite3_context; ** object and pointer to it must remain valid until then. ^SQLite will then ** manage the lifetime of its private copy. ** -** ^The sixth argument (the E argument) -** to sqlite3_bind_text64(S,K,Z,N,D,E) must be one of -** [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], -** or [SQLITE_UTF16LE] to specify the encoding of the text in the -** third parameter, Z. The special value [SQLITE_UTF8_ZT] means that the -** string argument is both UTF-8 encoded and is zero-terminated. In other -** words, SQLITE_UTF8_ZT means that the Z array is allocated to hold at -** least N+1 bytes and that the Z&#91;N&#93; byte is zero. If -** the E argument to sqlite3_bind_text64(S,K,Z,N,D,E) is not one of the +** ^The sixth argument to sqlite3_bind_text64() must be one of +** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] +** to specify the encoding of the text in the third parameter. If +** the sixth argument to sqlite3_bind_text64() is not one of the ** allowed values shown above, or if the text encoding is different ** from the encoding specified by the sixth parameter, then the behavior ** is undefined. @@ -5816,51 +5766,6 @@ int sqlite3_create_window_function( ** ** These constants define integer codes that represent the various ** text encodings supported by SQLite. -** -** <dl> -** [[SQLITE_UTF8]] <dt>SQLITE_UTF8</dt><dd>Text is encoding as UTF-8</dd> -** -** [[SQLITE_UTF16LE]] <dt>SQLITE_UTF16LE</dt><dd>Text is encoding as UTF-16 -** with each code point being expressed "little endian" - the least significant -** byte first. This is the usual encoding, for example on Windows.</dd> -** -** [[SQLITE_UTF16BE]] <dt>SQLITE_UTF16BE</dt><dd>Text is encoding as UTF-16 -** with each code point being expressed "big endian" - the most significant -** byte first. This encoding is less common, but is still sometimes seen, -** specially on older systems. -** -** [[SQLITE_UTF16]] <dt>SQLITE_UTF16</dt><dd>Text is encoding as UTF-16 -** with each code point being expressed either little endian or as big -** endian, according to the native endianness of the host computer. -** -** [[SQLITE_ANY]] <dt>SQLITE_ANY</dt><dd>This encoding value may only be used -** to declare the preferred text for [application-defined SQL functions] -** created using [sqlite3_create_function()] and similar. If the preferred -** encoding (the 4th parameter to sqlite3_create_function() - the eTextRep -** parameter) is SQLITE_ANY, that indicates that the function does not have -** a preference regarding the text encoding of its parameters and can take -** any text encoding that the SQLite core find convenient to supply. This -** option is deprecated. Please do not use it in new applications. -** -** [[SQLITE_UTF16_ALIGNED]] <dt>SQLITE_UTF16_ALIGNED</dt><dd>This encoding -** value may be used as the 3rd parameter (the eTextRep parameter) to -** [sqlite3_create_collation()] and similar. This encoding value means -** that the application-defined collating sequence created expects its -** input strings to be in UTF16 in native byte order, and that the start -** of the strings must be aligned to a 2-byte boundary. -** -** [[SQLITE_UTF8_ZT]] <dt>SQLITE_UTF8_ZT</dt><dd>This option can only be -** used to specify the text encoding to strings input to [sqlite3_result_text64()] -** and [sqlite3_bind_text64()]. It means that the input string (call it "z") -** is UTF-8 encoded and that it is zero-terminated. If the length parameter -** (call it "n") is non-negative, this encoding option means that the caller -** guarantees that z array contains at least n+1 bytes and that the z&#91;n&#93; -** byte has a value of zero. -** This option gives the same output as SQLITE_UTF8, but can be more efficient -** by avoiding the need to make a copy of the input string, in some cases. -** However, if z is allocated to hold fewer than n+1 bytes or if the -** z&#91;n&#93; byte is not zero, undefined behavior may result. -** </dl> */ #define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ #define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ @@ -5868,7 +5773,6 @@ int sqlite3_create_window_function( #define SQLITE_UTF16 4 /* Use native byte order */ #define SQLITE_ANY 5 /* Deprecated */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ -#define SQLITE_UTF8_ZT 16 /* Zero-terminated UTF8 */ /* ** CAPI3REF: Function Flags @@ -6374,14 +6278,10 @@ void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); ** ** There is no limit (other than available memory) on the number of different ** client data pointers (with different names) that can be attached to a -** single database connection. However, the current implementation stores -** the content on a linked list. Insert and retrieval performance will -** be proportional to the number of entries. The design use case, and -** the use case for which the implementation is optimized, is -** that an application will store only small number of client data names, -** typically just one or two. This interface is not intended to be a -** generalized key/value store for thousands or millions of keys. It -** will work for that, but performance might be disappointing. +** single database connection. However, the implementation is optimized +** for the case of having only one or two different client data names. +** Applications and wrapper libraries are discouraged from using more than +** one client data name each. ** ** There is no way to enumerate the client data pointers ** associated with a database connection. The N parameter can be thought @@ -6489,14 +6389,10 @@ typedef void (*sqlite3_destructor_type)(void*); ** set the return value of the application-defined function to be ** a text string which is represented as UTF-8, UTF-16 native byte order, ** UTF-16 little endian, or UTF-16 big endian, respectively. -** ^The sqlite3_result_text64(C,Z,N,D,E) interface sets the return value of an +** ^The sqlite3_result_text64() interface sets the return value of an ** application-defined function to be a text string in an encoding -** specified the E parameter, which must be one -** of [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], -** or [SQLITE_UTF16LE]. ^The special value [SQLITE_UTF8_ZT] means that -** the result text is both UTF-8 and zero-terminated. In other words, -** SQLITE_UTF8_ZT means that the Z array holds at least N+1 byes and that -** the Z&#91;N&#93; is zero. +** specified by the fifth (and last) parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. ** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces @@ -6583,7 +6479,7 @@ void sqlite3_result_int(sqlite3_context*, int); void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); void sqlite3_result_null(sqlite3_context*); void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); -void sqlite3_result_text64(sqlite3_context*, const char *z, sqlite3_uint64 n, +void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64, void(*)(void*), unsigned char encoding); void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); @@ -7522,7 +7418,7 @@ int sqlite3_table_column_metadata( ** ^The sqlite3_load_extension() interface attempts to load an ** [SQLite extension] library contained in the file zFile. If ** the file cannot be loaded directly, attempts are made to load -** with various operating-system specific filename extensions added. +** with various operating-system specific extensions added. ** So for example, if "samplelib" cannot be loaded, then names like ** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might ** be tried also. @@ -7530,10 +7426,10 @@ int sqlite3_table_column_metadata( ** ^The entry point is zProc. ** ^(zProc may be 0, in which case SQLite will try to come up with an ** entry point name on its own. It first tries "sqlite3_extension_init". -** If that does not work, it tries names of the form "sqlite3_X_init" -** where X consists of the lower-case equivalent of all ASCII alphabetic -** characters or all ASCII alphanumeric characters in the filename from -** the last "/" to the first following "." and omitting any initial "lib".)^ +** If that does not work, it constructs a name "sqlite3_X_init" where +** X consists of the lower-case equivalent of all ASCII alphabetic +** characters in the filename from the last "/" to the first following +** "." and omitting any initial "lib".)^ ** ^The sqlite3_load_extension() interface returns ** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. ** ^If an error occurs and pzErrMsg is not 0, then the @@ -8826,22 +8722,17 @@ sqlite3_str *sqlite3_str_new(sqlite3*); ** pass the returned value to [sqlite3_free()] to avoid a memory leak. ** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any ** errors were encountered during construction of the string. ^The -** [sqlite3_str_finish(X)] interface might also return a NULL pointer if the +** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the ** string in [sqlite3_str] object X is zero bytes long. -** -** ^The [sqlite3_str_free(X)] interface destroys both the sqlite3_str object -** X and the string content it contains. Calling sqlite3_str_free(X) is -** the equivalent of calling [sqlite3_free](sqlite3_str_finish(X)). */ char *sqlite3_str_finish(sqlite3_str*); -void sqlite3_str_free(sqlite3_str*); /* ** CAPI3REF: Add Content To A Dynamic String ** METHOD: sqlite3_str ** -** These interfaces add or remove content to an sqlite3_str object -** previously obtained from [sqlite3_str_new()]. +** These interfaces add content to an sqlite3_str object previously obtained +** from [sqlite3_str_new()]. ** ** ^The [sqlite3_str_appendf(X,F,...)] and ** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] @@ -8864,10 +8755,6 @@ void sqlite3_str_free(sqlite3_str*); ** ^The [sqlite3_str_reset(X)] method resets the string under construction ** inside [sqlite3_str] object X back to zero bytes in length. ** -** ^The [sqlite3_str_truncate(X,N)] method changes the length of the string -** under construction to be N bytes are less. This routine is a no-op if -** N is negative or if the string is already N bytes or smaller in size. -** ** These methods do not return a result code. ^If an error occurs, that fact ** is recorded in the [sqlite3_str] object and can be recovered by a ** subsequent call to [sqlite3_str_errcode(X)]. @@ -8878,7 +8765,6 @@ void sqlite3_str_append(sqlite3_str*, const char *zIn, int N); void sqlite3_str_appendall(sqlite3_str*, const char *zIn); void sqlite3_str_appendchar(sqlite3_str*, int N, char C); void sqlite3_str_reset(sqlite3_str*); -void sqlite3_str_truncate(sqlite3_str*,int N); /* ** CAPI3REF: Status Of A Dynamic String @@ -10712,9 +10598,9 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); ** a variable pointed to by the "pOut" parameter. ** ** The "flags" parameter must be passed a mask of flags. At present only -** one flag is defined - [SQLITE_SCANSTAT_COMPLEX]. If SQLITE_SCANSTAT_COMPLEX +** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX ** is specified, then status information is available for all elements -** of a query plan that are reported by "[EXPLAIN QUERY PLAN]" output. If +** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If ** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements ** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of ** the EXPLAIN QUERY PLAN output) are available. Invoking API @@ -10728,8 +10614,7 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); ** elements used to implement the statement - a non-zero value is returned and ** the variable that pOut points to is unchanged. ** -** See also: [sqlite3_stmt_scanstatus_reset()] and the -** [nexec and ncycle] columnes of the [bytecode virtual table]. +** See also: [sqlite3_stmt_scanstatus_reset()] */ int sqlite3_stmt_scanstatus( sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ @@ -11271,41 +11156,19 @@ int sqlite3_deserialize( /* ** CAPI3REF: Bind array values to the CARRAY table-valued function ** -** The sqlite3_carray_bind_v2(S,I,P,N,F,X,D) interface binds an array value to -** parameter that is the first argument of the [carray() table-valued function]. -** The S parameter is a pointer to the [prepared statement] that uses the carray() -** functions. I is the parameter index to be bound. I must be the index of the -** parameter that is the first argument to the carray() table-valued function. -** P is a pointer to the array to be bound, and N is the number of elements in -** the array. The F argument is one of constants [SQLITE_CARRAY_INT32], -** [SQLITE_CARRAY_INT64], [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], -** or [SQLITE_CARRAY_BLOB] to indicate the datatype of the array P. -** -** If the X argument is not a NULL pointer or one of the special -** values [SQLITE_STATIC] or [SQLITE_TRANSIENT], then SQLite will invoke -** the function X with argument D when it is finished using the data in P. -** The call to X(D) is a destructor for the array P. The destructor X(D) -** is invoked even if the call to sqlite3_carray_bind() fails. If the X -** parameter is the special-case value [SQLITE_STATIC], then SQLite assumes -** that the data static and the destructor is never invoked. If the X -** parameter is the special-case value [SQLITE_TRANSIENT], then -** sqlite3_carray_bind_v2() makes its own private copy of the data prior -** to returning and never invokes the destructor X. -** -** The sqlite3_carray_bind() function works the same as sqlite_carray_bind_v2() -** with a D parameter set to P. In other words, -** sqlite3_carray_bind(S,I,P,N,F,X) is same as -** sqlite3_carray_bind(S,I,P,N,F,X,P). -*/ -int sqlite3_carray_bind_v2( - sqlite3_stmt *pStmt, /* Statement to be bound */ - int i, /* Parameter index */ - void *aData, /* Pointer to array data */ - int nData, /* Number of data elements */ - int mFlags, /* CARRAY flags */ - void (*xDel)(void*), /* Destructor for aData */ - void *pDel /* Optional argument to xDel() */ -); +** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to +** one of the first argument of the [carray() table-valued function]. The +** S parameter is a pointer to the [prepared statement] that uses the carray() +** functions. I is the parameter index to be bound. P is a pointer to the +** array to be bound, and N is the number of eements in the array. The +** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], +** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to +** indicate the datatype of the array being bound. The X argument is not a +** NULL pointer, then SQLite will invoke the function X on the P parameter +** after it has finished using P, even if the call to +** sqlite3_carray_bind() fails. The special-case finalizer +** SQLITE_TRANSIENT has no effect here. +*/ int sqlite3_carray_bind( sqlite3_stmt *pStmt, /* Statement to be bound */ int i, /* Parameter index */ diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index cad1a2a00..5258faaed 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -371,11 +371,7 @@ struct sqlite3_api_routines { /* Version 3.51.0 and later */ int (*set_errmsg)(sqlite3*,int,const char*); int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); - /* Version 3.52.0 and later */ - void (*str_truncate)(sqlite3_str*,int); - void (*str_free)(sqlite3_str*); - int (*carray_bind)(sqlite3_stmt*,int,void*,int,int,void(*)(void*)); - int (*carray_bind_v2)(sqlite3_stmt*,int,void*,int,int,void(*)(void*),void*); + }; /* @@ -714,11 +710,6 @@ typedef int (*sqlite3_loadext_entry)( /* Version 3.51.0 and later */ #define sqlite3_set_errmsg sqlite3_api->set_errmsg #define sqlite3_db_status64 sqlite3_api->db_status64 -/* Version 3.52.0 and later */ -#define sqlite3_str_truncate sqlite3_api->str_truncate -#define sqlite3_str_free sqlite3_api->str_free -#define sqlite3_carray_bind sqlite3_api->carray_bind -#define sqlite3_carray_bind_v2 sqlite3_api->carray_bind_v2 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index c4a876861..fa53fe694 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -661,7 +661,6 @@ # define float sqlite_int64 # define fabs(X) ((X)<0?-(X):(X)) # define sqlite3IsOverflow(X) 0 -# define INFINITY (9223372036854775807LL) # ifndef SQLITE_BIG_DBL # define SQLITE_BIG_DBL (((sqlite3_int64)1)<<50) # endif @@ -1071,7 +1070,6 @@ typedef INT16_TYPE LogEst; #else # define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) #endif -#define TWO_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&1)==0) /* ** Disable MMAP on platforms where it is known to not work @@ -1539,7 +1537,7 @@ struct Schema { ** The number of different kinds of things that can be limited ** using the sqlite3_limit() interface. */ -#define SQLITE_N_LIMIT (SQLITE_LIMIT_PARSER_DEPTH+1) +#define SQLITE_N_LIMIT (SQLITE_LIMIT_WORKER_THREADS+1) /* ** Lookaside malloc is a set of fixed-size buffers that can be used @@ -1693,7 +1691,6 @@ struct sqlite3 { u8 noSharedCache; /* True if no shared-cache backends */ u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ u8 eOpenState; /* Current condition of the connection */ - u8 nFpDigit; /* Significant digits to keep on double->text */ int nextPagesize; /* Pagesize after VACUUM if >0 */ i64 nChange; /* Value returned by sqlite3_changes() */ i64 nTotalChange; /* Value returned by sqlite3_total_changes() */ @@ -3588,6 +3585,19 @@ struct Upsert { /* ** An instance of the following structure contains all information ** needed to generate code for a single SELECT statement. +** +** See the header comment on the computeLimitRegisters() routine for a +** detailed description of the meaning of the iLimit and iOffset fields. +** +** addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes. +** These addresses must be stored so that we can go back and fill in +** the P4_KEYINFO and P2 parameters later. Neither the KeyInfo nor +** the number of columns in P2 can be computed at the same time +** as the OP_OpenEphm instruction is coded because not +** enough information about the compound query is known at that point. +** The KeyInfo for addrOpenTran[0] and [1] contains collating sequences +** for the result set. The KeyInfo for addrOpenEphm[2] contains collating +** sequences for the ORDER BY clause. */ struct Select { u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */ @@ -3595,6 +3605,7 @@ struct Select { u32 selFlags; /* Various SF_* values */ int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */ u32 selId; /* Unique identifier number for this SELECT */ + int addrOpenEphm[2]; /* OP_OpenEphem opcodes related to this select */ ExprList *pEList; /* The fields of the result */ SrcList *pSrc; /* The FROM clause */ Expr *pWhere; /* The WHERE clause */ @@ -3626,7 +3637,7 @@ struct Select { #define SF_Resolved 0x0000004 /* Identifiers have been resolved */ #define SF_Aggregate 0x0000008 /* Contains agg functions or a GROUP BY */ #define SF_HasAgg 0x0000010 /* Contains aggregate functions */ -#define SF_ClonedRhsIn 0x0000020 /* Cloned RHS of an IN operator */ +#define SF_UsesEphemeral 0x0000020 /* Uses the OpenEphemeral opcode */ #define SF_Expanded 0x0000040 /* sqlite3SelectExpand() called on this */ #define SF_HasTypeInfo 0x0000080 /* FROM subqueries have Table metadata */ #define SF_Compound 0x0000100 /* Part of a compound query */ @@ -3636,14 +3647,14 @@ struct Select { #define SF_MinMaxAgg 0x0001000 /* Aggregate containing min() or max() */ #define SF_Recursive 0x0002000 /* The recursive part of a recursive CTE */ #define SF_FixedLimit 0x0004000 /* nSelectRow set by a constant LIMIT */ -/* 0x0008000 // available for reuse */ +#define SF_MaybeConvert 0x0008000 /* Need convertCompoundSelectToSubquery() */ #define SF_Converted 0x0010000 /* By convertCompoundSelectToSubquery() */ #define SF_IncludeHidden 0x0020000 /* Include hidden columns in output */ #define SF_ComplexResult 0x0040000 /* Result contains subquery or function */ #define SF_WhereBegin 0x0080000 /* Really a WhereBegin() call. Debug Only */ #define SF_WinRewrite 0x0100000 /* Window function rewrite accomplished */ #define SF_View 0x0200000 /* SELECT statement is a view */ -/* 0x0400000 // available for reuse */ +#define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ #define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ #define SF_PushDown 0x1000000 /* Modified by WHERE-clause push-down opt */ #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ @@ -3663,6 +3674,11 @@ struct Select { ** by one of the following macros. The "SRT" prefix means "SELECT Result ** Type". ** +** SRT_Union Store results as a key in a temporary index +** identified by pDest->iSDParm. +** +** SRT_Except Remove results from the temporary index pDest->iSDParm. +** ** SRT_Exists Store a 1 in memory cell pDest->iSDParm if the result ** set is not empty. ** @@ -3726,28 +3742,30 @@ struct Select { ** table. (pDest->iSDParm) is the number of key columns in ** each index record in this case. */ -#define SRT_Exists 1 /* Store 1 if the result is not empty */ -#define SRT_Discard 2 /* Do not save the results anywhere */ -#define SRT_DistFifo 3 /* Like SRT_Fifo, but unique results only */ -#define SRT_DistQueue 4 /* Like SRT_Queue, but unique results only */ +#define SRT_Union 1 /* Store result as keys in an index */ +#define SRT_Except 2 /* Remove result from a UNION index */ +#define SRT_Exists 3 /* Store 1 if the result is not empty */ +#define SRT_Discard 4 /* Do not save the results anywhere */ +#define SRT_DistFifo 5 /* Like SRT_Fifo, but unique results only */ +#define SRT_DistQueue 6 /* Like SRT_Queue, but unique results only */ /* The DISTINCT clause is ignored for all of the above. Not that ** IgnorableDistinct() implies IgnorableOrderby() */ #define IgnorableDistinct(X) ((X->eDest)<=SRT_DistQueue) -#define SRT_Queue 5 /* Store result in an queue */ -#define SRT_Fifo 6 /* Store result as data with an automatic rowid */ +#define SRT_Queue 7 /* Store result in an queue */ +#define SRT_Fifo 8 /* Store result as data with an automatic rowid */ /* The ORDER BY clause is ignored for all of the above */ #define IgnorableOrderby(X) ((X->eDest)<=SRT_Fifo) -#define SRT_Output 7 /* Output each row of result */ -#define SRT_Mem 8 /* Store result in a memory cell */ -#define SRT_Set 9 /* Store results as keys in an index */ -#define SRT_EphemTab 10 /* Create transient tab and store like SRT_Table */ -#define SRT_Coroutine 11 /* Generate a single row of result */ -#define SRT_Table 12 /* Store result as data with an automatic rowid */ -#define SRT_Upfrom 13 /* Store result as data with rowid */ +#define SRT_Output 9 /* Output each row of result */ +#define SRT_Mem 10 /* Store result in a memory cell */ +#define SRT_Set 11 /* Store results as keys in an index */ +#define SRT_EphemTab 12 /* Create transient tab and store like SRT_Table */ +#define SRT_Coroutine 13 /* Generate a single row of result */ +#define SRT_Table 14 /* Store result as data with an automatic rowid */ +#define SRT_Upfrom 15 /* Store result as data with rowid */ /* ** An instance of this object describes where to put of the results of @@ -3883,12 +3901,17 @@ struct Parse { u8 nested; /* Number of nested calls to the parser/code generator */ u8 nTempReg; /* Number of temporary registers in aTempReg[] */ u8 isMultiWrite; /* True if statement may modify/insert multiple rows */ + u8 mayAbort; /* True if statement may throw an ABORT exception */ + u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */ u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ + u8 bHasExists; /* Has a correlated "EXISTS (SELECT ....)" expression */ u8 mSubrtnSig; /* mini Bloom filter on available SubrtnSig.selId */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ + u8 bReturning; /* Coding a RETURNING trigger */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ + u8 disableTriggers; /* True to disable triggers */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ #endif @@ -3897,15 +3920,10 @@ struct Parse { u8 isCreate; /* CREATE TABLE, INDEX, or VIEW (but not TRIGGER) ** and ALTER TABLE ADD COLUMN. */ #endif - bft disableTriggers:1; /* True to disable triggers */ - bft mayAbort :1; /* True if statement may throw an ABORT exception */ - bft hasCompound :1; /* Need to invoke convertCompoundSelectToSubquery() */ - bft bReturning :1; /* Coding a RETURNING trigger */ - bft bHasExists :1; /* Has a correlated "EXISTS (SELECT ....)" expression */ - bft colNamesSet :1; /* TRUE after OP_ColumnName has been issued to pVdbe */ - bft bHasWith :1; /* True if statement contains WITH */ - bft okConstFactor:1; /* OK to factor out constants */ - bft checkSchema :1; /* Causes schema cookie check after an error */ + bft colNamesSet :1; /* TRUE after OP_ColumnName has been issued to pVdbe */ + bft bHasWith :1; /* True if statement contains WITH */ + bft okConstFactor :1; /* OK to factor out constants */ + bft checkSchema :1; /* Causes schema cookie check after an error */ int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ int nErr; /* Number of errors seen */ @@ -4134,19 +4152,19 @@ struct Trigger { ** orconf -> stores the ON CONFLICT algorithm ** pSelect -> The content to be inserted - either a SELECT statement or ** a VALUES clause. -** pSrc -> Table to insert into. +** zTarget -> Dequoted name of the table to insert into. ** pIdList -> If this is an INSERT INTO ... (<column-names>) VALUES ... ** statement, then this stores the column-names to be ** inserted into. ** pUpsert -> The ON CONFLICT clauses for an Upsert ** ** (op == TK_DELETE) -** pSrc -> Table to delete from +** zTarget -> Dequoted name of the table to delete from. ** pWhere -> The WHERE clause of the DELETE statement if one is specified. ** Otherwise NULL. ** ** (op == TK_UPDATE) -** pSrc -> Table to update, followed by any FROM clause tables. +** zTarget -> Dequoted name of the table to update. ** pWhere -> The WHERE clause of the UPDATE statement if one is specified. ** Otherwise NULL. ** pExprList -> A list of the columns to update and the expressions to update @@ -4166,7 +4184,8 @@ struct TriggerStep { u8 orconf; /* OE_Rollback etc. */ Trigger *pTrig; /* The trigger that this step is a part of */ Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */ - SrcList *pSrc; /* Table to insert/update/delete */ + char *zTarget; /* Target table for DELETE, UPDATE, INSERT */ + SrcList *pFrom; /* FROM clause for UPDATE statement (if any) */ Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ ExprList *pExprList; /* SET clause for UPDATE, or RETURNING clause */ IdList *pIdList; /* Column names for INSERT */ @@ -4249,11 +4268,10 @@ typedef struct { /* ** Allowed values for mInitFlags */ -#define INITFLAG_AlterMask 0x0007 /* Types of ALTER */ +#define INITFLAG_AlterMask 0x0003 /* Types of ALTER */ #define INITFLAG_AlterRename 0x0001 /* Reparse after a RENAME */ #define INITFLAG_AlterDrop 0x0002 /* Reparse after a DROP COLUMN */ #define INITFLAG_AlterAdd 0x0003 /* Reparse after an ADD COLUMN */ -#define INITFLAG_AlterDropCons 0x0004 /* Reparse after an ADD COLUMN */ /* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled ** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning @@ -4383,7 +4401,6 @@ struct Walker { NameContext *pNC; /* Naming context */ int n; /* A counter */ int iCur; /* A cursor number */ - int sz; /* String literal length */ SrcList *pSrcList; /* FROM clause */ struct CCurHint *pCCurHint; /* Used by codeCursorHint() */ struct RefSrcList *pRefSrcList; /* sqlite3ReferencesSrcList() */ @@ -4788,20 +4805,7 @@ int sqlite3LookasideUsed(sqlite3*,int*); sqlite3_mutex *sqlite3Pcache1Mutex(void); sqlite3_mutex *sqlite3MallocMutex(void); - -/* The SQLITE_THREAD_MISUSE_WARNINGS compile-time option used to be called -** SQLITE_ENABLE_MULTITHREADED_CHECKS. Keep that older macro for backwards -** compatibility, at least for a while... */ -#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS -# define SQLITE_THREAD_MISUSE_WARNINGS 1 -#endif - -/* SQLITE_THREAD_MISUSE_ABORT implies SQLITE_THREAD_MISUSE_WARNINGS */ -#ifdef SQLITE_THREAD_MISUSE_ABORT -# define SQLITE_THREAD_MISUSE_WARNINGS 1 -#endif - -#if defined(SQLITE_THREAD_MISUSE_WARNINGS) && !defined(SQLITE_MUTEX_OMIT) +#if defined(SQLITE_ENABLE_MULTITHREADED_CHECKS) && !defined(SQLITE_MUTEX_OMIT) void sqlite3MutexWarnOnContention(sqlite3_mutex*); #else # define sqlite3MutexWarnOnContention(x) @@ -4835,12 +4839,12 @@ struct PrintfArguments { ** value into an approximate decimal representation. */ struct FpDecode { + char sign; /* '+' or '-' */ + char isSpecial; /* 1: Infinity 2: NaN */ int n; /* Significant digits in the decode */ int iDP; /* Location of the decimal point */ char *z; /* Start of significant digits */ - char zBuf[20]; /* Storage for significant digits */ - char sign; /* '+' or '-' */ - char isSpecial; /* 1: Infinity 2: NaN */ + char zBuf[24]; /* Storage for significant digits */ }; void sqlite3FpDecode(FpDecode*,double,int,int); @@ -4929,7 +4933,6 @@ int sqlite3NoTempsInRange(Parse*,int,int); #endif Expr *sqlite3ExprAlloc(sqlite3*,int,const Token*,int); Expr *sqlite3Expr(sqlite3*,int,const char*); -Expr *sqlite3ExprInt32(sqlite3*,int); void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*); Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*); void sqlite3PExprAddSelect(Parse*, Expr*, Select*); @@ -5181,7 +5184,6 @@ int sqlite3ExprContainsSubquery(Expr*); int sqlite3ExprIsInteger(const Expr*, int*, Parse*); int sqlite3ExprCanBeNull(const Expr*); int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); -int sqlite3ExprIsLikeOperator(const Expr*); int sqlite3IsRowid(const char*); const char *sqlite3RowidAlias(Table *pTab); void sqlite3GenerateRowDelete( @@ -5250,16 +5252,17 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*, const char*,const char*); - TriggerStep *sqlite3TriggerInsertStep(Parse*,SrcList*, IdList*, + TriggerStep *sqlite3TriggerInsertStep(Parse*,Token*, IdList*, Select*,u8,Upsert*, const char*,const char*); - TriggerStep *sqlite3TriggerUpdateStep(Parse*,SrcList*,SrcList*,ExprList*, + TriggerStep *sqlite3TriggerUpdateStep(Parse*,Token*,SrcList*,ExprList*, Expr*, u8, const char*,const char*); - TriggerStep *sqlite3TriggerDeleteStep(Parse*,SrcList*, Expr*, + TriggerStep *sqlite3TriggerDeleteStep(Parse*,Token*, Expr*, const char*,const char*); void sqlite3DeleteTrigger(sqlite3*, Trigger*); void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); + SrcList *sqlite3TriggerStepSrc(Parse*, TriggerStep*); # define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) # define sqlite3IsToplevel(p) ((p)->pToplevel==0) #else @@ -5273,6 +5276,7 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); # define sqlite3ParseToplevel(p) p # define sqlite3IsToplevel(p) 1 # define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0 +# define sqlite3TriggerStepSrc(A,B) 0 #endif int sqlite3JoinType(Parse*, Token*, Token*, Token*); @@ -5305,7 +5309,7 @@ int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); int sqlite3RealSameAsInt(double,sqlite3_int64); i64 sqlite3RealToI64(double); int sqlite3Int64ToText(i64,char*); -int sqlite3AtoF(const char *z, double*); +int sqlite3AtoF(const char *z, double*, int, u8); int sqlite3GetInt32(const char *, int*); int sqlite3GetUInt32(const char*, u32*); int sqlite3Atoi(const char*); @@ -5449,13 +5453,10 @@ void sqlite3Reindex(Parse*, Token*, Token*); void sqlite3AlterFunctions(void); void sqlite3AlterRenameTable(Parse*, SrcList*, Token*); void sqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*); -void sqlite3AlterDropConstraint(Parse*,SrcList*,Token*,Token*); -void sqlite3AlterAddConstraint(Parse*,SrcList*,Token*,Token*,const char*,int); -void sqlite3AlterSetNotNull(Parse*, SrcList*, Token*, Token*); i64 sqlite3GetToken(const unsigned char *, int *); void sqlite3NestedParse(Parse*, const char*, ...); void sqlite3ExpirePreparedStatements(sqlite3*, int); -void sqlite3CodeRhsOfIN(Parse*, Expr*, int, int); +void sqlite3CodeRhsOfIN(Parse*, Expr*, int); int sqlite3CodeSubselect(Parse*, Expr*); void sqlite3SelectPrep(Parse*, Select*, NameContext*); int sqlite3ExpandSubquery(Parse*, SrcItem*); diff --git a/src/sqliteLimit.h b/src/sqliteLimit.h index ecbd0858e..ee467836a 100644 --- a/src/sqliteLimit.h +++ b/src/sqliteLimit.h @@ -81,42 +81,21 @@ ** It used to be the case that setting this value to zero would ** turn the limit off. That is no longer true. It is not possible ** to turn this limit off. -** -** The hard limit is the largest possible 32-bit signed integer less -** 1024, or 2147482624. */ #ifndef SQLITE_MAX_SQL_LENGTH # define SQLITE_MAX_SQL_LENGTH 1000000000 #endif /* -** The maximum depth of an expression tree. The expression tree depth -** is also limited indirectly by SQLITE_MAX_SQL_LENGTH and by -** SQLITE_MAX_PARSER_DEPTH. Reducing the maximum complexity of -** expressions can help prevent excess memory usage by hostile SQL. -** -** A value of 0 for this compile-time option causes all expression -** depth limiting code to be omitted. +** The maximum depth of an expression tree. This is limited to +** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might +** want to place more severe limits on the complexity of an +** expression. A value of 0 means that there is no limit. */ #ifndef SQLITE_MAX_EXPR_DEPTH # define SQLITE_MAX_EXPR_DEPTH 1000 #endif -/* -** The maximum depth of the LALR(1) stack used in the parser that -** interprets SQL inputs. The parser stack depth can also be limited -** indirectly by SQLITE_MAX_SQL_LENGTH. Limiting the parser stack -** depth can help prevent excess memory usage and excess CPU stack -** usage when processing hostile SQL. -** -** Prior to version 3.45.0 (2024-01-15), the parser stack was -** hard-coded to 100 entries, and that worked fine for almost all -** applications. So the upper bound on this limit need not be large. -*/ -#ifndef SQLITE_MAX_PARSER_DEPTH -# define SQLITE_MAX_PARSER_DEPTH 2500 -#endif - /* ** The maximum number of terms in a compound SELECT statement. ** The code generator for compound SELECT statements does one diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 2c7918926..02a4d84e4 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -124,15 +124,6 @@ /* Forward declaration */ typedef struct SqliteDb SqliteDb; -/* Add -DSQLITE_ENABLE_QRF_IN_TCL to add the Query Result Formatter (QRF) -** into the build of the TCL extension, when building using separate -** source files. The QRF is included automatically when building from -** the tclsqlite3.c amalgamation. -*/ -#if defined(SQLITE_ENABLE_QRF_IN_TCL) -#include "qrf.h" -#endif - /* ** New SQL functions can be created as TCL scripts. Each such function ** is described by an instance of the following structure. @@ -2044,367 +2035,6 @@ static void DbHookCmd( sqlite3_wal_hook(db, (pDb->pWalHook?DbWalHandler:0), pDb); } -/* -** Implementation of the "db format" command. -** -** Based on provided options, format the results of the SQL statement(s) -** provided into human-readable form using the Query Result Formatter (QRF) -** and return the resuling text. -** -** Syntax: db format OPTIONS SQL -** -** OPTIONS may be: -** -** -style ("auto"|"box"|"column"|...) Output style -** -esc ("auto"|"off"|"ascii"|"symbol") How to deal with ctrl chars -** -text ("auto"|"off"|"sql"|"csv"|...) How to escape TEXT values -** -title ("auto"|"off"|"sql"|...|"off") How to escape column names -** -blob ("auto"|"text"|"sql"|...) How to escape BLOB values -** -wordwrap ("auto"|"off"|"on") Try to wrap at word boundry? -** -textjsonb ("auto"|"off"|"on") Auto-convert JSONB to text? -** -splitcolumn ("auto"|"off"|"on") Enable split-column mode -** -defaultalign ("auto"|"left"|...) Default alignment -** -titalalign ("auto"|"left"|"right"|...) Default column name alignment -** -border ("auto"|"off"|"on") Border for box and table styles -** -wrap NUMBER Max width of any single column -** -screenwidth NUMBER Width of the display TTY -** -linelimit NUMBER Max lines for any cell -** -charlimit NUMBER Content truncated to this size -** -titlelimit NUMBER Max width of column titles -** -align LIST-OF-ALIGNMENT Alignment of columns -** -widths LIST-OF-NUMBERS Widths for individual columns -** -columnsep TEXT Column separator text -** -rowsep TEXT Row separator text -** -tablename TEXT Table name for style "insert" -** -null TEXT Text for NULL values -** -** A mapping from TCL "format" command options to sqlite3_qrf_spec fields -** is below. Use this to reference the QRF documentation: -** -** TCL Option spec field -** ---------- ---------- -** -style eStyle -** -esc eEsc -** -text eText -** -title eTitle, bTitle -** -blob eBlob -** -wordwrap bWordWrap -** -textjsonb bTextJsonb -** -splitcolumn bSplitColumn -** -defaultalign eDfltAlign -** -titlealign eTitleAlign -** -border bBorder -** -wrap nWrap -** -screenwidth nScreenWidth -** -linelimit nLineLimit -** -charlimit nCharLimit -** -titlelimit nTitleLimit -** -align nAlign, aAlign -** -widths nWidth, aWidth -** -columnsep zColumnSep -** -rowsep zRowSep -** -tablename zTableName -** -null zNull -*/ -static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ -#ifndef SQLITE_QRF_H - Tcl_SetResult(pDb->interp, "QRF not available in this build", TCL_VOLATILE); - return TCL_ERROR; -#else - char *zResult = 0; /* Result to be returned */ - const char *zSql = 0; /* SQL to run */ - int i; /* Loop counter */ - int rc; /* Result code */ - sqlite3_qrf_spec qrf; /* Formatting spec */ - static const char *azAlign[] = { - "auto", "bottom", "c", - "center", "e", "left", - "middle", "n", "ne", - "nw", "right", "s", - "se", "sw", "top", - "w", 0 - }; - static const unsigned char aAlignMap[] = { - QRF_ALIGN_Auto, QRF_ALIGN_Bottom, QRF_ALIGN_C, - QRF_ALIGN_Center, QRF_ALIGN_E, QRF_ALIGN_Left, - QRF_ALIGN_Middle, QRF_ALIGN_N, QRF_ALIGN_NE, - QRF_ALIGN_NW, QRF_ALIGN_Right, QRF_ALIGN_S, - QRF_ALIGN_SE, QRF_ALIGN_SW, QRF_ALIGN_Top, - QRF_ALIGN_W - }; - - memset(&qrf, 0, sizeof(qrf)); - qrf.iVersion = 1; - qrf.pzOutput = &zResult; - for(i=2; i<objc; i++){ - const char *zArg = Tcl_GetString(objv[i]); - const char *azBool[] = { "auto", "yes", "no", "on", "off", 0 }; - const unsigned char aBoolMap[] = { 0, 2, 1, 2, 1 }; - if( zArg[0]!='-' ){ - if( zSql ){ - Tcl_AppendResult(pDb->interp, "unknown argument: ", zArg, (char*)0); - rc = TCL_ERROR; - goto format_failed; - } - zSql = zArg; - }else if( i==objc-1 ){ - Tcl_AppendResult(pDb->interp, "option has no argument: ", zArg, (char*)0); - rc = TCL_ERROR; - goto format_failed; - }else if( strcmp(zArg,"-style")==0 ){ - static const char *azStyles[] = { - "auto", "box", "column", - "count", "csv", "eqp", - "explain", "html", "insert", - "jobject", "json", "line", - "list", "markdown", "quote", - "stats", "stats-est", "stats-vm", - "table", 0 - }; - static unsigned char aStyleMap[] = { - QRF_STYLE_Auto, QRF_STYLE_Box, QRF_STYLE_Column, - QRF_STYLE_Count, QRF_STYLE_Csv, QRF_STYLE_Eqp, - QRF_STYLE_Explain, QRF_STYLE_Html, QRF_STYLE_Insert, - QRF_STYLE_JObject, QRF_STYLE_Json, QRF_STYLE_Line, - QRF_STYLE_List, QRF_STYLE_Markdown, QRF_STYLE_Quote, - QRF_STYLE_Stats, QRF_STYLE_StatsEst, QRF_STYLE_StatsVm, - QRF_STYLE_Table - }; - int style; - rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azStyles, - "format style (-style)", 0, &style); - if( rc ) goto format_failed; - qrf.eStyle = aStyleMap[style]; - i++; - }else if( strcmp(zArg,"-esc")==0 ){ - static const char *azEsc[] = { - "ascii", "auto", "off", "symbol", 0 - }; - static unsigned char aEscMap[] = { - QRF_ESC_Ascii, QRF_ESC_Auto, QRF_ESC_Off, QRF_ESC_Symbol - }; - int esc; - rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azEsc, - "control character escape (-esc)", 0, &esc); - if( rc ) goto format_failed; - qrf.eEsc = aEscMap[esc]; - i++; - }else if( strcmp(zArg,"-text")==0 || strcmp(zArg, "-title")==0 ){ - /* NB: --title can be "off" or "on but --text may not be. Thus we put - ** the "off" and "on" choices first and start the search on the - ** thrid element of the array when processing --text */ - static const char *azText[] = { "off", "on", - "auto", "csv", "html", - "json", "plain", "relaxed", - "sql", "tcl", 0 - }; - static unsigned char aTextMap[] = { - QRF_TEXT_Auto, QRF_TEXT_Csv, QRF_TEXT_Html, - QRF_TEXT_Json, QRF_TEXT_Plain, QRF_TEXT_Relaxed, - QRF_TEXT_Sql, QRF_TEXT_Tcl - }; - int txt; - int k = zArg[2]=='e'; - rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], &azText[k*2], zArg, - 0, &txt); - if( rc ) goto format_failed; - if( k ){ - qrf.eText = aTextMap[txt]; - }else if( txt<=1 ){ - qrf.bTitles = txt ? QRF_Yes : QRF_No; - qrf.eTitle = QRF_TEXT_Auto; - }else{ - qrf.bTitles = QRF_Yes; - qrf.eTitle = aTextMap[txt-2]; - } - i++; - }else if( strcmp(zArg,"-blob")==0 ){ - static const char *azBlob[] = { - "auto", "hex", "json", - "tcl", "text", "sql", - "size", 0 - }; - static unsigned char aBlobMap[] = { - QRF_BLOB_Auto, QRF_BLOB_Hex, QRF_BLOB_Json, - QRF_BLOB_Tcl, QRF_BLOB_Text, QRF_BLOB_Sql, - QRF_BLOB_Size - }; - int blob; - rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBlob, - "BLOB encoding (-blob)", 0, &blob); - if( rc ) goto format_failed; - qrf.eBlob = aBlobMap[blob]; - i++; - }else if( strcmp(zArg,"-wordwrap")==0 ){ - int v = 0; - rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, - "-wordwrap", 0, &v); - if( rc ) goto format_failed; - qrf.bWordWrap = aBoolMap[v]; - i++; - }else if( strcmp(zArg,"-textjsonb")==0 - || strcmp(zArg,"-splitcolumn")==0 - || strcmp(zArg,"-border")==0 - ){ - int v = 0; - rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, - zArg, 0, &v); - if( rc ) goto format_failed; - if( zArg[1]=='t' ){ - qrf.bTextJsonb = aBoolMap[v]; - }else if( zArg[1]=='b' ){ - qrf.bBorder = aBoolMap[v]; - }else{ - qrf.bSplitColumn = aBoolMap[v]; - } - i++; - }else if( strcmp(zArg,"-defaultalign")==0 || strcmp(zArg,"-titlealign")==0){ - int ax = 0; - rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azAlign, - zArg[1]=='d' ? "default alignment (-defaultalign)" : - "title alignment (-titlealign)", - 0, &ax); - if( rc ) goto format_failed; - if( zArg[1]=='d' ){ - qrf.eDfltAlign = aAlignMap[ax]; - }else{ - qrf.eTitleAlign = aAlignMap[ax]; - } - i++; - }else if( strcmp(zArg,"-wrap")==0 - || strcmp(zArg,"-screenwidth")==0 - || strcmp(zArg,"-linelimit")==0 - || strcmp(zArg,"-titlelimit")==0 - ){ - int v = 0; - rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v); - if( rc ) goto format_failed; - if( v<QRF_MIN_WIDTH ){ - v = QRF_MIN_WIDTH; - }else if( v>QRF_MAX_WIDTH ){ - v = QRF_MAX_WIDTH; - } - if( zArg[1]=='w' ){ - qrf.nWrap = v; - }else if( zArg[1]=='s' ){ - qrf.nScreenWidth = v; - }else if( zArg[1]=='t' ){ - qrf.nTitleLimit = v; - }else{ - qrf.nLineLimit = v; - } - i++; - }else if( strcmp(zArg,"-charlimit")==0 ){ - int v = 0; - rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v); - if( rc ) goto format_failed; - if( v<0 ) v = 0; - qrf.nCharLimit = v; - i++; - }else if( strcmp(zArg,"-align")==0 ){ - Tcl_Size n = 0; - int jj; - rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n); - if( rc ) goto format_failed; - sqlite3_free(qrf.aAlign); - qrf.aAlign = sqlite3_malloc64( (n+1)*sizeof(qrf.aAlign[0]) ); - if( qrf.aAlign==0 ){ - Tcl_AppendResult(pDb->interp, "out of memory", (char*)0); - rc = TCL_ERROR; - goto format_failed; - } - memset(qrf.aAlign, 0, (n+1)*sizeof(qrf.aAlign[0])); - qrf.nAlign = n; - for(jj=0; jj<n; jj++){ - int x; - Tcl_Obj *pTerm; - rc = Tcl_ListObjIndex(pDb->interp, objv[i+1], jj, &pTerm); - if( rc ) goto format_failed; - rc = Tcl_GetIndexFromObj(pDb->interp, pTerm, azAlign, - "column alignment (-align)", 0, &x); - if( rc ) goto format_failed; - qrf.aAlign[jj] = aAlignMap[x]; - } - i++; - }else if( strcmp(zArg,"-widths")==0 ){ - Tcl_Size n = 0; - int jj; - rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n); - if( rc ) goto format_failed; - sqlite3_free(qrf.aWidth); - qrf.aWidth = sqlite3_malloc64( (n+1)*sizeof(qrf.aWidth[0]) ); - if( qrf.aWidth==0 ){ - Tcl_AppendResult(pDb->interp, "out of memory", (char*)0); - rc = TCL_ERROR; - goto format_failed; - } - memset(qrf.aWidth, 0, (n+1)*sizeof(qrf.aWidth[0])); - qrf.nWidth = n; - for(jj=0; jj<n; jj++){ - Tcl_Obj *pTerm; - int v; - rc = Tcl_ListObjIndex(pDb->interp, objv[i+1], jj, &pTerm); - if( rc ) goto format_failed; - rc = Tcl_GetIntFromObj(pDb->interp, pTerm, &v); - if( v<(-QRF_MAX_WIDTH) ){ - v = -QRF_MAX_WIDTH; - }else if( v>QRF_MAX_WIDTH ){ - v = QRF_MAX_WIDTH; - } - qrf.aWidth[jj] = (short int)v; - } - i++; - }else if( strcmp(zArg,"-columnsep")==0 ){ - qrf.zColumnSep = Tcl_GetString(objv[i+1]); - i++; - }else if( strcmp(zArg,"-rowsep")==0 ){ - qrf.zRowSep = Tcl_GetString(objv[i+1]); - i++; - }else if( strcmp(zArg,"-tablename")==0 ){ - qrf.zTableName = Tcl_GetString(objv[i+1]); - i++; - }else if( strcmp(zArg,"-null")==0 ){ - qrf.zNull = Tcl_GetString(objv[i+1]); - i++; - }else if( strcmp(zArg,"-version")==0 ){ - /* Undocumented. Testing use only */ - qrf.iVersion = atoi(Tcl_GetString(objv[i+1])); - i++; - }else{ - Tcl_AppendResult(pDb->interp, "unknown option: ", zArg, (char*)0); - rc = TCL_ERROR; - goto format_failed; - } - } - while( zSql && zSql[0] ){ - SqlPreparedStmt *pStmt = 0; /* Next statement to run */ - char *zErr = 0; /* Error message from QRF */ - - rc = dbPrepareAndBind(pDb, zSql, &zSql, &pStmt); - if( rc ) goto format_failed; - if( pStmt==0 ) continue; - rc = sqlite3_format_query_result(pStmt->pStmt, &qrf, &zErr); - dbReleaseStmt(pDb, pStmt, 0); - if( rc ){ - Tcl_SetResult(pDb->interp, zErr, TCL_VOLATILE); - sqlite3_free(zErr); - rc = TCL_ERROR; - goto format_failed; - } - } - Tcl_SetResult(pDb->interp, zResult, TCL_VOLATILE); - rc = TCL_OK; - /* Fall through...*/ - -format_failed: - sqlite3_free(qrf.aWidth); - sqlite3_free(qrf.aAlign); - sqlite3_free(zResult); - return rc; - -#endif -} - /* ** The "sqlite" command below creates a new Tcl command for each ** connection it opens to an SQLite database. This routine is invoked @@ -2434,15 +2064,15 @@ static int SQLITE_TCLAPI DbObjCmd( "commit_hook", "complete", "config", "copy", "deserialize", "enable_load_extension", "errorcode", "erroroffset", "eval", - "exists", "format", "function", - "incrblob", "interrupt", "last_insert_rowid", - "nullvalue", "onecolumn", "preupdate", - "profile", "progress", "rekey", - "restore", "rollback_hook", "serialize", - "status", "timeout", "total_changes", - "trace", "trace_v2", "transaction", - "unlock_notify", "update_hook", "version", - "wal_hook", 0 + "exists", "function", "incrblob", + "interrupt", "last_insert_rowid", "nullvalue", + "onecolumn", "preupdate", "profile", + "progress", "rekey", "restore", + "rollback_hook", "serialize", "status", + "timeout", "total_changes", "trace", + "trace_v2", "transaction", "unlock_notify", + "update_hook", "version", "wal_hook", + 0 }; enum DB_enum { DB_AUTHORIZER, DB_BACKUP, DB_BIND_FALLBACK, @@ -2451,15 +2081,14 @@ static int SQLITE_TCLAPI DbObjCmd( DB_COMMIT_HOOK, DB_COMPLETE, DB_CONFIG, DB_COPY, DB_DESERIALIZE, DB_ENABLE_LOAD_EXTENSION, DB_ERRORCODE, DB_ERROROFFSET, DB_EVAL, - DB_EXISTS, DB_FORMAT, DB_FUNCTION, - DB_INCRBLOB, DB_INTERRUPT, DB_LAST_INSERT_ROWID, - DB_NULLVALUE, DB_ONECOLUMN, DB_PREUPDATE, - DB_PROFILE, DB_PROGRESS, DB_REKEY, - DB_RESTORE, DB_ROLLBACK_HOOK, DB_SERIALIZE, - DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES, - DB_TRACE, DB_TRACE_V2, DB_TRANSACTION, - DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, DB_VERSION, - DB_WAL_HOOK + DB_EXISTS, DB_FUNCTION, DB_INCRBLOB, + DB_INTERRUPT, DB_LAST_INSERT_ROWID, DB_NULLVALUE, + DB_ONECOLUMN, DB_PREUPDATE, DB_PROFILE, + DB_PROGRESS, DB_REKEY, DB_RESTORE, + DB_ROLLBACK_HOOK, DB_SERIALIZE, DB_STATUS, + DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE, + DB_TRACE_V2, DB_TRANSACTION, DB_UNLOCK_NOTIFY, + DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK, }; /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */ @@ -3349,18 +2978,6 @@ static int SQLITE_TCLAPI DbObjCmd( break; } - /* - ** $db format [OPTIONS] SQL - ** - ** Run the SQL statement(s) given as the final argument. Use the - ** Query Result Formatter extension of SQLite to format the output as - ** text and return that text. - */ - case DB_FORMAT: { - rc = dbQrf(pDb, objc, objv); - break; - } - /* ** $db function NAME [OPTIONS] SCRIPT ** diff --git a/src/test1.c b/src/test1.c index 3ca5c837a..f8e83dc42 100644 --- a/src/test1.c +++ b/src/test1.c @@ -4411,7 +4411,7 @@ static void delIntptr(void *p){ } /* -** bind_carray_intptr STMT IPARAM INT-0 INT-1 INT-2... +** bind_carray_intptr STMT IPARAM INT0 INT1 INT2... */ static int SQLITE_TCLAPI bind_carray_intptr( void * clientData, @@ -4455,7 +4455,6 @@ static int SQLITE_TCLAPI bind_carray_intptr( ** -malloc ** -transient ** -static -** -v2 ** -int32 ** -int64 ** -double @@ -4478,7 +4477,6 @@ static int SQLITE_TCLAPI test_carray_bind( void *aData = 0; int isTransient = 0; int isStatic = 0; - int isV2 = 0; int isMalloc = 0; /* True to use custom xDel function */ int idx; int i, j; @@ -4511,22 +4509,16 @@ static int SQLITE_TCLAPI test_carray_bind( const char *z = Tcl_GetString(objv[i]); if( strcmp(z, "-transient")==0 ){ isTransient = 1; - isStatic = isMalloc = 0; xDel = SQLITE_TRANSIENT; }else if( strcmp(z, "-static")==0 ){ isStatic = 1; - isMalloc = isTransient = 0; xDel = SQLITE_STATIC; }else if( strcmp(z, "-malloc")==0 ){ isMalloc = 1; - isStatic = isTransient = 0; xDel = testCarrayFree; }else - if( strcmp(z, "-v2")==0 ){ - isV2 = 1; - }else if( strcmp(z, "-int32")==0 ){ eType = 0; /* CARRAY_INT32 */ }else @@ -4695,20 +4687,7 @@ static int SQLITE_TCLAPI test_carray_bind( if( rc==SQLITE_OK ){ if( mFlagsOverride==0 ) mFlagsOverride = eType; - if( isV2 ){ - void *pDel; - if( xDel==testCarrayFree ){ - u8 *p2 = (u8*)aData; - pDel = (void*)&p2[-16]; - xDel = sqlite3_free; - }else{ - pDel = aData; - } - rc = sqlite3_carray_bind_v2(pStmt, idx, aData, nData, mFlagsOverride, - xDel, pDel); - }else{ - rc = sqlite3_carray_bind(pStmt, idx, aData, nData, mFlagsOverride, xDel); - } + rc = sqlite3_carray_bind(pStmt, idx, aData, nData, mFlagsOverride, xDel); } if( isTransient ){ if( eType==3 && aData ){ @@ -7391,7 +7370,6 @@ static int SQLITE_TCLAPI test_limit( { "SQLITE_LIMIT_SQL_LENGTH", SQLITE_LIMIT_SQL_LENGTH }, { "SQLITE_LIMIT_COLUMN", SQLITE_LIMIT_COLUMN }, { "SQLITE_LIMIT_EXPR_DEPTH", SQLITE_LIMIT_EXPR_DEPTH }, - { "SQLITE_LIMIT_PARSER_DEPTH", SQLITE_LIMIT_PARSER_DEPTH }, { "SQLITE_LIMIT_COMPOUND_SELECT", SQLITE_LIMIT_COMPOUND_SELECT }, { "SQLITE_LIMIT_VDBE_OP", SQLITE_LIMIT_VDBE_OP }, { "SQLITE_LIMIT_FUNCTION_ARG", SQLITE_LIMIT_FUNCTION_ARG }, @@ -7403,7 +7381,7 @@ static int SQLITE_TCLAPI test_limit( /* Out of range test cases */ { "SQLITE_LIMIT_TOOSMALL", -1, }, - { "SQLITE_LIMIT_TOOBIG", SQLITE_LIMIT_PARSER_DEPTH+1 }, + { "SQLITE_LIMIT_TOOBIG", SQLITE_LIMIT_WORKER_THREADS+1 }, }; int i, id = 0; int val; @@ -7697,8 +7675,7 @@ static int SQLITE_TCLAPI test_wal_checkpoint_v2( int nCkpt = -555; Tcl_Obj *pRet; - const char * aMode[] = {"noop", "passive", "full", "restart", "truncate", 0}; - assert( SQLITE_CHECKPOINT_NOOP==-1 ); + const char * aMode[] = { "passive", "full", "restart", "truncate", 0 }; assert( SQLITE_CHECKPOINT_PASSIVE==0 ); assert( SQLITE_CHECKPOINT_FULL==1 ); assert( SQLITE_CHECKPOINT_RESTART==2 ); @@ -7712,15 +7689,12 @@ static int SQLITE_TCLAPI test_wal_checkpoint_v2( if( objc==4 ){ zDb = Tcl_GetString(objv[3]); } - if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) || ( + TCL_OK!=Tcl_GetIntFromObj(0, objv[2], &eMode) + && TCL_OK!=Tcl_GetIndexFromObj(interp, objv[2], aMode, "mode", 0, &eMode) + )){ return TCL_ERROR; } - if( TCL_OK!=Tcl_GetIntFromObj(0, objv[2], &eMode) ){ - if( TCL_OK!=Tcl_GetIndexFromObj(interp, objv[2], aMode, "mode", 0,&eMode) ){ - return TCL_ERROR; - } - eMode = eMode - 1; - } rc = sqlite3_wal_checkpoint_v2(db, zDb, eMode, &nLog, &nCkpt); if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ @@ -8644,7 +8618,6 @@ static int SQLITE_TCLAPI test_sqlite3_db_config( { "ATTACH_CREATE", SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE }, { "ATTACH_WRITE", SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE }, { "COMMENTS", SQLITE_DBCONFIG_ENABLE_COMMENTS }, - { "FP_DIGITS", SQLITE_DBCONFIG_FP_DIGITS }, }; int i; int v = 0; diff --git a/src/test_bestindex.c b/src/test_bestindex.c index 963abfec0..f6b5db0fb 100644 --- a/src/test_bestindex.c +++ b/src/test_bestindex.c @@ -828,6 +828,7 @@ static int tclUpdate( tcl_vtab *pTab = (tcl_vtab*)tab; Tcl_Interp *interp = pTab->interp; Tcl_Obj *pEval = Tcl_DuplicateObj(pTab->pCmd); + Tcl_Obj *pRes = 0; int rc = TCL_OK; Tcl_IncrRefCount(pEval); diff --git a/src/test_config.c b/src/test_config.c index bebf8625a..3dbef3c9a 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -82,7 +82,7 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options","configslower","1.0",TCL_GLOBAL_ONLY); #endif -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT Tcl_SetVar2(interp, "sqlite_options", "curdir", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "curdir", "0", TCL_GLOBAL_ONLY); @@ -94,6 +94,12 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "win32malloc", "0", TCL_GLOBAL_ONLY); #endif +#if defined(SQLITE_OS_WINRT) && SQLITE_OS_WINRT + Tcl_SetVar2(interp, "sqlite_options", "winrt", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "winrt", "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_DEBUG Tcl_SetVar2(interp, "sqlite_options", "debug", "1", TCL_GLOBAL_ONLY); #else @@ -679,14 +685,6 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "sqlite_options", "trace", "1", TCL_GLOBAL_ONLY); #endif -#ifdef SQLITE_THREAD_MISUSE_WARNINGS - Tcl_SetVar2(interp, "sqlite_options", "thread_misuse_warnings", - "1", TCL_GLOBAL_ONLY); -#else - Tcl_SetVar2(interp, "sqlite_options", "thread_misuse_warnings", - "0", TCL_GLOBAL_ONLY); -#endif - #ifdef SQLITE_OMIT_TRIGGER Tcl_SetVar2(interp, "sqlite_options", "trigger", "0", TCL_GLOBAL_ONLY); #else diff --git a/src/test_quota.c b/src/test_quota.c index 3eeacc7e7..d2f9cddd1 100644 --- a/src/test_quota.c +++ b/src/test_quota.c @@ -387,7 +387,11 @@ static char *quota_utf8_to_mbcs(const char *zUtf8){ zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) ); if( zTmpWide==0 ) return 0; MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide); +#ifdef SQLITE_OS_WINRT + codepage = CP_ACP; +#else codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; +#endif nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0); zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0; if( zMbcs ){ diff --git a/src/tokenize.c b/src/tokenize.c index 884d1acb8..152ada64f 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -508,7 +508,7 @@ i64 sqlite3GetToken(const unsigned char *z, int *tokenType){ } case CC_DOLLAR: case CC_VARALPHA: { - i64 n = 0; + int n = 0; testcase( z[0]=='$' ); testcase( z[0]=='@' ); testcase( z[0]==':' ); testcase( z[0]=='#' ); *tokenType = TK_VARIABLE; @@ -604,7 +604,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql){ int tokenType; /* type of the next token */ int lastTokenParsed = -1; /* type of the previous token */ sqlite3 *db = pParse->db; /* The database connection */ - i64 mxSqlLen; /* Max length of an SQL string */ + int mxSqlLen; /* Max length of an SQL string */ Parse *pParentParse = 0; /* Outer parse context, if any */ #ifdef sqlite3Parser_ENGINEALWAYSONSTACK yyParser sEngine; /* Space to hold the Lemon-generated Parser object */ diff --git a/src/treeview.c b/src/treeview.c index b482e1581..153fec88d 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -1300,13 +1300,7 @@ void sqlite3TreeViewTrigger( void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } -void sqlite3ShowSrcList(const SrcList *p){ - TreeView *pView = 0; - sqlite3TreeViewPush(&pView, 0); - sqlite3TreeViewLine(pView, "SRCLIST"); - sqlite3TreeViewSrcList(pView,p); - sqlite3TreeViewPop(&pView); -} +void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); } void sqlite3ShowSelect(const Select *p){ sqlite3TreeViewSelect(0,p,0); } void sqlite3ShowWith(const With *p){ sqlite3TreeViewWith(0,p,0); } void sqlite3ShowUpsert(const Upsert *p){ sqlite3TreeViewUpsert(0,p,0); } diff --git a/src/trigger.c b/src/trigger.c index 4f9068ad8..799fbe57f 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -26,7 +26,7 @@ void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){ sqlite3SelectDelete(db, pTmp->pSelect); sqlite3IdListDelete(db, pTmp->pIdList); sqlite3UpsertDelete(db, pTmp->pUpsert); - sqlite3SrcListDelete(db, pTmp->pSrc); + sqlite3SrcListDelete(db, pTmp->pFrom); sqlite3DbFree(db, pTmp->zSpan); sqlite3DbFree(db, pTmp); @@ -215,16 +215,11 @@ void sqlite3BeginTrigger( } } - /* NB: The SQLITE_ALLOW_TRIGGERS_ON_SYSTEM_TABLES compile-time option is - ** experimental and unsupported. Do not use it unless understand the - ** implications and you cannot get by without this capability. */ -#if !defined(SQLITE_ALLOW_TRIGGERS_ON_SYSTEM_TABLES) /* Experimental */ /* Do not create a trigger on a system table */ if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ sqlite3ErrorMsg(pParse, "cannot create trigger on system table"); goto trigger_cleanup; } -#endif /* INSTEAD of triggers are only for views and views only support INSTEAD ** of triggers. @@ -336,7 +331,6 @@ void sqlite3FinishTrigger( if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup; zName = pTrig->zName; iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema); - assert( iDb>=00 && iDb<db->nDb ); pTrig->step_list = pStepList; while( pStepList ){ pStepList->pTrig = pTrig; @@ -371,12 +365,12 @@ void sqlite3FinishTrigger( if( sqlite3ReadOnlyShadowTables(db) ){ TriggerStep *pStep; for(pStep=pTrig->step_list; pStep; pStep=pStep->pNext){ - if( pStep->pSrc!=0 - && sqlite3ShadowTableName(db, pStep->pSrc->a[0].zName) + if( pStep->zTarget!=0 + && sqlite3ShadowTableName(db, pStep->zTarget) ){ sqlite3ErrorMsg(pParse, "trigger \"%s\" may not write to shadow table \"%s\"", - pTrig->zName, pStep->pSrc->a[0].zName); + pTrig->zName, pStep->zTarget); goto triggerfinish_cleanup; } } @@ -467,39 +461,26 @@ TriggerStep *sqlite3TriggerSelectStep( static TriggerStep *triggerStepAllocate( Parse *pParse, /* Parser context */ u8 op, /* Trigger opcode */ - SrcList *pTabList, /* Target table */ + Token *pName, /* The target name */ const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ ){ - Trigger *pNew = pParse->pNewTrigger; sqlite3 *db = pParse->db; - TriggerStep *pTriggerStep = 0; + TriggerStep *pTriggerStep; - if( pParse->nErr==0 ){ - if( pNew - && pNew->pSchema!=db->aDb[1].pSchema - && pTabList->a[0].u4.zDatabase - ){ - sqlite3ErrorMsg(pParse, - "qualified table names are not allowed on INSERT, UPDATE, and DELETE " - "statements within triggers"); - }else{ - pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep)); - if( pTriggerStep ){ - pTriggerStep->pSrc = sqlite3SrcListDup(db, pTabList, EXPRDUP_REDUCE); - pTriggerStep->op = op; - pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); - if( pTriggerStep->pSrc && IN_RENAME_OBJECT ){ - sqlite3RenameTokenRemap(pParse, - pTriggerStep->pSrc->a[0].zName, - pTabList->a[0].zName - ); - } - } + if( pParse->nErr ) return 0; + pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1); + if( pTriggerStep ){ + char *z = (char*)&pTriggerStep[1]; + memcpy(z, pName->z, pName->n); + sqlite3Dequote(z); + pTriggerStep->zTarget = z; + pTriggerStep->op = op; + pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, pTriggerStep->zTarget, pName); } } - - sqlite3SrcListDelete(db, pTabList); return pTriggerStep; } @@ -512,7 +493,7 @@ static TriggerStep *triggerStepAllocate( */ TriggerStep *sqlite3TriggerInsertStep( Parse *pParse, /* Parser */ - SrcList *pTabList, /* Table to INSERT into */ + Token *pTableName, /* Name of the table into which we insert */ IdList *pColumn, /* List of columns in pTableName to insert into */ Select *pSelect, /* A SELECT statement that supplies values */ u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */ @@ -525,7 +506,7 @@ TriggerStep *sqlite3TriggerInsertStep( assert(pSelect != 0 || db->mallocFailed); - pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTabList, zStart, zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTableName,zStart,zEnd); if( pTriggerStep ){ if( IN_RENAME_OBJECT ){ pTriggerStep->pSelect = pSelect; @@ -557,7 +538,7 @@ TriggerStep *sqlite3TriggerInsertStep( */ TriggerStep *sqlite3TriggerUpdateStep( Parse *pParse, /* Parser */ - SrcList *pTabList, /* Name of the table to be updated */ + Token *pTableName, /* Name of the table to be updated */ SrcList *pFrom, /* FROM clause for an UPDATE-FROM, or NULL */ ExprList *pEList, /* The SET clause: list of column and new values */ Expr *pWhere, /* The WHERE clause */ @@ -568,36 +549,21 @@ TriggerStep *sqlite3TriggerUpdateStep( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTabList, zStart, zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTableName,zStart,zEnd); if( pTriggerStep ){ - SrcList *pFromDup = 0; if( IN_RENAME_OBJECT ){ pTriggerStep->pExprList = pEList; pTriggerStep->pWhere = pWhere; - pFromDup = pFrom; + pTriggerStep->pFrom = pFrom; pEList = 0; pWhere = 0; pFrom = 0; }else{ pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE); pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); - pFromDup = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE); + pTriggerStep->pFrom = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE); } pTriggerStep->orconf = orconf; - - if( pFromDup && !IN_RENAME_OBJECT){ - Select *pSub; - Token as = {0, 0}; - pSub = sqlite3SelectNew(pParse, 0, pFromDup, 0,0,0,0, SF_NestedFrom, 0); - pFromDup = sqlite3SrcListAppendFromTerm(pParse, 0, 0, 0, &as, pSub ,0); - } - if( pFromDup && pTriggerStep->pSrc ){ - pTriggerStep->pSrc = sqlite3SrcListAppendList( - pParse, pTriggerStep->pSrc, pFromDup - ); - }else{ - sqlite3SrcListDelete(db, pFromDup); - } } sqlite3ExprListDelete(db, pEList); sqlite3ExprDelete(db, pWhere); @@ -612,7 +578,7 @@ TriggerStep *sqlite3TriggerUpdateStep( */ TriggerStep *sqlite3TriggerDeleteStep( Parse *pParse, /* Parser */ - SrcList *pTabList, /* The table from which rows are deleted */ + Token *pTableName, /* The table from which rows are deleted */ Expr *pWhere, /* The WHERE clause */ const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ @@ -620,7 +586,7 @@ TriggerStep *sqlite3TriggerDeleteStep( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTabList, zStart, zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTableName,zStart,zEnd); if( pTriggerStep ){ if( IN_RENAME_OBJECT ){ pTriggerStep->pWhere = pWhere; @@ -820,7 +786,6 @@ static SQLITE_NOINLINE Trigger *triggersReallyExist( p = pList; if( (pParse->db->flags & SQLITE_EnableTrigger)==0 && pTab->pTrigger!=0 - && sqlite3SchemaToIndex(pParse->db, pTab->pTrigger->pSchema)!=1 ){ /* The SQLITE_DBCONFIG_ENABLE_TRIGGER setting is off. That means that ** only TEMP triggers are allowed. Truncate the pList so that it @@ -883,6 +848,52 @@ Trigger *sqlite3TriggersExist( return triggersReallyExist(pParse,pTab,op,pChanges,pMask); } +/* +** Convert the pStep->zTarget string into a SrcList and return a pointer +** to that SrcList. +** +** This routine adds a specific database name, if needed, to the target when +** forming the SrcList. This prevents a trigger in one database from +** referring to a target in another database. An exception is when the +** trigger is in TEMP in which case it can refer to any other database it +** wants. +*/ +SrcList *sqlite3TriggerStepSrc( + Parse *pParse, /* The parsing context */ + TriggerStep *pStep /* The trigger containing the target token */ +){ + sqlite3 *db = pParse->db; + SrcList *pSrc; /* SrcList to be returned */ + char *zName = sqlite3DbStrDup(db, pStep->zTarget); + pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + assert( pSrc==0 || pSrc->nSrc==1 ); + assert( zName || pSrc==0 ); + if( pSrc ){ + Schema *pSchema = pStep->pTrig->pSchema; + pSrc->a[0].zName = zName; + if( pSchema!=db->aDb[1].pSchema ){ + assert( pSrc->a[0].fg.fixedSchema || pSrc->a[0].u4.zDatabase==0 ); + pSrc->a[0].u4.pSchema = pSchema; + pSrc->a[0].fg.fixedSchema = 1; + } + if( pStep->pFrom ){ + SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0); + if( pDup && pDup->nSrc>1 && !IN_RENAME_OBJECT ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pDup,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pDup = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } + pSrc = sqlite3SrcListAppendList(pParse, pSrc, pDup); + } + }else{ + sqlite3DbFree(db, zName); + } + return pSrc; +} + /* ** Return true if the pExpr term from the RETURNING clause argument ** list is of the form "*". Raise an error if the terms if of the @@ -1148,7 +1159,7 @@ static int codeTriggerProgram( switch( pStep->op ){ case TK_UPDATE: { sqlite3Update(pParse, - sqlite3SrcListDup(db, pStep->pSrc, 0), + sqlite3TriggerStepSrc(pParse, pStep), sqlite3ExprListDup(db, pStep->pExprList, 0), sqlite3ExprDup(db, pStep->pWhere, 0), pParse->eOrconf, 0, 0, 0 @@ -1158,7 +1169,7 @@ static int codeTriggerProgram( } case TK_INSERT: { sqlite3Insert(pParse, - sqlite3SrcListDup(db, pStep->pSrc, 0), + sqlite3TriggerStepSrc(pParse, pStep), sqlite3SelectDup(db, pStep->pSelect, 0), sqlite3IdListDup(db, pStep->pIdList), pParse->eOrconf, @@ -1169,7 +1180,7 @@ static int codeTriggerProgram( } case TK_DELETE: { sqlite3DeleteFrom(pParse, - sqlite3SrcListDup(db, pStep->pSrc, 0), + sqlite3TriggerStepSrc(pParse, pStep), sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0 ); sqlite3VdbeAddOp0(v, OP_ResetCount); diff --git a/src/util.c b/src/util.c index 071029173..8e4fd516e 100644 --- a/src/util.c +++ b/src/util.c @@ -458,262 +458,48 @@ u8 sqlite3StrIHash(const char *z){ return h; } -/* -** Two inputs are multiplied to get a 128-bit result. Return -** the high-order 64 bits of that result. -*/ -static u64 sqlite3Multiply128(u64 a, u64 b){ -#if (defined(__GNUC__) || defined(__clang__)) \ - && (defined(__x86_64__) || defined(__aarch64__) || defined(__riscv)) - return ((__uint128_t)a * b) >> 64; -#elif defined(_MSC_VER) && defined(_M_X64) - return __umulh(a, b); -#else - u64 a1 = (u32)a; - u64 a2 = a >> 32; - u64 b1 = (u32)b; - u64 b2 = b >> 32; - u64 p0 = a1 * b1; - u64 p1 = a1 * b2; - u64 p2 = a2 * b1; - u64 p3 = a2 * b2; - u64 carry = ((p0 >> 32) + (u32)p1 + (u32)p2) >> 32; - return p3 + (p1 >> 32) + (p2 >> 32) + carry; -#endif -} - -/* -** Return a u64 with the N-th bit set. -*/ -#define U64_BIT(N) (((u64)1)<<(N)) - -/* -** Range of powers of 10 that we need to deal with when converting -** IEEE754 doubles to and from decimal. -*/ -#define POWERSOF10_FIRST (-348) -#define POWERSOF10_LAST (+347) - -/* -** For any p between -348 and +347, return the integer part of -** -** pow(10,p) * pow(2,63-pow10to2(p)) -** -** Or, in other words, for any p in range, return the most significant -** 64 bits of pow(10,p). The pow(10,p) value is shifted left or right, -** as appropriate so the most significant 64 bits fit exactly into a -** 64-bit unsigned integer. +/* Double-Double multiplication. (x[0],x[1]) *= (y,yy) ** -** Algorithm: -** -** (1) For p between 0 and 26, return the value directly from the aBase[] -** lookup table. -** -** (2) For p outside the range 0 to 26, use aScale[] for the initial value -** then refine that result (if necessary) by a single multiplication -** against aBase[]. +** Reference: +** T. J. Dekker, "A Floating-Point Technique for Extending the +** Available Precision". 1971-07-26. */ -static u64 powerOfTen(int p){ - static const u64 aBase[] = { - 0x8000000000000000LLU, /* 0: 1.0e+0 << 63 */ - 0xa000000000000000LLU, /* 1: 1.0e+1 << 60 */ - 0xc800000000000000LLU, /* 2: 1.0e+2 << 57 */ - 0xfa00000000000000LLU, /* 3: 1.0e+3 << 54 */ - 0x9c40000000000000LLU, /* 4: 1.0e+4 << 50 */ - 0xc350000000000000LLU, /* 5: 1.0e+5 << 47 */ - 0xf424000000000000LLU, /* 6: 1.0e+6 << 44 */ - 0x9896800000000000LLU, /* 7: 1.0e+7 << 40 */ - 0xbebc200000000000LLU, /* 8: 1.0e+8 << 37 */ - 0xee6b280000000000LLU, /* 9: 1.0e+9 << 34 */ - 0x9502f90000000000LLU, /* 10: 1.0e+10 << 30 */ - 0xba43b74000000000LLU, /* 11: 1.0e+11 << 27 */ - 0xe8d4a51000000000LLU, /* 12: 1.0e+12 << 24 */ - 0x9184e72a00000000LLU, /* 13: 1.0e+13 << 20 */ - 0xb5e620f480000000LLU, /* 14: 1.0e+14 << 17 */ - 0xe35fa931a0000000LLU, /* 15: 1.0e+15 << 14 */ - 0x8e1bc9bf04000000LLU, /* 16: 1.0e+16 << 10 */ - 0xb1a2bc2ec5000000LLU, /* 17: 1.0e+17 << 7 */ - 0xde0b6b3a76400000LLU, /* 18: 1.0e+18 << 4 */ - 0x8ac7230489e80000LLU, /* 19: 1.0e+19 >> 0 */ - 0xad78ebc5ac620000LLU, /* 20: 1.0e+20 >> 3 */ - 0xd8d726b7177a8000LLU, /* 21: 1.0e+21 >> 6 */ - 0x878678326eac9000LLU, /* 22: 1.0e+22 >> 10 */ - 0xa968163f0a57b400LLU, /* 23: 1.0e+23 >> 13 */ - 0xd3c21bcecceda100LLU, /* 24: 1.0e+24 >> 16 */ - 0x84595161401484a0LLU, /* 25: 1.0e+25 >> 20 */ - 0xa56fa5b99019a5c8LLU, /* 26: 1.0e+26 >> 23 */ - }; - static const u64 aScale[] = { - 0x8049a4ac0c5811aeLLU, /* 0: 1.0e-351 << 1229 */ - 0xcf42894a5dce35eaLLU, /* 1: 1.0e-324 << 1140 */ - 0xa76c582338ed2622LLU, /* 2: 1.0e-297 << 1050 */ - 0x873e4f75e2224e68LLU, /* 3: 1.0e-270 << 960 */ - 0xda7f5bf590966849LLU, /* 4: 1.0e-243 << 871 */ - 0xb080392cc4349dedLLU, /* 5: 1.0e-216 << 781 */ - 0x8e938662882af53eLLU, /* 6: 1.0e-189 << 691 */ - 0xe65829b3046b0afaLLU, /* 7: 1.0e-162 << 602 */ - 0xba121a4650e4ddecLLU, /* 8: 1.0e-135 << 512 */ - 0x964e858c91ba2655LLU, /* 9: 1.0e-108 << 422 */ - 0xf2d56790ab41c2a3LLU, /* 10: 1.0e-81 << 333 */ - 0xc428d05aa4751e4dLLU, /* 11: 1.0e-54 << 243 */ - 0x9e74d1b791e07e48LLU, /* 12: 1.0e-27 << 153 */ - 0x8000000000000000LLU, /* 13: 1.0e+0 << 63 */ - 0xcecb8f27f4200f3aLLU, /* 14: 1.0e+27 >> 26 */ - 0xa70c3c40a64e6c52LLU, /* 15: 1.0e+54 >> 116 */ - 0x86f0ac99b4e8dafdLLU, /* 16: 1.0e+81 >> 206 */ - 0xda01ee641a708deaLLU, /* 17: 1.0e+108 >> 295 */ - 0xb01ae745b101e9e4LLU, /* 18: 1.0e+135 >> 385 */ - 0x8e41ade9fbebc27dLLU, /* 19: 1.0e+162 >> 475 */ - 0xe5d3ef282a242e82LLU, /* 20: 1.0e+189 >> 564 */ - 0xb9a74a0637ce2ee1LLU, /* 21: 1.0e+216 >> 654 */ - 0x95f83d0a1fb69cd9LLU, /* 22: 1.0e+243 >> 744 */ - 0xf24a01a73cf2dcd0LLU, /* 23: 1.0e+270 >> 833 */ - 0xc3b8358109e84f07LLU, /* 24: 1.0e+297 >> 923 */ - 0x9e19db92b4e31ba9LLU, /* 25: 1.0e+324 >> 1013 */ - }; - int g, n; - u64 x, y; - - assert( p>=POWERSOF10_FIRST && p<=POWERSOF10_LAST ); - if( p<0 ){ - g = p/27; - n = p%27; - if( n ){ - g--; - n += 27; - } - }else if( p<27 ){ - return aBase[p]; - }else{ - g = p/27; - n = p%27; - } - y = aScale[g+13]; - if( n==0 ){ - return y; - } - x = sqlite3Multiply128(aBase[n],y); - if( (U64_BIT(63) & x)==0 ){ - x = (x<<1)|1; - } - return x; -} - -/* -** pow10to2(x) computes floor(log2(pow(10,x))). -** pow2to10(y) computes floor(log10(pow(2,y))). -** -** Conceptually, pow10to2(p) converts a base-10 exponent p into -** a corresponding base-2 exponent, and pow2to10(e) converts a base-2 -** exponent into a base-10 exponent. -** -** The conversions are based on the observation that: -** -** ln(10.0)/ln(2.0) == 108853/32768 (approximately) -** ln(2.0)/ln(10.0) == 78913/262144 (approximately) -** -** These ratios are approximate, but they are accurate to 5 digits, -** which is close enough for the usage here. Right-shift is used -** for division so that rounding of negative numbers happens in the -** right direction. -*/ -static int pwr10to2(int p){ return (p*108853) >> 15; } -static int pwr2to10(int p){ return (p*78913) >> 18; } - -/* -** Count leading zeros for a 64-bit unsigned integer. -*/ -static int countLeadingZeros(u64 m){ -#if defined(__GNUC__) || defined(__clang__) - return __builtin_clzll(m); -#else - int n = 0; - if( m <= 0x00000000ffffffffULL) { n += 32; m <<= 32; } - if( m <= 0x0000ffffffffffffULL) { n += 16; m <<= 16; } - if( m <= 0x00ffffffffffffffULL) { n += 8; m <<= 8; } - if( m <= 0x0fffffffffffffffULL) { n += 4; m <<= 4; } - if( m <= 0x3fffffffffffffffULL) { n += 2; m <<= 2; } - if( m <= 0x7fffffffffffffffULL) { n += 1; } - return n; -#endif -} - -/* -** Given m and e, which represent a quantity r == m*pow(2,e), -** return values *pD and *pP such that r == (*pD)*pow(10,*pP), -** approximately. *pD should contain at least n significant digits. -** -** The input m is required to have its highest bit set. In other words, -** m should be left-shifted, and e decremented, to maximize the value of m. -*/ -static void sqlite3Fp2Convert10(u64 m, int e, int n, u64 *pD, int *pP){ - int p; - u64 h; - assert( n>=1 && n<=18 ); - p = n - 1 - pwr2to10(e+63); - h = sqlite3Multiply128(m, powerOfTen(p)); - assert( -(e + pwr10to2(p) + 2) >= 0 ); - assert( -(e + pwr10to2(p) + 1) <= 63 ); - if( n==18 ){ - h >>= -(e + pwr10to2(p) + 2); - *pD = (h + ((h<<1)&2))>>1; - }else{ - *pD = h >> -(e + pwr10to2(p) + 1); - } - *pP = -p; -} - -/* -** Return an IEEE754 floating point value that approximates d*pow(10,p). -*/ -static double sqlite3Fp10Convert2(u64 d, int p){ - u64 out; - int e1; - int lz; - int lp; - int x; - u64 h; - double r; - assert( (d & U64_BIT(63))==0 ); - assert( d!=0 ); - if( p<POWERSOF10_FIRST ){ - return 0.0; - } - if( p>POWERSOF10_LAST ){ - return INFINITY; - } - lz = countLeadingZeros(d); - lp = pwr10to2(p); - e1 = lz - (lp + 11); - if( e1>1074 ){ - if( e1>=1130 ) return 0.0; - e1 = 1074; - } - h = sqlite3Multiply128(d<<lz, powerOfTen(p)); - x = lz - (e1 + lp + 3); - assert( x >= 0 ); - assert( x <= 63 ); - out = h >> x; - if( out >= U64_BIT(55)-2 ){ - out >>= 1; - e1--; - } - if( e1<=(-972) ){ - return INFINITY; - } - out = (out + 2) >> 2; - if( (out & U64_BIT(52))!=0 ){ - out = (out & ~U64_BIT(52)) | ((u64)(1075-e1)<<52); - } - memcpy(&r, &out, 8); - return r; +static void dekkerMul2(volatile double *x, double y, double yy){ + /* + ** The "volatile" keywords on parameter x[] and on local variables + ** below are needed force intermediate results to be truncated to + ** binary64 rather than be carried around in an extended-precision + ** format. The truncation is necessary for the Dekker algorithm to + ** work. Intel x86 floating point might omit the truncation without + ** the use of volatile. + */ + volatile double tx, ty, p, q, c, cc; + double hx, hy; + u64 m; + memcpy(&m, (void*)&x[0], 8); + m &= 0xfffffffffc000000LL; + memcpy(&hx, &m, 8); + tx = x[0] - hx; + memcpy(&m, &y, 8); + m &= 0xfffffffffc000000LL; + memcpy(&hy, &m, 8); + ty = y - hy; + p = hx*hy; + q = hx*ty + tx*hy; + c = p+q; + cc = p - c + q + tx*ty; + cc = x[0]*yy + x[1]*y + cc; + x[0] = c + cc; + x[1] = c - x[0]; + x[1] += cc; } /* ** The string z[] is an text representation of a real number. ** Convert this string to a double and write it into *pResult. ** -** z[] must be UTF-8 and zero-terminated. +** The string z[] is length bytes in length (bytes, not characters) and +** uses the encoding enc. The string is not necessarily zero-terminated. ** ** Return TRUE if the result is a valid real number (or integer) and FALSE ** if the string is empty or contains extraneous text. More specifically @@ -740,131 +526,198 @@ static double sqlite3Fp10Convert2(u64 d, int p){ #if defined(_MSC_VER) #pragma warning(disable : 4756) #endif -int sqlite3AtoF(const char *z, double *pResult){ +int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ #ifndef SQLITE_OMIT_FLOATING_POINT + int incr; + const char *zEnd; /* sign * significand * (10 ^ (esign * exponent)) */ - int neg = 0; /* True for a negative value */ - u64 s = 0; /* mantissa */ - int d = 0; /* Value is s * pow(10,d) */ + int sign = 1; /* sign of significand */ + u64 s = 0; /* significand */ + int d = 0; /* adjust exponent for shifting decimal point */ + int esign = 1; /* sign of exponent */ + int e = 0; /* exponent */ + int eValid = 1; /* True exponent is either not used or is well-formed */ int nDigit = 0; /* Number of digits processed */ - int eType = 1; /* 1: pure integer, 2+: fractional */ + int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ + u64 s2; /* round-tripped significand */ + double rr[2]; + assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); *pResult = 0.0; /* Default return value, in case of an error */ + if( length==0 ) return 0; + + if( enc==SQLITE_UTF8 ){ + incr = 1; + zEnd = z + length; + }else{ + int i; + incr = 2; + length &= ~1; + assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); + testcase( enc==SQLITE_UTF16LE ); + testcase( enc==SQLITE_UTF16BE ); + for(i=3-enc; i<length && z[i]==0; i+=2){} + if( i<length ) eType = -100; + zEnd = &z[i^1]; + z += (enc&1); + } /* skip leading spaces */ - while( sqlite3Isspace(*z) ) z++; + while( z<zEnd && sqlite3Isspace(*z) ) z+=incr; + if( z>=zEnd ) return 0; /* get sign of significand */ if( *z=='-' ){ - neg = 1; - z++; + sign = -1; + z+=incr; }else if( *z=='+' ){ - z++; + z+=incr; } /* copy max significant digits to significand */ - while( sqlite3Isdigit(*z) ){ + while( z<zEnd && sqlite3Isdigit(*z) ){ s = s*10 + (*z - '0'); - z++; nDigit++; - if( s>=((LARGEST_INT64-9)/10) ){ + z+=incr; nDigit++; + if( s>=((LARGEST_UINT64-9)/10) ){ /* skip non-significant significand digits ** (increase exponent by d to shift decimal left) */ - while( sqlite3Isdigit(*z) ){ z++; d++; } + while( z<zEnd && sqlite3Isdigit(*z) ){ z+=incr; d++; } } } + if( z>=zEnd ) goto do_atof_calc; /* if decimal point is present */ if( *z=='.' ){ - z++; + z+=incr; eType++; /* copy digits from after decimal to significand ** (decrease exponent by d to shift decimal right) */ - while( sqlite3Isdigit(*z) ){ - if( s<((LARGEST_INT64-9)/10) ){ + while( z<zEnd && sqlite3Isdigit(*z) ){ + if( s<((LARGEST_UINT64-9)/10) ){ s = s*10 + (*z - '0'); d--; nDigit++; } - z++; + z+=incr; } } + if( z>=zEnd ) goto do_atof_calc; /* if exponent is present */ if( *z=='e' || *z=='E' ){ - int esign = 1; /* sign of exponent */ - z++; + z+=incr; + eValid = 0; eType++; + /* This branch is needed to avoid a (harmless) buffer overread. The + ** special comment alerts the mutation tester that the correct answer + ** is obtained even if the branch is omitted */ + if( z>=zEnd ) goto do_atof_calc; /*PREVENTS-HARMLESS-OVERREAD*/ + /* get sign of exponent */ if( *z=='-' ){ esign = -1; - z++; + z+=incr; }else if( *z=='+' ){ - z++; + z+=incr; } /* copy digits to exponent */ - if( sqlite3Isdigit(*z) ){ - int exp = *z - '0'; - z++; - while( sqlite3Isdigit(*z) ){ - exp = exp<10000 ? (exp*10 + (*z - '0')) : 10000; - z++; - } - d += esign*exp; - }else{ - eType = -1; + while( z<zEnd && sqlite3Isdigit(*z) ){ + e = e<10000 ? (e*10 + (*z - '0')) : 10000; + z+=incr; + eValid = 1; } } /* skip trailing spaces */ - while( sqlite3Isspace(*z) ) z++; + while( z<zEnd && sqlite3Isspace(*z) ) z+=incr; +do_atof_calc: /* Zero is a special case */ if( s==0 ){ - *pResult = neg ? -0.0 : +0.0; + *pResult = sign<0 ? -0.0 : +0.0; + goto atof_return; + } + + /* adjust exponent by d, and update sign */ + e = (e*esign) + d; + + /* Try to adjust the exponent to make it smaller */ + while( e>0 && s<((LARGEST_UINT64-0x7ff)/10) ){ + s *= 10; + e--; + } + while( e<0 && (s%10)==0 ){ + s /= 10; + e++; + } + + rr[0] = (double)s; + assert( sizeof(s2)==sizeof(rr[0]) ); +#ifdef SQLITE_DEBUG + rr[1] = 18446744073709549568.0; + memcpy(&s2, &rr[1], sizeof(s2)); + assert( s2==0x43efffffffffffffLL ); +#endif + /* Largest double that can be safely converted to u64 + ** vvvvvvvvvvvvvvvvvvvvvv */ + if( rr[0]<=18446744073709549568.0 ){ + s2 = (u64)rr[0]; + rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); + }else{ + rr[1] = 0.0; + } + assert( rr[1]<=1.0e-10*rr[0] ); /* Equal only when rr[0]==0.0 */ + + if( e>0 ){ + while( e>=100 ){ + e -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( e>=10 ){ + e -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( e>=1 ){ + e -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } }else{ - *pResult = sqlite3Fp10Convert2(s,d); - if( neg ) *pResult = -*pResult; - assert( !sqlite3IsNaN(*pResult) ); + while( e<=-100 ){ + e += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( e<=-10 ){ + e += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( e<=-1 ){ + e += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); + } } + *pResult = rr[0]+rr[1]; + if( sqlite3IsNaN(*pResult) ) *pResult = 1e300*1e300; + if( sign<0 ) *pResult = -*pResult; + assert( !sqlite3IsNaN(*pResult) ); +atof_return: /* return true if number and no extra non-whitespace characters after */ - if( z[0]==0 && nDigit>0 ){ + if( z==zEnd && nDigit>0 && eValid && eType>0 ){ return eType; - }else if( eType>=2 && nDigit>0 ){ + }else if( eType>=2 && (eType==3 || eValid) && nDigit>0 ){ return -1; }else{ return 0; } #else - return !sqlite3Atoi64(z, pResult, strlen(z), SQLITE_UTF8); + return !sqlite3Atoi64(z, pResult, length, enc); #endif /* SQLITE_OMIT_FLOATING_POINT */ } #if defined(_MSC_VER) #pragma warning(default : 4756) #endif -/* -** Digit pairs used to convert a U64 or I64 into text, two digits -** at a time. -*/ -static const union { - char a[201]; - short int forceAlignment; -} sqlite3DigitPairs = { - "00010203040506070809" - "10111213141516171819" - "20212223242526272829" - "30313233343536373839" - "40414243444546474849" - "50515253545556575859" - "60616263646566676869" - "70717273747576777879" - "80818283848586878889" - "90919293949596979899" -}; - - /* ** Render an signed 64-bit integer as text. Store the result in zOut[] and ** return the length of the string that was stored, in bytes. The value @@ -876,35 +729,23 @@ static const union { int sqlite3Int64ToText(i64 v, char *zOut){ int i; u64 x; - union { - char a[23]; - u16 forceAlignment; - } u; - if( v>0 ){ - x = v; - }else if( v==0 ){ - zOut[0] = '0'; - zOut[1] = 0; - return 1; - }else{ + char zTemp[22]; + if( v<0 ){ x = (v==SMALLEST_INT64) ? ((u64)1)<<63 : (u64)-v; + }else{ + x = v; } - i = sizeof(u.a)-1; - u.a[i] = 0; - while( x>=10 ){ - int kk = (x%100)*2; - assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[kk]) ); - assert( TWO_BYTE_ALIGNMENT(&u.a[i-2]) ); - *(u16*)(&u.a[i-2]) = *(u16*)&sqlite3DigitPairs.a[kk]; - i -= 2; - x /= 100; - } - if( x ){ - u.a[--i] = x + '0'; - } - if( v<0 ) u.a[--i] = '-'; - memcpy(zOut, &u.a[i], sizeof(u.a)-i); - return sizeof(u.a)-1-i; + i = sizeof(zTemp)-2; + zTemp[sizeof(zTemp)-1] = 0; + while( 1 /*exit-by-break*/ ){ + zTemp[i] = (x%10) + '0'; + x = x/10; + if( x==0 ) break; + i--; + }; + if( v<0 ) zTemp[--i] = '-'; + memcpy(zOut, &zTemp[i], sizeof(zTemp)-i); + return sizeof(zTemp)-1-i; } /* @@ -1161,7 +1002,7 @@ int sqlite3Atoi(const char *z){ ** representation. ** ** If iRound<=0 then round to -iRound significant digits to the -** the right of the decimal point, or to a maximum of mxRound total +** the left of the decimal point, or to a maximum of mxRound total ** significant digits. ** ** If iRound>0 round to min(iRound,mxRound) significant digits total. @@ -1174,14 +1015,13 @@ int sqlite3Atoi(const char *z){ ** The p->z[] array is *not* zero-terminated. */ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ - int i; /* Index into zBuf[] where to put next character */ - int n; /* Number of digits */ - u64 v; /* mantissa */ - int e, exp = 0; /* Base-2 and base-10 exponent */ - char *zBuf; /* Local alias for p->zBuf */ - char *z; /* Local alias for p->z */ + int i; + u64 v; + int e, exp = 0; + double rr[2]; p->isSpecial = 0; + p->z = p->zBuf; assert( mxRound>0 ); /* Convert negative numbers to positive. Deal with Infinity, 0.0, and @@ -1199,94 +1039,78 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ p->sign = '+'; } memcpy(&v,&r,8); - e = (v>>52)&0x7ff; - if( e==0x7ff ){ + e = v>>52; + if( (e&0x7ff)==0x7ff ){ p->isSpecial = 1 + (v!=0x7ff0000000000000LL); p->n = 0; p->iDP = 0; - p->z = p->zBuf; return; } - v &= 0x000fffffffffffffULL; - if( e==0 ){ - int nn = countLeadingZeros(v); - v <<= nn; - e = -1074 - nn; + + /* Multiply r by powers of ten until it lands somewhere in between + ** 1.0e+19 and 1.0e+17. + ** + ** Use Dekker-style double-double computation to increase the + ** precision. + ** + ** The error terms on constants like 1.0e+100 computed using the + ** decimal extension, for example as follows: + ** + ** SELECT decimal_exp(decimal_sub('1.0e+100',decimal(1.0e+100))); + */ + rr[0] = r; + rr[1] = 0.0; + if( rr[0]>9.223372036854774784e+18 ){ + while( rr[0]>9.223372036854774784e+118 ){ + exp += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( rr[0]>9.223372036854774784e+28 ){ + exp += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( rr[0]>9.223372036854774784e+18 ){ + exp += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); + } }else{ - v = (v<<11) | U64_BIT(63); - e -= 1086; + while( rr[0]<9.223372036854774784e-83 ){ + exp -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( rr[0]<9.223372036854774784e+07 ){ + exp -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( rr[0]<9.22337203685477478e+17 ){ + exp -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } } - sqlite3Fp2Convert10(v, e, (iRound<=0||iRound>=18)?18:iRound+1, &v, &exp); + v = rr[1]<0.0 ? (u64)rr[0]-(u64)(-rr[1]) : (u64)rr[0]+(u64)rr[1]; - /* Extract significant digits, start at the right-most slot in p->zBuf - ** and working back to the right. "i" keeps track of the next slot in - ** which to store a digit. */ + /* Extract significant digits. */ i = sizeof(p->zBuf)-1; - zBuf = p->zBuf; assert( v>0 ); - while( v>=10 ){ - int kk = (v%100)*2; - assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[kk]) ); - assert( TWO_BYTE_ALIGNMENT(&zBuf[i-1]) ); - *(u16*)(&zBuf[i-1]) = *(u16*)&sqlite3DigitPairs.a[kk]; - i -= 2; - v /= 100; - } - if( v ){ - assert( v<10 ); - zBuf[i--] = v + '0'; - } + while( v ){ p->zBuf[i--] = (v%10) + '0'; v /= 10; } assert( i>=0 && i<sizeof(p->zBuf)-1 ); - n = sizeof(p->zBuf) - 1 - i; /* Total number of digits extracted */ - assert( n>0 ); - assert( n<sizeof(p->zBuf) ); - testcase( n==sizeof(p->zBuf)-1 ); - p->iDP = n + exp; + p->n = sizeof(p->zBuf) - 1 - i; + assert( p->n>0 ); + assert( p->n<sizeof(p->zBuf) ); + p->iDP = p->n + exp; if( iRound<=0 ){ iRound = p->iDP - iRound; - if( iRound==0 && zBuf[i+1]>='5' ){ + if( iRound==0 && p->zBuf[i+1]>='5' ){ iRound = 1; - zBuf[i--] = '0'; - n++; + p->zBuf[i--] = '0'; + p->n++; p->iDP++; } } - z = &zBuf[i+1]; /* z points to the first digit */ - if( iRound>0 && (iRound<n || n>mxRound) ){ + if( iRound>0 && (iRound<p->n || p->n>mxRound) ){ + char *z = &p->zBuf[i+1]; if( iRound>mxRound ) iRound = mxRound; - if( iRound==17 ){ - /* If the precision is exactly 17, which only happens with the "!" - ** flag (ex: "%!.17g") then try to reduce the precision if that - ** yields text that will round-trip to the original floating-point. - ** value. Thus, for exaple, 49.47 will render as 49.47, rather than - ** as 49.469999999999999. */ - if( z[15]=='9' && z[14]=='9' ){ - int jj, kk; - u64 v2; - for(jj=14; jj>0 && z[jj-1]=='9'; jj--){} - if( jj==0 ){ - v2 = 1; - }else{ - v2 = z[0] - '0'; - for(kk=1; kk<jj; kk++) v2 = (v2*10) + z[kk] - '0'; - v2++; - } - if( r==sqlite3Fp10Convert2(v2, exp + n - jj) ){ - iRound = jj+1; - } - }else if( p->iDP>=n || (z[15]=='0' && z[14]=='0' && z[13]=='0') ){ - int jj, kk; - u64 v2; - assert( z[0]!='0' ); - for(jj=14; z[jj-1]=='0'; jj--){} - v2 = z[0] - '0'; - for(kk=1; kk<jj; kk++) v2 = (v2*10) + z[kk] - '0'; - if( r==sqlite3Fp10Convert2(v2, exp + n - jj) ){ - iRound = jj+1; - } - } - } - n = iRound; + p->n = iRound; if( z[iRound]>='5' ){ int j = iRound-1; while( 1 /*exit-by-break*/ ){ @@ -1294,9 +1118,8 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ if( z[j]<='9' ) break; z[j] = '0'; if( j==0 ){ - z--; - z[0] = '1'; - n++; + p->z[i--] = '1'; + p->n++; p->iDP++; break; }else{ @@ -1305,13 +1128,13 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ } } } - assert( n>0 ); - while( z[n-1]=='0' ){ - n--; - assert( n>0 ); + p->z = &p->zBuf[i+1]; + assert( i+p->n < sizeof(p->zBuf) ); + assert( p->n>0 ); + while( p->z[p->n-1]=='0' ){ + p->n--; + assert( p->n>0 ); } - p->n = n; - p->z = z; } /* diff --git a/src/vacuum.c b/src/vacuum.c index 70e62e1ef..1b4838040 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -230,11 +230,9 @@ SQLITE_NOINLINE int sqlite3RunVacuum( pDb = &db->aDb[nDb]; assert( strcmp(pDb->zDbSName,zDbVacuum)==0 ); pTemp = pDb->pBt; - nRes = sqlite3BtreeGetRequestedReserve(pMain); if( pOut ){ sqlite3_file *id = sqlite3PagerFile(sqlite3BtreePager(pTemp)); i64 sz = 0; - const char *zFilename; if( id->pMethods!=0 && (sqlite3OsFileSize(id, &sz)!=SQLITE_OK || sz>0) ){ rc = SQLITE_ERROR; sqlite3SetString(pzErrMsg, db, "output file already exists"); @@ -246,16 +244,8 @@ SQLITE_NOINLINE int sqlite3RunVacuum( ** they are for the database being vacuumed, except that PAGER_CACHESPILL ** is always set. */ pgflags = db->aDb[iDb].safety_level | (db->flags & PAGER_FLAGS_MASK); - - /* If the VACUUM INTO target file is a URI filename and if the - ** "reserve=N" query parameter is present, reset the reserve to the - ** amount specified, if the amount is within range */ - zFilename = sqlite3BtreeGetFilename(pTemp); - if( ALWAYS(zFilename) ){ - int nNew = (int)sqlite3_uri_int64(zFilename, "reserve", nRes); - if( nNew>=0 && nNew<=255 ) nRes = nNew; - } } + nRes = sqlite3BtreeGetRequestedReserve(pMain); sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size); sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0)); diff --git a/src/vdbe.c b/src/vdbe.c index e2e98eb5f..b5a262e63 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -353,9 +353,10 @@ static int alsoAnInt(Mem *pRec, double rValue, i64 *piValue){ */ static void applyNumericAffinity(Mem *pRec, int bTryForInt){ double rValue; + u8 enc = pRec->enc; int rc; assert( (pRec->flags & (MEM_Str|MEM_Int|MEM_Real|MEM_IntReal))==MEM_Str ); - rValue = sqlite3MemRealValueRC(pRec, &rc); + rc = sqlite3AtoF(pRec->z, &rValue, pRec->n, enc); if( rc<=0 ) return; if( rc==1 && alsoAnInt(pRec, rValue, &pRec->u.i) ){ pRec->flags |= MEM_Int; @@ -437,10 +438,7 @@ int sqlite3_value_numeric_type(sqlite3_value *pVal){ int eType = sqlite3_value_type(pVal); if( eType==SQLITE_TEXT ){ Mem *pMem = (Mem*)pVal; - assert( pMem->db!=0 ); - sqlite3_mutex_enter(pMem->db->mutex); applyNumericAffinity(pMem, 0); - sqlite3_mutex_leave(pMem->db->mutex); eType = sqlite3_value_type(pVal); } return eType; @@ -473,7 +471,7 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ pMem->u.i = 0; return MEM_Int; } - pMem->u.r = sqlite3MemRealValueRC(pMem, &rc); + rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); if( rc<=0 ){ if( rc==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1 ){ pMem->u.i = ix; @@ -6622,15 +6620,20 @@ case OP_SorterInsert: { /* in2 */ break; } -/* Opcode: IdxDelete P1 P2 P3 * * +/* Opcode: IdxDelete P1 P2 P3 * P5 ** Synopsis: key=r[P2@P3] ** ** The content of P3 registers starting at register P2 form ** an unpacked index key. This opcode removes that entry from the ** index opened by cursor P1. ** -** Raise an SQLITE_CORRUPT_INDEX error if no matching index entry is found -** and not in writable_schema mode. +** If P5 is not zero, then raise an SQLITE_CORRUPT_INDEX error +** if no matching index entry is found. This happens when running +** an UPDATE or DELETE statement and the index entry to be updated +** or deleted is not found. For some uses of IdxDelete +** (example: the EXCEPT operator) it does not matter that no matching +** entry is found. For those cases, P5 is zero. Also, do not raise +** this (self-correcting and non-critical) error if in writable_schema mode. */ case OP_IdxDelete: { VdbeCursor *pC; @@ -6656,7 +6659,7 @@ case OP_IdxDelete: { if( res==0 ){ rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE); if( rc ) goto abort_due_to_error; - }else if( !sqlite3WritableSchema(db) ){ + }else if( pOp->p5 && !sqlite3WritableSchema(db) ){ rc = sqlite3ReportError(SQLITE_CORRUPT_INDEX, __LINE__, "index corruption"); goto abort_due_to_error; } diff --git a/src/vdbe.h b/src/vdbe.h index a2905eae4..28df764bc 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -186,7 +186,7 @@ typedef struct VdbeOpList VdbeOpList; ** Additional non-public SQLITE_PREPARE_* flags */ #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ -#define SQLITE_PREPARE_MASK 0x3f /* Mask of public flags */ +#define SQLITE_PREPARE_MASK 0x1f /* Mask of public flags */ /* ** Prototypes for the VDBE interface. See comments on the implementation diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 320721d06..8b68c339a 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -630,7 +630,6 @@ void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int); void sqlite3VdbeMemMove(Mem*, Mem*); int sqlite3VdbeMemNulTerminate(Mem*); int sqlite3VdbeMemSetStr(Mem*, const char*, i64, u8, void(*)(void*)); -int sqlite3VdbeMemSetText(Mem*, const char*, i64, void(*)(void*)); void sqlite3VdbeMemSetInt64(Mem*, i64); #ifdef SQLITE_OMIT_FLOATING_POINT # define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64 @@ -649,14 +648,13 @@ int sqlite3VdbeMemSetZeroBlob(Mem*,int); int sqlite3VdbeMemIsRowSet(const Mem*); #endif int sqlite3VdbeMemSetRowSet(Mem*); -int sqlite3VdbeMemZeroTerminateIfAble(Mem*); +void sqlite3VdbeMemZeroTerminateIfAble(Mem*); int sqlite3VdbeMemMakeWriteable(Mem*); int sqlite3VdbeMemStringify(Mem*, u8, u8); int sqlite3IntFloatCompare(i64,double); i64 sqlite3VdbeIntValue(const Mem*); int sqlite3VdbeMemIntegerify(Mem*); double sqlite3VdbeRealValue(Mem*); -SQLITE_NOINLINE double sqlite3MemRealValueRC(Mem*, int*); int sqlite3VdbeBooleanValue(Mem*, int ifNull); void sqlite3VdbeIntegerAffinity(Mem*); int sqlite3VdbeMemRealify(Mem*); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 9fd4715ce..ec849cc4f 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -392,23 +392,7 @@ static void setResultStrOrError( void (*xDel)(void*) /* Destructor function */ ){ Mem *pOut = pCtx->pOut; - int rc; - if( enc==SQLITE_UTF8 ){ - rc = sqlite3VdbeMemSetText(pOut, z, n, xDel); - }else if( enc==SQLITE_UTF8_ZT ){ - /* It is usually considered improper to assert() on an input. However, - ** the following assert() is checking for inputs that are documented - ** to result in undefined behavior. */ - assert( z==0 - || n<0 - || n>pOut->db->aLimit[SQLITE_LIMIT_LENGTH] - || z[n]==0 - ); - rc = sqlite3VdbeMemSetText(pOut, z, n, xDel); - pOut->flags |= MEM_Term; - }else{ - rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); - } + int rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); if( rc ){ if( rc==SQLITE_TOOBIG ){ sqlite3_result_error_toobig(pCtx); @@ -601,7 +585,7 @@ void sqlite3_result_text64( #endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); - if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF8_ZT ){ + if( enc!=SQLITE_UTF8 ){ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; n &= ~(u64)1; } @@ -752,8 +736,6 @@ static int doWalCallbacks(sqlite3 *db){ } } } -#else - UNUSED_PARAMETER(db); #endif return rc; } @@ -1710,25 +1692,13 @@ static int bindText( assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ if( zData!=0 ){ pVar = &p->aVar[i-1]; - if( encoding==SQLITE_UTF8 ){ - rc = sqlite3VdbeMemSetText(pVar, zData, nData, xDel); - }else if( encoding==SQLITE_UTF8_ZT ){ - /* It is usually consider improper to assert() on an input. - ** However, the following assert() is checking for inputs - ** that are documented to result in undefined behavior. */ - assert( zData==0 - || nData<0 - || nData>pVar->db->aLimit[SQLITE_LIMIT_LENGTH] - || ((u8*)zData)[nData]==0 - ); - rc = sqlite3VdbeMemSetText(pVar, zData, nData, xDel); - pVar->flags |= MEM_Term; - }else{ - rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); - if( encoding==0 ) pVar->enc = ENC(p->db); - } - if( rc==SQLITE_OK && encoding!=0 ){ - rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); + rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); + if( rc==SQLITE_OK ){ + if( encoding==0 ){ + pVar->enc = ENC(p->db); + }else{ + rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); + } } if( rc ){ sqlite3Error(p->db, rc); @@ -1840,7 +1810,7 @@ int sqlite3_bind_text64( unsigned char enc ){ assert( xDel!=SQLITE_DYNAMIC ); - if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF8_ZT ){ + if( enc!=SQLITE_UTF8 ){ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; nData &= ~(u64)1; } diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 603e85ddf..5368c0c42 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -2901,7 +2901,7 @@ int sqlite3VdbeSetColName( } assert( p->aColName!=0 ); pColName = &(p->aColName[idx+var*p->nResAlloc]); - rc = sqlite3VdbeMemSetText(pColName, zName, -1, xDel); + rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel); assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 ); return rc; } diff --git a/src/vdbemem.c b/src/vdbemem.c index 5689cb755..144f88936 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -107,27 +107,21 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ StrAccum acc; assert( p->flags & (MEM_Int|MEM_Real|MEM_IntReal) ); assert( sz>22 ); - if( p->flags & (MEM_Int|MEM_IntReal) ){ -#if GCC_VERSION>=7000000 && GCC_VERSION<15000000 && defined(__i386__) - /* Work-around for GCC bug or bugs: - ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270 - ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114659 - ** The problem appears to be fixed in GCC 15 */ + if( p->flags & MEM_Int ){ +#if GCC_VERSION>=7000000 + /* Work-around for GCC bug + ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270 */ i64 x; - assert( (MEM_Str&~p->flags)*4==sizeof(x) ); - memcpy(&x, (char*)&p->u, (MEM_Str&~p->flags)*4); + assert( (p->flags&MEM_Int)*2==sizeof(x) ); + memcpy(&x, (char*)&p->u, (p->flags&MEM_Int)*2); p->n = sqlite3Int64ToText(x, zBuf); #else p->n = sqlite3Int64ToText(p->u.i, zBuf); #endif - if( p->flags & MEM_IntReal ){ - memcpy(zBuf+p->n,".0", 3); - p->n += 2; - } }else{ sqlite3StrAccumInit(&acc, 0, zBuf, sz, 0); - sqlite3_str_appendf(&acc, "%!.*g", - (p->db ? p->db->nFpDigit : 17), p->u.r); + sqlite3_str_appendf(&acc, "%!.15g", + (p->flags & MEM_IntReal)!=0 ? (double)p->u.i : p->u.r); assert( acc.zText==zBuf && acc.mxAlloc<=0 ); zBuf[acc.nChar] = 0; /* Fast version of sqlite3StrAccumFinish(&acc) */ p->n = acc.nChar; @@ -176,9 +170,6 @@ int sqlite3VdbeMemValidStrRep(Mem *p){ assert( p->enc==SQLITE_UTF8 || p->z[((p->n+1)&~1)+1]==0 ); } if( (p->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ) return 1; - if( p->db==0 ){ - return 1; /* db->nFpDigit required to validate p->z[] */ - } memcpy(&tmp, p, sizeof(tmp)); vdbeMemRenderNum(sizeof(zBuf), zBuf, &tmp); z = p->z; @@ -329,16 +320,13 @@ int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ ** ** This is an optimization. Correct operation continues even if ** this routine is a no-op. -** -** Return true if the strig is zero-terminated after this routine is -** called and false if it is not. */ -int sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ +void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ if( (pMem->flags & (MEM_Str|MEM_Term|MEM_Ephem|MEM_Static))!=MEM_Str ){ /* pMem must be a string, and it cannot be an ephemeral or static string */ - return 0; + return; } - if( pMem->enc!=SQLITE_UTF8 ) return 0; + if( pMem->enc!=SQLITE_UTF8 ) return; assert( pMem->z!=0 ); if( pMem->flags & MEM_Dyn ){ if( pMem->xDel==sqlite3_free @@ -346,19 +334,18 @@ int sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; - return 1; + return; } if( pMem->xDel==sqlite3RCStrUnref ){ /* Blindly assume that all RCStr objects are zero-terminated */ pMem->flags |= MEM_Term; - return 1; + return; } }else if( pMem->szMalloc >= pMem->n+1 ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; - return 1; + return; } - return 0; } /* @@ -656,70 +643,18 @@ i64 sqlite3VdbeIntValue(const Mem *pMem){ } } -/* -** Invoke sqlite3AtoF() on the text value of pMem and return the -** double result. If sqlite3AtoF() returns an error code, write -** that code into *pRC if (*pRC)!=NULL. -** -** The caller must ensure that pMem->db!=0 and that pMem is in -** mode MEM_Str or MEM_Blob. -*/ -SQLITE_NOINLINE double sqlite3MemRealValueRC(Mem *pMem, int *pRC){ - double val = (double)0; - int rc = 0; - assert( pMem->db!=0 ); - assert( pMem->flags & (MEM_Str|MEM_Blob) ); - if( pMem->z==0 ){ - /* no-op */ - }else if( pMem->enc==SQLITE_UTF8 - && ((pMem->flags & MEM_Term)!=0 || sqlite3VdbeMemZeroTerminateIfAble(pMem)) - ){ - rc = sqlite3AtoF(pMem->z, &val); - }else if( pMem->n==0 ){ - /* no-op */ - }else if( pMem->enc==SQLITE_UTF8 ){ - char *zCopy = sqlite3DbStrNDup(pMem->db, pMem->z, pMem->n); - if( zCopy ){ - rc = sqlite3AtoF(zCopy, &val); - sqlite3DbFree(pMem->db, zCopy); - } - }else{ - int n, i, j; - char *zCopy; - const char *z; - - n = pMem->n & ~1; - zCopy = sqlite3DbMallocRaw(pMem->db, n/2 + 2); - if( zCopy ){ - z = pMem->z; - if( pMem->enc==SQLITE_UTF16LE ){ - for(i=j=0; i<n-1; i+=2, j++){ - zCopy[j] = z[i]; - if( z[i+1]!=0 ) break; - } - }else{ - for(i=j=0; i<n-1; i+=2, j++){ - if( z[i]!=0 ) break; - zCopy[j] = z[i+1]; - } - } - assert( j<=n/2 ); - zCopy[j] = 0; - rc = sqlite3AtoF(zCopy, &val); - if( i<n ) rc = -100; - sqlite3DbFree(pMem->db, zCopy); - } - } - if( pRC ) *pRC = rc; - return val; -} - /* ** Return the best representation of pMem that we can get into a ** double. If pMem is already a double or an integer, return its ** value. If it is a string or blob, try to convert it to a double. ** If it is a NULL, return 0.0. */ +static SQLITE_NOINLINE double memRealValue(Mem *pMem){ + /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ + double val = (double)0; + sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc); + return val; +} double sqlite3VdbeRealValue(Mem *pMem){ assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -730,7 +665,7 @@ double sqlite3VdbeRealValue(Mem *pMem){ testcase( pMem->flags & MEM_IntReal ); return (double)pMem->u.i; }else if( pMem->flags & (MEM_Str|MEM_Blob) ){ - return sqlite3MemRealValueRC(pMem, 0); + return memRealValue(pMem); }else{ /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ return (double)0; @@ -854,7 +789,7 @@ int sqlite3VdbeMemNumerify(Mem *pMem){ sqlite3_int64 ix; assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - pMem->u.r = sqlite3MemRealValueRC(pMem, &rc); + rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); if( ((rc==0 || rc==1) && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1) || sqlite3RealSameAsInt(pMem->u.r, (ix = sqlite3RealToI64(pMem->u.r))) ){ @@ -1319,84 +1254,6 @@ int sqlite3VdbeMemSetStr( return SQLITE_OK; } -/* Like sqlite3VdbeMemSetStr() except: -** -** enc is always SQLITE_UTF8 -** pMem->db is always non-NULL -*/ -int sqlite3VdbeMemSetText( - Mem *pMem, /* Memory cell to set to string value */ - const char *z, /* String pointer */ - i64 n, /* Bytes in string, or negative */ - void (*xDel)(void*) /* Destructor function */ -){ - i64 nByte = n; /* New value for pMem->n */ - u16 flags; - - assert( pMem!=0 ); - assert( pMem->db!=0 ); - assert( sqlite3_mutex_held(pMem->db->mutex) ); - assert( !sqlite3VdbeMemIsRowSet(pMem) ); - - /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ - if( !z ){ - sqlite3VdbeMemSetNull(pMem); - return SQLITE_OK; - } - - if( nByte<0 ){ - nByte = strlen(z); - flags = MEM_Str|MEM_Term; - }else{ - flags = MEM_Str; - } - if( nByte>(i64)pMem->db->aLimit[SQLITE_LIMIT_LENGTH] ){ - if( xDel && xDel!=SQLITE_TRANSIENT ){ - if( xDel==SQLITE_DYNAMIC ){ - sqlite3DbFree(pMem->db, (void*)z); - }else{ - xDel((void*)z); - } - } - sqlite3VdbeMemSetNull(pMem); - return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); - } - - /* The following block sets the new values of Mem.z and Mem.xDel. It - ** also sets a flag in local variable "flags" to indicate the memory - ** management (one of MEM_Dyn or MEM_Static). - */ - if( xDel==SQLITE_TRANSIENT ){ - i64 nAlloc = nByte + 1; - testcase( nAlloc==31 ); - testcase( nAlloc==32 ); - if( sqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){ - return SQLITE_NOMEM_BKPT; - } - assert( pMem->z!=0 ); - memcpy(pMem->z, z, nByte); - pMem->z[nByte] = 0; - }else{ - sqlite3VdbeMemRelease(pMem); - pMem->z = (char *)z; - if( xDel==SQLITE_DYNAMIC ){ - pMem->zMalloc = pMem->z; - pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); - pMem->xDel = 0; - }else if( xDel==SQLITE_STATIC ){ - pMem->xDel = xDel; - flags |= MEM_Static; - }else{ - pMem->xDel = xDel; - flags |= MEM_Dyn; - } - } - pMem->flags = flags; - pMem->n = (int)(nByte & 0x7fffffff); - pMem->enc = SQLITE_UTF8; - return SQLITE_OK; -} - /* ** Move data out of a btree key or data field and into a Mem structure. ** The data is payload from the entry that pCur is currently pointing @@ -1825,7 +1682,7 @@ static int valueFromExpr( if( affinity==SQLITE_AFF_BLOB ){ if( op==TK_FLOAT ){ assert( pVal && pVal->z && pVal->flags==(MEM_Str|MEM_Term) ); - sqlite3AtoF(pVal->z, &pVal->u.r); + sqlite3AtoF(pVal->z, &pVal->u.r, pVal->n, SQLITE_UTF8); pVal->flags = MEM_Real; }else if( op==TK_INTEGER ){ /* This case is required by -9223372036854775808 and other strings @@ -2093,11 +1950,6 @@ int sqlite3Stat4ValueFromExpr( ** ** If *ppVal is initially NULL then the caller is responsible for ** ensuring that the value written into *ppVal is eventually freed. -** -** If the buffer does not contain a well-formed record, this routine may -** read several bytes past the end of the buffer. Callers must therefore -** ensure that any buffer which may contain a corrupt record is padded -** with at least 8 bytes of addressable memory. */ int sqlite3Stat4Column( sqlite3 *db, /* Database handle */ diff --git a/src/where.c b/src/where.c index 2ef2ce0be..c4f2c5543 100644 --- a/src/where.c +++ b/src/where.c @@ -1514,14 +1514,11 @@ static sqlite3_index_info *allocateIndexInfo( break; } if( i==n ){ - int bSortByGroup = (pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0; nOrderBy = n; if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) && !pSrc->fg.rowidUsed ){ - eDistinct = 2 + bSortByGroup; + eDistinct = 2 + ((pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0); }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){ - eDistinct = 1 - bSortByGroup; - }else if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ - eDistinct = 3; + eDistinct = 1; } } } @@ -2932,67 +2929,6 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ return rc; } -/* -** Callback for estLikePatternLength(). -** -** If this node is a string literal that is longer pWalker->sz, then set -** pWalker->sz to the byte length of that string literal. -** -** pWalker->eCode indicates how to count characters: -** -** eCode==0 Count as a GLOB pattern -** eCode==1 Count as a LIKE pattern -*/ -static int exprNodePatternLengthEst(Walker *pWalker, Expr *pExpr){ - if( pExpr->op==TK_STRING ){ - int sz = 0; /* Pattern size in bytes */ - u8 *z = (u8*)pExpr->u.zToken; /* The pattern */ - u8 c; /* Next character of the pattern */ - u8 c1, c2, c3; /* Wildcards */ - if( pWalker->eCode ){ - c1 = '%'; - c2 = '_'; - c3 = 0; - }else{ - c1 = '*'; - c2 = '?'; - c3 = '['; - } - while( (c = *(z++))!=0 ){ - if( c==c3 ){ - if( *z ) z++; - while( *z && *z!=']' ) z++; - }else if( c!=c1 && c!=c2 ){ - sz++; - } - } - if( sz>pWalker->u.sz ) pWalker->u.sz = sz; - } - return WRC_Continue; -} - -/* -** Return the length of the longest string literal in the given -** expression. -** -** eCode indicates how to count characters: -** -** eCode==0 Count as a GLOB pattern -** eCode==1 Count as a LIKE pattern -*/ -static int estLikePatternLength(Expr *p, u16 eCode){ - Walker w; - w.u.sz = 0; - w.eCode = eCode; - w.xExprCallback = exprNodePatternLengthEst; - w.xSelectCallback = sqlite3SelectWalkFail; -#ifdef SQLITE_DEBUG - w.xSelectCallback2 = sqlite3SelectWalkAssert2; -#endif - sqlite3WalkExpr(&w, p); - return w.u.sz; -} - /* ** Adjust the WhereLoop.nOut value downward to account for terms of the ** WHERE clause that reference the loop but which are not used by an @@ -3021,13 +2957,6 @@ static int estLikePatternLength(Expr *p, u16 eCode){ ** "x" column is boolean or else -1 or 0 or 1 is a common default value ** on the "x" column and so in that case only cap the output row estimate ** at 1/2 instead of 1/4. -** -** Heuristic 3: If there is a LIKE or GLOB (or REGEXP or MATCH) operator -** with a large constant pattern, then reduce the size of the search -** space according to the length of the pattern, under the theory that -** longer patterns are less likely to match. This heuristic was added -** to give better output-row count estimates when preparing queries for -** the Join-Order Benchmarks. See forum thread 2026-01-30T09:57:54z */ static void whereLoopOutputAdjust( WhereClause *pWC, /* The WHERE clause */ @@ -3077,14 +3006,13 @@ static void whereLoopOutputAdjust( }else{ /* In the absence of explicit truth probabilities, use heuristics to ** guess a reasonable truth probability. */ - Expr *pOpExpr = pTerm->pExpr; pLoop->nOut--; if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && (pTerm->wtFlags & TERM_HIGHTRUTH)==0 /* tag-20200224-1 */ ){ - Expr *pRight = pOpExpr->pRight; + Expr *pRight = pTerm->pExpr->pRight; int k = 0; - testcase( pOpExpr->op==TK_IS ); + testcase( pTerm->pExpr->op==TK_IS ); if( sqlite3ExprIsInteger(pRight, &k, 0) && k>=(-1) && k<=1 ){ k = 10; }else{ @@ -3094,23 +3022,6 @@ static void whereLoopOutputAdjust( pTerm->wtFlags |= TERM_HEURTRUTH; iReduce = k; } - }else - if( ExprHasProperty(pOpExpr, EP_InfixFunc) - && pOpExpr->op==TK_FUNCTION - ){ - int eOp; - assert( ExprUseXList(pOpExpr) ); - assert( pOpExpr->x.pList->nExpr>=2 ); - eOp = sqlite3ExprIsLikeOperator(pOpExpr); - if( ALWAYS(eOp>0) ){ - int szPattern; - Expr *pRHS = pOpExpr->x.pList->a[0].pExpr; - eOp = eOp==SQLITE_INDEX_CONSTRAINT_LIKE; - szPattern = estLikePatternLength(pRHS, eOp); - if( szPattern>0 ){ - pLoop->nOut -= szPattern*2; - } - } } } } @@ -3182,7 +3093,7 @@ static int whereRangeVectorLen( idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn); if( aff!=idxaff ) break; - pColl = sqlite3ExprCompareCollSeq(pParse, pTerm->pExpr); + pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); if( pColl==0 ) break; if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break; } @@ -3571,7 +3482,6 @@ static int whereLoopAddBtreeIndex( pNew->rRun += nInMul + nIn; pNew->nOut += nInMul + nIn; whereLoopOutputAdjust(pBuilder->pWC, pNew, rSize); - if( pSrc->fg.fromExists ) pNew->nOut = 0; rc = whereLoopInsert(pBuilder, pNew); if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ @@ -4168,8 +4078,6 @@ static int whereLoopAddBtree( if( pSrc->fg.isSubquery ){ if( pSrc->fg.viaCoroutine ) pNew->wsFlags |= WHERE_COROUTINE; pNew->u.btree.pOrderBy = pSrc->u4.pSubq->pSelect->pOrderBy; - }else if( pSrc->fg.fromExists ){ - pNew->nOut = 0; } rc = whereLoopInsert(pBuilder, pNew); pNew->nOut = rSize; @@ -4272,7 +4180,6 @@ static int whereLoopAddBtree( ** positioned to the correct row during the right-join no-match ** loop. */ }else{ - if( pSrc->fg.fromExists ) pNew->nOut = 0; rc = whereLoopInsert(pBuilder, pNew); } pNew->nOut = rSize; @@ -4935,7 +4842,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ sqlite3 *db = pWInfo->pParse->db; int rc = SQLITE_OK; int bFirstPastRJ = 0; - int hasRightCrossJoin = 0; + int hasRightJoin = 0; WhereLoop *pNew; @@ -4962,34 +4869,15 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ ** prevents the right operand of a RIGHT JOIN from being swapped with ** other elements even further to the right. ** - ** The hasRightCrossJoin flag prevent FROM-clause terms from moving - ** from the right side of a LEFT JOIN or CROSS JOIN over to the - ** left side of that same join. This is a required restriction in - ** the case of LEFT JOIN - an incorrect answer may results if it is - ** not enforced. This restriction is not required for CROSS JOIN. - ** It is provided merely as a means of controlling join order, under - ** the theory that no real-world queries that care about performance - ** actually use the CROSS JOIN syntax. + ** The JT_LTORJ case and the hasRightJoin flag work together to + ** prevent FROM-clause terms from moving from the right side of + ** a LEFT JOIN over to the left side of that join if the LEFT JOIN + ** is itself on the left side of a RIGHT JOIN. */ - if( pItem->fg.jointype & (JT_LTORJ|JT_CROSS) ){ - testcase( pItem->fg.jointype & JT_LTORJ ); - testcase( pItem->fg.jointype & JT_CROSS ); - hasRightCrossJoin = 1; - } + if( pItem->fg.jointype & JT_LTORJ ) hasRightJoin = 1; mPrereq |= mPrior; bFirstPastRJ = (pItem->fg.jointype & JT_RIGHT)!=0; - }else if( pItem->fg.fromExists ){ - /* joins that result from the EXISTS-to-JOIN optimization should not - ** be moved to the left of any of their dependencies */ - WhereClause *pWC = &pWInfo->sWC; - WhereTerm *pTerm; - int i; - for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){ - if( (pNew->maskSelf & pTerm->prereqAll)!=0 ){ - mPrereq |= (pTerm->prereqAll & (pNew->maskSelf-1)); - } - } - }else if( !hasRightCrossJoin ){ + }else if( !hasRightJoin ){ mPrereq = 0; } #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -5212,7 +5100,9 @@ static i8 wherePathSatisfiesOrderBy( pLoop = pLast; } if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ - if( pLoop->u.vtab.isOrdered && pWInfo->pOrderBy==pOrderBy ){ + if( pLoop->u.vtab.isOrdered + && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) + ){ obSat = obDone; }else{ /* No further ORDER BY terms may be matched. So this call should @@ -5588,21 +5478,12 @@ static LogEst whereSortingCost( ** 12 otherwise ** ** For the purposes of this heuristic, a star-query is defined as a query -** with a central "fact" table that is joined against multiple -** "dimension" tables, subject to the following constraints: -** -** (aa) Only a five-way or larger join is considered for this -** optimization. If there are fewer than four terms in the FROM -** clause, this heuristic does not apply. -** -** (bb) The join between the fact table and the dimension tables must -** be an INNER join. CROSS and OUTER JOINs do not qualify. -** -** (cc) A table must have 3 or more dimension tables in order to be -** considered a fact table. (Was 4 prior to 2026-02-10.) -** -** (dd) A table that is a self-join cannot be a dimension table. -** Dimension tables are joined against fact tables. +** with a large central table that is joined using an INNER JOIN, +** not CROSS or OUTER JOINs, against four or more smaller tables. +** The central table is called the "fact" table. The smaller tables +** that get joined are "dimension tables". Also, any table that is +** self-joined cannot be a dimension table; we assume that dimension +** tables may only be joined against fact tables. ** ** SIDE EFFECT: (and really the whole point of this subroutine) ** @@ -5655,7 +5536,7 @@ static int computeMxChoice(WhereInfo *pWInfo){ } #endif /* SQLITE_DEBUG */ - if( nLoop>=4 /* Constraint (aa) */ + if( nLoop>=5 && !pWInfo->bStarDone && OptimizationEnabled(pWInfo->pParse->db, SQLITE_StarQuery) ){ @@ -5667,7 +5548,7 @@ static int computeMxChoice(WhereInfo *pWInfo){ pWInfo->bStarDone = 1; /* Only do this computation once */ - /* Look for fact tables with three or more dimensions where the + /* Look for fact tables with four or more dimensions where the ** dimension tables are not separately from the fact tables by an outer ** or cross join. Adjust cost weights if found. */ @@ -5684,17 +5565,18 @@ static int computeMxChoice(WhereInfo *pWInfo){ if( (pFactTab->fg.jointype & (JT_OUTER|JT_CROSS))!=0 ){ /* If the candidate fact-table is the right table of an outer join ** restrict the search for dimension-tables to be tables to the right - ** of the fact-table. Constraint (bb) */ - if( iFromIdx+3 > nLoop ){ - break; /* ^-- Impossible to reach nDep>=2 - Constraint (cc) */ - } + ** of the fact-table. */ + if( iFromIdx+4 > nLoop ) break; /* Impossible to reach nDep>=4 */ while( pStart && pStart->iTab<=iFromIdx ){ pStart = pStart->pNextLoop; } } for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){ if( (aFromTabs[pWLoop->iTab].fg.jointype & (JT_OUTER|JT_CROSS))!=0 ){ - break; /* Constraint (bb) */ + /* Fact-tables and dimension-tables cannot be separated by an + ** outer join (at least for the definition of fact- and dimension- + ** used by this heuristic). */ + break; } if( (pWLoop->prereq & m)!=0 /* pWInfo depends on iFromIdx */ && (pWLoop->maskSelf & mSeen)==0 /* pWInfo not already a dependency */ @@ -5708,9 +5590,7 @@ static int computeMxChoice(WhereInfo *pWInfo){ } } } - if( nDep<=2 ){ - continue; /* Constraint (cc) */ - } + if( nDep<=3 ) continue; /* If we reach this point, it means that pFactTab is a fact table ** with four or more dimensions connected by inner joins. Proceed @@ -5723,23 +5603,6 @@ static int computeMxChoice(WhereInfo *pWInfo){ pWLoop->rStarDelta = 0; } } -#endif -#ifdef WHERETRACE_ENABLED /* 0x80000 */ - if( sqlite3WhereTrace & 0x80000 ){ - Bitmask mShow = mSeen; - sqlite3DebugPrintf("Fact table %s(%d), dimensions:", - pFactTab->zAlias ? pFactTab->zAlias : pFactTab->pSTab->zName, - iFromIdx); - for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){ - if( mShow & pWLoop->maskSelf ){ - SrcItem *pDim = aFromTabs + pWLoop->iTab; - mShow &= ~pWLoop->maskSelf; - sqlite3DebugPrintf(" %s(%d)", - pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, pWLoop->iTab); - } - } - sqlite3DebugPrintf("\n"); - } #endif pWInfo->bStarUsed = 1; @@ -5763,8 +5626,10 @@ static int computeMxChoice(WhereInfo *pWInfo){ if( sqlite3WhereTrace & 0x80000 ){ SrcItem *pDim = aFromTabs + pWLoop->iTab; sqlite3DebugPrintf( - "Increase SCAN cost of %s to %d\n", - pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, mxRun + "Increase SCAN cost of dimension %s(%d) of fact %s(%d) to %d\n", + pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, pWLoop->iTab, + pFactTab->zAlias ? pFactTab->zAlias : pFactTab->pSTab->zName, + iFromIdx, mxRun ); } pWLoop->rStarDelta = mxRun - pWLoop->rRun; @@ -6578,7 +6443,6 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( for(pTerm=pWInfo->sWC.a; pTerm<pEnd; pTerm++){ if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){ pTerm->wtFlags |= TERM_CODED; - pTerm->prereqAll = 0; } } if( i!=pWInfo->nLevel-1 ){ @@ -7566,15 +7430,14 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } #endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */ } - if( pTabList->a[pLevel->iFrom].fg.fromExists - && (i==pWInfo->nLevel-1 - || pTabList->a[pWInfo->a[i+1].iFrom].fg.fromExists==0) - ){ - /* This is an EXISTS-to-JOIN optimization which is either the - ** inner-most loop, or the inner-most of a group of nested - ** EXISTS-to-JOIN optimization loops. If this loop sees a successful - ** row, it should break out of itself as well as other EXISTS-to-JOIN - ** loops in which is is directly nested. */ + 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( nOuter<i ){ if( !pTabList->a[pLevel[-nOuter-1].iFrom].fg.fromExists ) break; @@ -7582,11 +7445,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } testcase( nOuter>0 ); sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel[-nOuter].addrBrk); - if( nOuter ){ - VdbeComment((v, "EXISTS break %d..%d", i-nOuter, i)); - }else{ - VdbeComment((v, "EXISTS break %d", i)); - } + VdbeComment((v, "EXISTS break")); } sqlite3VdbeResolveLabel(v, pLevel->addrCont); if( pLevel->op!=OP_Noop ){ diff --git a/src/wherecode.c b/src/wherecode.c index 31d7990ad..65ed980b8 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -1573,7 +1573,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( SMASKBIT32(j) & pLoop->u.vtab.mHandleIn ){ int iTab = pParse->nTab++; int iCache = ++pParse->nMem; - sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab, 0); + sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab); sqlite3VdbeAddOp3(v, OP_VInitIn, iTab, iTarget, iCache); }else{ codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); @@ -2892,6 +2892,15 @@ SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); } } + if( pLevel->iIdxCur ){ + /* pSubWhere may contain expressions that read from an index on the + ** table on the RHS of the right join. All such expressions first test + ** if the index is pointing at a NULL row, and if so, read from the + ** table cursor instead. So ensure that the index cursor really is + ** pointing at a NULL row here, so that no values are read from it during + ** the scan of the RHS of the RIGHT join below. */ + sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur); + } pFrom = &uSrc.sSrc; pFrom->nSrc = 1; pFrom->nAlloc = 1; diff --git a/src/whereexpr.c b/src/whereexpr.c index 74bf624c8..0d99ca85e 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -293,14 +293,13 @@ static int isLikeOrGlob( ){ int isNum; double rDummy; - assert( zNew[iTo]==0 ); - isNum = sqlite3AtoF(zNew, &rDummy); + isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8); if( isNum<=0 ){ if( iTo==1 && zNew[0]=='-' ){ isNum = +1; }else{ zNew[iTo-1]++; - isNum = sqlite3AtoF(zNew, &rDummy); + isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8); zNew[iTo-1]--; } } @@ -343,34 +342,6 @@ static int isLikeOrGlob( } #endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ -/* -** If pExpr is one of "like", "glob", "match", or "regexp", then -** return the corresponding SQLITE_INDEX_CONSTRAINT_xxxx value. -** If not, return 0. -** -** pExpr is guaranteed to be a TK_FUNCTION. -*/ -int sqlite3ExprIsLikeOperator(const Expr *pExpr){ - static const struct { - const char *zOp; - unsigned char eOp; - } aOp[] = { - { "match", SQLITE_INDEX_CONSTRAINT_MATCH }, - { "glob", SQLITE_INDEX_CONSTRAINT_GLOB }, - { "like", SQLITE_INDEX_CONSTRAINT_LIKE }, - { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP } - }; - int i; - assert( pExpr->op==TK_FUNCTION ); - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - for(i=0; i<ArraySize(aOp); i++){ - if( sqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){ - return aOp[i].eOp; - } - } - return 0; -} - #ifndef SQLITE_OMIT_VIRTUALTABLE /* @@ -407,6 +378,15 @@ static int isAuxiliaryVtabOperator( Expr **ppRight /* Expression to left of MATCH/op2 */ ){ if( pExpr->op==TK_FUNCTION ){ + static const struct Op2 { + const char *zOp; + unsigned char eOp2; + } aOp[] = { + { "match", SQLITE_INDEX_CONSTRAINT_MATCH }, + { "glob", SQLITE_INDEX_CONSTRAINT_GLOB }, + { "like", SQLITE_INDEX_CONSTRAINT_LIKE }, + { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP } + }; ExprList *pList; Expr *pCol; /* Column reference */ int i; @@ -426,11 +406,16 @@ static int isAuxiliaryVtabOperator( */ pCol = pList->a[1].pExpr; assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) ); - if( ExprIsVtab(pCol) && (i = sqlite3ExprIsLikeOperator(pExpr))!=0 ){ - *peOp2 = i; - *ppRight = pList->a[0].pExpr; - *ppLeft = pCol; - return 1; + if( ExprIsVtab(pCol) ){ + for(i=0; i<ArraySize(aOp); i++){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + if( sqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){ + *peOp2 = aOp[i].eOp2; + *ppRight = pList->a[0].pExpr; + *ppLeft = pCol; + return 1; + } + } } /* We can also match against the first column of overloaded @@ -564,22 +549,16 @@ static void whereCombineDisjuncts( Expr *pNew; /* New virtual expression */ int op; /* Operator for the combined expression */ int idxNew; /* Index in pWC of the next virtual term */ - Expr *pA, *pB; /* Expressions associated with pOne and pTwo */ if( (pOne->wtFlags | pTwo->wtFlags) & TERM_VNULL ) return; if( (pOne->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (pTwo->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (eOp & (WO_EQ|WO_LT|WO_LE))!=eOp && (eOp & (WO_EQ|WO_GT|WO_GE))!=eOp ) return; - pA = pOne->pExpr; - pB = pTwo->pExpr; - assert( pA->pLeft!=0 && pA->pRight!=0 ); - assert( pB->pLeft!=0 && pB->pRight!=0 ); - if( sqlite3ExprCompare(0,pA->pLeft, pB->pLeft, -1) ) return; - if( sqlite3ExprCompare(0,pA->pRight, pB->pRight,-1) ) return; - if( ExprHasProperty(pA,EP_Commuted)!=ExprHasProperty(pB,EP_Commuted) ){ - return; - } + assert( pOne->pExpr->pLeft!=0 && pOne->pExpr->pRight!=0 ); + assert( pTwo->pExpr->pLeft!=0 && pTwo->pExpr->pRight!=0 ); + if( sqlite3ExprCompare(0,pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return; + if( sqlite3ExprCompare(0,pOne->pExpr->pRight, pTwo->pExpr->pRight,-1) )return; /* If we reach this point, it means the two subterms can be combined */ if( (eOp & (eOp-1))!=0 ){ if( eOp & (WO_LT|WO_LE) ){ @@ -590,7 +569,7 @@ static void whereCombineDisjuncts( } } db = pWC->pWInfo->pParse->db; - pNew = sqlite3ExprDup(db, pA, 0); + pNew = sqlite3ExprDup(db, pOne->pExpr, 0); if( pNew==0 ) return; for(op=TK_EQ; eOp!=(WO_EQ<<(op-TK_EQ)); op++){ assert( op<TK_GE ); } pNew->op = op; @@ -1630,11 +1609,13 @@ static void whereAddLimitExpr( int iVal = 0; if( sqlite3ExprIsInteger(pExpr, &iVal, pParse) && iVal>=0 ){ - Expr *pVal = sqlite3ExprInt32(db, iVal); + Expr *pVal = sqlite3Expr(db, TK_INTEGER, 0); if( pVal==0 ) return; + ExprSetProperty(pVal, EP_IntValue); + pVal->u.iValue = iVal; pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); }else{ - Expr *pVal = sqlite3ExprAlloc(db, TK_REGISTER, 0, 0); + Expr *pVal = sqlite3Expr(db, TK_REGISTER, 0); if( pVal==0 ) return; pVal->iTable = iReg; pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); diff --git a/src/window.c b/src/window.c index ea2781864..1f22ab194 100644 --- a/src/window.c +++ b/src/window.c @@ -717,7 +717,7 @@ void sqlite3WindowUpdate( pWin->eEnd = aUp[i].eEnd; pWin->eExclude = 0; if( pWin->eStart==TK_FOLLOWING ){ - pWin->pStart = sqlite3ExprInt32(db, 1); + pWin->pStart = sqlite3Expr(db, TK_INTEGER, "1"); } break; } @@ -1062,7 +1062,9 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ ** keep everything legal in this case. */ if( pSublist==0 ){ - pSublist = sqlite3ExprListAppend(pParse, 0, sqlite3ExprInt32(db, 0)); + pSublist = sqlite3ExprListAppend(pParse, 0, + sqlite3Expr(db, TK_INTEGER, "0") + ); } pSub = sqlite3SelectNew( diff --git a/test/alterauth2.test b/test/alterauth2.test index 260b635c3..6f9242d36 100644 --- a/test/alterauth2.test +++ b/test/alterauth2.test @@ -116,55 +116,4 @@ do_auth_test 1.3 { {SQLITE_UPDATE sqlite_temp_master sql temp {}} } -do_auth_test 1.4 { - ALTER TABLE t2 ALTER COLUMN b SET NOT NULL; -} { - {SQLITE_ALTER_TABLE main t2 b {}} - {SQLITE_FUNCTION {} sqlite_add_constraint {} {}} - {SQLITE_FUNCTION {} sqlite_drop_constraint {} {}} - {SQLITE_FUNCTION {} sqlite_fail {} {}} - {SQLITE_READ sqlite_master sql main {}} - {SQLITE_READ sqlite_master tbl_name main {}} - {SQLITE_READ sqlite_master type main {}} - {SQLITE_READ t2 b main {}} - {SQLITE_SELECT {} {} {} {}} - {SQLITE_UPDATE sqlite_master sql main {}} -} -do_auth_test 1.5 { - ALTER TABLE t2 ALTER COLUMN 'b' DROP NOT NULL; -} { - {SQLITE_ALTER_TABLE main t2 b {}} - {SQLITE_FUNCTION {} sqlite_drop_constraint {} {}} - {SQLITE_READ sqlite_master sql main {}} - {SQLITE_READ sqlite_master tbl_name main {}} - {SQLITE_READ sqlite_master type main {}} - {SQLITE_UPDATE sqlite_master sql main {}} -} - -do_auth_test 1.6 { - ALTER TABLE t2 ADD CONSTRAINT abc CHECK (aaa>b) -} { - {SQLITE_ALTER_TABLE main t2 {} {}} - {SQLITE_FUNCTION {} sqlite_add_constraint {} {}} - {SQLITE_FUNCTION {} sqlite_fail {} {}} - {SQLITE_FUNCTION {} sqlite_find_constraint {} {}} - {SQLITE_READ sqlite_master sql main {}} - {SQLITE_READ sqlite_master tbl_name main {}} - {SQLITE_READ sqlite_master type main {}} - {SQLITE_READ t2 aaa main {}} - {SQLITE_READ t2 b main {}} - {SQLITE_SELECT {} {} {} {}} - {SQLITE_UPDATE sqlite_master sql main {}} -} -do_auth_test 1.7 { - ALTER TABLE t2 DROP CONSTRAINT abc; -} { - {SQLITE_ALTER_TABLE main t2 {} {}} - {SQLITE_FUNCTION {} sqlite_drop_constraint {} {}} - {SQLITE_READ sqlite_master sql main {}} - {SQLITE_READ sqlite_master tbl_name main {}} - {SQLITE_READ sqlite_master type main {}} - {SQLITE_UPDATE sqlite_master sql main {}} -} - finish_test diff --git a/test/altercol.test b/test/altercol.test index d94e0529c..5f7de57a4 100644 --- a/test/altercol.test +++ b/test/altercol.test @@ -946,17 +946,4 @@ do_execsql_test 23.20 { ROLLBACK; } {t4new} -#------------------------------------------------------------------------- -reset_db -do_execsql_test 24.0 { - CREATE TABLE t1(a PRIMARY KEY, b); - CREATE TABLE t2(a, b, c); - INSERT INTO t2 VALUES(1, 1, 1); -} - -do_catchsql_test 24.1 { - PRAGMA foreign_keys = 1; - ALTER TABLE t2 ADD COLUMN d REFERENCES t1 DEFAULT 123; -} {1 {Cannot add a REFERENCES column with non-NULL default value}} - finish_test diff --git a/test/altercons.test b/test/altercons.test deleted file mode 100644 index fecdf858b..000000000 --- a/test/altercons.test +++ /dev/null @@ -1,442 +0,0 @@ -# 2025 September 18 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#************************************************************************* -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix altercons - -# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. -ifcapable !altertable { - finish_test - return -} - -foreach {tn before after} { - 1 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b)) } - { CREATE TABLE t1(a, b) } - - 2 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) NOT NULL) } - { CREATE TABLE t1(a, b NOT NULL) } - - 3 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b)NOT NULL) } - { CREATE TABLE t1(a, b NOT NULL) } - - 3 { CREATE TABLE t1(a, b NOT NULL CONSTRAINT abc CHECK(t1.a != t1.b)); } - { CREATE TABLE t1(a, b NOT NULL) } - - 4 { CREATE TABLE t1(a, b, CONSTRAINT abc CHECK(t1.a != t1.b)) } - { CREATE TABLE t1(a, b) } - - 5 { CREATE TABLE t1(a, b, CONSTRAINT abc CHECK(t1.a != t1.b), PRIMARY KEY(a))} - { CREATE TABLE t1(a, b, PRIMARY KEY(a)) } - - 6 { CREATE TABLE t1(a, b,CONSTRAINT abc CHECK(t1.a != t1.b),PRIMARY KEY(a))} - { CREATE TABLE t1(a, b,PRIMARY KEY(a)) } - - 7 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) CONSTRAINT def UNIQUE) } - { CREATE TABLE t1(a, b CONSTRAINT def UNIQUE) } - - 8 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) CHECK (123)) } - { CREATE TABLE t1(a, b CHECK (123)) } - - 9 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) DEFAULT NULL) } - { CREATE TABLE t1(a, b DEFAULT NULL) } - - 10 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) COLLATE nocase) } - { CREATE TABLE t1(a, b COLLATE nocase) } - - 11 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) REFERENCES t2) } - { CREATE TABLE t1(a, b REFERENCES t2) } - - 12 { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT abc CHECK(a!=b) CONSTRAINT three) } - { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT three) } - - 13 { CREATE TABLE t1(a, b, c, CONSTRAINT abc CONSTRAINT one CHECK(a!=b) CONSTRAINT three) } - { CREATE TABLE t1(a, b, c, CONSTRAINT one CHECK(a!=b) CONSTRAINT three) } - - 14 { CREATE TABLE t1(a, b, c, CONSTRAINT abc) } - { CREATE TABLE t1(a, b, c) } - - 15 { CREATE TABLE t1(a, b, c, - CONSTRAINT abc, CHECK( a!=b )) } - { CREATE TABLE t1(a, b, c, CHECK( a!=b )) } - - 16 { CREATE TABLE t1(a, b, c, CONSTRAINT abc /* hello */ CHECK( a!=b )) } - { CREATE TABLE t1(a, b, c) } - - 17 { CREATE TABLE t1(a, b, c, /* world */ CONSTRAINT abc CHECK( a!=b )) } - { CREATE TABLE t1(a, b, c) } - - 18 { CREATE TABLE t1(a, b, c -- comment - CONSTRAINT abc NOT NULL - ) } - { CREATE TABLE t1(a, b, c) } - - 19 { CREATE TABLE t1(a, b, c, -- comment - CONSTRAINT abc CHECK (a>b) CONSTRAINT two - ) } - { CREATE TABLE t1(a, b, c, CONSTRAINT two - ) } - - 20 { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT abc CHECK (a>b)CONSTRAINT two) } - { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT two) } - - 21 { CREATE TABLE t1(a, b, c CONSTRAINT abc AS (b+1)) } - { CREATE TABLE t1(a, b, c AS (b+1)) } - - 22 { CREATE TABLE t1(a, b, c CONSTRAINT abc GENERATED ALWAYS AS (b+1) STORED) } - { CREATE TABLE t1(a, b, c GENERATED ALWAYS AS (b+1) STORED) } -} { - reset_db - - do_execsql_test 1.$tn.0 $before - - do_execsql_test 1.$tn.1 { - ALTER TABLE t1 DROP CONSTRAINT abc; - } {} - - do_execsql_test 1.$tn.2 { - SELECT sql FROM sqlite_schema WHERE name='t1' - } [list [string trim $after]] -} - -#------------------------------------------------------------------------- - -do_execsql_test 2.0 { - CREATE TABLE t2(x, y CONSTRAINT ccc UNIQUE); -} -do_catchsql_test 2.1 { - ALTER TABLE t2 DROP CONSTRAINT ccc -} {1 {constraint may not be dropped: ccc}} -do_catchsql_test 2.2 { - ALTER TABLE t2 DROP CONSTRAINT ddd -} {1 {no such constraint: ddd}} - -#------------------------------------------------------------------------- -reset_db -foreach {tn col before after} { - 1 a { CREATE TABLE t1(a NOT NULL, b) } - { CREATE TABLE t1(a, b) } - - 2 a { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL, b) } - { CREATE TABLE t1(a, b) } - - 3 a { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL UNIQUE, b) } - { CREATE TABLE t1(a UNIQUE, b) } - - 4 b { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL UNIQUE, b) } - { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL UNIQUE, b) } - - 5 a { CREATE TABLE t1(a CHECK(a<b) NOT NULL, b) } - { CREATE TABLE t1(a CHECK(a<b), b) } - - 6 a { CREATE TABLE t1(a CHECK(a<b) CONSTRAINT nn NOT NULL, b) } - { CREATE TABLE t1(a CHECK(a<b), b) } - - 7 b { CREATE TABLE t1(a, b NOT NULL PRIMARY KEY) } - { CREATE TABLE t1(a, b PRIMARY KEY) } - - 8 b { CREATE TABLE t1(a, b CHECK ((b+a) IS NOT NULL) NOT NULL PRIMARY KEY) } - { CREATE TABLE t1(a, b CHECK ((b+a) IS NOT NULL) PRIMARY KEY) } - - 9 b { CREATE TABLE t1(a, b CONSTRAINT nn CHECK (b IS NOT NULL) NOT NULL) } - { CREATE TABLE t1(a, b CONSTRAINT nn CHECK (b IS NOT NULL)) } - - 10 b { CREATE TABLE t1(a, b NOT NULL AS (a+1)) } - { CREATE TABLE t1(a, b AS (a+1)) } - - 11 b { CREATE TABLE t1(a, b NOT NULL GENERATED ALWAYS AS (a+1)) } - { CREATE TABLE t1(a, b GENERATED ALWAYS AS (a+1)) } -} { - reset_db - - do_execsql_test 3.$tn.0 $before - - do_execsql_test 3.$tn.1 " - ALTER TABLE t1 ALTER COLUMN $col DROP NOT NULL - " - - do_execsql_test 3.$tn.2 { - SELECT sql FROM sqlite_schema WHERE name='t1' - } [list [string trim $after]] -} - -#------------------------------------------------------------------------- -# -reset_db -do_execsql_test 4.0 { - CREATE TABLE t2(x, y CONSTRAINT ccc UNIQUE); -} -do_execsql_test 4.1 { - ALTER TABLE t2 ALTER x DROP NOT NULL; - ALTER TABLE t2 ALTER x DROP NOT NULL; - ALTER TABLE t2 ALTER x DROP NOT NULL; -} {} - -#------------------------------------------------------------------------- -# -reset_db - -do_execsql_test 5.1 { - CREATE TABLE t3(a INTEGER PRIMARY KEY, b); - INSERT INTO t3 VALUES(1000, NULL); -} - -do_catchsql_test 5.2.1 { - ALTER TABLE t3 ALTER b SET NOT NULL -} {1 {constraint failed}} - -do_test 5.2.2 { - sqlite3_errcode db -} {SQLITE_CONSTRAINT} - -foreach {tn before alter after} { - 1 { CREATE TABLE t1(a, b) } - { ALTER TABLE t1 ALTER a SET NOT NULL } - { CREATE TABLE t1(a NOT NULL, b) } - - 2 { CREATE TABLE t1(a, b) } - { ALTER TABLE t1 ALTER a SET NOT NULL ON CONFLICT FAIL } - { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL, b) } - - 3 { CREATE TABLE t1(a, b) } - { ALTER TABLE t1 ALTER a SET NOT NULL ON CONFLICT fail; } - { CREATE TABLE t1(a NOT NULL ON CONFLICT fail, b) } - - 4 { CREATE TABLE t1(a, b) } - { ALTER TABLE t1 ALTER b SET NOT NULL ON CONFLICT IGNORE ; } - { CREATE TABLE t1(a, b NOT NULL ON CONFLICT IGNORE) } - - 5 { CREATE TABLE t1(a, 'a b c' VARCHAR(10), UNIQUE(a)) } - { ALTER TABLE t1 ALTER 'a b c' SET NOT NULL } - { CREATE TABLE t1(a, 'a b c' VARCHAR(10) NOT NULL, UNIQUE(a)) } -} { - reset_db - do_execsql_test 5.3.$tn.1 $before - do_execsql_test 5.3.$tn.2 $alter - do_execsql_test 5.3.$tn.3 { - SELECT sql FROM sqlite_schema WHERE name='t1'; - } [list [string trim $after]] -} - -do_execsql_test 5.4.1 { - CREATE TABLE x1(a, b, c); -} -do_catchsql_test 5.4.2 { - ALTER TABLE x1 ALTER d SET NOT NULL; -} {1 {no such column: d}} -do_catchsql_test 5.4.3 { - ALTER TABLE x2 ALTER c SET NOT NULL; -} {1 {no such table: x2}} -do_catchsql_test 5.4.4 { - ALTER TABLE temp.x1 ALTER c SET NOT NULL; -} {1 {no such table: temp.x1}} - -#------------------------------------------------------------------------- -# -reset_db - -do_execsql_test 6.1 { - CREATE TABLE t1(a, b, c); - INSERT INTO t1 VALUES(1, 2, 3); - INSERT INTO t1 VALUES(4, 5, 6); -} - -do_catchsql_test 6.2.1 { - ALTER TABLE t1 ADD CONSTRAINT nn CHECK (c!=6); -} {1 {constraint failed}} -do_execsql_test 6.2.2 { - DELETE FROM t1 WHERE c=6; - ALTER TABLE t1 ADD CONSTRAINT nn CHECK (c!=6); -} {} -do_catchsql_test 6.2.3 { - INSERT INTO t1 VALUES(4, 5, 6); -} {1 {CHECK constraint failed: nn}} - -foreach {tn before alter after} { - 1 { CREATE TABLE t1(a, b) } - { ALTER TABLE t1 ADD CONSTRAINT nn CHECK (a>=0) } - { CREATE TABLE t1(a, b, CONSTRAINT nn CHECK (a>=0)) } - - 2 { CREATE TABLE t1(a, b ) } - { ALTER TABLE t1 ADD CONSTRAINT nn CHECK (a>=0) } - { CREATE TABLE t1(a, b , CONSTRAINT nn CHECK (a>=0)) } - - 3 { CREATE TABLE t1(a, b ) } - { ALTER TABLE t1 ADD CHECK (a>=0) } - { CREATE TABLE t1(a, b , CHECK (a>=0)) } -} { - reset_db - do_execsql_test 6.3.$tn.1 $before - do_execsql_test 6.3.$tn.2 $alter - do_execsql_test 6.3.$tn.3 { - SELECT sql FROM sqlite_schema WHERE type='table'; - } [list [string trim $after]] -} - -do_execsql_test 6.4.1 { - CREATE TABLE b1(a, b, CONSTRAINT abc CHECK (a!=2)); -} -do_catchsql_test 6.4.2 { - ALTER TABLE b1 ADD CONSTRAINT abc CHECK (a!=3); -} {1 {constraint abc already exists}} -do_execsql_test 6.4.1 { - SELECT sql FROM sqlite_schema WHERE tbl_name='b1' -} {{CREATE TABLE b1(a, b, CONSTRAINT abc CHECK (a!=2))}} - -do_execsql_test 6.5 { - CREATE TABLE abc(x,y); -} - -do_catchsql_test 6.6 { - ALTER TABLE abc ADD CHECK (z>=0); -} {1 {no such column: z}} - -#------------------------------------------------------------------------- -# Try attaching a NOT NULL to a generated column. -# -reset_db -do_execsql_test 7.0 { - CREATE TABLE x1(a, b AS (a+1)); - INSERT INTO x1 VALUES(1), (2), (3), (NULL); -} - -do_catchsql_test 7.1 { - ALTER TABLE x1 ALTER b SET NOT NULL; -} {1 {constraint failed}} - -do_catchsql_test 7.2 { - DELETE FROM x1 WHERE b IS NULL; - ALTER TABLE x1 ALTER b SET NOT NULL; -} {0 {}} - -do_execsql_test 7.3 { - SELECT b FROM x1 -} {2 3 4} - -do_catchsql_test 7.4 { - ALTER TABLE x1 ALTER rowid SET NOT NULL; -} {1 {no such column: rowid}} - -do_execsql_test 7.5 { - CREATE VIEW v1 AS SELECT a, b FROM x1; -} -do_catchsql_test 7.6 { - ALTER TABLE v1 RENAME a TO c; -} {1 {cannot rename columns of view "v1"}} -do_catchsql_test 7.7 { - ALTER TABLE v1 ALTER a SET NOT NULL; -} {1 {cannot edit constraints of view "v1"}} -do_catchsql_test 7.8 { - ALTER TABLE sqlite_schema ALTER sql SET NOT NULL; -} {1 {table sqlite_master may not be altered}} -do_catchsql_test 7.9 { - ALTER TABLE v1 ALTER a DROP NOT NULL -} {1 {cannot edit constraints of view "v1"}} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 8.0 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b NOT NULL, c CHECK (c!=555), d); - INSERT INTO t1 VALUES(1, 1, 1, 1); - INSERT INTO t1 VALUES(2, 2, 2, 2); - INSERT INTO t1 VALUES(3, 3, 3, 3); -} - -do_execsql_test 8.1.1 { - ALTER TABLE t1 ALTER a SET NOT NULL; - ALTER TABLE t1 ALTER b SET NOT NULL; - ALTER TABLE t1 ALTER c SET NOT NULL; - ALTER TABLE t1 ALTER d SET NOT NULL; -} - -do_execsql_test 8.1.2 { - SELECT sql FROM sqlite_schema WHERE tbl_name = 't1' -} {{CREATE TABLE t1(a INTEGER PRIMARY KEY NOT NULL, b NOT NULL, c CHECK (c!=555) NOT NULL, d NOT NULL)}} - -do_execsql_test 8.1.3 { - SELECT * FROM t1 WHERE a=2; -} {2 2 2 2} - -do_execsql_test 8.2.1 { - ALTER TABLE t1 ALTER a DROP NOT NULL; - ALTER TABLE t1 ALTER b DROP NOT NULL; - ALTER TABLE t1 ALTER c DROP NOT NULL; - ALTER TABLE t1 ALTER d DROP NOT NULL; -} - -do_execsql_test 8.2.2 { - SELECT sql FROM sqlite_schema WHERE tbl_name = 't1' -} {{CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c CHECK (c!=555), d)}} - -do_execsql_test 8.2.3 { - SELECT * FROM t1 WHERE a=3; -} {3 3 3 3} - -#------------------------------------------------------------------------- -reset_db -forcedelete test.db2 -do_execsql_test 9.0 { - CREATE TABLE t1(x, y, z); - ATTACH 'test.db2' AS aux; - CREATE TABLE aux.t1(x, y, z); - INSERT INTO aux.t1 VALUES(1, 1, 1); - INSERT INTO aux.t1 VALUES(2, 2, 2); - INSERT INTO aux.t1 VALUES(3, 3, NULL); - - CREATE TABLE aux.t2(x, y, z); -} - -do_catchsql_test 9.1.1 { - ALTER TABLE aux.t1 ALTER COLUMN z SET NOT NULL -} {1 {constraint failed}} -do_execsql_test 9.1.2 { - UPDATE aux.t1 SET z=x; - ALTER TABLE aux.t1 ALTER COLUMN z SET NOT NULL; - SELECT sql FROM aux.sqlite_schema WHERE name='t1'; -} {{CREATE TABLE t1(x, y, z NOT NULL)}} -do_execsql_test 9.1.3 { - ALTER TABLE aux.t1 ALTER z DROP NOT NULL; - SELECT sql FROM aux.sqlite_schema WHERE name='t1'; -} {{CREATE TABLE t1(x, y, z)}} -do_execsql_test 9.1.4 { - ALTER TABLE t2 ALTER x SET NOT NULL; - SELECT sql FROM aux.sqlite_schema WHERE name='t2'; -} {{CREATE TABLE t2(x NOT NULL, y, z)}} -do_execsql_test 9.1.5 { - ALTER TABLE t2 ALTER x DROP NOT NULL; - SELECT sql FROM aux.sqlite_schema WHERE name='t2'; -} {{CREATE TABLE t2(x, y, z)}} - -do_catchsql_test 9.2.1 { - ALTER TABLE aux.t1 ADD CONSTRAINT bill CHECK (y!=2); -} {1 {constraint failed}} -do_execsql_test 9.2.2 { - UPDATE aux.t1 SET y=4 WHERE y=2; - ALTER TABLE aux.t1 ADD CONSTRAINT bill CHECK (y!=2); - SELECT sql FROM aux.sqlite_schema WHERE name='t1'; -} {{CREATE TABLE t1(x, y, z, CONSTRAINT bill CHECK (y!=2))}} -do_execsql_test 9.2.3 { - ALTER TABLE aux.t1 DROP CONSTRAINT bill; - SELECT sql FROM aux.sqlite_schema WHERE name='t1'; -} {{CREATE TABLE t1(x, y, z)}} -do_execsql_test 9.2.4 { - ALTER TABLE t2 ADD CONSTRAINT william CHECK (z!=''); - SELECT sql FROM aux.sqlite_schema WHERE name='t2'; -} {{CREATE TABLE t2(x, y, z, CONSTRAINT william CHECK (z!=''))}} -do_execsql_test 9.2.5 { - ALTER TABLE t2 DROP CONSTRAINT william; - SELECT sql FROM aux.sqlite_schema WHERE name='t2'; -} {{CREATE TABLE t2(x, y, z)}} - -finish_test - diff --git a/test/altercons2.test b/test/altercons2.test deleted file mode 100644 index a5bbaf6fc..000000000 --- a/test/altercons2.test +++ /dev/null @@ -1,247 +0,0 @@ -# 2025 September 18 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#************************************************************************* -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix altercons2 - -# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. -ifcapable !altertable { - finish_test - return -} - -foreach {tn newsql alter res final} { - 1 "CREATE TABLE t1(a, b" - "ALTER TABLE t1 ALTER c SET NOT NULL" - {1 {database disk image is malformed}} - "CREATE TABLE t1(a, b" - - 2 "CREATE TABLE t1(a, b, " - "ALTER TABLE t1 ALTER c DROP NOT NULL" - {0 {}} - "CREATE TABLE t1(a, b, " - - 3 "CREATE TABLE t1(a, b, CHECK( ..." - "ALTER TABLE t1 ALTER c DROP NOT NULL" - {0 {}} - "CREATE TABLE t1(a, b, CHECK( ..." - - 4 "CREATE TABLE t1(a, b, c NOT NULL" - "ALTER TABLE t1 ALTER c DROP NOT NULL" - {0 {}} - "CREATE TABLE t1(a, b, c " - - 5 "CREATE TABLE" - "ALTER TABLE t1 ALTER c DROP NOT NULL" - {1 {database disk image is malformed}} - "CREATE TABLE" - - 6 "CREATE TABLE" - "ALTER TABLE t1 ADD CONSTRAINT nn CHECK (a!=0)" - {1 {database disk image is malformed}} - "CREATE TABLE" - -} { - reset_db - do_execsql_test 1.$tn.0 { - CREATE TABLE t1(a, b, c NOT NULL, CONSTRAINT xyz CHECK( a!=0 )); - } - do_execsql_test 1.$tn.1 { - PRAGMA writable_schema = 1; - UPDATE sqlite_schema SET sql = $::newsql - } - do_catchsql_test 1.$tn.2 $alter $res - - do_execsql_test 1.$tn.3 { - SELECT sql FROM sqlite_schema WHERE name='t1' - } [list $final] -} - -#------------------------------------------------------------------------- - -reset_db -proc xAuth {t args} { - if {$t=="SQLITE_ALTER_TABLE"} { - return "SQLITE_DENY" - } - return "SQLITE_OK" -} -sqlite3 db test.db -db auth xAuth - -do_execsql_test 2.0 { - CREATE TABLE x1(a PRIMARY KEY, b CHECK(a!=b) NOT NULL, c); -} - -do_catchsql_test 2.1.1 { - ALTER TABLE x1 ADD CONSTRAINT ccc CHECK (a!='a') -} {1 {not authorized}} -do_execsql_test 2.1.2 { - SELECT sql FROM sqlite_schema WHERE name='x1' -} {{CREATE TABLE x1(a PRIMARY KEY, b CHECK(a!=b) NOT NULL, c)}} - -do_catchsql_test 2.2.1 { - ALTER TABLE x1 ALTER c SET NOT NULL -} {1 {not authorized}} -do_execsql_test 2.2.2 { - SELECT sql FROM sqlite_schema WHERE name='x1' -} {{CREATE TABLE x1(a PRIMARY KEY, b CHECK(a!=b) NOT NULL, c)}} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 3.0 { - CREATE TABLE t1(x); -} -do_execsql_test 3.1 { - ALTER TABLE t1 ALTER x SET NOT NULL; -} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 4.0 { - CREATE TABLE abc(a, b, c, CONSTRAINT one CONSTRAINT two CHECK (b!=c)); -} -do_execsql_test 4.1 { - ALTER TABLE abc DROP CONSTRAINT one -} -do_execsql_test 4.2 { - SELECT sql FROM sqlite_schema -} { - {CREATE TABLE abc(a, b, c, CONSTRAINT two CHECK (b!=c))} -} - -#------------------------------------------------------------------------- -reset_db - -# The columns must come before the table constraints in a CREATE TABLE -# statement. This is useful, as it means the DROP CONSTRAINT code does -# not have to handle the constraint immediately following the '(' at -# the start of the column-list. -do_catchsql_test 5.0 { - CREATE TABLE abc(a, b, c, CONSTRAINT two CHECK (b!=c), d) -} {1 {near "d": syntax error}} -do_catchsql_test 5.1 { - CREATE TABLE def(CONSTRAINT abc CHECK( b!=c ), a, b, c); -} {1 {near "CONSTRAINT": syntax error}} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 6.0 { - CREATE TABLE abc(a, b CONSTRAINT two COLLATE nocase CHECK (a!=b), c CONSTRAINT one DEFAULT 'abc'); -} - -do_execsql_test 6.1 { - ALTER TABLE abc DROP CONSTRAINT one; - ALTER TABLE abc DROP CONSTRAINT two; -} - -do_execsql_test 6.2 { - SELECT sql FROM sqlite_schema -} { - {CREATE TABLE abc(a, b COLLATE nocase CHECK (a!=b), c DEFAULT 'abc')} -} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 7.0 { - CREATE TABLE abc(a, b, c, CONSTRAINT one CHECK (a>b) FOREIGN KEY(a) REFERENCES abc); -} -do_execsql_test 7.1 { - ALTER TABLE abc DROP CONSTRAINT one -} -do_execsql_test 7.2 { - SELECT sql FROM sqlite_schema -} { - {CREATE TABLE abc(a, b, c, FOREIGN KEY(a) REFERENCES abc)} -} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 8.0 { - CREATE TABLE abc(a, b, c, CONSTRAINT one FOREIGN KEY(a) REFERENCES abc); -} -do_catchsql_test 8.1 { - ALTER TABLE abc DROP CONSTRAINT one -} {1 {constraint may not be dropped: one}} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 9.0 { - CREATE TABLE abc(a, b NOT NULL AS (a+1)) -} -do_execsql_test 9.1 { - ALTER TABLE abc ALTER b DROP NOT NULL; - SELECT sql FROM sqlite_schema; -} {{CREATE TABLE abc(a, b AS (a+1))}} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 10.0 { - CREATE TABLE abc(a, b GENERATED ALWAYS AS (a+1)); - INSERT INTO abc VALUES(1), (2); - SELECT * FROM abc; -} {1 2 2 3} - -do_execsql_test 10.1 { - ALTER TABLE abc ALTER b SET NOT NULL; -} -do_catchsql_test 10.2 { - INSERT INTO abc VALUES(NULL); -} {1 {NOT NULL constraint failed: abc.b}} -do_execsql_test 10.3 { - INSERT INTO abc VALUES(3); - ALTER TABLE abc ALTER COLUMN b DROP NOT NULL; -} -do_execsql_test 10.4 { - INSERT INTO abc VALUES(NULL); -} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 11.0 { - CREATE TABLE t1(a, b, c); -} - -do_execsql_test 11.1.1 { - ALTER TABLE t1 ADD CONSTRAINT c1 CHECK(a=b) --comment - ; -} - -do_execsql_test 11.1.2 {ALTER TABLE t1 ADD CONSTRAINT c2 CHECK(a=b) --comment} - -do_execsql_test 11.1.3 { - SELECT sql FROM sqlite_schema; -} { - {CREATE TABLE t1(a, b, c, CONSTRAINT c1 CHECK(a=b), CONSTRAINT c2 CHECK(a=b))} -} - -do_execsql_test 11.2.1 { - CREATE TABLE t2(a, b); -} -do_execsql_test 11.2.2 {ALTER TABLE t2 ALTER b SET NOT NULL --new cons} -do_execsql_test 11.2.3 { - SELECT sql FROM sqlite_schema WHERE name='t2'; -} { - {CREATE TABLE t2(a, b NOT NULL)} -} -do_execsql_test 11.2.3 { - ALTER TABLE t2 ALTER b DROP NOT NULL; - SELECT sql FROM sqlite_schema WHERE name='t2'; -} { - {CREATE TABLE t2(a, b)} -} -do_execsql_test 11.2.2 {ALTER TABLE t2 ALTER b SET NOT NULL --new cons -; -} -finish_test - diff --git a/test/alterfault.test b/test/alterfault.test index c9c0d77ba..b6b42973e 100644 --- a/test/alterfault.test +++ b/test/alterfault.test @@ -23,9 +23,6 @@ ifcapable !altertable { do_execsql_test 1.0 { CREATE TABLE t1(a); - CREATE TEMP TRIGGER tr1 AFTER INSERT ON t1 BEGIN - SELECT 123; - END; } faultsim_save_and_close @@ -39,40 +36,6 @@ do_faultsim_test 1.1 -faults oom* -prep { faultsim_test_result {0 {}} } -reset_db -do_execsql_test 2.0 { - CREATE TABLE x1(d, e CONSTRAINT abc NOT NULL, f); -} -faultsim_save_and_close - -foreach {tn sql} { - 1 { ALTER TABLE x1 ADD CHECK (d!=1) } - 2 { ALTER TABLE x1 ADD CONSTRAINT xyz CHECK (f>d+e); } - 3 { ALTER TABLE x1 DROP CONSTRAINT abc } - 4 { ALTER TABLE x1 ALTER f SET NOT NULL } - 5 { ALTER TABLE x1 ALTER e DROP NOT NULL } -} { - do_faultsim_test 2.$tn -faults oom* -prep { - faultsim_restore_and_reopen - } -body { - execsql $::sql - } -test { - faultsim_test_result {0 {}} - } -} -# Test an OOM when returning an error. -# -do_faultsim_test 2.e -faults oom* -prep { - faultsim_restore_and_reopen -} -body { - execsql { - ALTER TABLE x1 DROP CONSTRAINT nosuchconstraint - } -} -test { - faultsim_test_result \ - {1 {no such constraint: nosuchconstraint}} \ - {1 {SQL logic error}} -} finish_test diff --git a/test/altertab3.test b/test/altertab3.test index 36e08c769..92060fb41 100644 --- a/test/altertab3.test +++ b/test/altertab3.test @@ -778,52 +778,4 @@ do_execsql_test 31.2 { SELECT rr FROM t1 LIMIT 1 } {5.0} -#------------------------------------------------------------------------- -reset_db -do_execsql_test 32.1.0 { - CREATE TABLE t1( - a INT, - b INT, - -- comment with comma - c INT - ); -} -do_execsql_test 32.1.1 { - ALTER TABLE t1 DROP COLUMN c; -} -do_execsql_test 32.1.2 { - SELECT sql FROM sqlite_schema -} {{CREATE TABLE t1( - a INT, - b INT)}} - -reset_db -do_execsql_test 32.2.0 { - CREATE TABLE t1( - a INT, - b INT, - -- comment with, comma - c INT - ); -} -do_execsql_test 32.2.1 { - ALTER TABLE t1 DROP COLUMN c; -} -do_execsql_test 32.2.2 { - SELECT sql FROM sqlite_schema -} {{CREATE TABLE t1( - a INT, - b INT)}} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 33.1 { - CREATE TABLE x1(a TEXT, b INTEGER, c CHECK(c!=0)); -} - -do_execsql_test 33.2 { - ALTER TABLE x1 DROP COLUMN b; - SELECT sql FROM sqlite_schema; -} {{CREATE TABLE x1(a TEXT, c CHECK(c!=0))}} - finish_test diff --git a/test/altertrig.test b/test/altertrig.test index 223feaf8f..556dc3fea 100644 --- a/test/altertrig.test +++ b/test/altertrig.test @@ -41,7 +41,7 @@ do_execsql_test 1.0 { CREATE TABLE t4(a); CREATE TRIGGER r1 INSERT ON t1 BEGIN - UPDATE t1 SET d='xyz' FROM t2, t3; + UPDATE t1 SET d='xyz' FROM t2, t3; END; } diff --git a/test/atof2.test b/test/atof2.test deleted file mode 100644 index 5a68d1352..000000000 --- a/test/atof2.test +++ /dev/null @@ -1,35 +0,0 @@ -# 2026-02-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. -# -#*********************************************************************** -# -# Tests of the sqlite3AtoF() function. -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# Rounding cases: -# -do_execsql_test atof2-1.0 { - SELECT format('%g',192.496475); -} 192.496 -do_execsql_test atof2-1.1 { - SELECT format('%g',192.496501); -} 192.497 - -load_static_extension db ieee754 -do_execsql_test atof2-2.1 { - SELECT format('%!.30f',ieee754_inc(100.0,-1)); -} 99.9999999999999858 -do_execsql_test atof2-2.2 { - SELECT format('%!.30f',ieee754_inc(100.0,-2)); -} 99.9999999999999716 - -finish_test diff --git a/test/autoindex1.test b/test/autoindex1.test index be41d702c..1c8ce007f 100644 --- a/test/autoindex1.test +++ b/test/autoindex1.test @@ -185,7 +185,8 @@ do_eqp_test autoindex1-500.1 { QUERY PLAN |--SEARCH t501 USING INTEGER PRIMARY KEY (rowid=?) `--LIST SUBQUERY xxxxxx - `--SCAN t502 + |--SCAN t502 + `--CREATE BLOOM FILTER } do_eqp_test autoindex1-501 { SELECT b FROM t501 diff --git a/test/avfs.test b/test/avfs.test index 1503e8613..ffd6b309f 100644 --- a/test/avfs.test +++ b/test/avfs.test @@ -332,11 +332,9 @@ do_test 4.3 { set ofd [open $shdo w] if {$::cliDoesAr} { puts $ofd ".ar -u $shdo" - puts $ofd ".mode list" puts $ofd "select count(*) from sqlar where name = '$shdo';" } else { puts $ofd "insert into sqlar values (1);" - puts $ofd ".mode list" puts $ofd "select count(*) from sqlar;" } puts $ofd ".q" diff --git a/test/bestindex8.test b/test/bestindex8.test index 43d9cf6af..3ed7f6703 100644 --- a/test/bestindex8.test +++ b/test/bestindex8.test @@ -80,17 +80,17 @@ do_execsql_test 1.0 { foreach {tn sql bDistinct idxinsert bConsumed res} { 1 "SELECT a, b FROM vt1" 0 0 0 {a b c d a b c d} - 2 "SELECT DISTINCT a, b FROM vt1" 2 0 1 {a b c d} - 3 "SELECT DISTINCT a FROM vt1" 2 0 1 {a c} + 2 "SELECT DISTINCT a, b FROM vt1" 2 1 1 {a b c d} + 3 "SELECT DISTINCT a FROM vt1" 2 1 1 {a c} 4 "SELECT DISTINCT b FROM vt1" 2 1 0 {b d} - 5 "SELECT DISTINCT b FROM vt1 ORDER BY a" 3 1 1 {b d} - 6 "SELECT DISTINCT t0.c0 FROM vt1, t0 ORDER BY vt1.a" 3 1 1 {1 0} + 5 "SELECT DISTINCT b FROM vt1 ORDER BY a" 0 1 1 {b d} + 6 "SELECT DISTINCT t0.c0 FROM vt1, t0 ORDER BY vt1.a" 0 1 1 {1 0} 7 "SELECT DISTINCT a, b FROM vt1 ORDER BY a, b" 3 0 1 {a b c d} - 8 "SELECT DISTINCT a, b FROM vt1 ORDER BY a" 3 1 1 {a b c d} - 9 "SELECT DISTINCT a FROM vt1 ORDER BY a, b" 3 1 1 {a c} + 8 "SELECT DISTINCT a, b FROM vt1 ORDER BY a" 0 1 1 {a b c d} + 9 "SELECT DISTINCT a FROM vt1 ORDER BY a, b" 0 1 1 {a c} - 10 "SELECT DISTINCT a, b FROM vt1 WHERE b='b'" 2 0 1 {a b} - 11 "SELECT DISTINCT a, b FROM vt1 WHERE +b='b'" 2 0 1 {a b} + 10 "SELECT DISTINCT a, b FROM vt1 WHERE b='b'" 2 1 1 {a b} + 11 "SELECT DISTINCT a, b FROM vt1 WHERE +b='b'" 2 1 1 {a b} } { set ::lBestIndexDistinct "" set ::lOrderByConsumed 0 diff --git a/test/bestindexB.test b/test/bestindexB.test index 5850e35bd..b50e74fee 100644 --- a/test/bestindexB.test +++ b/test/bestindexB.test @@ -34,7 +34,7 @@ proc vtab_command {method args} { set orderby [$hdl orderby] if {[info exists ::xbestindex_sql]} { - # explain_i $::xbestindex_sql + explain_i $::xbestindex_sql set ::xbestindex_res [ execsql $::xbestindex_sql ] } diff --git a/test/bestindexF.test b/test/bestindexF.test deleted file mode 100644 index 4f49f610d..000000000 --- a/test/bestindexF.test +++ /dev/null @@ -1,294 +0,0 @@ -# 2025-12-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. -# -#*********************************************************************** -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix bestindexF - -ifcapable !vtab { - finish_test - return -} - - -proc vtab_command {method args} { - switch -- $method { - xConnect { - return "CREATE TABLE t1(a, b, c)" - } - - xBestIndex { - set hdl [lindex $args 0] - set ::vtab_orderby [$hdl orderby] - set ::vtab_distinct [$hdl distinct] - - if {$::vtab_orderby == "{column 0 desc 0} {column 1 desc 0}" - || $::vtab_orderby == "{column 0 desc 0}" - } { - return [list orderby 1] - } - - return "" - } - - xFilter { - set sql { - SELECT 1, 1, 'a', 555 - UNION ALL - SELECT 2, 1, 'a', NULL - UNION ALL - SELECT 3, 1, 'b', 'text' - UNION ALL - SELECT 4, 2, 'a', 3.14 - UNION ALL - SELECT 5, 2, 'b', 0 - } - return [list sql $sql] - } - } - - return {} -} - -register_tcl_module db - -do_execsql_test 1.0 { - CREATE VIRTUAL TABLE t1 USING tcl(vtab_command) -} - -proc uses_idxinsert {sql} { - return [expr [lsearch [db eval "explain $sql"] IdxInsert]>=0] -} -proc do_idxinsert_test {tn sql res} { - set uses [uses_idxinsert $sql] - uplevel [list do_execsql_test $tn "SELECT $uses ; $sql" $res] -} - -do_idxinsert_test 1.1.1 { - SELECT DISTINCT a, b FROM t1 -} {0 1 a 1 b 2 a 2 b} - -do_test 1.1.2 { - list $::vtab_distinct $::vtab_orderby -} {2 {{column 0 desc 0} {column 1 desc 0}}} - -do_execsql_test 1.3 { - CREATE TABLE t0(c0); - INSERT INTO t0 VALUES(0); - INSERT INTO t0 VALUES(1); -} - -do_idxinsert_test 1.4.1 { - SELECT DISTINCT t0.c0 FROM t1, t0 ORDER BY t1.a; -} {1 0 1} - -do_test 1.4.2 { - list $::vtab_distinct $::vtab_orderby -} {3 {{column 0 desc 0}}} - -#------------------------------------------------------------------------- -# -reset_db -proc vtab_command {method args} { - switch -- $method { - xConnect { - return "CREATE TABLE t1(a, b, c)" - } - - xBestIndex { - set hdl [lindex $args 0] - set ::vtab_orderby [$hdl orderby] - set ::vtab_distinct [$hdl distinct] - - # Set idxNum to 1 if DISTINCT is to be used in xFilter. - # - set idxStr [list ""] - if {$::vtab_distinct==2 || $::vtab_distinct==3} { - set idxStr [list DISTINCT] - } - - set orderby 0 - if {$::vtab_orderby == "{column 0 desc 1}" - || $::vtab_orderby == "{column 0 desc 0}" - } { - set orderby 1 - if {$::vtab_distinct==1 || $::vtab_distinct==2} { - lappend idxStr "ORDER BY ((a+2)%5)" - } else { - set sort "ORDER BY a" - if {$::vtab_orderby == "{column 0 desc 1}"} { - append sort " DESC" - } - lappend idxStr $sort - } - } else { - lappend idxStr "" - } - - return [list orderby $orderby idxstr $idxStr] - return "" - } - - xFilter { - set idxstr [lindex $args 1] - - set distinct [lindex $idxstr 0] - set orderby [lindex $idxstr 1] - set sql " - SELECT $distinct 0, a, b FROM real_t1 $orderby - " - return [list sql $sql] - } - } - - return {} -} - -do_execsql_test 2.0 { - CREATE TABLE real_t1(a, b); - - INSERT INTO real_t1 VALUES (1, 'a'); - INSERT INTO real_t1 VALUES (2, 'a'); - INSERT INTO real_t1 VALUES (1, 'a'); - - INSERT INTO real_t1 VALUES (2, 'b'); - INSERT INTO real_t1 VALUES (1, 'b'); - INSERT INTO real_t1 VALUES (2, 'b'); - - INSERT INTO real_t1 VALUES (3, 'a'); - INSERT INTO real_t1 VALUES (4, 'b'); - INSERT INTO real_t1 VALUES (3, 'a'); - - INSERT INTO real_t1 VALUES (4, 'b'); - INSERT INTO real_t1 VALUES (3, 'a'); - INSERT INTO real_t1 VALUES (4, 'b'); -} - -register_tcl_module db -do_execsql_test 2.0 { - CREATE VIRTUAL TABLE t1 USING tcl(vtab_command) -} - -do_execsql_test 2.1 { - SELECT a, b FROM t1 -} { - 1 a 2 a 1 a - 2 b 1 b 2 b - 3 a 4 b 3 a - 4 b 3 a 4 b -} - -# This is like do_execsql_test, except one value is prepended to the -# expected result - the P4 (idxStr) of the VFilter opcode. It is an error -# if $sql generates two or more VFilter instructions. -# -proc do_vtabsorter_test {tn sql expect} { - set vm [db eval "EXPLAIN $sql"] - - set ii [lsearch $vm VFilter] - set ::res [lindex $vm [expr $ii+4]] - - set ::idx [expr [lsearch $vm IdxInsert]>=0] - - set iSort [lsearch $vm SorterSort] - if {$iSort>=0} { - error "query is using sorter" - } - - uplevel [list do_test $tn.0 { set ::idx } [lindex $expect 0]] - uplevel [list do_test $tn.1 { set ::res } [lindex $expect 1]] - uplevel [list do_execsql_test $tn.2 $sql [lrange $expect 2 end]] -} - -do_vtabsorter_test 2.2 { - SELECT a, b FROM t1 -} { 0 "{} {}" - 1 a 2 a 1 a - 2 b 1 b 2 b - 3 a 4 b 3 a - 4 b 3 a 4 b -} - -do_vtabsorter_test 2.3 { - SELECT DISTINCT a FROM t1 -} { 0 "DISTINCT {ORDER BY ((a+2)%5)}" - 3 4 1 2 -} - -do_vtabsorter_test 2.4 { - SELECT DISTINCT a FROM t1 ORDER BY a -} { 0 "DISTINCT {ORDER BY a}" - 1 2 3 4 -} - -do_vtabsorter_test 2.5 { - SELECT DISTINCT a FROM t1 ORDER BY a DESC -} { 0 "DISTINCT {ORDER BY a DESC}" - 4 3 2 1 -} - -do_vtabsorter_test 2.6 { - SELECT a FROM t1 ORDER BY a -} { 0 "{} {ORDER BY a}" - 1 1 1 - 2 2 2 - 3 3 3 - 4 4 4 -} - -do_vtabsorter_test 2.7 { - SELECT a FROM t1 ORDER BY a DESC -} { 0 "{} {ORDER BY a DESC}" - 4 4 4 - 3 3 3 - 2 2 2 - 1 1 1 -} - -do_vtabsorter_test 2.8 { - SELECT a, count(*) FROM t1 GROUP BY a ORDER BY a -} { 0 "{} {ORDER BY a}" - 1 3 - 2 3 - 3 3 - 4 3 -} - -do_vtabsorter_test 2.9 { - SELECT a, count(*) FROM t1 GROUP BY a ORDER BY a DESC -} { 0 "{} {ORDER BY a DESC}" - 4 3 - 3 3 - 2 3 - 1 3 -} - -do_vtabsorter_test 2.10 { - SELECT a, count(*) FROM t1 GROUP BY a -} { 0 "{} {ORDER BY ((a+2)%5)}" - 3 3 - 4 3 - 1 3 - 2 3 -} - -do_vtabsorter_test 2.11 { - SELECT DISTINCT a, count(*) FROM t1 GROUP BY a -} { 1 "{} {ORDER BY ((a+2)%5)}" - 3 3 - 4 3 - 1 3 - 2 3 -} - -finish_test - diff --git a/test/carray01.test b/test/carray01.test index b17a481e1..86ea06996 100644 --- a/test/carray01.test +++ b/test/carray01.test @@ -51,10 +51,6 @@ do_test 101 { run_stmt $STMT } {1} do_test 102 { - sqlite3_carray_bind -v2 -malloc $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT -} {1} -do_test 103 { set STMT2 [sqlite3_prepare_v2 db { SELECT DISTINCT typeof(value) FROM carray(?3)} -1] sqlite3_carray_bind $STMT2 3 1 2 3 4 5 6 7 @@ -128,10 +124,6 @@ do_test 160 { sqlite3_carray_bind -double $STMT 3 1 2 3 4 5 6 7 run_stmt $STMT } {1} -do_test 161 { - sqlite3_carray_bind -double -v2 $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT -} {1} do_test 170 { sqlite3_carray_bind -text -static $STMT 3 1 2 3 4 6 7 run_stmt $STMT diff --git a/test/collate5.test b/test/collate5.test index b474c39d5..71d4efe25 100644 --- a/test/collate5.test +++ b/test/collate5.test @@ -122,9 +122,9 @@ do_test collate5-2.0 { } {} do_test collate5-2.1.1 { - string toupper [execsql { + execsql { SELECT a FROM collate5t1 UNION select a FROM collate5t2; - }] + } } {A B N} do_test collate5-2.1.2 { execsql { @@ -132,10 +132,10 @@ do_test collate5-2.1.2 { } } {A B N a b n} do_test collate5-2.1.3 { - string tolower [execsql { + execsql { SELECT a, b FROM collate5t1 UNION select a, b FROM collate5t2; - }] -} {a apple a apple b banana b banana n {}} + } +} {A Apple A apple B Banana b banana N {}} do_test collate5-2.1.4 { execsql { SELECT a, b FROM collate5t2 UNION select a, b FROM collate5t1; @@ -143,9 +143,9 @@ do_test collate5-2.1.4 { } {A Apple B banana N {} a apple b banana n {}} do_test collate5-2.2.1 { - string toupper [execsql { + execsql { SELECT a FROM collate5t1 EXCEPT select a FROM collate5t2; - }] + } } {N} do_test collate5-2.2.2 { execsql { @@ -153,10 +153,10 @@ do_test collate5-2.2.2 { } } {A a} do_test collate5-2.2.3 { - string tolower [execsql { + execsql { SELECT a, b FROM collate5t1 EXCEPT select a, b FROM collate5t2; - }] -} {a apple n {}} + } +} {A Apple N {}} do_test collate5-2.2.4 { execsql { SELECT a, b FROM collate5t2 EXCEPT select a, b FROM collate5t1 @@ -165,9 +165,9 @@ do_test collate5-2.2.4 { } {A apple a apple} do_test collate5-2.3.1 { - string toupper [execsql { + execsql { SELECT a FROM collate5t1 INTERSECT select a FROM collate5t2; - }] + } } {A B} do_test collate5-2.3.2 { execsql { @@ -175,10 +175,10 @@ do_test collate5-2.3.2 { } } {B b} do_test collate5-2.3.3 { - string tolower [execsql { + execsql { SELECT a, b FROM collate5t1 INTERSECT select a, b FROM collate5t2; - }] -} {a apple b banana} + } +} {a apple B banana} do_test collate5-2.3.4 { execsql { SELECT a, b FROM collate5t2 INTERSECT select a, b FROM collate5t1; diff --git a/test/cost.test b/test/cost.test index 5ef84c2b2..6106caba8 100644 --- a/test/cost.test +++ b/test/cost.test @@ -203,8 +203,8 @@ do_eqp_test 8.2 { } { QUERY PLAN |--SCAN track - |--SEARCH composer USING INTEGER PRIMARY KEY (rowid=?) |--SEARCH album USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH composer USING INTEGER PRIMARY KEY (rowid=?) `--USE TEMP B-TREE FOR DISTINCT } diff --git a/test/dblwidth-a.sql b/test/dblwidth-a.sql index bcd60359f..38c219698 100644 --- a/test/dblwidth-a.sql +++ b/test/dblwidth-a.sql @@ -1,50 +1,20 @@ -#!sqlite3 /* ** Run this script using "sqlite3" to confirm that the command-line ** shell properly handles the output of double-width characters. ** ** https://sqlite.org/forum/forumpost/008ac80276 */ -.testcase 100 .mode box CREATE TABLE data(word TEXT, description TEXT); INSERT INTO data VALUES('〈οὐκέτι〉','Greek without dblwidth <...>'); +.print .mode box SELECT * FROM data; -.check <<END -╭────────────┬──────────────────────────────╮ -│ word │ description │ -╞════════════╪══════════════════════════════╡ -│ 〈οὐκέτι〉 │ Greek without dblwidth <...> │ -╰────────────┴──────────────────────────────╯ -END - -.testcase 200 .mode table +.print .mode table SELECT * FROM data; -.check <<END -+------------+------------------------------+ -| word | description | -+------------+------------------------------+ -| 〈οὐκέτι〉 | Greek without dblwidth <...> | -+------------+------------------------------+ -END - -.testcase 300 .mode qbox +.print .mode qbox SELECT * FROM data; -.check <<END -╭──────────────┬────────────────────────────────╮ -│ word │ description │ -╞══════════════╪════════════════════════════════╡ -│ '〈οὐκέτι〉' │ 'Greek without dblwidth <...>' │ -╰──────────────┴────────────────────────────────╯ -END - -.testcase 400 .mode column +.print .mode column SELECT * FROM data; -.check <<END - word description ----------- ---------------------------- -〈οὐκέτι〉 Greek without dblwidth <...> -END diff --git a/test/distinct2.test b/test/distinct2.test index 5de4d3094..980b0b1e3 100644 --- a/test/distinct2.test +++ b/test/distinct2.test @@ -311,7 +311,7 @@ do_execsql_test 4010 { } do_execsql_test 4020 { SELECT b FROM t1 UNION SELECT 1; -} {1 {}} +} {1 { }} #------------------------------------------------------------------------- # diff --git a/test/dotcmd01.sql b/test/dotcmd01.sql deleted file mode 100644 index 751d32659..000000000 --- a/test/dotcmd01.sql +++ /dev/null @@ -1,63 +0,0 @@ -#!sqlite3 -# -# 2026-01-30 -# -# 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. -# -#*********************************************************************** -# -# Miscellaneous tests for dot-commands -# -# ./sqlite3 test/dotcmd01.sql -# - -.testcase setup -.open :memory: -.mode tty -.check '' - -# The ".eqp on" setting does not affect the output from .fullschema -# and similar. -# -.testctrl opt -Stat4 -.testcase 100 -CREATE TABLE t1(a,b,c); -WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<300) - INSERT INTO t1(a,b,c) - SELECT n%10, n%30, n%100 FROM c; -CREATE INDEX t1a ON t1(a); -CREATE INDEX t1b ON t1(b); -ANALYZE; -.eqp on -.fullschema -.check <<END -CREATE TABLE t1(a,b,c); -CREATE INDEX t1a ON t1(a); -CREATE INDEX t1b ON t1(b); -ANALYZE sqlite_schema; -INSERT INTO sqlite_stat1 VALUES('t1','t1b','300 10'); -INSERT INTO sqlite_stat1 VALUES('t1','t1a','300 30'); -ANALYZE sqlite_schema; -END - -.testcase 110 -.schema -.check <<END -CREATE TABLE t1(a,b,c); -CREATE INDEX t1a ON t1(a); -CREATE INDEX t1b ON t1(b); -CREATE TABLE sqlite_stat1(tbl,idx,stat); -END - -.testcase 120 -.tables -.check --glob "t1" - -.testcase 130 -.indexes -.check --glob t1a*t1b diff --git a/test/e_expr.test b/test/e_expr.test index 5c0bfb0c1..81d2fd172 100644 --- a/test/e_expr.test +++ b/test/e_expr.test @@ -1744,10 +1744,10 @@ do_execsql_test e_expr-32.2.8 { integer 9223372036854775807 \ integer 9223372036854775807 \ integer 9223372036854775807 \ - real 9.2233720368547758e+18 \ - real 9.2233720368547758e+18 \ - real 9.2233720368547758e+18 \ - real 9.2233720368547758e+18 \ + real 9.22337203685478e+18 \ + real 9.22337203685478e+18 \ + real 9.22337203685478e+18 \ + real 9.22337203685478e+18 \ integer -5 \ integer -5 \ ] diff --git a/test/e_update.test b/test/e_update.test index 2b3341dc7..a13b059b3 100644 --- a/test/e_update.test +++ b/test/e_update.test @@ -325,8 +325,6 @@ foreach {tn sql error ac data } { # EVIDENCE-OF: R-43190-62442 In other words, the schema-name. prefix on # the table name of the UPDATE is not allowed within triggers. # -# Update: Unless the trigger is in the temp schema. -# do_update_tests e_update-2.1 -error { qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers } { @@ -341,14 +339,12 @@ do_update_tests e_update-2.1 -error { UPDATE aux.t1 SET a=1, b=2; END; } {} -} -# Qualified table name is allowed as t4 is a temp table. -do_execsql_test e_update-2.1.3 { - CREATE TRIGGER tr1 AFTER DELETE ON t4 BEGIN - UPDATE main.t1 SET a=1, b=2; - END; - DROP TRIGGER tr1; + 3 { + CREATE TRIGGER tr1 AFTER DELETE ON t4 BEGIN + UPDATE main.t1 SET a=1, b=2; + END; + } {} } # EVIDENCE-OF: R-06085-13761 Unless the table to which the trigger is diff --git a/test/e_walckpt.test b/test/e_walckpt.test index 4aae52a78..3b1f3b015 100644 --- a/test/e_walckpt.test +++ b/test/e_walckpt.test @@ -605,15 +605,14 @@ foreach {tn script} { sqlite3 db test.db foreach {tn mode res} { 0 -1001 {1 {SQLITE_MISUSE - not an error}} - 1 -2 {1 {SQLITE_MISUSE - not an error}} - 2 -1 {0 {0 -1 -1}} - 3 0 {0 {0 -1 -1}} - 4 1 {0 {0 -1 -1}} - 5 2 {0 {0 -1 -1}} - 6 3 {0 {0 -1 -1}} - 7 4 {1 {SQLITE_MISUSE - not an error}} - 8 114 {1 {SQLITE_MISUSE - not an error}} - 9 1000000 {1 {SQLITE_MISUSE - not an error}} + 1 -1 {1 {SQLITE_MISUSE - not an error}} + 2 0 {0 {0 -1 -1}} + 3 1 {0 {0 -1 -1}} + 4 2 {0 {0 -1 -1}} + 5 3 {0 {0 -1 -1}} + 6 4 {1 {SQLITE_MISUSE - not an error}} + 7 114 {1 {SQLITE_MISUSE - not an error}} + 8 1000000 {1 {SQLITE_MISUSE - not an error}} } { do_test 4.$tn { list [catch "wal_checkpoint_v2 db $mode" msg] $msg diff --git a/test/eqp.test b/test/eqp.test index d2bdc49e3..147b5ceaf 100644 --- a/test/eqp.test +++ b/test/eqp.test @@ -124,10 +124,10 @@ do_eqp_test 1.8 { } { QUERY PLAN |--CO-ROUTINE (subquery-xxxxxx) - | `--MERGE (UNION) - | |--LEFT + | `--COMPOUND QUERY + | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW - | `--RIGHT + | `--UNION USING TEMP B-TREE | `--SCAN CONSTANT ROW |--SCAN (subquery-xxxxxx) `--SCAN t3 @@ -137,26 +137,24 @@ do_eqp_test 1.9 { } { QUERY PLAN |--CO-ROUTINE abc - | `--MERGE (EXCEPT) - | |--LEFT + | `--COMPOUND QUERY + | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW - | `--RIGHT - | |--SCAN t3 - | `--USE TEMP B-TREE FOR ORDER BY - |--SCAN t3 - `--SCAN abc + | `--EXCEPT USING TEMP B-TREE + | `--SCAN t3 + |--SCAN abc + `--SCAN t3 } do_eqp_test 1.10 { SELECT * FROM t3 JOIN (SELECT 1 INTERSECT SELECT a FROM t3 LIMIT 17) AS abc } { QUERY PLAN |--CO-ROUTINE abc - | `--MERGE (INTERSECT) - | |--LEFT + | `--COMPOUND QUERY + | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW - | `--RIGHT - | |--SCAN t3 - | `--USE TEMP B-TREE FOR ORDER BY + | `--INTERSECT USING TEMP B-TREE + | `--SCAN t3 |--SCAN abc `--SCAN t3 } @@ -457,11 +455,10 @@ do_eqp_test 4.3.1 { SELECT x FROM t1 UNION SELECT x FROM t2 } { QUERY PLAN - `--MERGE (UNION) - |--LEFT - | |--SCAN t1 - | `--USE TEMP B-TREE FOR ORDER BY - `--RIGHT + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN t1 + `--UNION USING TEMP B-TREE `--SCAN t2 USING COVERING INDEX t2i1 } @@ -469,17 +466,13 @@ do_eqp_test 4.3.2 { SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 } { QUERY PLAN - `--MERGE (UNION) - |--LEFT - | `--MERGE (UNION) - | |--LEFT - | | |--SCAN t1 - | | `--USE TEMP B-TREE FOR ORDER BY - | `--RIGHT - | `--SCAN t2 USING COVERING INDEX t2i1 - `--RIGHT - |--SCAN t1 - `--USE TEMP B-TREE FOR ORDER BY + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN t1 + |--UNION USING TEMP B-TREE + | `--SCAN t2 USING COVERING INDEX t2i1 + `--UNION USING TEMP B-TREE + `--SCAN t1 } do_eqp_test 4.3.3 { SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 ORDER BY 1 diff --git a/test/filectrl.test b/test/filectrl.test index eea9a4773..9b1a1c758 100644 --- a/test/filectrl.test +++ b/test/filectrl.test @@ -36,11 +36,13 @@ do_test filectrl-1.5 { sqlite3 db test_control_lockproxy.db file_control_lockproxy_test db [get_pwd] } {} -do_test filectrl-1.6 { - sqlite3 db test.db - set fn [file_control_tempfilename db] - set fn -} {/etilqs_/} +ifcapable !winrt { + do_test filectrl-1.6 { + sqlite3 db test.db + set fn [file_control_tempfilename db] + set fn + } {/etilqs_/} +} db close forcedelete .test_control_lockproxy.db-conch test.proxy forcedelete test.db test2.db diff --git a/test/fpconv1.test b/test/fpconv1.test index a93489907..195fdf990 100644 --- a/test/fpconv1.test +++ b/test/fpconv1.test @@ -17,82 +17,28 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl - -# Unusual rendering cases: -# -do_execsql_test fpconv1-1.0 { - SELECT 1.23 - 2.34; -} {-1.1099999999999999} -# ^--- Not -1.11 as you would expect. -1.11 has a different bit pattern - -do_execsql_test fpconv1-1.1 { - SELECT 1.23 * 2.34; -} {2.8781999999999996} -# ^--- Not 2.8782 as you would expect. 2.8782 has a different bit pattern - -# Change significant digits to 15 and get a different result. -sqlite3_db_config db FP_DIGITS 15 -do_execsql_test fpconv1-1.2 { - SELECT 1.23 - 2.34; -} {-1.11} -do_execsql_test fpconv1-1.3 { - SELECT 1.23 * 2.34; -} {2.8782} -sqlite3_db_config db FP_DIGITS 17 - - if {[catch {load_static_extension db decimal} error]} { puts "Skipping decimal tests, hit load error: $error" finish_test; return } sqlite3_create_function db -do_execsql_test fpconv1-2.0 { +do_execsql_test fpconv1-1.0 { WITH RECURSIVE /* Number of random floating-point values to try. - ** On a circa 2021 Ryzen 5950X running Mint Linux, and - ** compiled with -O0 -DSQLITE_DEBUG, this test runs at - ** about 150000 cases per second ------------------vvvvvvv */ - c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<500_000), + ** On a circa 2016 x64 linux box, this test runs at + ** about 80000 cases per second -------------------vvvvvv */ + c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100000), fp(y) AS MATERIALIZED ( SELECT CAST( format('%+d.%019d0e%+03d', random()%10,abs(random()),random()%200) AS real) FROM c ) SELECT y FROM fp - WHERE -log10(abs(decimal_sub(dtostr(y,24),format('%!.24e',y))/y))<17.0; + WHERE -log10(abs(decimal_sub(dtostr(y,24),format('%!.24e',y))/y))<15.0; /* Number of digits of accuracy required -------^^^^ */ } {} # ^---- Expect a empty set as the result. The output is all tested numbers -# that fail to preserve at least 16 significant digits of accuracy. - -######################################################################## -# Random test to ensure that double -> text -> double conversions -# round-trip exactly. -# - -load_static_extension db ieee754 - -do_execsql_test fpconv1-3.0 { - WITH RECURSIVE - c(x,s) AS MATERIALIZED (VALUES(1,random()&0xffefffffffffffff) - UNION ALL - SELECT x+1,random()&0xffefffffffffffff - FROM c WHERE x<1_000_000), - fp(y,s) AS ( - SELECT ieee754_from_int(s),s FROM c - ), - fp2(yt,y,s) AS ( - SELECT y||'', y, s FROM fp - ) - SELECT format('%#016x',s) aS 'orig-hex', - format('%#016x',ieee754_to_int(CAST(yt AS real))) AS 'full-roundtrip', - yt AS 'rendered-as', - decimal_exp(yt,30) AS 'text-decimal', - decimal_exp(ieee754_from_int(s),30) AS 'float-decimal' - FROM fp2 - WHERE ieee754_to_int(CAST(yt AS real))<>s; -} {} -# ^---- Values that fail to round-trip will be reported +# that fail to preserve at least 15 significant digits of accuracy. finish_test diff --git a/test/fptest01.sql b/test/fptest01.sql deleted file mode 100644 index 6221760cf..000000000 --- a/test/fptest01.sql +++ /dev/null @@ -1,76 +0,0 @@ -#!sqlite3 -# -# 2026-03-01 -# -# 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. -# -#*********************************************************************** -# -# Floating-point to text conversions -# - -# Verify that binary64 -> text -> binary64 conversions round-trip -# successfully for 98,256 different edge-case binary64 values. The -# query result is all cases that do not round-trip without change, -# and so the query result should be an empty set. -# -.testcase 100 -.mode list -WITH - i1(i) AS (VALUES(0) UNION ALL SELECT i+1 FROM i1 WHERE i<15), - i2(j) AS (VALUES(0) UNION ALL SELECT j+1 FROM i2 WHERE j<0x7fe), - i3(k) AS (VALUES(0x0000000000000000), - (0x000ffffffffffff0), - (0x0008080808080800)), - fpint(n) AS (SELECT (j<<52)+i+k FROM i2, i1, i3), - fp(n,r) AS (SELECT n, ieee754_from_int(n) FROM fpint) -SELECT n, r FROM fp WHERE r<>(0.0 + (r||'')); -.check '' - -# Another batch of 106,444 edge cases: All postiive floating point -# values that have only a single bit set in the mantissa part of the -# number. -# -.testcase 110 -WITH - i1(i) AS (VALUES(0) UNION ALL SELECT i+1 FROM i1 WHERE i<51), - i2(j) AS (VALUES(0) UNION ALL SELECT j+1 FROM i2 WHERE j<0x7fe), - fpint(n) AS (SELECT (j<<52)+(1<<i) FROM i2, i1), - fp(n,r) AS (SELECT n, ieee754_from_int(n) FROM fpint) -SELECT n, r FROM fp WHERE r<>(0.0 + (r||'')); -.check '' - -# Verify that text -> binary64 conversions agree with system strtod(). -# for 98,256 different edge-cases. -# -.testcase 200 -.mode list -WITH - i1(i) AS (VALUES(0) UNION ALL SELECT i+1 FROM i1 WHERE i<15), - i2(j) AS (VALUES(0) UNION ALL SELECT j+1 FROM i2 WHERE j<0x7fe), - i3(k) AS (VALUES(0x0000000000000000), - (0x000ffffffffffff0), - (0x0008080808080800)), - fpint(n) AS (SELECT (j<<52)+i+k FROM i2, i1, i3), - fp(r) AS (SELECT ieee754_from_int(n) FROM fpint) -SELECT r FROM fp WHERE r<>strtod(r||''); -.check '' - - -# Another batch of 106,444 edge cases: All postiive floating point -# values that have only a single bit set in the mantissa part of the -# number. -# -.testcase 210 -WITH - i1(i) AS (VALUES(0) UNION ALL SELECT i+1 FROM i1 WHERE i<51), - i2(j) AS (VALUES(0) UNION ALL SELECT j+1 FROM i2 WHERE j<0x7fe), - fpint(n) AS (SELECT (j<<52)+(1<<i) FROM i2, i1), - fp(r) AS (SELECT ieee754_from_int(n) FROM fpint) -SELECT r FROM fp WHERE r<>strtod(r||''); -.check '' diff --git a/test/fts3comp1.test b/test/fts3comp1.test index b5077a355..9f13aaa64 100644 --- a/test/fts3comp1.test +++ b/test/fts3comp1.test @@ -112,81 +112,4 @@ do_catchsql_test 2.2 { CREATE VIRTUAL TABLE t2 USING fts4(x, uncompress=unzip) } {1 {missing compress parameter in fts4 constructor}} -#-------------------------------------------------------------------------- -reset_db -do_execsql_test 3.0 { - PRAGMA trusted_schema = OFF; -} - -set ::myfunc_invoked 0 -proc myfunc {data} { - incr ::myfunc_invoked - return $data -} -db func myfunc myfunc - -do_execsql_test 3.1 { - CREATE VIEW v1 AS SELECT myfunc('xyz'); -} - -do_catchsql_test 3.2 { - SELECT * FROM v1 -} {1 {unsafe use of myfunc()}} - -do_execsql_test 3.3 { - CREATE VIRTUAL TABLE f1 USING fts4(x, compress=myfunc, uncompress=myfunc); -} - -do_catchsql_test 3.4 { - INSERT INTO f1(rowid, x) VALUES(123, 'one two three'); -} {1 {SQL logic error}} - -do_test 3.5 { - set ::myfunc_invoked -} {0} - -do_execsql_test 3.6.1 { - CREATE TABLE t1(x); - CREATE TABLE t2(y); - - CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN - INSERT INTO t2 VALUES( myfunc(new.x) ); - END; -} - -do_catchsql_test 3.6.2 { - INSERT INTO t1 VALUES('hello world'); -} {1 {unsafe use of myfunc()}} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 4.0 { - CREATE VIRTUAL TABLE v1 USING fts4(x, compress=comp, uncompress=uncomp); -} - -proc comp {data} { return $data } -proc uncomp {data} { return $data } - -db func comp comp -db func uncomp uncomp - -do_catchsql_test 4.1 { - INSERT INTO v1 VALUES('one two three'); -} {0 {}} - -db close -sqlite3 db test.db -db func comp -directonly comp - -do_catchsql_test 4.2 { - INSERT INTO v1 VALUES('one two three'); -} {1 {SQL logic error}} - -db func uncomp -directonly uncomp - -do_catchsql_test 4.3 { - SELECT * FROM v1 -} {1 {SQL logic error}} - - finish_test diff --git a/test/fts4content.test b/test/fts4content.test index 8268e734a..980586ea3 100644 --- a/test/fts4content.test +++ b/test/fts4content.test @@ -638,6 +638,7 @@ do_catchsql_test 11.1 { # Check that an fts4 table cannot be its own content table. # reset_db +breakpoint do_execsql_test 12.1.1 { CREATE VIRTUAL TABLE t1 USING fts4(a, content=t1 ); INSERT INTO t1(rowid, a) VALUES(1, 'abc'); @@ -668,64 +669,6 @@ do_catchsql_test 12.2.4 { SELECT count(*) FROM t1; } {1 {SQL logic error}} -#--------------------------------------------------------------------------- -# Check that an fts4 table cannot read from an unsafe vtab in a non-trusted -# schema. -reset_db -do_execsql_test 13.0 { - PRAGMA trusted_schema = off; - CREATE VIRTUAL TABLE t1 USING fts4(data, content=sqlite_dbpage); -} - -do_catchsql_test 13.1 { - INSERT INTO t1(t1) VALUES('rebuild'); -} {1 {SQL logic error}} - -proc vtab_command {method args} { - switch -- $method { - xConnect { - return "CREATE TABLE t1(a)" - } - - xBestIndex { - return "" - } - - xFilter { - return [list sql {SELECT 1, 123}] - } - - xUpdate { - return 123 - } - } - - return {} -} - -register_tcl_module db xyz - -do_execsql_test 13.2.0 { - CREATE VIRTUAL TABLE aa USING tcl(vtab_command); -} - -do_execsql_test 13.2.1 { - INSERT INTO aa VALUES('one two three'); -} - -do_test 13.2.2 { - set ::stmt [sqlite3_prepare_v3 db \ - "INSERT INTO aa VALUES('one two three');" -1 0x00 - ] - sqlite3_finalize $::stmt -} {SQLITE_OK} -do_test 13.2.2 { - list [catch { - set ::stmt [sqlite3_prepare_v3 db \ - "INSERT INTO aa VALUES('one two three');" -1 0x20 - ] - } msg] $msg -} {1 {(1) unsafe use of virtual table "aa"}} finish_test diff --git a/test/fts4merge5.test b/test/fts4merge5.test index 90870ca34..1fad778b9 100644 --- a/test/fts4merge5.test +++ b/test/fts4merge5.test @@ -53,9 +53,6 @@ for {set tn 1} {1} {incr tn} { } } -do_catchsql_test 1.5 { - INSERT INTO x1(x1) VALUES('maxpendinAB64'); -} {1 {SQL logic error}} finish_test diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index f056d2d93..a3377770a 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -1954,7 +1954,6 @@ int main(int argc, char **argv){ char **azSrcDb = 0; /* Array of source database names */ int iSrcDb; /* Loop over all source databases */ int nTest = 0; /* Total number of tests performed */ - int nSliceSkip = 0; /* Skipped due to --slice */ char *zDbName = ""; /* Appreviated name of a source database */ const char *zFailCode = 0; /* Value of the TEST_FAILURE env variable */ int cellSzCkFlag = 0; /* --cell-size-check */ @@ -2521,7 +2520,6 @@ int main(int argc, char **argv){ if( isDbSql(pSql->a, pSql->sz) ){ if( iSliceSz>0 && (nTest%iSliceSz)!=iSliceIdx ){ nTest++; - nSliceSkip++; continue; } sqlite3_snprintf(sizeof(g.zTestName), g.zTestName, "sqlid=%d",pSql->id); @@ -2584,7 +2582,6 @@ int main(int argc, char **argv){ const char *zVfs = "inmem"; if( iSliceSz>0 && (nTest%iSliceSz)!=iSliceIdx ){ nTest++; - nSliceSkip++; continue; } sqlite3_snprintf(sizeof(g.zTestName), g.zTestName, "sqlid=%d,dbid=%d", @@ -2696,13 +2693,12 @@ int main(int argc, char **argv){ if( briefFlag ){ sqlite3_int64 iElapse = timeOfDay() - iBegin; if( iSliceSz>0 ){ - printf( - "%s %s: 0 errors out of %d tests, slice %d/%d, %d.%03d seconds\n", - pathTail(argv[0]), pathTail(g.zDbFile), - nTest - nSliceSkip, iSliceIdx, iSliceSz, - (int)(iElapse/1000), (int)(iElapse%1000)); + printf("%s %s: slice %d/%d of %d tests, %d.%03d seconds\n", + pathTail(argv[0]), pathTail(g.zDbFile), + iSliceIdx, iSliceSz, nTest, + (int)(iElapse/1000), (int)(iElapse%1000)); }else{ - printf("%s %s: 0 errors out of %d tests, %d.%03d seconds\n", + printf("%s %s: 0 errors, %d tests, %d.%03d seconds\n", pathTail(argv[0]), pathTail(g.zDbFile), nTest, (int)(iElapse/1000), (int)(iElapse%1000)); } diff --git a/test/fuzzinvariants.c b/test/fuzzinvariants.c index 9341b2056..6a5cfda68 100644 --- a/test/fuzzinvariants.c +++ b/test/fuzzinvariants.c @@ -261,12 +261,9 @@ int fuzz_invariant( sqlite3_finalize(pCk); /* Invariants do not necessarily work if there are virtual tables - ** or scalar subqueries involved in the query */ - rc = sqlite3_prepare_v2(db, - "SELECT 1 FROM bytecode(?1)" - " WHERE opcode='VOpen' OR" - " (opcode='Explain' AND p4 GLOB 'SCALAR SUBQUERY*')", - -1, &pCk, 0); + ** involved in the query */ + rc = sqlite3_prepare_v2(db, + "SELECT 1 FROM bytecode(?1) WHERE opcode='VOpen'", -1, &pCk, 0); if( rc==SQLITE_OK ){ if( eVerbosity>=2 ){ char *zSql = sqlite3_expanded_sql(pCk); diff --git a/test/import01.sql b/test/import01.sql deleted file mode 100644 index 6e029b8b2..000000000 --- a/test/import01.sql +++ /dev/null @@ -1,217 +0,0 @@ -#!sqlite3 -# -# 2025-12-28 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Test cases for the ".import" command of the CLI. -# To run these tests: -# -# ./sqlite3 test/import01.sql -# - -.testcase setup -.open :memory: -.mode tty -.check '' - -.testcase 100 -CREATE TABLE t1(a,b,c); -.import -csv <<END t1 -111,222,333 -abc,def,ghi -END -SELECT * FROM t1; -.check <<END -╭───────┬───────┬───────╮ -│ a │ b │ c │ -╞═══════╪═══════╪═══════╡ -│ '111' │ '222' │ '333' │ -│ abc │ def │ ghi │ -╰───────┴───────┴───────╯ -END - -.testcase 110 -DELETE FROM t1; -.import -colsep ";" <<END t1 -this;is a;test -one;two;three -END -SELECT * FROM t1; -.check <<END -╭──────┬──────┬───────╮ -│ a │ b │ c │ -╞══════╪══════╪═══════╡ -│ this │ is a │ test │ -│ one │ two │ three │ -╰──────┴──────┴───────╯ -END - -.testcase 120 -DELETE FROM t1; -.import -colsep "," -rowsep ';' <<END t1 -this,is a,test;one,two,three; -END -SELECT * FROM t1; -.check <<END -╭──────┬──────┬───────╮ -│ a │ b │ c │ -╞══════╪══════╪═══════╡ -│ this │ is a │ test │ -│ one │ two │ three │ -╰──────┴──────┴───────╯ -END - -.testcase 130 -DELETE FROM t1; -.import -csv -colsep "," -rowsep "\n" <<END t1 -this,"is a","test ""with quotes""" -"second",,"line" -END -SELECT * FROM t1; -.check <<END -╭────────┬──────┬────────────────────╮ -│ a │ b │ c │ -╞════════╪══════╪════════════════════╡ -│ this │ is a │ test "with quotes" │ -│ second │ │ line │ -╰────────┴──────┴────────────────────╯ -END -.testcase 131 -DELETE FROM t1; -.import -ascii -colsep "," -rowsep "\n" <<END t1 -this,"is a","test ""with quotes""" -"second",,"line" -END -SELECT * FROM t1; -.check <<END -╭──────────┬────────┬────────────────────────╮ -│ a │ b │ c │ -╞══════════╪════════╪════════════════════════╡ -│ this │ "is a" │ "test ""with quotes""" │ -│ "second" │ │ "line" │ -╰──────────┴────────┴────────────────────────╯ -END - -.testcase 140 -DROP TABLE t1; -.import -csv <<END t1 -"abc","def","xy z" -"This","is","a" -"test","...", -END -SELECT * FROM t1; -.check <<END -╭──────┬─────┬──────╮ -│ abc │ def │ xy z │ -╞══════╪═════╪══════╡ -│ This │ is │ a │ -│ test │ ... │ │ -╰──────┴─────┴──────╯ -END -.testcase 141 -SELECT sql FROM sqlite_schema WHERE name='t1'; -.check <<END -╭───────────────────────────────────╮ -│ sql │ -╞═══════════════════════════════════╡ -│ CREATE TABLE "t1"( │ -│ "abc" ANY, "def" ANY, "xy z" ANY) │ -╰───────────────────────────────────╯ -END - -.testcase 150 -DROP TABLE t1; -.import -csv -v <<END t1 -"abc","def","xy z" -"This","is","a" -"test","...", -END -SELECT * FROM t1; -.check <<END -Column separator ",", row separator "\n" -CREATE TABLE "main"."t1"( -"abc" ANY, "def" ANY, "xy z" ANY) - -Added 2 rows with 0 errors using 3 lines of input -╭──────┬─────┬──────╮ -│ abc │ def │ xy z │ -╞══════╪═════╪══════╡ -│ This │ is │ a │ -│ test │ ... │ │ -╰──────┴─────┴──────╯ -END - -.testcase 160 -DROP TABLE t1; -.import -csv -schema TEMP <<END t2 -"x" -"abcdef" -END -SELECT * FROM t2; -.tables -.check <<END -╭────────╮ -│ x │ -╞════════╡ -│ abcdef │ -╰────────╯ -temp.t2 -END - -.testcase 170 -.import -csv -skip 2 <<END t3 -a,b,c -d,e,f -g,h,i -j,k,l -m,n,o -END -SELECT * FROM t3; -.check <<END -╭───┬───┬───╮ -│ g │ h │ i │ -╞═══╪═══╪═══╡ -│ j │ k │ l │ -│ m │ n │ o │ -╰───┴───┴───╯ -END - -.testcase 180 -DELETE FROM t3; -.import -csv -skip 7 <<END t3 -a,b,c -d,e,f -g,h,i -j,k,l -m,n,o -END -SELECT * FROM t3; -.check <<END -END - -.testcase 200 --error-prefix ERROR: -.import -csv -.check 'ERROR: Missing FILE argument' -.testcase 201 -.import -csv file1.csv -.check 'ERROR: Missing TABLE argument' -.testcase --error-prefix test-error: 202 -.import -csvxyzzy file1.csv -.check <<END -test-error: .import -csvxyzzy file1.csv -test-error: ^--- unknown option -END -.testcase 203 -.import -csv file1.csv t4 -colsep -.check <<END -test-error: .import -csv file1.csv t4 -colsep -test-error: missing argument ---^ -END diff --git a/test/imposter1.sql b/test/imposter1.sql deleted file mode 100644 index 9604b43f4..000000000 --- a/test/imposter1.sql +++ /dev/null @@ -1,32 +0,0 @@ -#!sqlite3 -# -# 2025-12-16 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Test cases for the .imposter command. -# -.mode box -reset -.testcase 100 -CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c INT); -INSERT INTO t1 VALUES(1,'two',3),(4,'five',6); -CREATE INDEX t1bc ON t1(b,c); -.imposter T1BC x1 -----------^^^^--- Different case that the original -SELECT * FROM x1; -.check <<END -CREATE TABLE "x1"("b","c","_ROWID_",PRIMARY KEY("b","c","_ROWID_"))WITHOUT ROWID; -╭──────┬───┬─────────╮ -│ b │ c │ _ROWID_ │ -╞══════╪═══╪═════════╡ -│ five │ 6 │ 4 │ -│ two │ 3 │ 1 │ -╰──────┴───┴─────────╯ -END diff --git a/test/insert5.test b/test/insert5.test index 3ae99a5d2..1e58902e0 100644 --- a/test/insert5.test +++ b/test/insert5.test @@ -96,19 +96,22 @@ do_test insert5-2.8 { } } {1} +# UPDATE: Using a column from the outer query (main.id) in the GROUP BY +# or ORDER BY of a sub-query is no longer supported. +# +# do_test insert5-2.9 { +# uses_temp_table { +# INSERT INTO b +# SELECT * FROM main +# WHERE id > 10 AND (SELECT count(*) FROM v2 GROUP BY main.id) +# } +# } {} do_test insert5-2.9 { catchsql { INSERT INTO b SELECT * FROM main WHERE id > 10 AND (SELECT count(*) FROM v2 GROUP BY main.id) } -} {0 {}} -do_execsql_test insert5-2.10 { - CREATE TABLE t1(a INT); - INSERT INTO t1 VALUES(2); - CREATE TABLE t2(c INT, d INT); - INSERT INTO t2 VALUES(3,4),(10,NULL); - SELECT (SELECT c FROM t2 ORDER BY coalesce(d,a) LIMIT 1) FROM t1; -} {10} +} {1 {no such column: main.id}} finish_test diff --git a/test/intck01.sql b/test/intck01.sql deleted file mode 100644 index b1996aeeb..000000000 --- a/test/intck01.sql +++ /dev/null @@ -1,23 +0,0 @@ -#!sqlite3 -# -# 2026-03-01 -# -# 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. -# -#*********************************************************************** -# -# Bug report sqlite.org/forum/forumpost/efc9bc9cb3 -# -.testcase 100 -.mode quote -.intck 1 -SELECT parse_create_index('CREATE IDEX i ON t("x',0); -.check <<END -1 steps, 0 errors -NULL -END diff --git a/test/join.test b/test/join.test index a1ce7da0c..b33a7560a 100644 --- a/test/join.test +++ b/test/join.test @@ -1369,45 +1369,4 @@ do_execsql_test join-32.3 { INNER JOIN t2 ON +y IS z; } {NULL NULL 123 NULL} -# 2025-12-24 https://sqlite.org/forum/forumpost/11a53f2bad -# -# Chained omit-noop-join optimization -# -reset_db -db null NULL -do_execsql_test join-33.1 { - CREATE TABLE t1(a1 INTEGER PRIMARY KEY, b1); - CREATE TABLE t2(a2 INTEGER PRIMARY KEY, b2); - CREATE TABLE t3(a3 INTEGER PRIMARY KEY, b3); - CREATE TABLE t4(a4 INTEGER PRIMARY KEY, b4); - INSERT INTO t1 VALUES(1,11),(2,12),(3,13), (5,15); - INSERT INTO t2 VALUES(1,21), (3,23),(4,24),(5,25); - INSERT INTO t3 VALUES (2,32),(3,33), (5,35); - INSERT INTO t4 VALUES(1,41),(2,42), (4,44),(5,45); - CREATE VIEW vchain AS - SELECT a1, b1, b2, b3, b4 - FROM t1 LEFT JOIN t2 ON a1=a2 - LEFT JOIN t3 ON a2=a3 - LEFT JOIN t4 ON a3=a4; -} -do_execsql_test join-33.2 { - SELECT a1 FROM vchain ORDER BY a1; -} {1 2 3 5} -do_eqp_test join-33.2-eqp { - SELECT a1 FROM vchain ORDER BY a1; -} { - QUERY PLAN - `--SCAN t1 -} -do_execsql_test join-33.3 { - SELECT a1, b2 FROM vchain ORDER BY a1; -} {1 21 2 NULL 3 23 5 25} -do_eqp_test join-33.3-eqp { - SELECT a1, b2 FROM vchain ORDER BY a1; -} { - QUERY PLAN - |--SCAN t1 - `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN -} - finish_test diff --git a/test/joinI.test b/test/joinI.test index 3390afc6e..d0194579d 100644 --- a/test/joinI.test +++ b/test/joinI.test @@ -168,5 +168,48 @@ do_catchsql_test 6.8 { ) FROM t5; } {1 {ON clause references tables to its right}} +#------------------------------------------------------------------------- +# Forum post: https://sqlite.org/forum/forumpost/e3dba5426a +# +reset_db +do_execsql_test 7.0 { + CREATE TABLE t1(a); + INSERT INTO t1 VALUES(1); + CREATE TABLE t2(d, e); + CREATE INDEX t2def ON t2(d, (e+1)); + INSERT INTO t2 VALUES(1, 3); + INSERT INTO t2 VALUES(2, 555); + INSERT INTO t2 VALUES(3, 3); +} +do_execsql_test 7.1 { + SELECT * FROM t1 RIGHT JOIN t2 ON ( a=d ) WHERE (t2.e+1)!=4; +} {{} 2 555} + +reset_db +do_execsql_test 7.2 { + CREATE TABLE rt0(c0 INTEGER PRIMARY KEY, c1, c2); + CREATE TABLE t1 (c1 INTEGER, c2 BLOB, c4 INTEGER); + CREATE UNIQUE INDEX i81 ON t1(c1, c4, +c2); + + INSERT INTO t1(c4) VALUES ('a'); + INSERT INTO rt0(c1, c2) VALUES (0.0, 0.0); + INSERT INTO t1(c2, c1) VALUES (1, 'b'); +} +do_execsql_test 7.2 { + SELECT count(*) FROM ( + SELECT DISTINCT * FROM rt0 FULL JOIN t1 ON rt0.c0=t1.c1 + WHERE ((rt0.c1 OR t1.c4)) IS NOT (+ t1.c2) + ); +} {1} +do_execsql_test 7.3 { + SELECT count(*) FROM ( + SELECT DISTINCT * FROM rt0 LEFT JOIN t1 ON rt0.c0=t1.c1 + WHERE ((rt0.c1 OR t1.c4)) IS NOT (+ t1.c2) + UNION + SELECT DISTINCT * FROM rt0 RIGHT JOIN t1 ON rt0.c0=t1.c1 + WHERE ((rt0.c1 OR t1.c4)) IS NOT (+ t1.c2) + ); +} {1} + finish_test diff --git a/test/json102.test b/test/json102.test index 54f66a83f..54a0e1e0e 100644 --- a/test/json102.test +++ b/test/json102.test @@ -375,27 +375,6 @@ do_execsql_test json102-440-3 { do_execsql_test json102-440-4 { SELECT json(jsonb_remove(jsonb('[0,1,2,3,4]'),'$[2]')); } {{[0,1,3,4]}} -do_execsql_test json102-445-1 { - SELECT json_remove('[0,1,2,3,4]','$[5]'); -} {{[0,1,2,3,4]}} -do_execsql_test json102-445-2 { - SELECT json_remove('[0,1,2,3,4]','$[6]'); -} {{[0,1,2,3,4]}} -do_execsql_test json102-445-3 { - SELECT json_remove('[0,1,2,3,4]','$[4294967295]'); -} {{[0,1,2,3,4]}} -do_execsql_test json102-445-4 { - SELECT json_remove('[0,1,2,3,4]','$[4294967296]'); -} {{[0,1,2,3,4]}} -do_execsql_test json102-445-5 { - SELECT json_remove('[0,1,2,3,4]','$[4294967297]'); -} {{[0,1,2,3,4]}} -do_execsql_test json102-445-6 { - SELECT json_remove('[0,1,2,3,4]','$[42949672950]'); -} {{[0,1,2,3,4]}} -do_execsql_test json102-445-7 { - SELECT json_remove('[0,1,2,3,4]','$[42949672960]'); -} {{[0,1,2,3,4]}} do_execsql_test json102-450 { SELECT json_remove('[0,1,2,3,4]','$[2]','$[0]'); } {{[1,3,4]}} diff --git a/test/json103.test b/test/json103.test index 9eadf29f8..f94217ac1 100644 --- a/test/json103.test +++ b/test/json103.test @@ -27,9 +27,6 @@ do_execsql_test json103-100 { do_catchsql_test json103-101 { SELECT json_group_array(a) FROM t1; } {1 {JSON cannot hold BLOB values}} -do_execsql_test json103-102 { - SELECT quote(jsonb_group_array(a)) FROM t1 WHERE a<0 AND typeof(a)!='blob'; -} {X'0B'} do_execsql_test json103-110 { SELECT json_group_array(a) FROM t1 WHERE rowid BETWEEN 31 AND 39; @@ -48,10 +45,6 @@ do_execsql_test json103-200 { do_catchsql_test json103-201 { SELECT json_group_object(c,a) FROM t1; } {1 {JSON cannot hold BLOB values}} -do_execsql_test json103-202 { - SELECT quote(jsonb_group_object(c,a)) FROM t1 WHERE a<0 AND typeof(a)!='blob'; -} {X'0C'} - do_execsql_test json103-210 { SELECT json_group_object(c,a) FROM t1 diff --git a/test/json105.test b/test/json105.test index 4a5572cf0..509db94e1 100644 --- a/test/json105.test +++ b/test/json105.test @@ -29,11 +29,6 @@ json_extract_test 30 {'$.b[#-2]'} {'[2,3]'} json_extract_test 31 {'$.b[#-02]'} {'[2,3]'} json_extract_test 40 {'$.b[#-3]'} 1 json_extract_test 50 {'$.b[#-4]'} NULL -json_extract_test 51 {'$.b[#-4296967295]'} NULL -json_extract_test 52 {'$.b[#-4296967296]'} NULL -json_extract_test 53 {'$.b[#-4296967297]'} NULL -json_extract_test 54 {'$.b[#-42969672950]'} NULL -json_extract_test 55 {'$.b[#-42969672960]'} NULL json_extract_test 60 {'$.b[#-2][#-1]'} 3 json_extract_test 70 {'$.b[0]','$.b[#-1]'} {'[1,4]'} diff --git a/test/json109.test b/test/json109.test deleted file mode 100644 index 1631a1f0f..000000000 --- a/test/json109.test +++ /dev/null @@ -1,72 +0,0 @@ -# 2026-01-17 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Test cases for json_array_insert(). -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix json109 - -do_execsql_test 1.1 { - SELECT json_array_insert('[1,2,3]','$[0]',999,'$[0]',888); -} {{[888,999,1,2,3]}} -do_execsql_test 1.2 { - SELECT json_array_insert('[1,2,3]','$[0]',999,'$[#]',888); -} {{[999,1,2,3,888]}} -do_execsql_test 1.3 { - SELECT json_array_insert('[1,2,3]','$[1]',888); -} {{[1,888,2,3]}} -do_execsql_test 1.4 { - SELECT json_array_insert('[1,2,3]','$[2]',888); -} {{[1,2,888,3]}} -do_execsql_test 1.5 { - SELECT json_array_insert('[1,2,3]','$[3]',888); -} {{[1,2,3,888]}} -do_execsql_test 1.6 { - SELECT json_array_insert('[1,2,3]','$[#-1]',888); -} {{[1,2,888,3]}} -do_execsql_test 1.7 { - SELECT json_array_insert('[1,2,3]','$[#-2]',888); -} {{[1,888,2,3]}} -do_execsql_test 1.8 { - SELECT json_array_insert('[1,2,3]','$[#-3]',888); -} {{[888,1,2,3]}} -do_execsql_test 1.9 { - SELECT json_array_insert('[1,2,3]','$[#-4]',888); -} {{[1,2,3]}} - -do_catchsql_test 2.1 { - SELECT json_array_insert('{a:[1,2,3]}','$.a',888); -} {1 {not an array element: '$.a'}} -do_catchsql_test 2.2 { - SELECT json_array_insert('{a:[1,2,3]}','$.b',888); -} {1 {not an array element: '$.b'}} -do_catchsql_test 2.3 { - SELECT json_array_insert('{a:[1,2,3]}','$.b[0]',888); -} {0 {{{"a":[1,2,3],"b":[888]}}}} -do_catchsql_test 2.4 { - SELECT json_array_insert('{a:[1,2,3]}','$.b.c.d[0]',888); -} {0 {{{"a":[1,2,3],"b":{"c":{"d":[888]}}}}}} -do_catchsql_test 2.5 { - SELECT json_array_insert('{a:[1,2,3]}','$.b.c.d[0',888); -} {1 {not an array element: '$.b.c.d[0'}} -do_catchsql_test 2.6 { - SELECT json_array_insert('{a:[1,2,3]}','$.b.c.d',888); -} {1 {not an array element: '$.b.c.d'}} -do_catchsql_test 2.7 { - SELECT json_array_insert('{a:[1,2,3]}','$[0]',888); -} {0 {{{"a":[1,2,3]}}}} -do_catchsql_test 2.8 { - SELECT json_array_insert('{a:[1,2,3]}','$.b[0]',888,'$.a[1]','999','$.c',0); -} {1 {not an array element: '$.c'}} - -finish_test diff --git a/test/misc5.test b/test/misc5.test index 80b8d3c67..43ee2781a 100644 --- a/test/misc5.test +++ b/test/misc5.test @@ -595,7 +595,7 @@ do_test misc5-7.1.2 { } append sql "0$tail); SELECT * FROM t1;" catchsql $sql -} {1 {Recursion limit}} +} {0 900} # Parser stack overflow is silently ignored when it occurs while parsing the diff --git a/test/modeA.sql b/test/modeA.sql deleted file mode 100644 index 4e62093b2..000000000 --- a/test/modeA.sql +++ /dev/null @@ -1,303 +0,0 @@ -#!sqlite3 -# -# 2025-11-12 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Test cases for the ".mode" command of the CLI. -# To run these tests: -# -# ./sqlite3 test/modeA.sql -# -# -.open :memory: -CREATE TABLE t1(a,b,c,d,e); -INSERT INTO t1 VALUES(1,2.5,'three',x'4444',NULL); -INSERT INTO t1 SELECT b,c,d,e,a FROM t1; -INSERT INTO t1 SELECT d,e,a,b,c FROM t1; -.mode box - -.testcase 100 -SELECT * FROM t1; -.check <<END -╭─────┬───────┬───────┬───────┬───────╮ -│ a │ b │ c │ d │ e │ -╞═════╪═══════╪═══════╪═══════╪═══════╡ -│ 1 │ 2.5 │ three │ DD │ │ -│ 2.5 │ three │ DD │ │ 1 │ -│ DD │ │ 1 │ 2.5 │ three │ -│ │ 1 │ 2.5 │ three │ DD │ -╰─────┴───────┴───────┴───────┴───────╯ -END - -.testcase 110 -.mode --null xyz -SELECT * FROM t1; -.check <<END -╭─────┬───────┬───────┬───────┬───────╮ -│ a │ b │ c │ d │ e │ -╞═════╪═══════╪═══════╪═══════╪═══════╡ -│ 1 │ 2.5 │ three │ DD │ xyz │ -│ 2.5 │ three │ DD │ xyz │ 1 │ -│ DD │ xyz │ 1 │ 2.5 │ three │ -│ xyz │ 1 │ 2.5 │ three │ DD │ -╰─────┴───────┴───────┴───────┴───────╯ -END - -# Default output mode is qbox --quote relaxed -# -.mode tty --wrap 10 -CREATE TABLE t2(a,b,c,d); -INSERT INTO t2 VALUES(1,2.5,'three',x'4444'); -INSERT INTO t2 VALUES('The quick fox jumps over the lazy brown dog',2,3,4); -INSERT INTO t2 VALUES('10','', -1.25,NULL); -INSERT INTO t2 VALUES('a,b,c','"Double-Quoted"','-1.25','NULL'); -.testcase 120 -SELECT * FROM t2; -.check <<END -╭────────────┬────────────┬─────────┬─────────╮ -│ a │ b │ c │ d │ -╞════════════╪════════════╪═════════╪═════════╡ -│ 1 │ 2.5 │ three │ x'4444' │ -├────────────┼────────────┼─────────┼─────────┤ -│ The quick │ 2 │ 3 │ 4 │ -│ fox jumps │ │ │ │ -│ over the │ │ │ │ -│ lazy brown │ │ │ │ -│ dog │ │ │ │ -├────────────┼────────────┼─────────┼─────────┤ -│ '10' │ │ -1.25 │ NULL │ -├────────────┼────────────┼─────────┼─────────┤ -│ a,b,c │ "Double- │ '-1.25' │ 'NULL' │ -│ │ Quoted" │ │ │ -╰────────────┴────────────┴─────────┴─────────╯ -END -.testcase 130 -.mode -.check <<END -.mode qbox --limits on --quote relaxed --sw auto --textjsonb on -END -.testcase 140 -.mode -v -.check <<END -.mode qbox --align "" --border on --blob-quote auto --colsep "" --escape auto --limits on --null "NULL" --quote relaxed --rowsep "" --sw auto --tablename "" --textjsonb on --titles on --widths "" --wordwrap off --wrap 10 -END -.testcase 150 --error-prefix "Error:" -.mode foo -.check <<END -Error: .mode foo -Error: ^--- unknown mode -Error: Use ".help .mode" for more info -END - -.testcase 160 -.mode --null xyzzy -v -.output -glob ' --null "xyzzy"' -.testcase 170 -.mode -null abcde -v -.output -glob ' --null "abcde"' - -# Test cases for the ".explain off" command -.mode box -reset -.testcase 180 -EXPLAIN SELECT * FROM t1; -.output --notglob *────* --keep -.output --notglob "* id │ parent │ notused │ detail *" --keep -.output --glob "* Init *" -.testcase 190 -EXPLAIN QUERY PLAN SELECT * FROM t1; -.output --glob "*`--SCAN *" -.explain off -.testcase 200 -EXPLAIN SELECT * FROM t1; -.output --glob *────* -.testcase 210 -EXPLAIN QUERY PLAN SELECT * FROM t1; -.output --glob "* id │ parent │ notused │ detail *" -.explain auto - -# Test cases for limit settings in the .mode command. -.testcase 300 -.mode box --reset -.mode -.check <<END -.mode box -END -.testcase 310 -.mode --limits 5,300,20 -.mode -.check <<END -.mode box --limits on -END -.testcase 320 -.mode --limits 5,300,19 -.mode -.check <<END -.mode box --limits 5,300,19 -END -.testcase 330 -.mode --limits 0,0,0 -.mode -v -.check <<END -.mode box --align "" --border on --blob-quote auto --colsep "" --escape auto --limits off --null "" --quote off --rowsep "" --sw 0 --tablename "" --textjsonb off --titles on --widths "" --wordwrap off -END - -.testcase 400 -.mode --linelimit 123 -.mode -.check <<END -.mode box --limits 123,0,0 -END - -.testcase 410 -.mode --linelimit 0 -charlimit 123 -.mode -.check <<END -.mode box --limits 0,123,0 -END - -.testcase 420 -.mode --charlimit 0 -titlelimit 123 -.mode -.check <<END -.mode box --limits 0,0,123 -END - -.testcase 430 -.mode list -.mode -.check <<END -.mode list -END - -.testcase 440 -.mode -limits 0,123,0 -.mode -.check <<END -.mode list --limits 0,123,0 -END - -.testcase 450 -.mode -limits 123,0,0 -.mode -.check <<END -.mode list -END - -# --titlelimit functionality -# -.testcase 500 -.mode line --limits off --titlelimit 20 -SELECT a AS 'abcdefghijklmnopqrstuvwxyz', b FROM t2 WHERE c=3; -.check <<END -abcdefghijklmnopq...: The quick fox jumps over the lazy brown dog - b: 2 -END -.testcase 510 -.mode line --titlelimit 10 -SELECT a AS 'abcdefghijklmnopqrstuvwxyz', b FROM t2 WHERE c=3; -.check <<END -abcdefg...: The quick fox jumps over the lazy brown dog - b: 2 -END -.testcase 520 -.mode line --titlelimit 2 -SELECT a AS 'abcdefghijklmnopqrstuvwxyz', b FROM t2 WHERE c=3; -.check <<END -ab: The quick fox jumps over the lazy brown dog - b: 2 -END -.testcase 530 -.mode line --titlelimit 4 -SELECT a AS 'abcd', b FROM t2 WHERE c=3; -.check <<END -abcd: The quick fox jumps over the lazy brown dog - b: 2 -END -.testcase 540 -.mode line --titlelimit 3 -SELECT a AS 'abcd', b FROM t2 WHERE c=3; -.check <<END -...: The quick fox jumps over the lazy brown dog - b: 2 -END - -# https://sqlite.org/forum/forumpost/2025-12-31T19:14:24z -# -# For legacy compatibility, ".header" settings are not changed -# by ".mode" unless the --title or --reset option is used on .mode. -# -.testcase 600 -DROP TABLE IF EXISTS t1; -CREATE TABLE t1(a,b,c); -INSERT INTO t1 VALUES(1,2,3); -.header on -.mode csv -SELECT * FROM t1; -.check --glob a,b,c* - -.testcase 610 -.mode csv -reset -SELECT * FROM t1; -.check 1,2,3 - -.testcase 620 -.mode tty -.mode csv -.header on -SELECT * FROM t1; -.check --glob a,b,c* - -.testcase 630 -.mode tty -.mode csv --title on -SELECT * FROM t1; -.check --glob a,b,c* -.testcase 631 -.mode tty -.mode csv --title off -SELECT * FROM t1; -.check 1,2,3 - -# Verification of claims about .insert mode in the climode.html -# documentation. -.testcase 700 -CREATE TABLE tbl1(one,two); -INSERT INTO tbl1 VALUES('hello!',10),('goodbye',20); -.mode insert new_table -SELECT * FROM tbl1; -.check <<END -INSERT INTO new_table VALUES('hello!',10); -INSERT INTO new_table VALUES('goodbye',20); -END -.testcase 710 -.mode insert new_table --titles on -SELECT * FROM tbl1; -.check <<END -INSERT INTO new_table(one,two) VALUES('hello!',10); -INSERT INTO new_table(one,two) VALUES('goodbye',20); -END -.testcase 720 -.mode insert new_table --titles off -SELECT * FROM tbl1; -.check <<END -INSERT INTO new_table VALUES('hello!',10); -INSERT INTO new_table VALUES('goodbye',20); -END - -# QRF reports an error if the string is too big. -# -.testcase 800 -.mode box -.limit length 1000 -WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<100) -SELECT hex(randomblob(100)) c; -.check -glob "*: string or blob too big" -.limit length 10000000 diff --git a/test/mutex1.test b/test/mutex1.test index de291f4c9..cb189a7a8 100644 --- a/test/mutex1.test +++ b/test/mutex1.test @@ -115,10 +115,6 @@ ifcapable threadsafe1&&shared_cache { } } { - ifcapable thread_misuse_warnings { - if {$mode ne "serialized"} continue - } - # For journal_mode=memory, the static_prng mutex is not required. This # is because the header of an in-memory journal does not contain # any random bytes, and so no call to sqlite3_randomness() is made. @@ -181,18 +177,16 @@ ifcapable threadsafe1&&shared_cache { # Open and use a connection in "nomutex" mode. Test that no recursive # mutexes are obtained. - ifcapable !thread_misuse_warnings { - do_test mutex1.3.1 { - catch {db close} - clear_mutex_counters - sqlite3 db test.db -nomutex 1 - execsql { SELECT * FROM abc } - } {1 2 3 1 2 3 1 2 3} - do_test mutex1.3.2 { - mutex_counters counters - set counters(recursive) - } {0} - } + do_test mutex1.3.1 { + catch {db close} + clear_mutex_counters + sqlite3 db test.db -nomutex 1 + execsql { SELECT * FROM abc } + } {1 2 3 1 2 3 1 2 3} + do_test mutex1.3.2 { + mutex_counters counters + set counters(recursive) + } {0} } # Test the sqlite3_db_mutex() function. diff --git a/test/notnull2.test b/test/notnull2.test index f49a13b56..67d7c26a8 100644 --- a/test/notnull2.test +++ b/test/notnull2.test @@ -66,7 +66,7 @@ do_vmstep_test 1.5.2 { SELECT count(*) FROM t2 WHERE EXISTS( SELECT 1 FROM t1 WHERE t1.a=450 AND t2.c IS NULL ) -} 5000 {0} +} 4000 {0} #------------------------------------------------------------------------- reset_db diff --git a/test/offset1.test b/test/offset1.test index fb68f0d02..5b04bd836 100644 --- a/test/offset1.test +++ b/test/offset1.test @@ -190,20 +190,6 @@ do_execsql_test offset1-2.0 { ORDER BY salary asc); } {} do_execsql_test offset1-2.1 { - SELECT * FROM v ORDER BY +id; -} { - 11 Diane London hr 70 - 12 Bob London hr 78 - 21 Emma London it 84 - 22 Grace Berlin it 90 - 23 Henry London it 104 - 24 Irene Berlin it 104 - 25 Frank Berlin it 120 - 31 Cindy Berlin sales 96 - 32 Dave London sales 96 - 33 Alice Berlin sales 100 -} -do_execsql_test offset1-2.2 { SELECT * FROM v LIMIT 5 OFFSET 2; } { 22 Grace Berlin it 90 @@ -212,19 +198,5 @@ do_execsql_test offset1-2.2 { 11 Diane London hr 70 33 Alice Berlin sales 100 } -do_execsql_test offset1-2.3 { - SELECT * FROM v LIMIT 3 OFFSET 6; -} { - 33 Alice Berlin sales 100 - 23 Henry London it 104 - 24 Irene Berlin it 104 -} -do_execsql_test offset1-2.4 { - SELECT * FROM v LIMIT 3 OFFSET 1; -} { - 32 Dave London sales 96 - 22 Grace Berlin it 90 - 21 Emma London it 84 -} finish_test diff --git a/test/qrf01.test b/test/qrf01.test deleted file mode 100644 index 3ae027957..000000000 --- a/test/qrf01.test +++ /dev/null @@ -1,1167 +0,0 @@ -# 2025-11-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. -# -#*********************************************************************** -# -# Test cases for the Query Result Formatter (QRF) -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix qrf01 - -do_execsql_test 1.0 { - CREATE TABLE t1(a, b, c); - INSERT INTO t1 VALUES(1,2.5,'three'),(x'424c4f42',NULL,'Ἀμήν'); -} - -do_test 1.10 { - set result "\n[db format {SELECT * FROM t1}]" -} { -╭──────┬─────┬───────╮ -│ a │ b │ c │ -╞══════╪═════╪═══════╡ -│ 1 │ 2.5 │ three │ -│ BLOB │ │ Ἀμήν │ -╰──────┴─────┴───────╯ -} -do_test 1.11a { - set result "\n[db format -title off {SELECT * FROM t1}]" -} { -╭──────┬─────┬───────╮ -│ 1 │ 2.5 │ three │ -│ BLOB │ │ Ἀμήν │ -╰──────┴─────┴───────╯ -} -do_test 1.11b { - set result "\n[db format -text sql {SELECT * FROM t1}]" -} { -╭─────────────┬─────┬─────────╮ -│ a │ b │ c │ -╞═════════════╪═════╪═════════╡ -│ 1 │ 2.5 │ 'three' │ -│ x'424c4f42' │ │ 'Ἀμήν' │ -╰─────────────┴─────┴─────────╯ -} -do_test 1.11c { - set result "\n[db format -text sql -border off {SELECT * FROM t1}]" -} { - a │ b │ c -═════════════╪═════╪═════════ - 1 │ 2.5 │ 'three' - x'424c4f42' │ │ 'Ἀμήν' -} -do_test 1.11d { - set result "\n[db format -text relaxed -blob sql -border off \ - {SELECT * FROM t1}]" -} { - a │ b │ c -═════════════╪═════╪═══════ - 1 │ 2.5 │ three - x'424c4f42' │ │ Ἀμήν -} -do_test 1.12 { - set result "\n[db format -text csv {SELECT * FROM t1}]" -} { -╭────────────────────┬─────┬────────╮ -│ a │ b │ c │ -╞════════════════════╪═════╪════════╡ -│ 1 │ 2.5 │ three │ -│ "\102\114\117\102" │ │ "Ἀμήν" │ -╰────────────────────┴─────┴────────╯ -} -do_test 1.13 { - set result "\n[db format -text csv -blob hex {SELECT * FROM t1}]" -} { -╭──────────┬─────┬────────╮ -│ a │ b │ c │ -╞══════════╪═════╪════════╡ -│ 1 │ 2.5 │ three │ -│ 424c4f42 │ │ "Ἀμήν" │ -╰──────────┴─────┴────────╯ -} -do_test 1.14 { - catch {db format -text unk -blob hex {SELECT * FROM t1}} res - set res -} {bad -text "unk": must be auto, csv, html, json, plain, relaxed, sql, or tcl} -do_test 1.15 { - catch {db format -text sql -blob unk {SELECT * FROM t1}} res - set res -} {bad BLOB encoding (-blob) "unk": must be auto, hex, json, tcl, text, sql, or size} -do_test 1.16 { - catch {db format -text sql -style unk {SELECT * FROM t1}} res - set res -} {bad format style (-style) "unk": must be auto, box, column, count, csv, eqp, explain, html, insert, jobject, json, line, list, markdown, quote, stats, stats-est, stats-vm, or table} - - -do_test 1.20 { - set result "\n[db format -style box {SELECT * FROM t1}]" -} { -╭──────┬─────┬───────╮ -│ a │ b │ c │ -╞══════╪═════╪═══════╡ -│ 1 │ 2.5 │ three │ -│ BLOB │ │ Ἀμήν │ -╰──────┴─────┴───────╯ -} - -do_test 1.30 { - set result "\n[db format -style table {SELECT * FROM t1}]" -} { -+------+-----+-------+ -| a | b | c | -+------+-----+-------+ -| 1 | 2.5 | three | -| BLOB | | Ἀμήν | -+------+-----+-------+ -} -do_test 1.31 { - set result "\n[db format -style table -title off {SELECT * FROM t1}]" -} { -+------+-----+-------+ -| 1 | 2.5 | three | -| BLOB | | Ἀμήν | -+------+-----+-------+ -} -do_test 1.32 { - set result "\n[db format -style table -border off {SELECT * FROM t1}]" -} { - a | b | c -------+-----+------- - 1 | 2.5 | three - BLOB | | Ἀμήν -} -do_test 1.33 { - set result "\n[db format -style table -border off \ - -screenwidth 15 \ - {SELECT * FROM t1}]" -} { - a | b | c -----+---+----- - 1|2.5|three -BLOB| |Ἀμήν -} -do_test 1.34 { - set result "\n[db format -style box -border off \ - -screenwidth 30 \ - {SELECT * FROM t1}]" -} { - a │ b │ c -══════╪═════╪═══════ - 1 │ 2.5 │ three - BLOB │ │ Ἀμήν -} -do_test 1.35 { - set result "\n[db format -style box -border off \ - -screenwidth 15 \ - {SELECT * FROM t1}]" -} { - a │ b │ c -════╪═══╪═════ - 1│2.5│three -BLOB│ │Ἀμήν -} - -do_test 1.40 { - set result "\n[db format -style column {SELECT * FROM t1}]" -} { -a b c ----- --- ----- -1 2.5 three -BLOB Ἀμήν -} -do_test 1.41 { - set result "\n[db format -style column -title off {SELECT * FROM t1}]" -} { -1 2.5 three -BLOB Ἀμήν -} - -do_test 1.50 { - db format -style count {SELECT * FROM t1} -} 2 - -do_test 1.60a { - db format -style list -columnsep , -rowsep \r\n -text csv -blob tcl {SELECT * FROM t1} -} "1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" -do_test 1.60b { - db format -style csv -columnsep xyz -rowsep pqr -text sql -blob sql {SELECT * FROM t1} -} "1,2.5,three\r\nx'424c4f42',,\"Ἀμήν\"\r\n" -do_test 1.61a { - db format -style list -columnsep , -rowsep \r\n -text csv -title auto -blob tcl {SELECT * FROM t1} -} "a,b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" -do_test 1.61b { - db format -style csv -title auto -blob tcl {SELECT * FROM t1} -} "a,b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" -do_test 1.62a { - db format -style list -columnsep , -rowsep \r\n -text csv -title csv -blob tcl {SELECT a AS 'a x y', b, c FROM t1} -} "\"a x y\",b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" -do_test 1.62b { - db format -style csv -title csv -blob tcl {SELECT a AS 'a x y', b, c FROM t1} -} "\"a x y\",b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" - -do_test 1.70 { - set result "\n[db format -style html {SELECT * FROM t1}]" -} { -<TR> -<TD>1 -<TD>2.5 -<TD>three -</TR> -<TR> -<TD>BLOB -<TD>null -<TD>Ἀμήν -</TR> -} -do_test 1.71 { - set result "\n[db format -style html -title auto {SELECT * FROM t1}]" -} { -<TR> -<TH>a -<TH>b -<TH>c -</TR> -<TR> -<TD>1 -<TD>2.5 -<TD>three -</TR> -<TR> -<TD>BLOB -<TD>null -<TD>Ἀμήν -</TR> -} - -do_test 1.80 { - set result "\n[db format -style insert {SELECT * FROM t1}]" -} { -INSERT INTO tab VALUES(1,2.5,'three'); -INSERT INTO tab VALUES(x'424c4f42',NULL,'Ἀμήν'); -} -do_test 1.81 { - set result "\n[db format -style insert -tablename t1 {SELECT * FROM t1}]" -} { -INSERT INTO t1 VALUES(1,2.5,'three'); -INSERT INTO t1 VALUES(x'424c4f42',NULL,'Ἀμήν'); -} -do_test 1.82 { - set result "\n[db format -style insert -tablename t1 -title auto \ - {SELECT * FROM t1}]" -} { -INSERT INTO t1(a,b,c) VALUES(1,2.5,'three'); -INSERT INTO t1(a,b,c) VALUES(x'424c4f42',NULL,'Ἀμήν'); -} -do_test 1.83 { - set result "\n[db format -style insert -tablename drop -title on \ - {SELECT a AS "a-b", b, c AS "123" FROM t1}]" -} { -INSERT INTO "drop"("a-b",b,"123") VALUES(1,2.5,'three'); -INSERT INTO "drop"("a-b",b,"123") VALUES(x'424c4f42',NULL,'Ἀμήν'); -} - -do_test 1.90 { - set result "\n[db format -style json {SELECT * FROM t1}]" -} { -[{"a":1,"b":2.5,"c":"three"}, -{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν"}] -} -do_test 1.91 { - set result "\n[db format -style jobject {SELECT * FROM t1}]" -} { -{"a":1,"b":2.5,"c":"three"} -{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν"} -} -do_test 1.92 { - set result "\n[db format -style jobject {SELECT *, unistr('abc\u000a123\u000d\u000axyz') AS xyz FROM t1}]" -} { -{"a":1,"b":2.5,"c":"three","xyz":"abc\n123\r\nxyz"} -{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν","xyz":"abc\n123\r\nxyz"} -} - -do_test 1.100 { - set result "\n[db format -style line {SELECT * FROM t1}]" -} { -a: 1 -b: 2.5 -c: three - -a: BLOB -b: -c: Ἀμήν -} -do_test 1.101 { - set result "\n[db format -style line -null (NULL) {SELECT * FROM t1}]" -} { -a: 1 -b: 2.5 -c: three - -a: BLOB -b: (NULL) -c: Ἀμήν -} -do_test 1.102 { - set result "\n[db format -style line -null (NULL) -columnsep { = } \ - -text sql {SELECT * FROM t1}]" -} { -a = 1 -b = 2.5 -c = 'three' - -a = x'424c4f42' -b = (NULL) -c = 'Ἀμήν' -} - -do_test 1.110 { - set result "\n[db format -style list {SELECT * FROM t1}]" -} { -1|2.5|three -BLOB||Ἀμήν -} -do_test 1.111 { - set result "\n[db format -style list -title on {SELECT * FROM t1}]" -} { -a|b|c -1|2.5|three -BLOB||Ἀμήν -} -do_test 1.112 { - set result "\n[db format -style list -title on -text sql -null NULL \ - -title plain {SELECT * FROM t1}]" -} { -a|b|c -1|2.5|'three' -x'424c4f42'|NULL|'Ἀμήν' -} -do_test 1.118 { - set rc [catch {db format -style list -title unk {SELECT * FROM t1}} res] - lappend rc $res -} {1 {bad -title "unk": must be off, on, auto, csv, html, json, plain, relaxed, sql, or tcl}} - - -do_test 1.120 { - set result "\n[db format -style markdown {SELECT * FROM t1}]" -} { -| a | b | c | -|------|-----|-------| -| 1 | 2.5 | three | -| BLOB | | Ἀμήν | -} -do_test 1.121 { - set result "\n[db format -style markdown -title off {SELECT * FROM t1}]" -} { -| 1 | 2.5 | three | -| BLOB | | Ἀμήν | -} - -do_test 1.130 { - set result "\n[db format -style quote {SELECT * FROM t1}]" -} { -1,2.5,'three' -x'424c4f42',NULL,'Ἀμήν' -} -do_test 1.131 { - set result "\n[db format -style quote -title on {SELECT * FROM t1}]" -} { -'a','b','c' -1,2.5,'three' -x'424c4f42',NULL,'Ἀμήν' -} - - -do_execsql_test 2.0 { - DELETE FROM t1; - INSERT INTO t1 VALUES(1,2,'The quick fox jumps over the lazy brown dog.'); -} -do_test 2.1 { - set result "\n[db format -widths {5 -5 19} -wordwrap on \ - {SELECT * FROM t1}]" -} { -╭───────┬───────┬─────────────────────╮ -│ a │ b │ c │ -╞═══════╪═══════╪═════════════════════╡ -│ 1 │ 2 │ The quick fox jumps │ -│ │ │ over the lazy brown │ -│ │ │ dog. │ -╰───────┴───────┴─────────────────────╯ -} -do_test 2.2 { - set result "\n[db format -widths {5 -5 19} -wordwrap off \ - {SELECT * FROM t1}]" -} { -╭───────┬───────┬─────────────────────╮ -│ a │ b │ c │ -╞═══════╪═══════╪═════════════════════╡ -│ 1 │ 2 │ The quick fox jumps │ -│ │ │ over the lazy brown │ -│ │ │ dog. │ -╰───────┴───────┴─────────────────────╯ -} -do_test 2.3 { - set result "\n[db format -widths {5 -5 18} -wordwrap on \ - {SELECT * FROM t1}]" -} { -╭───────┬───────┬────────────────────╮ -│ a │ b │ c │ -╞═══════╪═══════╪════════════════════╡ -│ 1 │ 2 │ The quick fox │ -│ │ │ jumps over the │ -│ │ │ lazy brown dog. │ -╰───────┴───────┴────────────────────╯ -} -do_test 2.4 { - set result "\n[db format -widths {5 -5 -18} -wordwrap on \ - {SELECT * FROM t1}]" -} { -╭───────┬───────┬────────────────────╮ -│ a │ b │ c │ -╞═══════╪═══════╪════════════════════╡ -│ 1 │ 2 │ The quick fox │ -│ │ │ jumps over the │ -│ │ │ lazy brown dog. │ -╰───────┴───────┴────────────────────╯ -} -do_test 2.5 { - set result "\n[db format -widths {5 -5 19} -wordwrap off \ - {SELECT * FROM t1}]" -} { -╭───────┬───────┬─────────────────────╮ -│ a │ b │ c │ -╞═══════╪═══════╪═════════════════════╡ -│ 1 │ 2 │ The quick fox jumps │ -│ │ │ over the lazy brown │ -│ │ │ dog. │ -╰───────┴───────┴─────────────────────╯ -} -do_test 2.6 { - set result "\n[db format -widths {5 -5 18} -wordwrap off \ - {SELECT * FROM t1}]" -} { -╭───────┬───────┬────────────────────╮ -│ a │ b │ c │ -╞═══════╪═══════╪════════════════════╡ -│ 1 │ 2 │ The quick fox jump │ -│ │ │ s over the lazy br │ -│ │ │ own dog. │ -╰───────┴───────┴────────────────────╯ -} -do_test 2.7 { - set result "\n[db format -widths {5 5 18} -wordwrap yes \ - -align {left center right} -titlealign right \ - {SELECT * FROM t1}]" -} { -╭───────┬───────┬────────────────────╮ -│ a │ b │ c │ -╞═══════╪═══════╪════════════════════╡ -│ 1 │ 2 │ The quick fox │ -│ │ │ jumps over the │ -│ │ │ lazy brown dog. │ -╰───────┴───────┴────────────────────╯ -} -do_test 2.8 { - set result "\n[db format -widths {5 8 11} -wordwrap yes \ - -align {auto auto center} -titlealign left \ - -defaultalign right \ - {SELECT * FROM t1}]" -} { -╭───────┬──────────┬─────────────╮ -│ a │ b │ c │ -╞═══════╪══════════╪═════════════╡ -│ 1 │ 2 │ The quick │ -│ │ │ fox jumps │ -│ │ │ over the │ -│ │ │ lazy brown │ -│ │ │ dog. │ -╰───────┴──────────┴─────────────╯ -} -do_test 2.9 { - catch {db format -align {auto xyz 123} {SELECT * FROM t1}} res - set res -} {bad column alignment (-align) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w} -do_test 2.10 { - catch {db format -defaultalign xyz {SELECT * FROM t1}} res - set res -} {bad default alignment (-defaultalign) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w} -do_test 2.11 { - catch {db format -titlealign xyz {SELECT * FROM t1}} res - set res -} {bad title alignment (-titlealign) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w} - - -do_execsql_test 2.30 { - UPDATE t1 SET c='Η γρήγορη αλεπού πηδάει πάνω από το τεμπέλικο καφέ σκυλί'; - SELECT hex(c) FROM t1; -} {CE9720CEB3CF81CEAECEB3CEBFCF81CEB720CEB1CEBBCEB5CF80CEBFCF8D20CF80CEB7CEB4CEACCEB5CEB920CF80CEACCEBDCF8920CEB1CF80CF8C20CF84CEBF20CF84CEB5CEBCCF80CEADCEBBCEB9CEBACEBF20CEBACEB1CF86CEAD20CF83CEBACF85CEBBCEAF} -do_test 2.31 { - set result "\n[db format -widths {5 -5 18} -wordwrap on \ - {SELECT * FROM t1}]" -} { -╭───────┬───────┬────────────────────╮ -│ a │ b │ c │ -╞═══════╪═══════╪════════════════════╡ -│ 1 │ 2 │ Η γρήγορη αλεπού │ -│ │ │ πηδάει πάνω από το │ -│ │ │ τεμπέλικο καφέ │ -│ │ │ σκυλί │ -╰───────┴───────┴────────────────────╯ -} -do_test 2.32 { - set result "\n[db format -widths {5 5 18} -align {left center center} -wordwrap on \ - {SELECT * FROM t1}]" -} { -╭───────┬───────┬────────────────────╮ -│ a │ b │ c │ -╞═══════╪═══════╪════════════════════╡ -│ 1 │ 2 │ Η γρήγορη αλεπού │ -│ │ │ πηδάει πάνω από το │ -│ │ │ τεμπέλικο καφέ │ -│ │ │ σκυλί │ -╰───────┴───────┴────────────────────╯ -} - - -do_execsql_test 3.0 { - DELETE FROM t1; - INSERT INTO t1 VALUES(1,2,unistr('abc\u001b[1;31m123\u001b[0mxyz')); -} -do_test 3.1 { - set result "\n[db format {SELECT * FROM t1}]" -} { -╭───┬───┬────────────────────────╮ -│ a │ b │ c │ -╞═══╪═══╪════════════════════════╡ -│ 1 │ 2 │ abc^[[1;31m123^[[0mxyz │ -╰───┴───┴────────────────────────╯ -} -do_test 3.2 { - set result "\n[db format -esc off {SELECT * FROM t1}]" - string map [list \033 X] $result -} { -╭───┬───┬───────────╮ -│ a │ b │ c │ -╞═══╪═══╪═══════════╡ -│ 1 │ 2 │ abcX[1;31m123X[0mxyz │ -╰───┴───┴───────────╯ -} -do_test 3.3 { - set result "\n[db format -esc symbol {SELECT * FROM t1}]" -} { -╭───┬───┬──────────────────────╮ -│ a │ b │ c │ -╞═══╪═══╪══════════════════════╡ -│ 1 │ 2 │ abc␛[1;31m123␛[0mxyz │ -╰───┴───┴──────────────────────╯ -} -do_test 3.4 { - set result "\n[db format -esc ascii {SELECT * FROM t1}]" -} { -╭───┬───┬────────────────────────╮ -│ a │ b │ c │ -╞═══╪═══╪════════════════════════╡ -│ 1 │ 2 │ abc^[[1;31m123^[[0mxyz │ -╰───┴───┴────────────────────────╯ -} -do_test 3.5 { - catch {db format -esc unk {SELECT * FROM t1}} res - set res -} {bad control character escape (-esc) "unk": must be ascii, auto, off, or symbol} - -do_execsql_test 4.0 { - DELETE FROM t1; - INSERT INTO t1 VALUES(json('{a:5,b:6}'), jsonb('{c:1,d:2}'), 99); -} -do_test 4.1 { - set result "\n[db format -text sql {SELECT * FROM t1}]" -} { -╭─────────────────┬───────────────────────┬────╮ -│ a │ b │ c │ -╞═════════════════╪═══════════════════════╪════╡ -│ '{"a":5,"b":6}' │ x'8c1763133117641332' │ 99 │ -╰─────────────────┴───────────────────────┴────╯ -} -do_test 4.2 { - set result "\n[db format -text sql -textjsonb on {SELECT * FROM t1}]" -} { -╭─────────────────┬────────────────────────┬────╮ -│ a │ b │ c │ -╞═════════════════╪════════════════════════╪════╡ -│ '{"a":5,"b":6}' │ jsonb('{"c":1,"d":2}') │ 99 │ -╰─────────────────┴────────────────────────┴────╯ -} -do_test 4.3 { - set result "\n[db format -text plain -textjsonb on -wrap 11 \ - {SELECT a AS json, b AS jsonb, c AS num FROM t1}]" -} { -╭─────────────┬─────────────┬─────╮ -│ json │ jsonb │ num │ -╞═════════════╪═════════════╪═════╡ -│ {"a":5,"b": │ {"c":1,"d": │ 99 │ -│ 6} │ 2} │ │ -╰─────────────┴─────────────┴─────╯ -} - -do_execsql_test 5.0 { - DROP TABLE t1; - CREATE TABLE t1(name, mtime, value); - INSERT INTO t1 VALUES - ('entry-one',1708791504,zeroblob(300)), - (unistr('one\u000atwo\u000athree'),1333206973,NULL), - ('sample-jsonb',1333101221,jsonb('{ - "alpha":53.11688723, - "beta":"qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth);", - "zeta":[15,null,1333206973,"fd8ffe000104a46494600010101"]}')); -} -do_test 5.1 { - set sql {SELECT name, mtime, datetime(mtime,'unixepoch') AS time, - value FROM t1 ORDER BY mtime} - set result "\n[db format -style line -screenwidth 60 -blob sql \ - -text sql -wordwrap off -linelimit 77 \ - -columnsep { = } $sql]" -} { - name = 'sample-jsonb' -mtime = 1333101221 - time = '2012-03-30 09:53:41' -value = x'cc7c57616c706861b535332e31313638383732334762657461 - c73071726657696474685072696e7428702c20702d3e704f7574 - 2c202d702d3e752e734c696e652e6d78436f6c577468293b477a - 657461cb2c23313500a331333333323036393733c71b66643866 - 6665303030313034613436343934363030303130313031' - - name = unistr('one\u000atwo\u000athree') -mtime = 1333206973 - time = '2012-03-31 15:16:13' -value = - - name = 'entry-one' -mtime = 1708791504 - time = '2024-02-24 16:18:24' -value = x'00000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 000000000000000000000000000000' -} -do_test 5.2a { - set sql {SELECT name, mtime, datetime(mtime,'unixepoch') AS time, - value FROM t1 ORDER BY mtime} - set result "\n[db format -style line -screenwidth 60 -blob sql \ - -text plain -esc off -textjsonb yes -columnsep { = }\ - -wordwrap yes -linelimit 3 $sql]" -} { - name = sample-jsonb -mtime = 1333101221 - time = 2012-03-30 09:53:41 -value = {"alpha":53.11688723,"beta":"qrfWidthPrint(p, - p->pOut, -p->u.sLine.mxColWth);","zeta":[15,null, - 1333206973,"fd8ffe000104a46494600010101"]} - - name = one - two - three -mtime = 1333206973 - time = 2012-03-31 15:16:13 -value = - - name = entry-one -mtime = 1708791504 - time = 2024-02-24 16:18:24 -value = x'00000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000 - ... -} -set sqlnolabel "SELECT name, mtime, datetime(mtime,'unixepoch'),\ - value FROM t1 ORDER BY mtime" -do_test 5.2b { - set result "\n[db format -style line -screenwidth 60 -blob sql \ - -text plain -esc off -textjsonb no -titlelimit 12 \ - -wordwrap yes -linelimit 3 $sqlnolabel ]" -} { - name: sample-jsonb - mtime: 1333101221 -datetime(...: 2012-03-30 09:53:41 - value: x'cc7c57616c706861b535332e31313638383732334762 - 657461c73071726657696474685072696e7428702c2070 - 2d3e704f75742c202d702d3e752e734c696e652e6d7843 - ... - - name: one - two - three - mtime: 1333206973 -datetime(...: 2012-03-31 15:16:13 - value: - - name: entry-one - mtime: 1708791504 -datetime(...: 2024-02-24 16:18:24 - value: x'00000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000 - ... -} -set sql "SELECT name, mtime, datetime(mtime,'unixepoch') AS time,\ - value FROM t1 ORDER BY mtime" -do_test 5.3a { - set result "\n[db format -style box -widths {0 10 10 14}\ - -align {left right right center} \ - -blob sql \ - -text plain -esc off -textjsonb no \ - -wordwrap yes -linelimit 2 $sql]" -} { -╭──────────────┬────────────┬────────────┬────────────────╮ -│ name │ mtime │ time │ value │ -╞══════════════╪════════════╪════════════╪════════════════╡ -│ sample-jsonb │ 1333101221 │ 2012-03-30 │ x'cc7c57616c70 │ -│ │ │ 09:53:41 │ 6861b535332e31 │ -│ │ │ │ ... │ -├──────────────┼────────────┼────────────┼────────────────┤ -│ one │ 1333206973 │ 2012-03-31 │ │ -│ two │ │ 15:16:13 │ │ -│ ... │ │ │ │ -├──────────────┼────────────┼────────────┼────────────────┤ -│ entry-one │ 1708791504 │ 2024-02-24 │ x'000000000000 │ -│ │ │ 16:18:24 │ 00000000000000 │ -│ │ │ │ ... │ -╰──────────────┴────────────┴────────────┴────────────────╯ -} -do_test 5.3b { - set result "\n[db format -style box -widths {0 10 0 14} \ - -align {left right right center} \ - -blob sql -titlelimit 12 \ - -text plain -esc off -textjsonb no \ - -wordwrap yes -linelimit 2 $sqlnolabel ]" -} { -╭──────────────┬────────────┬─────────────────────┬────────────────╮ -│ name │ mtime │ datetime(... │ value │ -╞══════════════╪════════════╪═════════════════════╪════════════════╡ -│ sample-jsonb │ 1333101221 │ 2012-03-30 09:53:41 │ x'cc7c57616c70 │ -│ │ │ │ 6861b535332e31 │ -│ │ │ │ ... │ -├──────────────┼────────────┼─────────────────────┼────────────────┤ -│ one │ 1333206973 │ 2012-03-31 15:16:13 │ │ -│ two │ │ │ │ -│ ... │ │ │ │ -├──────────────┼────────────┼─────────────────────┼────────────────┤ -│ entry-one │ 1708791504 │ 2024-02-24 16:18:24 │ x'000000000000 │ -│ │ │ │ 00000000000000 │ -│ │ │ │ ... │ -╰──────────────┴────────────┴─────────────────────┴────────────────╯ -} -do_test 5.3c { - set result "\n[db format -style table -widths {0 10 10 14}\ - -align {center right right right} \ - -blob sql \ - -text plain -esc off -textjsonb no \ - -wordwrap yes -linelimit 2 $sql]" -} { -+--------------+------------+------------+----------------+ -| name | mtime | time | value | -+--------------+------------+------------+----------------+ -| sample-jsonb | 1333101221 | 2012-03-30 | x'cc7c57616c70 | -| | | 09:53:41 | 6861b535332e31 | -| | | | ... | -+--------------+------------+------------+----------------+ -| one | 1333206973 | 2012-03-31 | | -| two | | 15:16:13 | | -| ... | | | | -+--------------+------------+------------+----------------+ -| entry-one | 1708791504 | 2024-02-24 | x'000000000000 | -| | | 16:18:24 | 00000000000000 | -| | | | ... | -+--------------+------------+------------+----------------+ -} -do_test 5.3c { - set result "\n[db format -style column -widths {0 10 10 14}\ - -align {center right right right} \ - -blob sql \ - -text plain -esc off -textjsonb no \ - -wordwrap yes -linelimit 2 $sql]" -} { - name mtime time value ------------- ---------- ---------- -------------- -sample-jsonb 1333101221 2012-03-30 x'cc7c57616c70 - 09:53:41 6861b535332e31 - ... - - one 1333206973 2012-03-31 - two 15:16:13 - ... - - entry-one 1708791504 2024-02-24 x'000000000000 - 16:18:24 00000000000000 - ... -} -do_test 5.4 { - db eval { - CREATE TABLE t2(a,b,c,d,e); - WITH v(x) AS (SELECT 'abcdefghijklmnopqrstuvwxyz') - INSERT INTO t2 SELECT x,x,x,x,x FROM v; - } - set sql {SELECT char(0x61,0xa,0x62,0xa,0x63,0xa,0x64) a, - mtime b, mtime c, mtime d, mtime e FROM t1} - set result "\n[db format -style box -widths {1 2 3 4 5}\ - -linelimit 3 -wordwrap off {SELECT *, 'x' AS x FROM t2}]" -} { -╭────┬────┬─────┬──────┬───────┬───╮ -│ a │ b │ c │ d │ e │ x │ -╞════╪════╪═════╪══════╪═══════╪═══╡ -│ ab │ ab │ abc │ abcd │ abcde │ x │ -│ cd │ cd │ def │ efgh │ fghij │ │ -│ ef │ ef │ ghi │ ijkl │ klmno │ │ -│ .. │ .. │ ... │ ... │ ... │ │ -╰────┴────┴─────┴──────┴───────┴───╯ -} - -do_execsql_test 6.0 { - DELETE FROM t2; - INSERT INTO t2 VALUES - (1, 2.5, 'three', x'342028666f757229', null); -} -do_test 6.1a { - set result "\n[db format -style list -null NULL \ - -text tcl -columnsep , \ - {SELECT * FROM t2}]" -} { -1,2.5,"three","\064\040\050\146\157\165\162\051",NULL -} - -do_execsql_test 7.0 { - CREATE TABLE t7(a,b); - INSERT INTO t7 VALUES('abcdefghijklmnop', - 'abcぁdefかghiのjklはmnop'); -} -do_test 7.1 { - set result "\n[db format -style list -charlimit 13 \ - {SELECT * FROM t7}]" -} { -abcdefghijklm...|abcぁdefかghi... -} -do_test 7.2 { - set result "\n[db format -style list -charlimit 14 \ - {SELECT * FROM t7}]" -} { -abcdefghijklmn...|abcぁdefかghi... -} -do_test 7.3 { - set result "\n[db format -style list -charlimit 15 \ - {SELECT * FROM t7}]" -} { -abcdefghijklmno...|abcぁdefかghiの... -} -do_test 7.4 { - set result "\n[db format -style list -charlimit 16 \ - {SELECT * FROM t7}]" -} { -abcdefghijklmnop|abcぁdefかghiのj... -} - -do_test 8.0 { - set result "\n[db format -style table { - WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<10) - SELECT 'aaa' AS 'a', format('%.*c',n,'b')||char(9)||'xx' AS x FROM c}]" -} { -+-----+--------------------+ -| a | x | -+-----+--------------------+ -| aaa | b xx | -| aaa | bb xx | -| aaa | bbb xx | -| aaa | bbbb xx | -| aaa | bbbbb xx | -| aaa | bbbbbb xx | -| aaa | bbbbbbb xx | -| aaa | bbbbbbbb xx | -| aaa | bbbbbbbbb xx | -| aaa | bbbbbbbbbb xx | -+-----+--------------------+ -} -do_test 8.1 { - set result "\n[db format -style table { - WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<10) - SELECT 'aaaa' AS 'a', format('%.*c',n,'b')||char(9)||'xx' AS x FROM c}]" -} { -+------+--------------------+ -| a | x | -+------+--------------------+ -| aaaa | b xx | -| aaaa | bb xx | -| aaaa | bbb xx | -| aaaa | bbbb xx | -| aaaa | bbbbb xx | -| aaaa | bbbbbb xx | -| aaaa | bbbbbbb xx | -| aaaa | bbbbbbbb xx | -| aaaa | bbbbbbbbb xx | -| aaaa | bbbbbbbbbb xx | -+------+--------------------+ -} -do_test 8.3 { - set result "\n[db format -style table -esc off { - WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<15) - SELECT 'aaa' AS 'a',n, 'xx'||char(n)||'yy' AS xy FROM c - WHERE n NOT IN (8,10,13,14)}]" -} { -+-----+----+------------+ -| a | n | xy | -+-----+----+------------+ -| aaa | 1 | xx␁yy | -| aaa | 2 | xx␂yy | -| aaa | 3 | xx␃yy | -| aaa | 4 | xx␄yy | -| aaa | 5 | xx␅yy | -| aaa | 6 | xx␆yy | -| aaa | 7 | xx␇yy | -| aaa | 9 | xx yy | -| aaa | 11 | xx␋yy | -| aaa | 12 | xx␌yy | -| aaa | 15 | xx␏yy | -+-----+----+------------+ -} -do_test 8.4 { - set result "\n[db format -style table -esc off { - WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<15) - SELECT 'aaa' AS 'a',n, 'xx'||char(n)||'yy'||char(9)||'zz' AS xyz FROM c - WHERE n NOT IN (8,10,13,14)}]" -} { -+-----+----+--------------------+ -| a | n | xyz | -+-----+----+--------------------+ -| aaa | 1 | xx␁yy zz | -| aaa | 2 | xx␂yy zz | -| aaa | 3 | xx␃yy zz | -| aaa | 4 | xx␄yy zz | -| aaa | 5 | xx␅yy zz | -| aaa | 6 | xx␆yy zz | -| aaa | 7 | xx␇yy zz | -| aaa | 9 | xx yy zz | -| aaa | 11 | xx␋yy zz | -| aaa | 12 | xx␌yy zz | -| aaa | 15 | xx␏yy zz | -+-----+----+--------------------+ -} - -do_test 9.1 { - db eval { - CREATE TABLE t9(x); - INSERT INTO t9 VALUES - (x'4331323334'), - (x'c30431323334'), - (x'd3000431323334'), - (x'e30000000431323334'), - (x'f3000000000000000431323334'); - } - db format -style list -text plain -rowsep , -textjsonb on \ - {SELECT * FROM t9} -} {1234,1234,1234,1234,1234,} -do_test 9.2 { - db format -style list -text sql -rowsep , -textjsonb on \ - {SELECT * FROM t9} -} {jsonb('1234'),jsonb('1234'),jsonb('1234'),jsonb('1234'),jsonb('1234'),} -do_test 9.3 { - db format -style json {SELECT * FROM t9 WHERE rowid<0} -} {} -do_test 9.4 { - db format -style jobject {SELECT * FROM t9 WHERE rowid<0} -} {} - -do_test 10.1 { - db eval { - DROP TABLE IF EXISTS t1; - CREATE TABLE t1(x); - INSERT INTO t1(x) VALUES - ('alice'), - ('bob'), - ('cinderella-cinderella'), - ('daniel'), - ('emma'), - ('fred'), - ('gertrude'), - ('harold'), - ('ingrid'), - ('jake'), - ('lisa'), - ('mike'), - ('nina'), - ('octavian'), - ('paula'), - ('quintus'), - ('rita'), - ('sam'), - ('tammy'), - ('ulysses'), - ('violet'), - ('william'), - ('xanthippe'), - ('yates'), - ('zoe'); - } - set result "\n[db format -style column -title off -screenwidth 41 -splitcolumn on \ - {SELECT x FROM t1}]" -} { -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -} -do_test 10.2 { - set result "\n[db format -style column -title off -screenwidth 42 -splitcolumn on \ - {SELECT x FROM t1}]" -} { -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -} -do_test 10.3 { - set result "\n[db format -style column -title off -screenwidth 51 -splitcolumn on \ - {SELECT x FROM t1}]" -} { -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -} -do_test 10.4 { - set result "\n[db format -style column -title off -screenwidth 61 -splitcolumn on \ - {SELECT x FROM t1}]" -} { -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -} -do_test 10.5 { - set result "\n[db format -style column -title off -screenwidth 74 -splitcolumn on \ - {SELECT x FROM t1}]" -} { -alice emma ingrid nina rita violet zoe -bob fred jake octavian sam william -cinderella-cinderella gertrude lisa paula tammy xanthippe -daniel harold mike quintus ulysses yates -} - -do_test 11.1 { - set result "\n[db format -style table -blob size {SELECT randomblob(1234)}]" -} { -+------------------+ -| randomblob(1234) | -+------------------+ -| (1234-byte blob) | -+------------------+ -} - -do_test 12.1 { - set result "\n[db format -style box -text html \ - {SELECT 'abc','','xyz'}]" -} { -╭───────┬────┬───────╮ -│ 'abc' │ '' │ 'xyz' │ -╞═══════╪════╪═══════╡ -│ abc │ │ xyz │ -╰───────┴────┴───────╯ -} - -# Tests for "relaxed" quoting -# -do_test 13.2 { - db eval { - CREATE TABLE t13(a,b); - INSERT INTO t13(a,b) VALUES - (1,'NULL'), - (0,'-NULL-'), - (0,''), - (1,'''abcde'), - (1,'abcde'''), - (0,'abcde'), - (1,' abcde'), - (1,'abcde '), - (1,'+0'), - (1,'-0'), - (1,'012345'), - (0,'012xyz345'), - (1,'0123.45'), - (0,'12.34.56'), - (0,'12.3e'), - (1,'12.3e+123'), - (1,'12.3e-34'), - (1,'12.3E56'), - (1,'12E56'), - (0,'12.5E5.6'), - (0,'12.5e+'), - (0,'12.5e-'), - (1,'+Inf'),(1,'-Inf'),(1,'Inf'); - } - set result \n[db format -style box -text relaxed -null NULL \ - -align {center left} \ - {SELECT if(a,'yes','') AS 'quoted?', b AS string - FROM t13 ORDER BY rowid}] -} { -╭─────────┬─────────────╮ -│ quoted? │ string │ -╞═════════╪═════════════╡ -│ yes │ 'NULL' │ -│ │ -NULL- │ -│ │ │ -│ yes │ '''abcde' │ -│ yes │ 'abcde''' │ -│ │ abcde │ -│ yes │ ' abcde' │ -│ yes │ 'abcde ' │ -│ yes │ '+0' │ -│ yes │ '-0' │ -│ yes │ '012345' │ -│ │ 012xyz345 │ -│ yes │ '0123.45' │ -│ │ 12.34.56 │ -│ │ 12.3e │ -│ yes │ '12.3e+123' │ -│ yes │ '12.3e-34' │ -│ yes │ '12.3E56' │ -│ yes │ '12E56' │ -│ │ 12.5E5.6 │ -│ │ 12.5e+ │ -│ │ 12.5e- │ -│ yes │ '+Inf' │ -│ yes │ '-Inf' │ -│ yes │ 'Inf' │ -╰─────────┴─────────────╯ -} - -db close - -finish_test diff --git a/test/qrf02.test b/test/qrf02.test deleted file mode 100644 index 07e1568f7..000000000 --- a/test/qrf02.test +++ /dev/null @@ -1,47 +0,0 @@ -# 2025-11-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. -# -#*********************************************************************** -# -# Test cases for the Query Result Formatter (QRF) -# -# These tests are for EXPLAIN and EXPLAIN QUERY PLAN formatting, the -# output of which can change when enhancments are made to the query -# planner. So expect to have to modify the expected results of these -# test cases in the future. -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix qrf02 - -do_execsql_test 1.0 { - CREATE TABLE t1(a); - INSERT INTO t1 VALUES(1); -} - -set result [db format {EXPLAIN SELECT * FROM t1}] -do_test 1.10 { - set result -} {/*addr opcode p1 p2 p3 p4 p5 comment ----- ------------- ---- ---- ---- ------------- -- ------------- -0 Init */} -regsub -all {\d+} $result {N} result2 -do_test 1.11 { - set result2 -} "/.*\nN Rewind .*\nN Column .*/" - -do_test 1.20 { - set result "\n[db format {EXPLAIN QUERY PLAN SELECT * FROM t1}]" -} { -QUERY PLAN -`--SCAN t1 -} - -finish_test diff --git a/test/qrf03.test b/test/qrf03.test deleted file mode 100644 index c0457df7f..000000000 --- a/test/qrf03.test +++ /dev/null @@ -1,176 +0,0 @@ -# 2025-11-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. -# -#*********************************************************************** -# -# Test cases for the Query Result Formatter (QRF) -# -# Format narrowing due to nScreenWidth -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix qrf03 - -do_execsql_test 1.0 { - CREATE TABLE mlink( - mid INTEGER, - fid INTEGER, - pmid INTEGER, - pid INTEGER, - fnid INTEGER REFERENCES filename, - pfnid INTEGER, - mperm INTEGER, - isaux BOOLEAN DEFAULT 0 - ); - INSERT INTO mlink VALUES(28775,28774,28773,28706,1,0,0,0); - INSERT INTO mlink VALUES(28773,28706,28770,28685,1,0,0,0); - INSERT INTO mlink VALUES(28770,28736,28769,28695,2,0,0,0); - INSERT INTO mlink VALUES(28770,28697,28769,28698,3,0,0,0); - INSERT INTO mlink VALUES(28767,28768,28759,28746,4,0,0,0); - CREATE TABLE event( - type TEXT, - mtime DATETIME, - objid INTEGER PRIMARY KEY, - tagid INTEGER, - uid INTEGER REFERENCES user, - bgcolor TEXT, - euser TEXT, - user TEXT, - ecomment TEXT, - comment TEXT, - brief TEXT, - omtime DATETIME - ); - INSERT INTO event VALUES('ci',2460994.978048461023,126223,NULL,NULL,NULL,NULL,'drh',NULL,unistr('Data structure improvements on columnar layout. Prep work for getting\u000acolumnar layouts to respond to nScreenWidth.'),NULL,2460994.978048461023); - INSERT INTO event VALUES('ci',2460994.836955601816,126218,NULL,NULL,NULL,NULL,'stephan',NULL,'API doc typo fix.',NULL,2460994.836955601816); - INSERT INTO event VALUES('ci',2460994.88823369192,126212,NULL,NULL,NULL,NULL,'stephan',NULL,'Move sqlite3-api-cleanup.js into post-js-footer.js to remove the final direct Emscripten dependency from the intermediary build product sqlite3-api.js (the whole library, waiting to be bootstrapped). This is partly in response to [forum:4b7d45433731d2e0|forum post 4b7d45433731d2e0], which demonstrates a potential use case for a standalone sqlite3-api.js. This is a build/doc change, not a functional one.',NULL,2460994.88823369192); - INSERT INTO event VALUES('ci',2460994.516081551089,126211,NULL,NULL,NULL,NULL,'drh',NULL,unistr('Improve columnar layout in QRF so that it correctly deals with control\u000acharacters, and especially tabs.'),NULL,2460994.516081551089); - INSERT INTO event VALUES('ci',2460994.409343171865,126208,NULL,NULL,NULL,NULL,'drh',NULL,'Make use of the new sqlite3_str_free() interface in the CLI.',NULL,2460994.409343171865); -} - -do_test 1.10 { - set x "\n[db format -style box -screenwidth 68 \ - {SELECT * FROM mlink ORDER BY rowid}]" - -} { -╭───────┬───────┬───────┬───────┬──────┬───────┬───────┬───────╮ -│ mid │ fid │ pmid │ pid │ fnid │ pfnid │ mperm │ isaux │ -╞═══════╪═══════╪═══════╪═══════╪══════╪═══════╪═══════╪═══════╡ -│ 28775 │ 28774 │ 28773 │ 28706 │ 1 │ 0 │ 0 │ 0 │ -│ 28773 │ 28706 │ 28770 │ 28685 │ 1 │ 0 │ 0 │ 0 │ -│ 28770 │ 28736 │ 28769 │ 28695 │ 2 │ 0 │ 0 │ 0 │ -│ 28770 │ 28697 │ 28769 │ 28698 │ 3 │ 0 │ 0 │ 0 │ -│ 28767 │ 28768 │ 28759 │ 28746 │ 4 │ 0 │ 0 │ 0 │ -╰───────┴───────┴───────┴───────┴──────┴───────┴───────┴───────╯ -} -do_test 1.11 { - set x "\n[db format -style box -screenwidth 52 \ - {SELECT * FROM mlink ORDER BY rowid}]" - -} { -╭─────┬─────┬─────┬─────┬────┬─────┬─────┬─────╮ -│ mid │ fid │pmid │ pid │fnid│pfnid│mperm│isaux│ -╞═════╪═════╪═════╪═════╪════╪═════╪═════╪═════╡ -│28775│28774│28773│28706│ 1│ 0│ 0│ 0│ -│28773│28706│28770│28685│ 1│ 0│ 0│ 0│ -│28770│28736│28769│28695│ 2│ 0│ 0│ 0│ -│28770│28697│28769│28698│ 3│ 0│ 0│ 0│ -│28767│28768│28759│28746│ 4│ 0│ 0│ 0│ -╰─────┴─────┴─────┴─────┴────┴─────┴─────┴─────╯ -} - -do_test 1.20 { - set x "\n[db format -style table -screenwidth 68 \ - {SELECT * FROM mlink ORDER BY rowid}]" - -} { -+-------+-------+-------+-------+------+-------+-------+-------+ -| mid | fid | pmid | pid | fnid | pfnid | mperm | isaux | -+-------+-------+-------+-------+------+-------+-------+-------+ -| 28775 | 28774 | 28773 | 28706 | 1 | 0 | 0 | 0 | -| 28773 | 28706 | 28770 | 28685 | 1 | 0 | 0 | 0 | -| 28770 | 28736 | 28769 | 28695 | 2 | 0 | 0 | 0 | -| 28770 | 28697 | 28769 | 28698 | 3 | 0 | 0 | 0 | -| 28767 | 28768 | 28759 | 28746 | 4 | 0 | 0 | 0 | -+-------+-------+-------+-------+------+-------+-------+-------+ -} -do_test 1.21 { - set x "\n[db format -style table -screenwidth 52 \ - {SELECT * FROM mlink ORDER BY rowid}]" - -} { -+-----+-----+-----+-----+----+-----+-----+-----+ -| mid | fid |pmid | pid |fnid|pfnid|mperm|isaux| -+-----+-----+-----+-----+----+-----+-----+-----+ -|28775|28774|28773|28706| 1| 0| 0| 0| -|28773|28706|28770|28685| 1| 0| 0| 0| -|28770|28736|28769|28695| 2| 0| 0| 0| -|28770|28697|28769|28698| 3| 0| 0| 0| -|28767|28768|28759|28746| 4| 0| 0| 0| -+-----+-----+-----+-----+----+-----+-----+-----+ -} - -do_test 1.30 { - set x "\n[db format -style markdown -screenwidth 68 \ - {SELECT * FROM mlink ORDER BY rowid}]" - -} { -| mid | fid | pmid | pid | fnid | pfnid | mperm | isaux | -|-------|-------|-------|-------|------|-------|-------|-------| -| 28775 | 28774 | 28773 | 28706 | 1 | 0 | 0 | 0 | -| 28773 | 28706 | 28770 | 28685 | 1 | 0 | 0 | 0 | -| 28770 | 28736 | 28769 | 28695 | 2 | 0 | 0 | 0 | -| 28770 | 28697 | 28769 | 28698 | 3 | 0 | 0 | 0 | -| 28767 | 28768 | 28759 | 28746 | 4 | 0 | 0 | 0 | -} -do_test 1.31 { - set x "\n[db format -style markdown -screenwidth 52 \ - {SELECT * FROM mlink ORDER BY rowid}]" - -} { -| mid | fid |pmid | pid |fnid|pfnid|mperm|isaux| -|-----|-----|-----|-----|----|-----|-----|-----| -|28775|28774|28773|28706| 1| 0| 0| 0| -|28773|28706|28770|28685| 1| 0| 0| 0| -|28770|28736|28769|28695| 2| 0| 0| 0| -|28770|28697|28769|28698| 3| 0| 0| 0| -|28767|28768|28759|28746| 4| 0| 0| 0| -} - -do_test 1.40 { - set x "\n[db format -style column -screenwidth 68 \ - {SELECT * FROM mlink ORDER BY rowid}]" - -} { - mid fid pmid pid fnid pfnid mperm isaux ------ ----- ----- ----- ---- ----- ----- ----- -28775 28774 28773 28706 1 0 0 0 -28773 28706 28770 28685 1 0 0 0 -28770 28736 28769 28695 2 0 0 0 -28770 28697 28769 28698 3 0 0 0 -28767 28768 28759 28746 4 0 0 0 -} -do_test 1.41 { - set x "\n[db format -style column -screenwidth 52 \ - {SELECT * FROM mlink ORDER BY rowid}]" - -} { - mid fid pmid pid fnid pfnid mperm isaux ------ ----- ----- ----- ---- ----- ----- ----- -28775 28774 28773 28706 1 0 0 0 -28773 28706 28770 28685 1 0 0 0 -28770 28736 28769 28695 2 0 0 0 -28770 28697 28769 28698 3 0 0 0 -28767 28768 28759 28746 4 0 0 0 -} - - - -finish_test diff --git a/test/qrf04.test b/test/qrf04.test deleted file mode 100644 index 0b231d921..000000000 --- a/test/qrf04.test +++ /dev/null @@ -1,750 +0,0 @@ -# 2025-11-23 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Test cases for the Query Result Formatter (QRF), and especially -# the bSplitColumn feature. -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix qrf01 - -# The expected output from test 1.1. The "do_test" procedure normally -# ignores differences in whitespace, but whitespace is important for -# this test, so we have to do the comparison ourselves. -# -set expected { -<---- 22 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 23 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 24 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 25 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 26 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 27 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 28 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 29 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 30 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 31 ----> -alice -bob -cinderella-cinderella -daniel -emma -fred -gertrude -harold -ingrid -jake -lisa -mike -nina -octavian -paula -quintus -rita -sam -tammy -ulysses -violet -william -xanthippe -yates -zoe -<---- 32 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 33 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 34 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 35 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 36 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 37 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 38 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 39 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 40 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 41 ----> -alice octavian -bob paula -cinderella-cinderella quintus -daniel rita -emma sam -fred tammy -gertrude ulysses -harold violet -ingrid william -jake xanthippe -lisa yates -mike zoe -nina -<---- 42 ----> -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -<---- 43 ----> -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -<---- 44 ----> -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -<---- 45 ----> -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -<---- 46 ----> -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -<---- 47 ----> -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -<---- 48 ----> -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -<---- 49 ----> -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -<---- 50 ----> -alice jake tammy -bob lisa ulysses -cinderella-cinderella mike violet -daniel nina william -emma octavian xanthippe -fred paula yates -gertrude quintus zoe -harold rita -ingrid sam -<---- 51 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 52 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 53 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 54 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 55 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 56 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 57 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 58 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 59 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 60 ----> -alice harold paula william -bob ingrid quintus xanthippe -cinderella-cinderella jake rita yates -daniel lisa sam zoe -emma mike tammy -fred nina ulysses -gertrude octavian violet -<---- 61 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 62 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 63 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 64 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 65 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 66 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 67 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 68 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 69 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 70 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 71 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 72 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 73 ----> -alice fred lisa quintus violet -bob gertrude mike rita william -cinderella-cinderella harold nina sam xanthippe -daniel ingrid octavian tammy yates -emma jake paula ulysses zoe -<---- 74 ----> -alice emma ingrid nina rita violet zoe -bob fred jake octavian sam william -cinderella-cinderella gertrude lisa paula tammy xanthippe -daniel harold mike quintus ulysses yates -<---- 75 ----> -alice emma ingrid nina rita violet zoe -bob fred jake octavian sam william -cinderella-cinderella gertrude lisa paula tammy xanthippe -daniel harold mike quintus ulysses yates -<---- 76 ----> -alice emma ingrid nina rita violet zoe -bob fred jake octavian sam william -cinderella-cinderella gertrude lisa paula tammy xanthippe -daniel harold mike quintus ulysses yates -<---- 77 ----> -alice emma ingrid nina rita violet zoe -bob fred jake octavian sam william -cinderella-cinderella gertrude lisa paula tammy xanthippe -daniel harold mike quintus ulysses yates -<---- 78 ----> -alice emma ingrid nina rita violet zoe -bob fred jake octavian sam william -cinderella-cinderella gertrude lisa paula tammy xanthippe -daniel harold mike quintus ulysses yates -<---- 79 ----> -alice emma ingrid nina rita violet zoe -bob fred jake octavian sam william -cinderella-cinderella gertrude lisa paula tammy xanthippe -daniel harold mike quintus ulysses yates -<---- 80 ----> -alice emma ingrid nina rita violet zoe -bob fred jake octavian sam william -cinderella-cinderella gertrude lisa paula tammy xanthippe -daniel harold mike quintus ulysses yates -} - -do_test 1.0 { - db eval { - CREATE TABLE t1(x); - INSERT INTO t1(x) VALUES - ('alice'), - ('bob'), - ('cinderella-cinderella'), - ('daniel'), - ('emma'), - ('fred'), - ('gertrude'), - ('harold'), - ('ingrid'), - ('jake'), - ('lisa'), - ('mike'), - ('nina'), - ('octavian'), - ('paula'), - ('quintus'), - ('rita'), - ('sam'), - ('tammy'), - ('ulysses'), - ('violet'), - ('william'), - ('xanthippe'), - ('yates'), - ('zoe'); - } - set res \n - for {set i 22} {$i<=80} {incr i} { - set sp [expr {$i-13}] - append res [format "<----%*s%3d%*s---->\n" \ - [expr {$sp/2}] {} $i [expr {$sp-$sp/2}] {}] - append res [db format -style column -title off \ - -screenwidth $i -splitcolumn on \ - {SELECT x FROM t1 ORDER BY x ASC}] - } - expr {$res eq $::expected} -} {1} diff --git a/test/qrf05.test b/test/qrf05.test deleted file mode 100644 index 0d5a4d7f9..000000000 --- a/test/qrf05.test +++ /dev/null @@ -1,37 +0,0 @@ -# 2025-12-02 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Test cases for the Query Result Formatter (QRF) -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix qrf05 - -do_execsql_test 1.0 { - CREATE TABLE t1(a INT NOT NULL); -} -do_test 1.1 { - set rc [catch {db format -style list \ - {INSERT INTO t1 VALUES(123) RETURNING *}} msg] - list $rc [string trim $msg] -} {0 123} -do_test 1.2 { - set rc [catch {db format -style list \ - {INSERT INTO t1 VALUES(NULL) RETURNING *}} msg] - list $rc [string trim $msg] -} {1 {NOT NULL constraint failed: t1.a}} -do_test 1.3 { - set rc [catch {db format -version 99 {SELECT * FROM t1}} msg] - list $rc [string trim $msg] -} {1 {unusable sqlite3_qrf_spec.iVersion (99)}} - -finish_test diff --git a/test/qrf06.test b/test/qrf06.test deleted file mode 100644 index 5fa62c26f..000000000 --- a/test/qrf06.test +++ /dev/null @@ -1,576 +0,0 @@ -# 2025-12-02 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Test cases for the Query Result Formatter (QRF), and especially -# the sqlite3_qrf_wcwidth() function and its utilization. -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix qrf06 - -# Data -db eval { - BEGIN TRANSACTION; - CREATE TABLE language(name TEXT); - INSERT INTO language(name) VALUES - ('العربية'), - ('Deutsch'), - ('English'), - ('Español'), - ('فارسی'), - ('Français'), - ('Italiano'), - ('مصرى'), - ('Nederlands'), - ('日本語'), - ('Polski'), - ('Português'), - ('Sinugboanong Binisaya'), - ('Svenska'), - ('Українська'), - ('Tiếng Việt'), - ('Winaray'), - ('中文'), - ('Русский'), - ('Afrikaans'), - ('Shqip'), - ('Asturianu'), - ('Azərbaycanca'), - ('Български'), - ('閩南語 / Bân-lâm-gú'), - ('বাংলা'), - ('Беларуская'), - ('Català'), - ('Čeština'), - ('Cymraeg'), - ('Dansk'), - ('Eesti'), - ('Ελληνικά'), - ('Esperanto'), - ('Euskara'), - ('Galego'), - ('한국어'), - ('Հայերեն'), - ('हिन्दी'), - ('Hrvatski'), - ('Bahasa Indonesia'), - ('עברית'), - ('ქართული'), - ('Ladin'), - ('Latina'), - ('Latviešu'), - ('Lietuvių'), - ('Magyar'), - ('Македонски'), - ('Malagasy'), - ('मराठी'), - ('Bahasa Melayu'), - ('Bahaso Minangkabau'), - ('မြန်မာဘာသာ'), - ('Norskbokmålnynorsk'), - ('Нохчийн'), - ('Oʻzbekcha / Ўзбекча'), - ('Қазақша / Qazaqşa / قازاقشا'), - ('Română'), - ('Simple English'), - ('Slovenčina'), - ('Slovenščina'), - ('Српски / Srpski'), - ('Srpskohrvatski / Српскохрватски'), - ('Suomi'), - ('Kiswahili'), - ('தமிழ்'), - ('Татарча / Tatarça'), - ('తెలుగు'), - ('ภาษาไทย'), - ('Тоҷикӣ'), - ('تۆرکجه'), - ('Türkçe'), - ('اردو'), - ('粵語'), - ('Bahsa Acèh'), - ('Alemannisch'), - ('አማርኛ'), - ('Aragonés'), - ('Արեւմտահայերէն'), - ('Bahasa Hulontalo'), - ('Basa Bali'), - ('Bahasa Banjar'), - ('Basa Banyumasan'), - ('Башҡортса'), - ('Беларуская (тарашкевіца)'), - ('Bikol Central'), - ('বিষ্ণুপ্রিয়া মণিপুরী'), - ('Boarisch'), - ('Bosanski'), - ('Brezhoneg'), - ('Чӑвашла'), - ('Dagbanli'), - ('الدارجة'), - ('Diné Bizaad'), - ('Emigliàn–Rumagnòl'), - ('Fiji Hindi'), - ('Føroyskt'), - ('Frysk'), - ('Fulfulde'), - ('Gaeilge'), - ('Gàidhlig'), - ('گیلکی'), - ('ગુજરાતી'), - ('Hak-kâ-ngî / 客家語'), - ('Hausa'), - ('Hornjoserbsce'), - ('Ido'), - ('Igbo'), - ('Ilokano'), - ('Interlingua'), - ('Interlingue'), - ('Ирон'), - ('Íslenska'), - ('Jawa'), - ('ಕನ್ನಡ'), - ('Kapampangan'), - ('ភាសាខ្មែរ'), - ('Kotava'), - ('Kreyòl Ayisyen'), - ('Kurdî / كوردی'), - ('کوردیی ناوەندی'), - ('Кыргызча'), - ('Кырык мары'), - ('Lëtzebuergesch'), - ('Lìgure'), - ('Limburgs'), - ('Lombard'), - ('मैथिली'), - ('മലയാളം'), - ('მარგალური'), - ('مازِرونی'), - ('Mìng-dĕ̤ng-ngṳ̄ / 閩東語'), - ('Монгол'), - ('Napulitano'), - ('नेपाल भाषा'), - ('Nordfriisk'), - ('Occitan'), - ('Олык марий'), - ('ଓଡି଼ଆ'), - ('অসমীযা়'), - ('ਪੰਜਾਬੀ'), - ('پنجابی (شاہ مکھی)'), - ('پښتو'), - ('Piemontèis'), - ('Plattdüütsch'), - ('Qaraqalpaqsha'), - ('Qırımtatarca'), - ('Runa Simi'), - ('Русиньскый'), - ('संस्कृतम्'), - ('ᱥᱟᱱᱛᱟᱲᱤ'), - ('سرائیکی'), - ('Саха Тыла'), - ('Scots'), - ('ChiShona'), - ('Sicilianu'), - ('සිංහල'), - ('سنڌي'), - ('Ślůnski'), - ('Basa Sunda'), - ('Taclḥit'), - ('Tagalog'), - ('ၽႃႇသႃႇတႆး'), - ('ⵜⴰⵎⴰⵣⵉⵖⵜ ⵜⴰⵏⴰⵡⴰⵢⵜ'), - ('tolışi'), - ('chiTumbuka'), - ('Basa Ugi'), - ('Vèneto'), - ('Volapük'), - ('Walon'), - ('文言'), - ('吴语'), - ('ייִדיש'), - ('Yorùbá'), - ('Zazaki'), - ('žemaitėška'), - ('isiZulu'), - ('नेपाली'), - ('ꯃꯤꯇꯩ ꯂꯣꯟ'), - ('Dzhudezmo / לאדינו'), - ('Адыгэбзэ'), - ('Ænglisc'), - ('Anarâškielâ'), - ('अंगिका'), - ('Аԥсшәа'), - ('armãneashti'), - ('Arpitan'), - ('atikamekw'), - ('ܐܬܘܪܝܐ'), - ('Avañe’ẽ'), - ('Авар'), - ('Aymar'), - ('Batak Toba'), - ('Betawi'), - ('भोजपुरी'), - ('Bislama'), - ('བོད་ཡིག'), - ('Буряад'), - ('Chavacano de Zamboanga'), - ('Chichewa'), - ('Corsu'), - ('Vahcuengh / 話僮'), - ('Dagaare'), - ('Davvisámegiella'), - ('Deitsch'), - ('ދިވެހިބަސް'), - ('Dolnoserbski'), - ('Dusun Bundu-liwan'), - ('Эрзянь'), - ('Estremeñu'), - ('Eʋegbe'), - ('Farefare'), - ('Fɔ̀ngbè'), - ('Furlan'), - ('Gaelg'), - ('Gagauz'), - ('ГӀалгӀай'), - ('Ghanaian Pidgin'), - ('Gĩkũyũ'), - ('赣语 / 贛語'), - ('Gungbe'), - ('Хальмг'), - ('ʻŌlelo Hawaiʻi'), - ('Ikinyarwanda'), - ('Jaku Iban'), - ('Kabɩyɛ'), - ('Yerwa Kanuri'), - ('Kaszëbsczi'), - ('Kernewek'), - ('Коми'), - ('Перем коми'), - ('Kongo'), - ('कोंकणी / Konknni'), - ('كٲشُر'), - ('Kriyòl Gwiyannen'), - ('Kumoring'), - ('Kʋsaal'), - ('ພາສາລາວ'), - ('Лакку'), - ('Latgaļu'), - ('Лезги'), - ('Li Niha'), - ('Lingála'), - ('Lingua Franca Nova'), - ('livvinkarjala'), - ('lojban'), - ('Luganda'), - ('Madhurâ'), - ('Malti'), - ('Mandailing'), - ('Māori'), - ('Mfantse'), - ('Mirandés'), - ('Мокшень'), - ('ဘာသာ မန်'), - ('Moore'), - ('ߒߞߏ'), - ('Na Vosa Vaka-Viti'), - ('Nāhuatlahtōlli'), - ('Naijá'), - ('Nedersaksisch'), - ('Nouormand / Normaund'), - ('Novial'), - ('Afaan Oromoo'), - ('ပအိုဝ်ႏဘာႏသာႏ'), - ('Pangasinán'), - ('Pangcah'), - ('Papiamentu'), - ('Patois'), - ('Pfälzisch'), - ('Picard'), - ('Къарачай–малкъар'), - ('Ripoarisch'), - ('Rumantsch'), - ('Sakizaya'), - ('Gagana Sāmoa'), - ('Sardu'), - ('Seediq'), - ('Seeltersk'), - ('Sesotho'), - ('Sesotho sa Leboa'), - ('Setswana'), - ('ꠍꠤꠟꠐꠤ'), - ('Словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ'), - ('Soomaaliga'), - ('Sranantongo'), - ('SiSwati'), - ('Reo tahiti'), - ('Taqbaylit'), - ('Tarandíne'), - ('Tayal'), - ('Tetun'), - ('Tok Pisin'), - ('faka Tonga'), - ('Türkmençe'), - ('Twi'), - ('Tyap'), - ('Тыва дыл'), - ('Удмурт'), - ('ئۇيغۇرچه'), - ('Vepsän'), - ('võro'), - ('West-Vlams'), - ('Wolof'), - ('isiXhosa'), - ('Zeêuws'), - ('алтай тил'), - ('अवधी'), - ('डोटेली'), - ('ತುಳು'), - ('ရခိုင်'), - ('Bajau Sama'), - ('Bamanankan'), - ('Chamoru'), - ('རྫོང་ཁ'), - ('𐌲𐌿𐍄𐌹𐍃𐌺x'), - ('Igala'), - ('ᐃᓄᒃᑎᑐᑦ / Inuktitut'), - ('Iñupiak'), - ('isiNdebele seSewula'), - ('Kalaallisut'), - ('Nupe'), - ('Obolo'), - ('पालि'), - ('pinayuanan'), - ('Ποντιακά'), - ('romani čhib'), - ('Ikirundi'), - ('руски'), - ('Sängö'), - ('ᥖᥭᥰᥖᥬᥳᥑᥨᥒᥰ'), - ('ትግርኛ'), - ('Thuɔŋjäŋ'), - ('ᏣᎳᎩ'), - ('Tsėhesenėstsestotse'), - ('Xitsonga'), - ('Tshivenḓa'), - ('Wayuunaiki'), - ('адыгабзэ'); - COMMIT; -} - -do_test 1.2 { - set res \n[db format -style box { - SELECT name, rowid AS id FROM language - WHERE length(name)=2 - ORDER BY name - }] - set exp { -╭──────┬─────╮ -│ name │ id │ -╞══════╪═════╡ -│ 中文 │ 18 │ -│ 吴语 │ 173 │ -│ 文言 │ 172 │ -│ 粵語 │ 75 │ -╰──────┴─────╯ -} - if {$res ne $exp} { - puts [list $res] - puts [list $exp] - } - string compare $res $exp -} {0} - -do_test 1.3 { - set res \n[db format -style box { - SELECT name, rowid AS id FROM language - WHERE length(name)=3 - ORDER BY name - }] - set exp { -╭────────┬─────╮ -│ name │ id │ -╞════════╪═════╡ -│ Ido │ 108 │ -│ Twi │ 297 │ -│ ߒߞߏ │ 258 │ -│ ᏣᎳᎩ │ 335 │ -│ 日本語 │ 10 │ -│ 한국어 │ 37 │ -╰────────┴─────╯ -} - if {$res ne $exp} { - puts [list $res] - puts [list $exp] - } - string compare $res $exp -} {0} - -do_test 1.4 { - set res \n[db format -style box { - SELECT name, rowid AS id FROM language - WHERE length(name)=4 - ORDER BY name - }] - set exp { -╭──────┬─────╮ -│ name │ id │ -╞══════╪═════╡ -│ Igbo │ 109 │ -│ Jawa │ 115 │ -│ Nupe │ 323 │ -│ Tyap │ 298 │ -│ võro │ 303 │ -│ Авар │ 192 │ -│ Ирон │ 113 │ -│ Коми │ 231 │ -│ اردو │ 74 │ -│ سنڌي │ 159 │ -│ مصرى │ 8 │ -│ پښتو │ 144 │ -│ अवधी │ 309 │ -│ पालि │ 325 │ -│ ತುಳು │ 311 │ -│ ትግርኛ │ 333 │ -│ አማርኛ │ 78 │ -╰──────┴─────╯ -} - if {$res ne $exp} { - puts [list $res] - puts [list $exp] - } - string compare $res $exp -} {0} - -do_test 1.5 { - set res \n[db format -style box { - SELECT name, rowid AS id FROM language - WHERE length(name)=5 - ORDER BY name - }] - set exp { -╭───────┬─────╮ -│ name │ id │ -╞═══════╪═════╡ -│ Aymar │ 193 │ -│ Corsu │ 202 │ -│ Dansk │ 31 │ -│ Eesti │ 32 │ -│ Frysk │ 99 │ -│ Gaelg │ 216 │ -│ Hausa │ 106 │ -│ Igala │ 318 │ -│ Kongo │ 233 │ -│ Ladin │ 44 │ -│ Malti │ 250 │ -│ Moore │ 257 │ -│ Māori │ 252 │ -│ Naijá │ 261 │ -│ Obolo │ 324 │ -│ Sardu │ 278 │ -│ Scots │ 155 │ -│ Shqip │ 21 │ -│ Suomi │ 65 │ -│ Sängö │ 331 │ -│ Tayal │ 292 │ -│ Tetun │ 293 │ -│ Walon │ 171 │ -│ Wolof │ 305 │ -│ Лакку │ 240 │ -│ Лезги │ 242 │ -│ руски │ 330 │ -│ עברית │ 42 │ -│ فارسی │ 5 │ -│ كٲشُر │ 235 │ -│ گیلکی │ 103 │ -│ मराठी │ 51 │ -│ বাংলা │ 26 │ -│ ଓଡି଼ଆ │ 140 │ -│ தமிழ் │ 67 │ -│ ಕನ್ನಡ │ 116 │ -│ සිංහල │ 158 │ -│ ꠍꠤꠟꠐꠤ │ 284 │ -╰───────┴─────╯ -} - if {$res ne $exp} { - puts [list $res] - puts [list $exp] - } - string compare $res $exp -} {0} - -do_test 1.6 { - set res \n[db format -style box { - SELECT name, rowid AS id FROM language - WHERE length(name)=6 - ORDER BY name - }] - set exp { -╭────────┬─────╮ -│ name │ id │ -╞════════╪═════╡ -│ Betawi │ 195 │ -│ Català │ 28 │ -│ Eʋegbe │ 212 │ -│ Furlan │ 215 │ -│ Gagauz │ 217 │ -│ Galego │ 36 │ -│ Gungbe │ 222 │ -│ Gĩkũyũ │ 220 │ -│ Kabɩyɛ │ 227 │ -│ Kotava │ 119 │ -│ Kʋsaal │ 238 │ -│ Latina │ 45 │ -│ Lìgure │ 126 │ -│ Magyar │ 48 │ -│ Novial │ 264 │ -│ Patois │ 270 │ -│ Picard │ 272 │ -│ Polski │ 11 │ -│ Română │ 59 │ -│ Seediq │ 279 │ -│ Türkçe │ 73 │ -│ Vepsän │ 302 │ -│ Vèneto │ 169 │ -│ Yorùbá │ 175 │ -│ Zazaki │ 176 │ -│ Zeêuws │ 307 │ -│ lojban │ 247 │ -│ tolışi │ 166 │ -│ Аԥсшәа │ 186 │ -│ Буряад │ 199 │ -│ Монгол │ 134 │ -│ Тоҷикӣ │ 71 │ -│ Удмурт │ 300 │ -│ Хальмг │ 223 │ -│ Эрзянь │ 210 │ -│ ייִדיש │ 174 │ -│ تۆرکجه │ 72 │ -│ ܐܬܘܪܝܐ │ 190 │ -│ अंगिका │ 185 │ -│ डोटेली │ 310 │ -│ नेपाली │ 179 │ -│ मैथिली │ 129 │ -│ हिन्दी │ 39 │ -│ ਪੰਜਾਬੀ │ 142 │ -│ తెలుగు │ 69 │ -│ മലയാളം │ 130 │ -│ རྫོང་ཁ │ 316 │ -│ ရခိုင် │ 312 │ -╰────────┴─────╯ -} - if {$res ne $exp} { - puts [list $res] - puts [list $exp] - } - string compare $res $exp -} {0} - -finish_test diff --git a/test/recover.test b/test/recover.test index 7035e407c..ad6b7298d 100644 --- a/test/recover.test +++ b/test/recover.test @@ -42,7 +42,7 @@ proc compare_dbs {db1 db2} { proc recover_with_opts {opts} { set cmd ".recover $opts" - set fd [open [list |$::CLI -noinit test.db $cmd]] + set fd [open [list |$::CLI test.db $cmd]] fconfigure $fd -translation binary set sql [read $fd] close $fd diff --git a/test/regexp1.sql b/test/regexp1.sql deleted file mode 100644 index c1938885a..000000000 --- a/test/regexp1.sql +++ /dev/null @@ -1,32 +0,0 @@ -#!sqlite3 -# -# 2025-12-16 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Test cases for the oversized patterns in the REGEXP extension found -# at ext/misc/regexp.c. -# -.mode list -.testcase 100 --- 0 1 2 3 4 --- 123456789 123456789 123456789 123456789 123 -SELECT 'abcdefg' REGEXP '((((((((((((((((((abcdefg))))))))))))))))))'; -.check 1 - -.testcase 110 -.limit like_pattern_length 42 -SELECT 'abcdefg' REGEXP '((((((((((((((((((abcdefg))))))))))))))))))'; -.check -glob "Error near line #: REGEXP pattern too big*" - -.testcase 120 -.limit like_pattern_length 43 -SELECT 'abcdefg' REGEXP '((((((((((((((((((abcdefg))))))))))))))))))'; -.check 1 diff --git a/test/regexp1.test b/test/regexp1.test index fb123284b..0401b13d7 100644 --- a/test/regexp1.test +++ b/test/regexp1.test @@ -331,29 +331,5 @@ do_execsql_test regexp1-7.12 { SELECT char(0x61,0x10ffff,0x62) REGEXP char(0x10ffff); } 1 -do_execsql_test regexp1-8.0 { - CREATE TABLE t2(a); - INSERT INTO t2 VALUES('abc-def'); - SELECT length(a) FROM t2; -} {7} - -do_execsql_test regexp1-8.1 { - SELECT rowid FROM t2 WHERE a REGEXP '[1-5]'; -} {} -do_execsql_test regexp1-8.2 { - SELECT rowid FROM t2 WHERE a REGEXP '[1\-5]'; -} {1} -do_execsql_test regexp1-8.3 { - SELECT rowid FROM t2 WHERE a REGEXP '[x\-]'; -} {1} -do_catchsql_test regexp1-8.4 { - SELECT rowid FROM t2 WHERE a REGEXP '[x-]'; -} {1 {unclosed '['}} -do_execsql_test regexp1-8.5 { - SELECT rowid FROM t2 WHERE a REGEXP '-'; -} {1} -do_execsql_test regexp1-8.6 { - SELECT rowid FROM t2 WHERE a REGEXP '\-'; -} {1} finish_test diff --git a/test/rowvalue4.test b/test/rowvalue4.test index 5e02f0fc2..1ef5fc292 100644 --- a/test/rowvalue4.test +++ b/test/rowvalue4.test @@ -236,7 +236,8 @@ do_eqp_test 5.1 { QUERY PLAN |--SEARCH d2 USING INDEX d2ab (a=? AND b=?) |--LIST SUBQUERY xxxxxx - | `--SCAN d1 + | |--SCAN d1 + | `--CREATE BLOOM FILTER `--LIST SUBQUERY xxxxxx |--SCAN d1 `--CREATE BLOOM FILTER diff --git a/test/rowvalueA.test b/test/rowvalueA.test index 16429f985..8760c2c39 100644 --- a/test/rowvalueA.test +++ b/test/rowvalueA.test @@ -73,29 +73,4 @@ do_catchsql_test 2.3 { SELECT 2 IN ( (1, 2), (3, 4), (5, 6) ) } {1 {row value misused}} -#------------------------------------------------------------------------- -# Test the fix for forum post https://sqlite.org/forum/forumpost/6ceca07fc3 -# -do_execsql_test 3.0 { - CREATE TABLE x2 (x, y); - INSERT INTO x2 VALUES (1234, 'abc'); - - CREATE TABLE x1 (a, b PRIMARY KEY COLLATE NOCASE) WITHOUT ROWID; - INSERT INTO x1 VALUES (1234, 'ABCD'); -} - -do_execsql_test 3.1 { - SELECT * FROM x2 CROSS JOIN x1 WHERE (1234, x2.y) > (x1.a, x1.b); -} {1234 abc 1234 ABCD} - -do_execsql_test 3.2 { - CREATE INDEX x1a ON x1(a); -} - -do_execsql_test 3.3 { - SELECT * FROM x2 CROSS JOIN x1 WHERE (1234, x2.y) > (x1.a, x1.b); -} {1234 abc 1234 ABCD} - - - finish_test diff --git a/test/schema.test b/test/schema.test index a6564293b..c7daef20b 100644 --- a/test/schema.test +++ b/test/schema.test @@ -227,10 +227,10 @@ ifcapable auth { set ::STMT [sqlite3_prepare $::DB {SELECT * FROM sqlite_master} -1 TAIL] db auth {} sqlite3_step $::STMT - } {SQLITE_ERROR} + } {SQLITE_ROW} do_test schema-8.12 { sqlite3_finalize $::STMT - } {SQLITE_SCHEMA} + } {SQLITE_OK} } diff --git a/test/select9.test b/test/select9.test index bef56d83f..bbed8e18f 100644 --- a/test/select9.test +++ b/test/select9.test @@ -406,7 +406,7 @@ do_test select9-4.4 { do_test select9-4.5 { execsql { CREATE VIEW v1 AS SELECT a FROM t1 UNION SELECT d FROM t2 } cksort { SELECT a FROM v1 ORDER BY 1 LIMIT 5 } -} {1 2 3 4 5 nosort} +} {1 2 3 4 5 sort} do_test select9-4.X { execsql { DROP INDEX i1; diff --git a/test/shell1.test b/test/shell1.test index 1d111d616..abf214a90 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -216,14 +216,10 @@ do_test shell1-2.2.4 { } {0 {}} do_test shell1-2.2.5 { catchcmd "test.db" ".mode \"insert FOO" -} {1 {line 1: .mode "insert FOO -line 1: ^--- unknown mode -line 1: Use ".help .mode" for more info}} +} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}} do_test shell1-2.2.6 { catchcmd "test.db" ".mode \'insert FOO" -} {1 {line 1: .mode 'insert FOO -line 1: ^--- unknown mode -line 1: Use ".help .mode" for more info}} +} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}} # check multiple tokens, and quoted tokens do_test shell1-2.3.1 { @@ -251,9 +247,7 @@ do_test shell1-2.3.7 { # check quoted args are unquoted do_test shell1-2.4.1 { catchcmd "test.db" ".mode FOO" -} {1 {line 1: .mode FOO -line 1: ^--- unknown mode -line 1: Use ".help .mode" for more info}} +} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}} do_test shell1-2.4.2 { catchcmd "test.db" ".mode csv" } {0 {}} @@ -302,7 +296,9 @@ do_test shell1-3.2.4 { catchcmd "test.db" ".bail OFF BAD" } {1 {Usage: .bail on|off}} -ifcapable vtab { +# This test will not work on winrt, as winrt has no concept of the absolute +# paths that the test expects in the result. It uses relative paths only. +ifcapable vtab&&!winrt { # .databases List names and files of attached databases do_test shell1-3.3.1 { catchcmd "-csv test.db" ".databases" @@ -368,6 +364,7 @@ do_test shell1-3.7.4 { catchcmd "test.db" ".explain OFF BAD" } {0 {}} + # .header(s) ON|OFF Turn display of headers on or off do_test shell1-3.9.1 { catchcmd "test.db" ".header" @@ -403,7 +400,7 @@ do_test shell1-3.10.1 { # look for a few of the possible help commands list [regexp {.help} $res] \ [regexp {.quit} $res] \ - [regexp {.mode} $res] + [regexp {.show} $res] } {1 1 1} do_test shell1-3.10.2 { # we allow .help to take extra args (it is help after all) @@ -411,24 +408,20 @@ do_test shell1-3.10.2 { # look for a few of the possible help commands list [regexp {.help} $res] \ [regexp {.quit} $res] \ - [regexp {.mode} $res] + [regexp {.show} $res] } {1 1 1} # .import FILE TABLE Import data from FILE into TABLE do_test shell1-3.11.1 { catchcmd "test.db" ".import" -} {/1 .line 1: Missing FILE argument.*/} +} {/1 .ERROR: missing FILE argument.*/} do_test shell1-3.11.2 { catchcmd "test.db" ".import FOO" -} {/1 .line 1: Missing TABLE argument.*/} +} {/1 .ERROR: missing TABLE argument.*/} do_test shell1-3.11.3 { # too many arguments catchcmd "test.db" ".import FOO BAR BAD" -} {1 {line 1: .import FOO BAR BAD -line 1: ^--- unknown argument}} -do_test shell1-3.11.4 { - catchcmd "test.db" ".import <<END t1\na,b,c\n1,2,3" -} {1 {line 1: Content terminator "END" not found.}} +} {/1 .ERROR: extra argument: "BAD".*./} # .indexes ?TABLE? Show names of all indexes # If TABLE specified, only show indexes for tables @@ -458,13 +451,11 @@ do_test shell1-3.12.3 { # tabs Tab-separated values # tcl TCL list elements do_test shell1-3.13.1 { - catchcmd "test.db" ".mode batch\n.mode" -} {0 {.mode list}} + catchcmd "test.db" ".mode" +} {0 {current output mode: list --escape ascii}} do_test shell1-3.13.2 { catchcmd "test.db" ".mode FOO" -} {1 {line 1: .mode FOO -line 1: ^--- unknown mode -line 1: Use ".help .mode" for more info}} +} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}} do_test shell1-3.13.3 { catchcmd "test.db" ".mode csv" } {0 {}} @@ -512,7 +503,7 @@ do_test shell1-3.15.1 { .print x" } {0 x} do_test shell1-3.15.2 { - catchcmd "test.db" ".mode batch\n.output FOO + catchcmd "test.db" ".output FOO .print x .output SELECT readfile('FOO');" @@ -521,8 +512,17 @@ SELECT readfile('FOO');" do_test shell1-3.15.3 { # too many arguments catchcmd "test.db" ".output FOO BAD" -} {1 {line 1: .output FOO BAD -line 1: ^--- surplus argument}} +} {1 {ERROR: extra parameter: "BAD". Usage: +.output ?FILE? Send output to FILE or stdout if FILE is omitted + If FILE begins with '|' then open it as a pipe. + If FILE is 'off' then output is disabled. + Options: + --bom Prefix output with a UTF8 byte-order mark + -e Send output to the system text editor + --plain Use text/plain for -w option + -w Send output to a web browser + -x Send output as CSV to a spreadsheet +child process exited abnormally}} # .output stdout Send output to the screen do_test shell1-3.16.1 { @@ -531,8 +531,17 @@ do_test shell1-3.16.1 { do_test shell1-3.16.2 { # too many arguments catchcmd "test.db" ".output stdout BAD" -} {1 {line 1: .output stdout BAD -line 1: ^--- surplus argument}} +} {1 {ERROR: extra parameter: "BAD". Usage: +.output ?FILE? Send output to FILE or stdout if FILE is omitted + If FILE begins with '|' then open it as a pipe. + If FILE is 'off' then output is disabled. + Options: + --bom Prefix output with a UTF8 byte-order mark + -e Send output to the system text editor + --plain Use text/plain for -w option + -w Send output to a web browser + -x Send output as CSV to a spreadsheet +child process exited abnormally}} # .prompt MAIN CONTINUE Replace the standard prompts do_test shell1-3.17.1 { @@ -616,20 +625,6 @@ CREATE VIEW v1 AS SELECT y+1 FROM v2 catch {db eval {DROP VIEW v1; DROP VIEW v2; DROP TABLE t1;}} } -do_test shell1-3.21.5 { - exec {*}$CLI -noinit test.db \ - {CREATE TABLE t2(a INTEGER PRIMARY KEY, b BLOB DEFAULT(jsonb('[]')),c TEXT NOT NULL)STRICT;} \ - {.schema -indent t2} -} {CREATE TABLE t2( - a INTEGER PRIMARY KEY, - b BLOB DEFAULT(jsonb('[]')), - c TEXT NOT NULL -)STRICT;} -do_test shell1-3.21.6 { - exec {*}$CLI -noinit test.db \ - {DROP TABLE t2;} \ - {.schema -indent t2} -} {} # .separator STRING Change column separator used by output and .import do_test shell1-3.22.1 { @@ -648,7 +643,7 @@ do_test shell1-3.22.4 { # .show Show the current values for various settings do_test shell1-3.23.1 { - set res [catchcmd "test.db" ".mode batch\n.show"] + set res [catchcmd "test.db" ".show"] list [regexp {echo:} $res] \ [regexp {explain:} $res] \ [regexp {headers:} $res] \ @@ -684,7 +679,7 @@ do_test shell1-3.23b.4 { # Adverse interaction between .stats and .eqp # do_test shell1-3.23b.5 { - catchcmd "test.db" [string map {"\n " "\n"} {.mode batch + catchcmd "test.db" [string map {"\n " "\n"} { CREATE TEMP TABLE t1(x); INSERT INTO t1 VALUES(1),(2); .stats on @@ -746,27 +741,30 @@ do_test shell1-3.26.5 { do_test shell1-3.26.6 { catchcmd "test.db" ".mode column\n.header off\n.width -10 10\nSELECT 'abcdefg', 123456;" # this should be treated the same as a '1' width for col 1 and 2 -} {0 { abcdefg 123456}} +} {0 { abcdefg 123456 }} # .timer ON|OFF Turn the CPU timer measurement on or off do_test shell1-3.27.1 { catchcmd "test.db" ".timer" -} {1 {Usage: .timer on|off|once}} -do_test shell1-3.27.2 { - catchcmd "test.db" ".timer ON" -} {0 {}} +} {1 {Usage: .timer on|off}} +ifcapable !winrt { + # No timer support on winrt. + do_test shell1-3.27.2 { + catchcmd "test.db" ".timer ON" + } {0 {}} +} do_test shell1-3.27.3 { catchcmd "test.db" ".timer OFF" } {0 {}} do_test shell1-3.27.4 { # too many arguments catchcmd "test.db" ".timer OFF BAD" -} {1 {Usage: .timer on|off|once}} +} {1 {Usage: .timer on|off}} -do_test shell1-3.28.1 { +do_test shell1-3-28.1 { catchcmd test.db \ - ".mode batch\n.log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');" + ".log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');" } "0 {(123) hello\n456}" do_test shell1-3-29.1 { @@ -805,14 +803,14 @@ INSERT INTO t1 VALUES(''); INSERT INTO t1 VALUES(1); INSERT INTO t1 VALUES(2.25); INSERT INTO t1 VALUES('hello'); -INSERT INTO t1 VALUES(x'807f'); +INSERT INTO t1 VALUES(X'807f'); CREATE TABLE t3(x,y); INSERT INTO t3 VALUES(1,NULL); INSERT INTO t3 VALUES(2,''); INSERT INTO t3 VALUES(3,1); INSERT INTO t3 VALUES(4,2.25); INSERT INTO t3 VALUES(5,'hello'); -INSERT INTO t3 VALUES(6,x'807f'); +INSERT INTO t3 VALUES(6,X'807f'); COMMIT;}} @@ -830,14 +828,14 @@ INSERT INTO t1(rowid,x) VALUES(2,''); INSERT INTO t1(rowid,x) VALUES(3,1); INSERT INTO t1(rowid,x) VALUES(4,2.25); INSERT INTO t1(rowid,x) VALUES(5,'hello'); -INSERT INTO t1(rowid,x) VALUES(6,x'807f'); +INSERT INTO t1(rowid,x) VALUES(6,X'807f'); CREATE TABLE t3(x,y); INSERT INTO t3(rowid,x,y) VALUES(1,1,NULL); INSERT INTO t3(rowid,x,y) VALUES(2,2,''); INSERT INTO t3(rowid,x,y) VALUES(3,3,1); INSERT INTO t3(rowid,x,y) VALUES(4,4,2.25); INSERT INTO t3(rowid,x,y) VALUES(5,5,'hello'); -INSERT INTO t3(rowid,x,y) VALUES(6,6,x'807f'); +INSERT INTO t3(rowid,x,y) VALUES(6,6,X'807f'); COMMIT;}} # If the table contains an INTEGER PRIMARY KEY, do not record a separate @@ -856,12 +854,12 @@ do_test shell1-4.1.2 { } {0 {PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE t1(x INTEGER PRIMARY KEY, y); -INSERT INTO t1(x,y) VALUES(1,NULL); -INSERT INTO t1(x,y) VALUES(2,''); -INSERT INTO t1(x,y) VALUES(3,1); -INSERT INTO t1(x,y) VALUES(4,2.25); -INSERT INTO t1(x,y) VALUES(5,'hello'); -INSERT INTO t1(x,y) VALUES(6,x'807f'); +INSERT INTO t1 VALUES(1,NULL); +INSERT INTO t1 VALUES(2,''); +INSERT INTO t1 VALUES(3,1); +INSERT INTO t1 VALUES(4,2.25); +INSERT INTO t1 VALUES(5,'hello'); +INSERT INTO t1 VALUES(6,X'807f'); COMMIT;}} # Verify that the table named [table] is correctly quoted and that @@ -885,7 +883,7 @@ INSERT INTO "table"(rowid,x,y) VALUES(2,12,''); INSERT INTO "table"(rowid,x,y) VALUES(3,23,1); INSERT INTO "table"(rowid,x,y) VALUES(4,34,2.25); INSERT INTO "table"(rowid,x,y) VALUES(5,45,'hello'); -INSERT INTO "table"(rowid,x,y) VALUES(6,56,x'807f'); +INSERT INTO "table"(rowid,x,y) VALUES(6,56,X'807f'); COMMIT;}} # Do not record rowids for a WITHOUT ROWID table. Also check correct quoting @@ -904,12 +902,12 @@ do_test shell1-4.1.4 { } {0 {PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE [ta<>ble](x INTEGER PRIMARY KEY, y) WITHOUT ROWID; -INSERT INTO "ta<>ble"(x,y) VALUES(1,NULL); -INSERT INTO "ta<>ble"(x,y) VALUES(12,''); -INSERT INTO "ta<>ble"(x,y) VALUES(23,1); -INSERT INTO "ta<>ble"(x,y) VALUES(34,2.25); -INSERT INTO "ta<>ble"(x,y) VALUES(45,'hello'); -INSERT INTO "ta<>ble"(x,y) VALUES(56,x'807f'); +INSERT INTO "ta<>ble" VALUES(1,NULL); +INSERT INTO "ta<>ble" VALUES(12,''); +INSERT INTO "ta<>ble" VALUES(23,1); +INSERT INTO "ta<>ble" VALUES(34,2.25); +INSERT INTO "ta<>ble" VALUES(45,'hello'); +INSERT INTO "ta<>ble" VALUES(56,X'807f'); COMMIT;}} # Do not record rowids if the rowid is inaccessible @@ -926,9 +924,9 @@ do_test shell1-4.1.5 { } {0 {PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE t1(_ROWID_,rowid,oid); -INSERT INTO t1(_ROWID_,rowid,oid) VALUES(1,NULL,'alpha'); -INSERT INTO t1(_ROWID_,rowid,oid) VALUES(12,'',99); -INSERT INTO t1(_ROWID_,rowid,oid) VALUES(23,1,x'b0b1b2'); +INSERT INTO t1 VALUES(1,NULL,'alpha'); +INSERT INTO t1 VALUES(12,'',99); +INSERT INTO t1 VALUES(23,1,X'b0b1b2'); COMMIT;}} } else { @@ -943,7 +941,7 @@ do_test shell1-4.1.6 { (4,2.25), (5,'hello'), (6,x'807f'); } catchcmd test2.db {.dump --preserve-rowids} -} {/.* --preserve-rowids option is not compatible with SQLITE_OMIT_VIRTUALTABLE/} +} {1 {The --preserve-rowids option is not compatible with SQLITE_OMIT_VIRTUALTABLE}} } @@ -1022,7 +1020,7 @@ INSERT INTO t1 VALUES(''); INSERT INTO t1 VALUES(1); INSERT INTO t1 VALUES(2.25); INSERT INTO t1 VALUES('hello'); -INSERT INTO t1 VALUES(x'807f');}} +INSERT INTO t1 VALUES(X'807f');}} # Test the output of ".mode insert" with headers # @@ -1033,7 +1031,7 @@ INSERT INTO t1(x) VALUES(''); INSERT INTO t1(x) VALUES(1); INSERT INTO t1(x) VALUES(2.25); INSERT INTO t1(x) VALUES('hello'); -INSERT INTO t1(x) VALUES(x'807f');}} +INSERT INTO t1(x) VALUES(X'807f');}} # Test the output of ".mode insert" # @@ -1044,7 +1042,7 @@ INSERT INTO t3 VALUES(2,''); INSERT INTO t3 VALUES(3,1); INSERT INTO t3 VALUES(4,2.25); INSERT INTO t3 VALUES(5,'hello'); -INSERT INTO t3 VALUES(6,x'807f');}} +INSERT INTO t3 VALUES(6,X'807f');}} # Test the output of ".mode insert" with headers # @@ -1055,7 +1053,7 @@ INSERT INTO t3(x,y) VALUES(2,''); INSERT INTO t3(x,y) VALUES(3,1); INSERT INTO t3(x,y) VALUES(4,2.25); INSERT INTO t3(x,y) VALUES(5,'hello'); -INSERT INTO t3(x,y) VALUES(6,x'807f');}} +INSERT INTO t3(x,y) VALUES(6,X'807f');}} # Test the output of ".mode tcl" # @@ -1071,8 +1069,8 @@ do_test shell1-4.3 { catchcmd test.db ".mode tcl\nselect * from t1;" } {0 {"" "" -1 -2.25 +"1" +"2.25" "hello" "\200\177"}} @@ -1085,15 +1083,15 @@ do_test shell1-4.4 { } catchcmd test.db ".mode tcl\nselect * from t2;" } {0 {"" "" -1 2.25 +"1" "2.25" "hello" "\200\177"}} # Test the output of ".mode tcl" with ".nullvalue" # do_test shell1-4.5 { catchcmd test.db ".mode tcl\n.nullvalue NULL\nselect * from t2;" -} {0 {NULL "" -1 2.25 +} {0 {"NULL" "" +"1" "2.25" "hello" "\200\177"}} # Test the output of ".mode tcl" with Tcl reserved characters @@ -1117,11 +1115,10 @@ do_test shell1-4.6 { # do_test shell1-4.7 { catchcmd test.db ".mode quote\nselect x'0123456789ABCDEF';" -} {0 x'0123456789abcdef'} +} {0 X'0123456789abcdef'} # Test using arbitrary byte data with the shell via standard input/output. # -if 0 { # Causes a valgrind error in TCL. Seems to be a TCL problem. do_test shell1-5.0 { # # NOTE: Skip NUL byte because it appears to be incompatible with command @@ -1188,7 +1185,6 @@ do_test shell1-5.0 { } } } {} -} # These test cases do not work on MinGW if 0 { @@ -1278,7 +1274,7 @@ do_test shell1-7.1.7 { # information. # do_test shell1-8.1 { - catchcmd ":memory:" {.mode batch + catchcmd ":memory:" { -- The pow2 table will hold all the necessary powers of two. CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT); WITH RECURSIVE c(x,v) AS ( @@ -1304,20 +1300,20 @@ do_test_with_ansi_output shell1-8.2 { .mode box SELECT ieee754(47.49) AS x; } -} {0 {╭───────────────────────────────╮ +} {0 {┌───────────────────────────────┐ │ x │ -╞═══════════════════════════════╡ +├───────────────────────────────┤ │ ieee754(6683623321994527,-47) │ -╰───────────────────────────────╯}} +└───────────────────────────────┘}} do_test_with_ansi_output shell1-8.3 { catchcmd ":memory: --box" { select ieee754(6683623321994527,-47) as x; } -} {0 {╭───────╮ +} {0 {┌───────┐ │ x │ -╞═══════╡ +├───────┤ │ 47.49 │ -╰───────╯}} +└───────┘}} do_test shell1-8.4 { catchcmd ":memory: --table" {SELECT ieee754_mantissa(47.49) AS M, ieee754_exponent(47.49) AS E;} } {0 {+------------------+-----+ @@ -1325,42 +1321,35 @@ do_test shell1-8.4 { +------------------+-----+ | 6683623321994527 | -47 | +------------------+-----+}} -do_test shell1-8.4b { - catchcmd ":memory: --psql" \ - {SELECT ieee754_mantissa(47.49) AS M, ieee754_exponent(47.49) AS E;} -} {0 { M | E -------------------+----- - 6683623321994527 | -47}} do_test_with_ansi_output shell1-8.5 { catchcmd ":memory: --box" { create table t(a text, b int); insert into t values ('too long for one line', 1), ('shorter', NULL); .header on -.mode box --wordwrap off .width 10 10 .nullvalue NADA select * from t;} -} {0 {╭────────────┬────────────╮ +} {0 {┌────────────┬────────────┐ │ a │ b │ -╞════════════╪════════════╡ -│ too long f │ 1 │ +├────────────┼────────────┤ +│ too long f │ 1 │ │ or one lin │ │ │ e │ │ ├────────────┼────────────┤ │ shorter │ NADA │ -╰────────────┴────────────╯}} +└────────────┴────────────┘}} #---------------------------------------------------------------------------- # Test cases shell1-9.*: Basic test that "dot" commands and SQL intermix ok. # do_test shell1-9.1 { catchcmd :memory: { -.mode csv --rowsep "\n" +.mode csv /* x */ select 1,2; --x -- .nada ; -.mode csv --rowsep "\n" +.mode csv --x select 2,1; select 3,4; } @@ -1398,30 +1387,4 @@ select base85(zeroblob(2000000000)); } } {/1.*too big.*/} -#---------------------------------------------------------------------------- -# As of 2025-11-17, the default mode is: -# -# qbox --screenwidth auto --linelimit 5 --charlimit 300 --textjsonb on -# -do_test shell1-12.1 { - catchcmd :memory: {.mode tty -quote sql -.print -SELECT jsonb(1234) AS x;} -} {0 { -╭───────────────╮ -│ x │ -╞═══════════════╡ -│ jsonb('1234') │ -╰───────────────╯}} -do_test shell1-12.2 { - catchcmd :memory: {.mode box --textjsonb on -.print -SELECT jsonb(1234) AS x;} -} {0 { -╭──────╮ -│ x │ -╞══════╡ -│ 1234 │ -╰──────╯}} - finish_test diff --git a/test/shell2.test b/test/shell2.test index 7141c4d49..5f700a9a1 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -44,7 +44,7 @@ do_test shell2-1.1.1 { # Shell silently ignores extra parameters. # Ticket [f5cb008a65]. do_test shell2-1.2.1 { - catchcmdex {:memory: -list "select+3" "select+4"} + catchcmdex {:memory: "select+3" "select+4"} } {0 {3 4 }} @@ -64,7 +64,7 @@ do_test shell2-1.3 { UPDATE OR REPLACE t5 SET a = 4 WHERE a = 1; } -} {1 {Error near line 9: too many levels of trigger recursion}} +} {1 {Runtime error near line 9: too many levels of trigger recursion}} @@ -75,8 +75,7 @@ do_test shell2-1.3 { # NB. whitespace is important do_test shell2-1.4.1 { forcedelete foo.db - catchcmd "foo.db" {.mode batch -CREATE TABLE foo(a); + catchcmd "foo.db" {CREATE TABLE foo(a); INSERT INTO foo(a) VALUES(1); SELECT * FROM foo;} } {0 1} @@ -97,9 +96,7 @@ SELECT * FROM foo; # NB. whitespace is important do_test shell2-1.4.3 { forcedelete foo.db - catchcmd "foo.db" { -.mode batch -.echo ON + catchcmd "foo.db" {.echo ON CREATE TABLE foo(a); INSERT INTO foo(a) VALUES(1); SELECT * FROM foo;} @@ -113,9 +110,7 @@ SELECT * FROM foo; # NB. whitespace is important do_test shell2-1.4.4 { forcedelete foo.db - catchcmd "foo.db" { -.mode batch -.echo ON + catchcmd "foo.db" {.echo ON CREATE TABLE foo(a); .echo OFF INSERT INTO foo(a) VALUES(1); @@ -129,9 +124,7 @@ SELECT * FROM foo;} # NB. whitespace is important do_test shell2-1.4.5 { forcedelete foo.db - catchcmdex "foo.db" { -.mode batch -.echo ON + catchcmdex "foo.db" {.echo ON CREATE TABLE foo1(a); INSERT INTO foo1(a) VALUES(1); CREATE TABLE foo2(b); @@ -160,9 +153,7 @@ SELECT * FROM foo1; SELECT * FROM foo2; # NB. whitespace is important do_test shell2-1.4.6 { forcedelete foo.db - catchcmdex "foo.db" { -.mode batch -.echo ON + catchcmdex "foo.db" {.echo ON .headers ON CREATE TABLE foo1(a); INSERT INTO foo1(a) VALUES(1); @@ -217,7 +208,6 @@ do_test shell2-1.4.9 { do_test shell2-1.4.9 { forcedelete clone.db set res [catchcmd :memory: [string trim { -.mode batch CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT); INSERT INTO t VALUES (1),(2); .clone clone.db @@ -232,7 +222,6 @@ ifcapable vtab { # See overflow report at https://sqlite.org/forum/forumpost/5d34ce5280 do_test shell2-1.4.10 { set res [catchcmd :memory: [string trim { - .mode batch SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1); SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1); SELECT avg(value),min(value),max(value) FROM generate_series( @@ -259,65 +248,6 @@ do_test shell2-1.4.10 { 0 1 2}} -do_test shell2-1.4.10b { - set res [catchcmd :memory: [string trim { - .mode tty -.print - SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1); - SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1); - SELECT avg(value),min(value),max(value) FROM generate_series( - -9223372036854775808,9223372036854775807,1085102592571150095); - SELECT * FROM generate_series(-9223372036854775808,9223372036854775807, - 9223372036854775807); - SELECT value FROM generate_series(-4611686018427387904, - 4611686018427387904, 4611686018427387904) ORDER BY value DESC; - SELECT * FROM generate_series(0,-2,-1); - SELECT * FROM generate_series(0,-2); - SELECT * FROM generate_series(0,2) LIMIT 3;}]] -} {0 { -╭─────────────────────╮ -│ value │ -╞═════════════════════╡ -│ 9223372036854775807 │ -╰─────────────────────╯ -╭─────────────────────╮ -│ value │ -╞═════════════════════╡ -│ 9223372036854775807 │ -╰─────────────────────╯ -╭────────────┬──────────────────────┬─────────────────────╮ -│ avg(value) │ min(value) │ max(value) │ -╞════════════╪══════════════════════╪═════════════════════╡ -│ -0.5 │ -9223372036854775808 │ 9223372036854775807 │ -╰────────────┴──────────────────────┴─────────────────────╯ -╭──────────────────────╮ -│ value │ -╞══════════════════════╡ -│ -9223372036854775808 │ -│ -1 │ -│ 9223372036854775806 │ -╰──────────────────────╯ -╭──────────────────────╮ -│ value │ -╞══════════════════════╡ -│ 4611686018427387904 │ -│ 0 │ -│ -4611686018427387904 │ -╰──────────────────────╯ -╭───────╮ -│ value │ -╞═══════╡ -│ 0 │ -│ -1 │ -│ -2 │ -╰───────╯ -╭───────╮ -│ value │ -╞═══════╡ -│ 0 │ -│ 1 │ -│ 2 │ -╰───────╯}} } ;# ifcapable vtab ifcapable vtab { @@ -330,17 +260,17 @@ do_test shell2-1.4.11 { close $df set res [catchcmd :memory: [string trim { CREATE TABLE t(line text); -.mode ascii -colsep "\377" -rowsep "\n" +.mode ascii +.separator "\377" "\n" .import dummy.csv t SELECT count(*) FROM t;}]] -} {1 {0 -Error: .import column separator must be ASCII}} +} {0 1} } ;# ifcapable vtab # Bug from forum post 7cbe081746dd3803 # Keywords as column names were producing an error message. do_test shell2-1.4.12 { - set res [catchcmd :memory: [string trim {.mode batch + set res [catchcmd :memory: [string trim { CREATE TABLE "group"("order" text); INSERT INTO "group" VALUES ('ABC'); .sha3sum}]] diff --git a/test/shell4.test b/test/shell4.test index 3614909c7..3ced0702e 100644 --- a/test/shell4.test +++ b/test/shell4.test @@ -136,15 +136,15 @@ SELECT * FROM t1;} do_test shell4-3.1 { set fd [open t1.txt wb] - puts $fd ".mode list\nSELECT 'squirrel';" + puts $fd "SELECT 'squirrel';" close $fd - exec $::CLI_ONLY --noinit :memory: --interactive ".read t1.txt" + exec $::CLI_ONLY :memory: --interactive ".read t1.txt" } {squirrel} do_test_with_ansi_output shell4-3.2 { set fd [open t1.txt wb] - puts $fd ".mode list\nSELECT 'pound: \302\243';" + puts $fd "SELECT 'pound: \302\243';" close $fd - exec $::CLI_ONLY --noinit :memory: --interactive ".read t1.txt" + exec $::CLI_ONLY :memory: --interactive ".read t1.txt" } {pound: £} do_test shell4-4.1 { diff --git a/test/shell5.test b/test/shell5.test index 559dc3ce7..70a2298bc 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -33,15 +33,14 @@ forcedelete test.db test.db-journal test.db-wal # .import FILE TABLE Import data from FILE into TABLE do_test shell5-1.1.1 { catchcmd "test.db" ".import" -} {/1 .line 1: Missing FILE argument.*/} +} {/1 .ERROR: missing FILE argument.*/} do_test shell5-1.1.2 { catchcmd "test.db" ".import FOO" -} {/1 .line 1: Missing TABLE argument.*/} +} {/1 .ERROR: missing TABLE argument.*/} do_test shell5-1.1.3 { # too many arguments catchcmd "test.db" ".import FOO BAR BAD" -} {1 {line 1: .import FOO BAR BAD -line 1: ^--- unknown argument}} +} {/1 .ERROR: extra argument.*/} # .separator STRING Change separator used by output mode and .import do_test shell5-1.2.1 { @@ -60,13 +59,13 @@ do_test shell5-1.2.4 { # column separator should default to "|" do_test shell5-1.3.1.1 { - set res [catchcmd "test.db" ".mode list\n.show"] + set res [catchcmd "test.db" ".show"] list [regexp {colseparator: \"\|\"} $res] } {1} # row separator should default to "\n" do_test shell5-1.3.1.2 { - set res [catchcmd "test.db" ".mode list\n.show"] + set res [catchcmd "test.db" ".show"] list [regexp {rowseparator: \"\\n\"} $res] } {1} @@ -83,7 +82,7 @@ do_test shell5-1.4.1 { forcedelete FOO set res [catchcmd "test.db" {CREATE TABLE t1(a, b); .import FOO t1}] -} {1 {line 2: cannot open "FOO"}} +} {1 {Error: cannot open "FOO"}} # the remainder of these test cases require virtual tables. # @@ -98,9 +97,7 @@ do_test shell5-1.4.2 { forcedelete shell5.csv set in [open shell5.csv w] close $in - set res [catchcmd ":memory:" { -.mode list -ATTACH 'test.db' AS test; + set res [catchcmd ":memory:" {ATTACH 'test.db' AS test; .import -schema test shell5.csv t1 SELECT COUNT(*) FROM test.t1;}] } {0 0} @@ -119,7 +116,7 @@ do_test shell5-1.4.4 { set in [open shell5.csv w] puts $in "1|2|3" close $in - set res [catchcmd ":memory: --list" {ATTACH 'test.db' AS test; + set res [catchcmd ":memory:" {ATTACH 'test.db' AS test; .import --schema test shell5.csv t1}] } {1 {shell5.csv:1: expected 2 columns but found 3 - extras ignored}} @@ -128,7 +125,7 @@ do_test shell5-1.4.5 { set in [open shell5.csv w] puts $in "1|2" close $in - set res [catchcmd "test.db -list" {DELETE FROM t1; + set res [catchcmd "test.db" {DELETE FROM t1; .import shell5.csv t1 SELECT COUNT(*) FROM t1;}] } {0 1} @@ -141,9 +138,7 @@ do_test shell5-1.4.6 { puts $in "2|3" puts $in "3|4" close $in - set res [catchcmd ":memory:" { -.mode list -ATTACH 'test.db' AS test; + set res [catchcmd ":memory:" {ATTACH 'test.db' AS test; .import -schema test shell5.csv t1 SELECT COUNT(*) FROM test.t1;}] } {0 3} @@ -153,9 +148,7 @@ do_test shell5-1.4.7 { set in [open shell5.csv w] puts $in "4,5" close $in - set res [catchcmd ":memory:" { -.mode list -ATTACH 'test.db' AS test; + set res [catchcmd ":memory:" {ATTACH 'test.db' AS test; .separator , .import --schema test shell5.csv t1 SELECT COUNT(*) FROM test.t1;}] @@ -166,13 +159,12 @@ do_test shell5-1.4.8.1 { set in [open shell5.csv w] puts $in "5|Now is the time for all good men to come to the aid of their country." close $in - set res [catchcmd "test.db" {.mode list -.import shell5.csv t1 + set res [catchcmd "test.db" {.import shell5.csv t1 SELECT COUNT(*) FROM t1;}] } {0 5} do_test shell5-1.4.8.2 { - catchcmd "test.db -list" {SELECT b FROM t1 WHERE a='5';} + catchcmd "test.db" {SELECT b FROM t1 WHERE a='5';} } {0 {Now is the time for all good men to come to the aid of their country.}} # import file with 1 row, 2 columns, quoted text data @@ -182,12 +174,12 @@ do_test shell5-1.4.9.1 { set in [open shell5.csv w] puts $in "6|'Now is the time for all good men to come to the aid of their country.'" close $in - set res [catchcmd "test.db -list" {.import shell5.csv t1 + set res [catchcmd "test.db" {.import shell5.csv t1 SELECT COUNT(*) FROM t1;}] } {0 6} do_test shell5-1.4.9.2 { - catchcmd "test.db -list" {SELECT b FROM t1 WHERE a='6';} + catchcmd "test.db" {SELECT b FROM t1 WHERE a='6';} } {0 {'Now is the time for all good men to come to the aid of their country.'}} # import file with 1 row, 2 columns, quoted text data @@ -195,12 +187,12 @@ do_test shell5-1.4.10.1 { set in [open shell5.csv w] puts $in "7|\"Now is the time for all good men to come to the aid of their country.\"" close $in - set res [catchcmd "test.db -list" {.import shell5.csv t1 + set res [catchcmd "test.db" {.import shell5.csv t1 SELECT COUNT(*) FROM t1;}] } {0 7} do_test shell5-1.4.10.2 { - catchcmd "test.db -list" {SELECT b FROM t1 WHERE a='7';} + catchcmd "test.db" {SELECT b FROM t1 WHERE a='7';} } {0 {Now is the time for all good men to come to the aid of their country.}} # import file with 2 rows, 2 columns and an initial BOM @@ -211,7 +203,7 @@ do_test shell5-1.4.11 { puts $in "2|3" puts $in "4|5" close $in - set res [catchcmd "test.db -list" {CREATE TABLE t2(x INT, y INT); + set res [catchcmd "test.db" {CREATE TABLE t2(x INT, y INT); .import shell5.csv t2 .mode quote .header on @@ -226,7 +218,7 @@ do_test shell5-1.4.12 { puts $in "\xef\xbb\xbf\"two\"|3" puts $in "4|5" close $in - set res [catchcmd "test.db -list" {DELETE FROM t2; + set res [catchcmd "test.db" {DELETE FROM t2; .import shell5.csv t2 .mode quote .header on @@ -240,7 +232,7 @@ do_test shell5-1.5.1 { set in [open shell5.csv w] puts $in "8|$str" close $in - set res [catchcmd "test.db -list" {.import shell5.csv t1 + set res [catchcmd "test.db" {.import shell5.csv t1 SELECT length(b) FROM t1 WHERE a='8';}] } {0 999} @@ -260,7 +252,7 @@ do_test shell5-1.6.1 { set in [open shell5.csv w] puts $in $data close $in - set res [catchcmd "test.db -list" {DROP TABLE IF EXISTS t2; + set res [catchcmd "test.db" {DROP TABLE IF EXISTS t2; .import shell5.csv t2 SELECT COUNT(*) FROM t2;}] } {0 1} @@ -276,7 +268,6 @@ do_test shell5-1.7.1 { close $in set res [catchcmd "test.db" {.mode csv .import shell5.csv t3 -.mode quote SELECT COUNT(*) FROM t3;}] } [list 0 $rows] @@ -338,24 +329,6 @@ do_test shell5-1.10 { db eval {SELECT hex(c) FROM t1 ORDER BY rowid} } {636F6C756D6E33 783320220D0A64617461222033 783320220A64617461222033} -# The --escape option -# -do_test shell5-1.10.1 { - set out [open shell5.csv w] - fconfigure $out -translation lf - puts $out {column1,column2,column3,column4} - puts $out "x1,x2%\"x3,\"x3\\\"data\\\"3\",x4" - close $out - db close - forcedelete test.db - catchcmd test.db { - CREATE TABLE t1(a,b,c,d); -.import --csv --qesc \\ --esc % shell5.csv t1 - } - sqlite3 db test.db - db eval {SELECT b, c FROM t1 ORDER BY rowid} -} {column2 column3 x2\"x3 x3\"data\"3} - # Blank last column with \r\n line endings. do_test shell5-1.11 { set out [open shell5.csv w] @@ -522,10 +495,9 @@ do_test shell5-4.4 { CREATE TEMP TABLE t8(a, b, c); .import shell5.csv t8 .nullvalue # -.mode quote SELECT * FROM temp.t8 }] -} {0 '1','2','3'} +} {0 1,2,3} #---------------------------------------------------------------------------- # Tests for the shell automatic column rename. @@ -541,7 +513,7 @@ do_test shell5-5.1 { close $out forcedelete test.db catchcmd test.db {.import -csv shell5.csv t1 -.mode line --colsep ' = ' +.mode line SELECT * FROM t1;} } {1 { ? = 0 x_02 = x2 @@ -557,7 +529,7 @@ Columns renamed during .import shell5.csv due to duplicates: "z" to "z_05", "z" to "z_08"}} -do_test shell5-5.1b { +do_test shell5-5.1 { set out [open shell5.csv w] fconfigure $out -translation lf puts $out {"COW","cow","CoW","cOw"} @@ -567,10 +539,10 @@ do_test shell5-5.1b { catchcmd test.db {.import -csv shell5.csv t1 .mode line SELECT * FROM t1;} -} {1 {COW_1: uuu -cow_2: lll -CoW_3: ulu -cOw_4: lul +} {1 {COW_1 = uuu +cow_2 = lll +CoW_3 = ulu +cOw_4 = lul Columns renamed during .import shell5.csv due to duplicates: "COW" to "COW_1", "cow" to "cow_2", @@ -589,7 +561,7 @@ do_test_with_ansi_output shell5-6.1 { close $out forcedelete test.db catchcmd test.db {.import -csv shell5.csv t1 -.mode line --colsep " = " +.mode line SELECT * FROM t1;} } {0 { あい = 1 うえお = 2}} @@ -604,8 +576,8 @@ do_test_with_ansi_output shell5-6.2 { catchcmd test.db {.import -csv shell5.csv t1 .mode line SELECT * FROM t1;} -} {0 {1: あい -2: うえお}} +} {0 { 1 = あい + 2 = うえお}} # 2024-03-11 https://sqlite.org/forum/forumpost/ca014d7358 # Import into a table that contains computed columns. @@ -616,7 +588,7 @@ do_test shell5-7.1 { puts $out {aaa|bbb} close $out forcedelete test.db - catchcmd ":memory: -list" {CREATE TABLE t1(a TEXT, b TEXT, c AS (a||b)); + catchcmd :memory: {CREATE TABLE t1(a TEXT, b TEXT, c AS (a||b)); .import shell5.csv t1 SELECT * FROM t1;} } {0 aaa|bbb|aaabbb} @@ -633,36 +605,4 @@ do_test shell5-8.1 { catchcmd :memory: {.import --csv shell5.csv '""""""""""""""""""""""""""""""""""""""""""""""'} } {0 {}} - -# 2025-12-29 https://sqlite.org/forum/forumpost/6c1c0e213d -# .import honor .bail -# -do_test shell5-9.1 { - catchcmd ":memory:" { - CREATE TABLE t1(a,b,c INT CHECK(c<>5)); -.bail on -.import -csv <<END t1 -1,2,3 -"a","b","c" -3,4,5 -"q","r","s" -END -SELECT * FROM t1;} -} {1 {<stdin>:7: INSERT failed: CHECK constraint failed: c<>5}} -do_test shell5-9.2 { - catchcmd ":memory:" { - CREATE TABLE t1(a,b,c INT CHECK(c<>5)); -.bail off -.import -csv <<END t1 -1,2,3 -"a","b","c" -3,4,5 -"q","r","s" -END -SELECT * FROM t1;} -} {1 {1|2|3 -a|b|c -q|r|s -<stdin>:7: INSERT failed: CHECK constraint failed: c<>5}} - finish_test diff --git a/test/shell8.test b/test/shell8.test index 40579a599..e55539636 100644 --- a/test/shell8.test +++ b/test/shell8.test @@ -217,46 +217,6 @@ if {$tcl_platform(platform)=="unix"} { do_test 3.3 { catchcmd shell8.db {.ar -x} } {0 {}} - - # Test defenses against using symlinks to write outside - # of the destination directory. See forum thread at - # sqlite.org/forum/forumpost/2026-02-21T11:04:36z - # - forcedelete shell8.db - forcedelete ar1 - forcedelete ar2 - forcedelete ar3 - file mkdir ar2 - file mkdir ar3 - set pwd [pwd] - sqlite3 db shell8.db - db eval { - CREATE TABLE sqlar( - name TEXT PRIMARY KEY, -- name of the file - mode INT, -- access permissions - mtime INT, -- last modification time - sz INT, -- original file size - data BLOB -- compressed content - ); - INSERT INTO sqlar VALUES - ('abc',33188,0,-1,'content for abc'), - ('escape',40960,0,-1,$pwd||'/ar3'), - ('escape/def',33188,0,-1,'content for escape/def'), - ('ghi',33188,0,-1,'content for ghi'); - } - do_test 3.4.1 { - catchcmd shell8.db {.ar -x --directory ar2} - lsort [glob -tails -directory ar2 -nocomplain *] - } {abc escape ghi} - do_test 3.4.2 { - lsort [glob -tails -directory ar3 -nocomplain *] - } {} - # ^^--- An extraction into ar2 should not leak any files into ar3 - - forcedelete shell8.db - forcedelete ar2 - forcedelete ar3 - } finish_test diff --git a/test/shellA.test b/test/shellA.test index 3b28c921c..f3959d428 100644 --- a/test/shellA.test +++ b/test/shellA.test @@ -36,11 +36,11 @@ do_execsql_test shellA-1.0 { # and that our calls to the CLI are working. # do_test_with_ansi_output shellA-1.2 { - exec {*}$CLI -noinit test.db {.mode box -quote off --escape symbol} {SELECT * FROM t1;} + exec {*}$CLI test.db {.mode box --escape symbol} {SELECT * FROM t1;} } { -╭───┬──────────────────────────╮ +┌───┬──────────────────────────┐ │ a │ x │ -╞═══╪══════════════════════════╡ +├───┼──────────────────────────┤ │ 1 │ line with ' single quote │ ├───┼──────────────────────────┤ │ 2 │ ␛[31mVT-100 codes␛[0m │ @@ -57,39 +57,39 @@ do_test_with_ansi_output shellA-1.2 { │ 7 │ carriage␍return │ ├───┼──────────────────────────┤ │ 8 │ last line │ -╰───┴──────────────────────────╯ +└───┴──────────────────────────┘ } # ".mode list" # do_test shellA-1.3 { - exec {*}$CLI -noinit -list test.db {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI test.db {SELECT x FROM t1 WHERE a=2;} } { ^[[31mVT-100 codes^[[0m } do_test_with_ansi_output shellA-1.4 { - exec {*}$CLI -noinit -list test.db --escape symbol {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI test.db --escape symbol {SELECT x FROM t1 WHERE a=2;} } { ␛[31mVT-100 codes␛[0m } do_test shellA-1.5 { - exec {*}$CLI -noinit -list test.db --escape ascii {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI test.db --escape ascii {SELECT x FROM t1 WHERE a=2;} } { ^[[31mVT-100 codes^[[0m } do_test_with_ansi_output shellA-1.6 { - exec {*}$CLI -noinit test.db {.mode list --escape symbol} {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI test.db {.mode list --escape symbol} {SELECT x FROM t1 WHERE a=2;} } { ␛[31mVT-100 codes␛[0m } do_test shellA-1.7 { - exec {*}$CLI -noinit test.db {.mode list --escape ascii} {SELECT x FROM t1 WHERE a=2;} + exec {*}$CLI test.db {.mode list --escape ascii} {SELECT x FROM t1 WHERE a=2;} } { ^[[31mVT-100 codes^[[0m } do_test shellA-1.8 { file delete -force out.txt - exec {*}$CLI -noinit test.db {.mode list --escape off} {SELECT x FROM t1 WHERE a=7;} \ + exec {*}$CLI test.db {.mode list --escape off} {SELECT x FROM t1 WHERE a=7;} \ >out.txt set fd [open out.txt rb] set res [read $fd] @@ -98,93 +98,92 @@ do_test shellA-1.8 { } "carriage\rreturn" do_test shellA-1.9 { set rc [catch { - exec {*}$CLI -noinit test.db {.mode test --escape xyz} + exec {*}$CLI test.db {.mode test --escape xyz} } msg] lappend rc $msg -} {1 {argv[3]: .mode test --escape xyz -argv[3]: ^--- unknown mode -argv[3]: Use ".help .mode" for more info}} +} {1 {unknown control character escape mode "xyz" - choices: ascii symbol off}} do_test shellA-1.10 { set rc [catch { - exec {*}$CLI --noinit --escape abc test.db .q + exec {*}$CLI --escape abc test.db .q } msg] lappend rc $msg -} {1 {unknown control character escape mode "abc" - choices: auto off ascii symbol}} +} {1 {unknown control character escape mode "abc" - choices: ascii symbol off}} # ".mode quote" # do_test shellA-2.1 { - exec {*}$CLI -noinit test.db --quote {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} + exec {*}$CLI test.db --quote {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { 1,'line with '' single quote' 2,unistr('\u001b[31mVT-100 codes\u001b[0m') -6,unistr('new\u000aline') +6,'new +line' 7,unistr('carriage\u000dreturn') 8,'last line' } do_test shellA-2.2 { - exec {*}$CLI -noinit test.db --quote {.mode -v} -} {/*.mode quote* --escape auto*/} + exec {*}$CLI test.db --quote {.mode} +} {current output mode: quote --escape ascii} do_test shellA-2.3 { - exec {*}$CLI -noinit test.db --quote --escape SYMBOL {.mode} -} {.mode quote --escape symbol} + exec {*}$CLI test.db --quote --escape SYMBOL {.mode} +} {current output mode: quote --escape symbol} do_test shellA-2.4 { - exec {*}$CLI -noinit test.db --quote --escape OFF {.mode} -} {.mode quote --escape off} + exec {*}$CLI test.db --quote --escape OFF {.mode} +} {current output mode: quote --escape off} # ".mode line" # do_test_with_ansi_output shellA-3.1 { - exec {*}$CLI -noinit test.db --line --escape symbol \ + exec {*}$CLI test.db --line --escape symbol \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { - a: 1 - x: line with ' single quote + a = 1 + x = line with ' single quote - a: 2 - x: ␛[31mVT-100 codes␛[0m + a = 2 + x = ␛[31mVT-100 codes␛[0m - a: 6 - x: new - line + a = 6 + x = new +line - a: 7 - x: carriage␍return + a = 7 + x = carriage␍return - a: 8 - x: last line + a = 8 + x = last line } do_test shellA-3.2 { - exec {*}$CLI -noinit test.db --line --escape ascii \ + exec {*}$CLI test.db --line --escape ascii \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { - a: 1 - x: line with ' single quote + a = 1 + x = line with ' single quote - a: 2 - x: ^[[31mVT-100 codes^[[0m + a = 2 + x = ^[[31mVT-100 codes^[[0m - a: 6 - x: new - line + a = 6 + x = new +line - a: 7 - x: carriage^Mreturn + a = 7 + x = carriage^Mreturn - a: 8 - x: last line + a = 8 + x = last line } # ".mode box" # do_test_with_ansi_output shellA-4.1 { - exec {*}$CLI -noinit test.db --box --escape ascii \ + exec {*}$CLI test.db --box --escape ascii \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { -╭───┬──────────────────────────╮ +┌───┬──────────────────────────┐ │ a │ x │ -╞═══╪══════════════════════════╡ +├───┼──────────────────────────┤ │ 1 │ line with ' single quote │ ├───┼──────────────────────────┤ │ 2 │ ^[[31mVT-100 codes^[[0m │ @@ -195,56 +194,31 @@ do_test_with_ansi_output shellA-4.1 { │ 7 │ carriage^Mreturn │ ├───┼──────────────────────────┤ │ 8 │ last line │ -╰───┴──────────────────────────╯ -} -do_test_with_ansi_output shellA-4.1b { - exec {*}$CLI -noinit test.db --box --escape ascii \ - {.mode -border off} \ - {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} -} { - a │ x -═══╪══════════════════════════ - 1 │ line with ' single quote -───┼────────────────────────── - 2 │ ^[[31mVT-100 codes^[[0m -───┼────────────────────────── - 6 │ new - │ line -───┼────────────────────────── - 7 │ carriage^Mreturn -───┼────────────────────────── - 8 │ last line +└───┴──────────────────────────┘ } do_test_with_ansi_output shellA-4.2 { - exec {*}$CLI -noinit test.db {.mode qbox} {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} + exec {*}$CLI test.db {.mode qbox} {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { -╭───┬───────────────────────────────────────────╮ +┌───┬───────────────────────────────────────────┐ │ a │ x │ -╞═══╪═══════════════════════════════════════════╡ +├───┼───────────────────────────────────────────┤ │ 1 │ 'line with '' single quote' │ +├───┼───────────────────────────────────────────┤ │ 2 │ unistr('\u001b[31mVT-100 codes\u001b[0m') │ -│ 6 │ unistr('new\u000aline') │ +├───┼───────────────────────────────────────────┤ +│ 6 │ 'new │ +│ │ line' │ +├───┼───────────────────────────────────────────┤ │ 7 │ unistr('carriage\u000dreturn') │ +├───┼───────────────────────────────────────────┤ │ 8 │ 'last line' │ -╰───┴───────────────────────────────────────────╯ -} -do_test_with_ansi_output shellA-4.2b { - exec {*}$CLI -noinit test.db {.mode qbox -border off} \ - {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} -} { - a │ x -═══╪═══════════════════════════════════════════ - 1 │ 'line with '' single quote' - 2 │ unistr('\u001b[31mVT-100 codes\u001b[0m') - 6 │ unistr('new\u000aline') - 7 │ unistr('carriage\u000dreturn') - 8 │ 'last line' +└───┴───────────────────────────────────────────┘ } # ".mode insert" # do_test shellA-5.1 { - exec {*}$CLI -noinit test.db {.mode insert t1 --escape ascii} \ + exec {*}$CLI test.db {.mode insert t1 --escape ascii} \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { INSERT INTO t1 VALUES(1,'line with '' single quote'); @@ -254,7 +228,7 @@ INSERT INTO t1 VALUES(7,unistr('carriage\u000dreturn')); INSERT INTO t1 VALUES(8,'last line'); } do_test shellA-5.2 { - exec {*}$CLI -noinit test.db {.mode insert t1 --escape symbol} \ + exec {*}$CLI test.db {.mode insert t1 --escape symbol} \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} } { INSERT INTO t1 VALUES(1,'line with '' single quote'); @@ -265,7 +239,7 @@ INSERT INTO t1 VALUES(8,'last line'); } do_test shellA-5.3 { file delete -force out.txt - exec {*}$CLI -noinit test.db {.mode insert t1 --escape off} \ + exec {*}$CLI test.db {.mode insert t1 --escape off} \ {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} >out.txt set fd [open out.txt rb] set res [read $fd] @@ -280,83 +254,4 @@ INSERT INTO t1 VALUES(7,'carriage\rreturn'); INSERT INTO t1 VALUES(8,'last line'); " -# ".mode split" -# -do_test shellA-6.1 { - db eval { - CREATE TABLE t2(x); - INSERT INTO t2(x) VALUES - ('one'), ('two'), ('three'), ('four'), ('five'), - ('six'), ('seven'), ('eight'), ('nine'), ('ten'), - ('eleven'), ('twelve'), ('thirteen'), ('fourteen'); - } - exec {*}$CLI -noinit test.db \ - {.print} \ - {.mode split -screenwidth 30} \ - {SELECT x FROM t2} -} { -one five nine thirteen -two six ten fourteen -three seven eleven -four eight twelve} -# 3456789 123456789 123456789 - -do_test shellA-6.2 { - exec {*}$CLI -noinit test.db \ - {.print} \ - {.mode split -screenwidth 30} \ - {SELECT x FROM t2} \ - {.mode column -titles off} \ - {SELECT x FROM t2} -} { -one five nine thirteen -two six ten fourteen -three seven eleven -four eight twelve -one -two -three -four -five -six -seven -eight -nine -ten -eleven -twelve -thirteen -fourteen} - -do_test shellA-6.3 { - exec {*}$CLI -noinit test.db \ - {.print} \ - {.mode table} \ - {.mode --once split -screenwidth 30} \ - {SELECT x FROM t2} \ - {SELECT x FROM t2} -} { -one five nine thirteen -two six ten fourteen -three seven eleven -four eight twelve -+----------+ -| x | -+----------+ -| one | -| two | -| three | -| four | -| five | -| six | -| seven | -| eight | -| nine | -| ten | -| eleven | -| twelve | -| thirteen | -| fourteen | -+----------+} - finish_test diff --git a/test/shellB.test b/test/shellB.test deleted file mode 100644 index d98a77cb5..000000000 --- a/test/shellB.test +++ /dev/null @@ -1,53 +0,0 @@ -# 2025-11-12 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# TESTRUNNER: shell -# -# Test cases for the command-line shell using the newly renovated -# ".testcase" and ".check" commands. -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set CLI [test_cli_invocation] - -# Run an instance of the CLI on the file $name. -# Capture the number of test cases and the number of -# errors and increment the counts. -# -proc do_clitest {name} { - set mapping [list <NAME> $::testdir/$name <CLI> $::CLI] - set script [string map $mapping { - catch {exec <CLI> :memory: ".read <NAME>" 2>@stdout} res - set ntest 0 - set nerr 999 - regexp {(\d+) tests? run with (\d+) errors?} $res all ntest nerr - set_test_counter count [expr {[set_test_counter count]+$ntest-1}] - set_test_counter errors [expr {[set_test_counter errors]+$nerr}] - if {$nerr==0} {set res "error count: 0"} - set res - }] - # puts $script - do_test shellB-$name $script {error count: 0} -} - -do_clitest modeA.sql -do_clitest dblwidth-a.sql -do_clitest vt100-a.sql -do_clitest regexp1.sql -do_clitest imposter1.sql -do_clitest dotcmd01.sql -ifcapable vtab { - do_clitest import01.sql - do_clitest intck01.sql -} -do_clitest fptest01.sql - -finish_test diff --git a/test/speedtest.md b/test/speedtest.md index 98cba93ff..135e562ae 100644 --- a/test/speedtest.md +++ b/test/speedtest.md @@ -8,9 +8,8 @@ You will need: * valgrind * tclsh * A script or program named "open" that brings up *.txt files in an - editor for viewing. (Macs provide this by default. On Linux it's - called xdg-open and some distributions symlink it to "open". You'll - need to come up with your own on Windows.) + editor for viewing. (Macs provide this by default. You'll need to + come up with your own on Linux and Windows.) * An SQLite source tree The procedure described in this document is not the only way to make diff --git a/test/speedtest.tcl b/test/speedtest.tcl index 26bc27530..9cb81c0fc 100755 --- a/test/speedtest.tcl +++ b/test/speedtest.tcl @@ -27,7 +27,7 @@ Other options include: --lookaside N SZ Lookahead uses N slots of SZ bytes each. --osmalloc Use the OS native malloc() instead of MEMSYS5 --pagesize N Use N as the page size. - --quiet | -q "Quiet". Put results in file but don't pop up editor + --quiet | -q "Quite". Put results in file but don't pop up editor --size N Change the test size. 100 means 100%. Default: 5. --testset TEST Specify the specific testset to use. The default is "mix1". Other options include: "main", "json", diff --git a/test/tabfunc01.test b/test/tabfunc01.test index 60f546ce4..9a2017c46 100644 --- a/test/tabfunc01.test +++ b/test/tabfunc01.test @@ -668,20 +668,6 @@ do_execsql_test 1370 { SELECT * FROM generate_series(0,0,0); } {} -reset_db -load_static_extension db series -do_execsql_test 1400 { - CREATE TABLE t1(x); - CREATE TABLE t2(y); -} -do_catchsql_test 1410 { - SELECT x, y, value - FROM (t1 RIGHT JOIN generate_series(t2.y,5) AS value) JOIN t2; -} {1 {table-function argument references tables to its right}} -do_catchsql_test 1420 { - SELECT x, y, value - FROM t2 JOIN (t1 RIGHT JOIN generate_series(t2.y,5) AS value) -} {1 {no such column: t2.y}} # Free up memory allocations diff --git a/test/tclsqlite.test b/test/tclsqlite.test index 6cababad3..5f373ea18 100644 --- a/test/tclsqlite.test +++ b/test/tclsqlite.test @@ -42,7 +42,7 @@ do_test tcl-1.1.1 { do_test tcl-1.2 { set v [catch {db bogus} msg] lappend v $msg -} {1 {bad option "bogus": must be authorizer, backup, bind_fallback, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, config, copy, deserialize, enable_load_extension, errorcode, erroroffset, eval, exists, format, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, serialize, status, timeout, total_changes, trace, trace_v2, transaction, unlock_notify, update_hook, version, or wal_hook}} +} {1 {bad option "bogus": must be authorizer, backup, bind_fallback, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, config, copy, deserialize, enable_load_extension, errorcode, erroroffset, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, serialize, status, timeout, total_changes, trace, trace_v2, transaction, unlock_notify, update_hook, version, or wal_hook}} do_test tcl-1.2.1 { set v [catch {db cache bogus} msg] lappend v $msg diff --git a/test/temptrigfault.tes b/test/temptrigfault.tes deleted file mode 100644 index 4b124e1f7..000000000 --- a/test/temptrigfault.tes +++ /dev/null @@ -1,120 +0,0 @@ -# 2025 November 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. -# -#*********************************************************************** -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -set testprefix temptrigfault - -forcedelete test.db2 -do_execsql_test 1.0 { - CREATE TABLE t1(x, y); - ATTACH 'test.db2' AS aux; - CREATE TABLE aux.t1(x, y); -} - -do_faultsim_test 1.1 -faults oom* -prep { -} -body { - execsql { - CREATE TEMP TRIGGER tmptrig AFTER INSERT ON t1 BEGIN - INSERT INTO aux.t1 VALUES(new.x, new.y); - END; - } -} -test { - faultsim_test_result {0 {}} - catchsql { DROP TRIGGER tmptrig } -} - -do_execsql_test 2.0 { - CREATE TEMP TRIGGER tmptrig AFTER INSERT ON t1 BEGIN - INSERT INTO aux.t1 VALUES(new.x, new.y); - END; -} - -do_faultsim_test 2 -faults oom* -prep { -} -body { - execsql { - INSERT INTO t1 VALUES('x', 'y'); - } -} -test { - faultsim_test_result {0 {}} -} - -do_execsql_test 3.0.1 { - SELECT * FROM t1; - DELETE FROM t1; - DELETE FROM aux.t1; -} [db eval {SELECT * FROM aux.t1}] - -do_execsql_test 3.0.2 { - CREATE TEMP TRIGGER tmptrig2 AFTER INSERT ON aux.t1 BEGIN - INSERT INTO t1 VALUES(new.x||'2', new.y||'2'); - END; -} - -do_faultsim_test 3 -faults oom* -prep { -} -body { - execsql { - INSERT INTO aux.t1 VALUES('aaa', 'bbb'); - } -} -test { - faultsim_test_result {0 {}} -} - -proc repeatlist {list n} { - set ret [list] - for {set i 0} {$i < $n} {incr i} { - set ret [concat $ret $list] - } - set ret -} - -do_execsql_test 3.x.1 { - SELECT * FROM main.t1; -} [repeatlist {aaa2 bbb2} 5] - -do_execsql_test 3.x.2 { - SELECT * FROM aux.t1; -} [repeatlist {aaa bbb aaa2 bbb2} 5] - -faultsim_save_and_close -do_faultsim_test 4 -faults oom* -prep { - faultsim_restore_and_reopen - execsql { ATTACH 'test.db2' AS aux; } -} -body { - execsql { - CREATE TEMP TRIGGER xyz AFTER DELETE ON main.t1 BEGIN - DELETE FROM aux.t1 WHERE rowid=old.rowid; - END; - - DELETE FROM t1 WHERE rowid=2; - } -} -test { - faultsim_test_result {0 {}} {1 {unable to open a temporary database file for storing temporary tables}} -} - -faultsim_save_and_close -do_faultsim_test 5 -faults oom* -prep { - faultsim_restore_and_reopen - execsql { ATTACH 'test.db2' AS aux; } -} -body { - execsql { - CREATE TEMP TRIGGER xyz AFTER UPDATE ON aux.t1 BEGIN - UPDATE main.t1 SET x=new.x, y=new.y WHERE rowid=new.rowid; - END; - UPDATE aux.t1 SET x=x||x WHERE rowid=1+abs(random() % 5); - } -} -test { - faultsim_test_result {0 {}} {1 {unable to open a temporary database file for storing temporary tables}} -} - - -finish_test diff --git a/test/temptrigger.test b/test/temptrigger.test index 74390f3b8..e4277adf6 100644 --- a/test/temptrigger.test +++ b/test/temptrigger.test @@ -276,191 +276,4 @@ do_catchsql_test 6.3 { } {1 error} db2 close -#------------------------------------------------------------------------- -reset_db -forcedelete test.db2 - -do_execsql_test 7.0 { - CREATE TABLE m1(a, b); - ATTACH 'test.db2' AS aux; - CREATE TABLE aux.a1(c, d); -} - -do_execsql_test 7.1 { - CREATE TEMP TRIGGER tr1 AFTER INSERT ON m1 BEGIN - INSERT INTO a1 VALUES(new.a, new.b); - END; - - INSERT INTO m1 VALUES(5, 6); - SELECT * FROM aux.a1; -} {5 6} - -do_execsql_test 7.2 { - CREATE TABLE a1(e, f); - INSERT INTO m1 VALUES(7, 8); -} - -do_execsql_test 7.3.1 { SELECT * FROM main.a1 } {7 8} -do_execsql_test 7.3.2 { SELECT * FROM aux.a1 } {5 6} - -do_execsql_test 7.4 { - DROP TRIGGER tr1; - CREATE TEMP TRIGGER tr1 AFTER INSERT ON m1 BEGIN - INSERT INTO a1 SELECT d, c FROM aux.a1; - END; - - DELETE FROM aux.a1; - DELETE FROM main.a1; - INSERT INTO aux.a1 VALUES('hello', 'world'); -} - -do_execsql_test 7.5 { - INSERT INTO m1 VALUES(9, 10); - SELECT * FROM main.a1; -} {world hello} - -do_catchsql_test 7.6 { - DROP TRIGGER tr1; - CREATE TRIGGER tr1 AFTER INSERT ON m1 BEGIN - INSERT INTO a1 SELECT d, c FROM aux.a1; - END; -} {1 {trigger tr1 cannot reference objects in database aux}} - -#------------------------------------------------------------------------- -# Check that temp triggers may INSERT/UPDATE/DELETE to fully qualified -# table names. -reset_db -forcedelete {*}[glob -nocomplain *mj*] -forcedelete test.db2 -do_execsql_test 8.0 { - ATTACH 'test.db2' AS aux; - CREATE TABLE t1(a, b); - CREATE TABLE t2(c, d); - CREATE TABLE aux.t1(e, f); - CREATE TABLE aux.t2(g, h); -} - -do_catchsql_test 8.1.1 { - CREATE TRIGGER tr1 AFTER INSERT ON t2 BEGIN - INSERT INTO aux.t1 VALUES(new.c, new.d); - END; -} {1 {qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers}} - -do_execsql_test 8.1.2 { - CREATE TEMP TRIGGER tr1 AFTER INSERT ON t2 BEGIN - INSERT INTO aux.t1 VALUES(new.c, new.d); - END; - - INSERT INTO main.t2 VALUES('x', 'y'); - SELECT * FROM aux.t1; -} {x y} - -do_execsql_test 8.1.3 { SELECT * FROM t1 } {} - -do_catchsql_test 8.2.1 { - CREATE TRIGGER aux.tr2 AFTER UPDATE ON aux.t1 BEGIN - UPDATE main.t2 SET c=new.e, d=new.f; - END; -} {1 {qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers}} - -do_execsql_test 8.2.2 { - CREATE TEMP TRIGGER tr2 AFTER UPDATE ON aux.t1 BEGIN - UPDATE main.t2 SET c=new.e, d=new.f; - END; - - UPDATE aux.t1 SET e=1, f=2; - SELECT * FROM t2; -} {1 2} - -do_execsql_test 8.2.3 { SELECT * FROM aux.t2 } {} - -do_catchsql_test 8.3.1 { - CREATE TRIGGER tr3 AFTER DELETE ON t2 BEGIN - DELETE FROM aux.t1; - END; -} {1 {qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers}} - -do_execsql_test 8.3.2 { - INSERT INTO main.t1 VALUES('a', 'b'); - CREATE TEMP TRIGGER tr3 AFTER DELETE ON t2 BEGIN - DELETE FROM aux.t1; - END; - - DELETE FROM main.t2; - SELECT * FROM aux.t1; -} {} - -do_execsql_test 8.3.3 { SELECT * FROM t1 } {a b} - -#------------------------------------------------------------------------- -reset_db -set nDb 8 -do_test 9.0 { - for {set ii 0} {$ii < $nDb} {incr ii} { - db eval "ATTACH ':memory:' AS db$ii" - db eval "CREATE TABLE db$ii.tbl(a, b, c)" - } - - for {set ii 0} {$ii < ($nDb-1)} {incr ii} { - set jj [expr $ii+1] - db eval " - CREATE TEMP TRIGGER tr$ii AFTER INSERT ON db$ii.tbl BEGIN - INSERT INTO db$jj.tbl VALUES(new.b, new.c, new.a); - END; - " - } -} {} - -do_execsql_test 9.1 { INSERT INTO db0.tbl VALUES('a', 'b', 'c'); } -do_execsql_test 9.1.1 { SELECT * FROM db0.tbl } {a b c} -do_execsql_test 9.1.2 { SELECT * FROM db1.tbl } {b c a} -do_execsql_test 9.1.3 { SELECT * FROM db2.tbl } {c a b} -do_execsql_test 9.1.1 { SELECT * FROM db3.tbl } {a b c} -do_execsql_test 9.1.2 { SELECT * FROM db4.tbl } {b c a} -do_execsql_test 9.1.3 { SELECT * FROM db5.tbl } {c a b} -do_execsql_test 9.1.1 { SELECT * FROM db6.tbl } {a b c} -do_execsql_test 9.1.2 { SELECT * FROM db7.tbl } {b c a} - -do_test 9.2 { - for {set ii 0} {$ii < ($nDb-1)} {incr ii} { - set jj [expr $ii+1] - db eval " - CREATE TEMP TRIGGER tru$ii AFTER UPDATE ON db$ii.tbl BEGIN - UPDATE db$jj.tbl SET a=new.b, b=new.c, c=new.a; - END; - " - } -} {} - -do_execsql_test 9.3 { UPDATE db0.tbl SET a=1, b=2, c=3 } -do_execsql_test 9.3.1 { SELECT * FROM db0.tbl } {1 2 3} -do_execsql_test 9.3.2 { SELECT * FROM db1.tbl } {2 3 1} -do_execsql_test 9.3.3 { SELECT * FROM db2.tbl } {3 1 2} -do_execsql_test 9.3.1 { SELECT * FROM db3.tbl } {1 2 3} -do_execsql_test 9.3.2 { SELECT * FROM db4.tbl } {2 3 1} -do_execsql_test 9.3.3 { SELECT * FROM db5.tbl } {3 1 2} -do_execsql_test 9.3.1 { SELECT * FROM db6.tbl } {1 2 3} -do_execsql_test 9.3.2 { SELECT * FROM db7.tbl } {2 3 1} - -do_test 9.4 { - for {set ii 0} {$ii < ($nDb-1)} {incr ii} { - set jj [expr $ii+1] - db eval " - CREATE TEMP TRIGGER trd$ii BEFORE DELETE ON db$ii.tbl BEGIN - DELETE FROM db$jj.tbl; - END; - " - } -} {} - -do_execsql_test 9.5 { DELETE FROM db0.tbl } -do_execsql_test 9.5.1 { SELECT * FROM db0.tbl } {} -do_execsql_test 9.5.2 { SELECT * FROM db1.tbl } {} -do_execsql_test 9.5.3 { SELECT * FROM db2.tbl } {} -do_execsql_test 9.5.1 { SELECT * FROM db3.tbl } {} -do_execsql_test 9.5.2 { SELECT * FROM db4.tbl } {} -do_execsql_test 9.5.3 { SELECT * FROM db5.tbl } {} -do_execsql_test 9.5.1 { SELECT * FROM db6.tbl } {} -do_execsql_test 9.5.2 { SELECT * FROM db7.tbl } {} - finish_test diff --git a/test/tester.tcl b/test/tester.tcl index 856df5421..3fad39668 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -1785,6 +1785,11 @@ proc crashsql {args} { # cfSync(), which can be different then what TCL uses by # default, so here we force it to the "nativename" format. set cfile [string map {\\ \\\\} [file nativename [file join [get_pwd] $crashfile]]] + ifcapable winrt { + # Except on winrt. Winrt has no way to transform a relative path into + # an absolute one, so it just uses the relative paths. + set cfile $crashfile + } set f [open crash.tcl w] puts $f "sqlite3_initialize ; sqlite3_shutdown" diff --git a/test/testrunner.tcl b/test/testrunner.tcl index 1f81690f2..515036368 100755 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -101,7 +101,6 @@ Usage: $a0 help $a0 joblist ?PATTERN? $a0 njob ?NJOB? - $a0 retest $a0 script ?-msvc? CONFIG $a0 status ?-d SECS? ?--cls? $a0 halt @@ -121,21 +120,20 @@ Usage: --stop-on-error Stop running after any reported error --zipvfs ZIPVFSDIR ZIPVFS source directory -Special values for PERMUTATION include: +Special values for PERMUTATION that work with plain tclsh: - list - show allowed PERMUTATION arguments. + list - show all allowed PERMUTATION arguments. mdevtest - tests recommended prior to normal development check-ins. - devtest - alias for "mdevtest" release - full release test with various builds. sdevtest - like mdevtest but using ASAN and UBSAN. + +Other PERMUTATION arguments must be run using testfixture, not tclsh: + all - all tcl test scripts, plus a subset of test scripts rerun with various permutations. full - all tcl test scripts. veryquick - a fast subset of the tcl test scripts. This is the default. -The interpreter that runs this script can be an ordinary "tclsh" as long -as "package require sqlite3" works, or it can be "testfixture". - If no PATTERN arguments are present, all tests specified by the PERMUTATION are run. Otherwise, each pattern is interpreted as a glob pattern. Only those tcl tests for which the final component of the filename matches at @@ -169,9 +167,6 @@ only the parts that contain the error messages. The --summary option just shows the jobs that failed. If PATTERN are provided, the error information is only provided for jobs that match PATTERN. -The "retest" command reruns tests that failed or were never completed -by a prior invocation of testrunner.tcl. - Full documentation here: https://sqlite.org/src/doc/trunk/doc/testrunner.md }]] @@ -217,8 +212,7 @@ proc default_njob {} { if {$nCore<=2} { set nHelper 1 } else { - set nHelper [expr int($nCore*0.8)] - if {$nHelper>20} {set nHelper 20} + set nHelper [expr int($nCore*0.5)] } return $nHelper } @@ -303,8 +297,6 @@ switch -nocase -glob -- $tcl_platform(os) { error "cannot determine platform!" } } -set TRG(testfixture-fullpath) [file join $dir $TRG(testfixture)] -set TRG(interp) [info nameofexec] #------------------------------------------------------------------------- #------------------------------------------------------------------------- @@ -714,18 +706,9 @@ if {[llength $argv]>=1 } } - set once 1 - while {![file readable $TRG(dbname)]} { - if {$delay==0} { - puts "Database missing: $TRG(dbname)" - exit - } - if {$once} { - set once 0 - puts "Waiting for testing to start...." - flush stdout - } - after [expr {$delay*1000}] + if {![file readable $TRG(dbname)]} { + puts "Database missing: $TRG(dbname)" + exit } sqlite3 mydb $TRG(dbname) mydb timeout 2000 @@ -1165,7 +1148,7 @@ proc add_tcl_jobs {build config patternlist {shelldepid ""}} { set testrunner_tcl [file normalize [info script]] if {$build==""} { - set testfixture $TRG(interp) + set testfixture [info nameofexec] } else { set testfixture [file join [lindex $build 1] $TRG(testfixture)] } @@ -1286,26 +1269,6 @@ proc add_fuzztest_jobs {buildname patternlist} { set subcmd [lrange $interpreter 1 end] set interpreter [lindex $interpreter 0] - # For fuzzcheck-asan and fuzzcheck-ubsan, break up some - # fuzzdata files into multiple slices, for improved - # concurrency. - # - if {[string match *fuzzcheck-*san $interpreter]} { - set newscripts {} - foreach s $scripts { - if {[string match {*fuzzdata[12].db} $s] - && ![string match slice $s]} { - set N 6 - for {set i 0} {$i<$N} {incr i} { - lappend newscripts [list --slice $i $N $s] - } - } else { - lappend newscripts $s - } - } - set scripts $newscripts - } - if {[string match fuzzcheck* $interpreter] && [info exists env(FUZZDB)] && [file readable $env(FUZZDB)] @@ -1396,30 +1359,14 @@ proc add_devtest_jobs {lBld patternlist} { } } -# Check to ensure that TRG(interp) is a full-blown "testfixture" and -# not just a "tclsh". -# -# The value of TRG(interp) defaults to whatever interpreter is running -# this script, which might be either tclsh or testfixture. If tclsh is -# running this script, change $TRG(interp) to be an instance of testfixture. -# If no testfixture exists in the directory from which this script is run, -# attempt to build one. -# -# Do not return unless $TRG(interp) is a valid testfixture. If unable -# to find and/or construct one, abort with an error message. +# Check to ensure that the interpreter is a full-blown "testfixture" +# build and not just a "tclsh". If this is not the case, issue an +# error message and exit. # proc must_be_testfixture {} { - global TRG if {[lsearch [info commands] sqlite3_soft_heap_limit]<0} { - if {![file exec $TRG(testfixture-fullpath)]} { - puts "make testfixture" - catch {exec make testfixture >@stdout 2>@stderr} - } - if {![file exec $TRG(testfixture-fullpath)]} { - puts "Requires testfixture, and I was unable to build it." - exit 1 - } - set TRG(interp) $TRG(testfixture-fullpath) + puts "Use testfixture, not tclsh, for these arguments." + exit 1 } } @@ -1494,15 +1441,11 @@ proc add_jobs_from_cmdline {patternlist} { list { set allperm [array names ::testspec] - lappend allperm all devtest mdevtest sdevtest release list + lappend allperm all mdevtest sdevtest release list puts "Allowed values for the PERMUTATION argument: [lsort $allperm]" exit 0 } - retest { - # no-op - } - default { must_be_testfixture if {[info exists ::testspec($first)]} { @@ -1539,8 +1482,6 @@ proc add_jobs_from_cmdline {patternlist} { } } -# Initializer, or reinitialize, the testrunner.db database file. -# proc make_new_testset {} { global TRG @@ -1584,8 +1525,7 @@ proc mark_job_as_finished {jobid output state endtm} { SET output=$output, state=$state, endtime=$endtm, span=$endtm-starttime, ntest=$ntest, nerr=$nerr, svers=$svers, pltfm=$pltfm WHERE jobid=$jobid; - UPDATE jobs SET state=$childstate - WHERE depid=$jobid AND state!='halt' AND state!='done'; + UPDATE jobs SET state=$childstate WHERE depid=$jobid AND state!='halt'; UPDATE config SET value=value+$nerr WHERE name='nfail'; UPDATE config SET value=value+$ntest WHERE name='ntest'; } @@ -1657,7 +1597,7 @@ proc launch_another_job {iJob} { global O global T - set testfixture $TRG(interp) + set testfixture [info nameofexec] set script $TRG(info_script) set O($iJob) "" @@ -1858,27 +1798,6 @@ proc run_testset {} { } -# If the argument is "retest", simply rerun all tests from the previous -# run that are marked as one of "ready", "running", "failed", or "omit" -# plus redo any build of dependencies those tests. -# -proc handle_retest {} { - set cnt 0 - if {[catch {trdb exists {SELECT jobid FROM jobs}} cnt] || $cnt==0} { - puts "No test available to rerun" - exit 1 - } - trdb eval {UPDATE jobs SET state='ready' - WHERE state IN ('running','failed','omit')} - for {set kk 0} {$kk<2} {incr kk} { - trdb eval { - UPDATE jobs SET state='ready' - WHERE jobid IN (SELECT depid FROM jobs WHERE state='ready'); - UPDATE jobs SET state='' WHERE state='ready' AND depid<>''; - } - } -} - # Handle the --buildonly option, if it was specified. # proc handle_buildonly {} { @@ -1917,21 +1836,14 @@ proc explain_tests {} { sqlite3 trdb $TRG(dbname) trdb timeout $TRG(timeout) -if {[llength $TRG(patternlist)]==1 && $TRG(patternlist) eq "retest"} { - set tm 0 - handle_retest -} else { - set tm [lindex [time { make_new_testset }] 0] -} +set tm [lindex [time { make_new_testset }] 0] if {$TRG(explain)} { explain_tests } else { if {$TRG(nJob)>1} { puts "splitting work across $TRG(nJob) cores" } - if {$tm>0} { - puts "built testset in [expr $tm/1000]ms.." - } + puts "built testset in [expr $tm/1000]ms.." handle_buildonly run_testset } diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index 4daee0274..e74caee1d 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -37,6 +37,7 @@ namespace eval trd { set tcltest(win.Windows-Memdebug) veryquick set tcltest(win.Windows-Win32Heap) veryquick set tcltest(win.Windows-Sanitize) veryquick + set tcltest(win.Windows-WinRT) veryquick set tcltest(win.Default) {full win_unc_locking} # Extra [make xyz] tests that should be run for various builds. @@ -192,7 +193,6 @@ namespace eval trd { -DSQLITE_ENABLE_HIDDEN_COLUMNS -DSQLITE_MAX_ATTACHED=125 -DSQLITE_MUTATION_TEST - -DSQLITE_THREAD_MISUSE_ABORT --enable-fts5 } set build(Debug-Two) { @@ -364,6 +364,12 @@ namespace eval trd { set build(Windows-Sanitize) { ASAN=1 } + + set build(Windows-WinRT) { + FOR_WINRT=1 + ENABLE_SETLK=1 + -DSQLITE_TEMP_STORE=3 + } } diff --git a/test/testrunner_estwork.tcl b/test/testrunner_estwork.tcl index e02eb22dc..c139394a5 100644 --- a/test/testrunner_estwork.tcl +++ b/test/testrunner_estwork.tcl @@ -364,7 +364,6 @@ set estwork(shell6.test) 3 set estwork(shell8.test) 104 set estwork(shell9.test) 3 set estwork(shellA.test) 2 -set estwork(shellB.test) 2 set estwork(shmlock.test) 27 set estwork(sidedelete.test) 10 set estwork(skipscan1.test) 7 diff --git a/test/tkt-99378177930f87bd.test b/test/tkt-99378177930f87bd.test index 495867280..ba9fdc702 100644 --- a/test/tkt-99378177930f87bd.test +++ b/test/tkt-99378177930f87bd.test @@ -33,8 +33,6 @@ do_execsql_test tkt-99378-100 { (2, '{"x":2}', 4, 5), (3, '{"x":1}', 6, 7); CREATE INDEX t1x ON t1(d, a, b->>'x', c); - CREATE TABLE t2(y); - INSERT INTO t2(y) VALUES(9); } {} do_execsql_test tkt-99378-110 { SELECT a, @@ -50,20 +48,6 @@ do_execsql_test tkt-99378-110 { 2 2 1 26 22 3 1 1 6 6 } -do_execsql_test tkt-99378-111 { - SELECT if(a,a,y), - SUM(1) AS t1, - SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, - SUM(c) AS t3, - SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 - FROM t2 CROSS JOIN t1 - WHERE d BETWEEN 0 and 10 - GROUP BY a; -} { - 1 2 1 16 12 - 2 2 1 26 22 - 3 1 1 6 6 -} # The proof that the index on the expression is being used is in the # fact that the byte code contains no "Function" opcodes. In other words, @@ -81,17 +65,6 @@ do_execsql_test tkt-99378-120 { WHERE d BETWEEN 0 and 10 GROUP BY a; } {~/Function/} -do_execsql_test tkt-99378-121 { - EXPLAIN - SELECT if(a,a,y), - SUM(1) AS t1, - SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, - SUM(c) AS t3, - SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 - FROM t2 CROSS JOIN t1 - WHERE d BETWEEN 0 and 10 - GROUP BY a; -} {~/Function/} do_execsql_test tkt-99378-130 { @@ -209,7 +182,6 @@ do_execsql_test tkt-99378-310 { # do_execsql_test tkt-99378-400 { DROP TABLE t1; - DROP TABLE t2; CREATE TABLE t0(w); INSERT INTO t0(w) VALUES(1); CREATE TABLE t1(x); diff --git a/test/tkt2339.test b/test/tkt2339.test index 4bbedb828..41acd377c 100644 --- a/test/tkt2339.test +++ b/test/tkt2339.test @@ -47,12 +47,12 @@ do_test tkt2339.2 { } } {4 3 14 13} do_test tkt2339.3 { - lsort -integer [execsql { + execsql { SELECT * FROM (SELECT * FROM t1 ORDER BY num DESC) UNION ALL SELECT * FROM (SELECT * FROM t2 ORDER BY num DESC LIMIT 2) - }] -} {1 2 3 4 13 14} + } +} {4 3 2 1 14 13} do_test tkt2339.4 { execsql { SELECT * FROM (SELECT * FROM t1 ORDER BY num DESC LIMIT 2) diff --git a/test/values.test b/test/values.test index 58e764ce6..c3c52ceb1 100644 --- a/test/values.test +++ b/test/values.test @@ -21,9 +21,9 @@ do_execsql_test 1.0 { } -#explain_i { -# INSERT INTO x1(a, b, c) VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); -#} +explain_i { + INSERT INTO x1(a, b, c) VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); +} do_execsql_test 1.1.1 { INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); } diff --git a/test/vt02.c b/test/vt02.c index e08d649c1..06fade096 100644 --- a/test/vt02.c +++ b/test/vt02.c @@ -45,29 +45,8 @@ ** a=... AND b=... AND c=... ** a=... AND b=... AND c=... AND d=... ** -** The table will also recognize IN constraints on column D using the -** sqlite3_vtab_in_first() and sqlite3_vtab_in_next() interfaces: -** -** a=... AND d IN (...) -** a=... AND b=... AND d IN (...) -** a=... AND b=... AND c=... AND d IN (...) -** -** Various ORDER BY constraints are also recognized and consumed. -** -** ORDER BY x -** ORDER BY a -** ORDER BY a, b -** ORDER BY a, b, c -** ORDER BY a, b, c, d -** -** The above also work if every term is DESC rather than ASC. However, -** the orderByConsumed is not set if there are a mixture of ASC and DESC -** terms in the ORDER BY clause. -** -** The sqlite3_vtab_distinct() interface is used to recognize DISTINCT -** and GROUP BY constraints and consume the corresponding sorting requirements. -** -** The OFFSET constraint is recognized and consumed. +** Various ORDER BY constraints are also recognized and consumed. The +** OFFSET constraint is recognized and consumed. ** ** ## TABLE-VALUED FUNCTION ** @@ -111,13 +90,6 @@ ** vector of results sent to xFilter. Only the first ** few are used, as required by idxNum. ** -** 0x80 If sqlite3_vtab_distinct() says that duplicate rows -** may be omitted (values 2 or 3), then maybe omit -** some but not all of the duplicate rows. -** -** 0x100 Do not omit any duplicate rows even if -** sqlite3_vtab_distinct() says that is ok to do. -** ** Because these flags take effect during xBestIndex, the RHS of the ** flag= constraint must be accessible. In other words, the RHS of flag= ** needs to be an integer literal, not another column of a join or a @@ -164,12 +136,14 @@ ** ## COMPILING AND RUNNING ** ** This file can also be compiled separately as a loadable extension -** for SQLite (as long as the -DTH3_VERSION is not defined). To compile as a -** loadable extension do something like this: +** for SQLite (as long as the -DTH3_VERSION is not defined). To compile as a +** loadable extension do his: +** +** gcc -Wall -g -shared -fPIC -I. -DSQLITE_DEBUG vt02.c -o vt02.so ** -** (linux) gcc -shared -fPIC -I. vt02.c -o vt02.so -** (mac) clang -dynamiclib -fPIC -I. vt02.c -o vt02.dylib -** (windows) cl vt02.c -link -dll -out:vt02.dll +** Or on Windows: +** +** cl vt02.c -link -dll -out:vt02.dll ** ** Then load into the CLI using: ** @@ -193,7 +167,6 @@ ** 2x increment by 100 ** 3x increment by 1000 ** 1xx Use offset provided by argv[N] -** 1xxx Reverse output order (to implement ORDER BY ... DESC) */ #ifndef TH3_VERSION /* These bits for separate compilation as a loadable extension, only */ @@ -222,8 +195,6 @@ struct vt02_vtab { #define VT02_NO_OFFSET 0x0004 /* Omit the offset optimization */ #define VT02_ALLOC_IDXSTR 0x0008 /* Alloate an idxStr */ #define VT02_BAD_IDXNUM 0x0010 /* Generate an invalid idxNum */ -#define VT02_PARTIAL_DEDUP 0x0080 /* Omit some but not all duplicate rows */ -#define VT02_NO_DEDUP 0x0100 /* Include all duplicate rows */ /* ** A cursor @@ -232,7 +203,6 @@ struct vt02_cur { sqlite3_vtab_cursor parent; /* Base class. Must be first */ sqlite3_int64 i; /* Current entry */ sqlite3_int64 iEof; /* Indicate EOF when reaching this value */ - sqlite3_int64 iMin; /* EOF if dropping below this value */ int iIncr; /* Amount by which to increment */ unsigned int mD; /* Mask of allowed D-column values */ }; @@ -322,7 +292,7 @@ static int vt02Close(sqlite3_vtab_cursor *pCursor){ */ static int vt02Eof(sqlite3_vtab_cursor *pCursor){ vt02_cur *pCur = (vt02_cur*)pCursor; - return pCur->i<pCur->iMin || pCur->i>=pCur->iEof; + return pCur->i<0 || pCur->i>=pCur->iEof; } /* Advance the cursor to the next row in the table @@ -331,8 +301,8 @@ static int vt02Next(sqlite3_vtab_cursor *pCursor){ vt02_cur *pCur = (vt02_cur*)pCursor; do{ pCur->i += pCur->iIncr; - if( pCur->i<pCur->iMin || pCur->i>=pCur->iEof ) break; - }while( (pCur->mD & (1<<(pCur->i%10)))==0 ); + if( pCur->i<0 ) pCur->i = pCur->iEof; + }while( (pCur->mD & (1<<(pCur->i%10)))==0 && pCur->i<pCur->iEof ); return SQLITE_OK; } @@ -354,7 +324,6 @@ static int vt02Next(sqlite3_vtab_cursor *pCursor){ ** 2x increment by 100 ** 3x increment by 1000 ** 1xx Use offset provided by argv[N] -** 1xxx Output rows in reverse order */ static int vt02Filter( sqlite3_vtab_cursor *pCursor, /* The cursor to rewind */ @@ -365,17 +334,11 @@ static int vt02Filter( ){ vt02_cur *pCur = (vt02_cur*)pCursor; /* The vt02 cursor */ int bUseOffset = 0; /* True to use OFFSET value */ - int bReverse = 0; /* Output rows in reverse order */ int iArg = 0; /* argv[] values used so far */ int iOrigIdxNum = idxNum; /* Original value for idxNum */ pCur->iIncr = 1; - pCur->iMin = 0; pCur->mD = 0x3ff; - if( idxNum>=1000 ){ - bReverse = 1; - idxNum -= 1000; - } if( idxNum>=100 ){ bUseOffset = 1; idxNum -= 100; @@ -453,17 +416,9 @@ static int vt02Filter( }else{ goto vt02_bad_idxnum; } - if( bReverse ){ - sqlite3_int64 x; - x = pCur->i + ((pCur->iEof - pCur->i)/pCur->iIncr)*pCur->iIncr; - if( x>=pCur->iEof ) x -= pCur->iIncr; - pCur->iIncr = -pCur->iIncr; - pCur->iMin = pCur->i; - pCur->i = x; - } if( bUseOffset ){ int nSkip = sqlite3_value_int(argv[iArg]); - while( nSkip-- > 0 && !vt02Eof(pCursor) ) vt02Next(pCursor); + while( nSkip-- > 0 && pCur->i<pCur->iEof ) vt02Next(pCursor); } return SQLITE_OK; @@ -883,52 +838,35 @@ static int vt02BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ ** the same answer. */ if( pInfo->nOrderBy>0 && (flags & VT02_NO_SORT_OPT)==0 ){ - int eDistinct = sqlite3_vtab_distinct(pInfo); if( pInfo->idxNum==1 ){ /* There will only be one row of output. So it is always sorted. */ pInfo->orderByConsumed = 1; }else - if( pInfo->aOrderBy[0].iColumn<=0 ){ - /* First column of order by is X */ - if( pInfo->aOrderBy[0].desc ){ - pInfo->idxNum += 1000; /* Reverse output order */ - } + if( pInfo->aOrderBy[0].iColumn<=0 + && pInfo->aOrderBy[0].desc==0 + ){ + /* First column of order by is X ascending */ pInfo->orderByConsumed = 1; }else - if( eDistinct>=1 ){ + if( sqlite3_vtab_distinct(pInfo)>=1 ){ unsigned int x = 0; - int nDesc = 0; - int nAsc = 0; for(i=0; i<pInfo->nOrderBy; i++){ int iCol = pInfo->aOrderBy[i].iColumn; if( iCol<0 ) iCol = 0; - if( pInfo->aOrderBy[i].desc ){ - nDesc++; - }else{ - nAsc++; - } x |= 1<<iCol; } - if( nDesc>0 && nAsc>0 ){ - if( eDistinct!=1 ) eDistinct = -999; /* Never set orderByConsumed */ - }else if( nAsc==0 ){ - pInfo->idxNum += 1000; /* Reverse output order */ - } - if( eDistinct>=2 && (flags & VT02_NO_DEDUP)!=0 ){ - eDistinct = 1; - } - if( eDistinct>=2 ){ /* DISTINCT or (DISTINCT and ORDER BY) */ + if( sqlite3_vtab_distinct(pInfo)==2 ){ if( x==0x02 ){ /* DISTINCT A */ - pInfo->idxNum += (flags & VT02_PARTIAL_DEDUP)!=0 ? 20 : 30; + pInfo->idxNum += 30; pInfo->orderByConsumed = 1; }else if( x==0x06 ){ /* DISTINCT A,B */ - pInfo->idxNum += (flags & VT02_PARTIAL_DEDUP)!=0 ? 10 : 20; + pInfo->idxNum += 20; pInfo->orderByConsumed = 1; }else if( x==0x0e ){ /* DISTINCT A,B,C */ - pInfo->idxNum += (flags & VT02_PARTIAL_DEDUP)!=0 ? 0 : 10; + pInfo->idxNum += 10; pInfo->orderByConsumed = 1; }else if( x & 0x01 ){ /* DISTINCT X */ @@ -937,7 +875,7 @@ static int vt02BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ /* DISTINCT A,B,C,D */ pInfo->orderByConsumed = 1; } - }else if( eDistinct==1 ){ /* GROUP BY */ + }else{ if( x==0x02 ){ /* GROUP BY A */ pInfo->orderByConsumed = 1; @@ -955,21 +893,6 @@ static int vt02BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ pInfo->orderByConsumed = 1; } } - }else{ - int nDesc = 0; - int nAsc = 0; - for(i=0; i<pInfo->nOrderBy; i++){ - if( pInfo->aOrderBy[i].iColumn!=i+1 ) break; - if( pInfo->aOrderBy[i].desc ){ - nDesc++; - }else{ - nAsc++; - } - } - if( i==pInfo->nOrderBy && (nDesc==0 || nAsc==0) ){ - pInfo->orderByConsumed = 1; - if( nDesc ) pInfo->idxNum += 1000; - } } } @@ -978,7 +901,7 @@ static int vt02BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ pInfo->needToFreeIdxStr = 1; } if( flags & VT02_BAD_IDXNUM ){ - pInfo->idxNum += 10000; + pInfo->idxNum += 1000; } if( iOffset>=0 ){ diff --git a/test/vt100-a.sql b/test/vt100-a.sql index f141f637f..a0d3f46be 100644 --- a/test/vt100-a.sql +++ b/test/vt100-a.sql @@ -9,40 +9,11 @@ INSERT INTO t1 VALUES ('one','twotwotwo','thirty-three'), (unistr('\u001b[91mRED\u001b[0m'),'fourfour','fifty-five'), ('six','seven','eighty-eight'); -.testcase 100 +.print With -escape off SELECT * FROM t1; -.check <<END -╭─────┬───────────┬──────────────╮ -│ a │ b │ c │ -╞═════╪═══════════╪══════════════╡ -│ one │ twotwotwo │ thirty-three │ -│ RED │ fourfour │ fifty-five │ -│ six │ seven │ eighty-eight │ -╰─────┴───────────┴──────────────╯ -END - .mode box -escape ascii -.testcase 200 +.print With -escape ascii SELECT * FROM t1; -.check <<END -╭────────────────┬───────────┬──────────────╮ -│ a │ b │ c │ -╞════════════════╪═══════════╪══════════════╡ -│ one │ twotwotwo │ thirty-three │ -│ ^[[91mRED^[[0m │ fourfour │ fifty-five │ -│ six │ seven │ eighty-eight │ -╰────────────────┴───────────┴──────────────╯ -END - -.testcase 300 .mode box -escape symbol +.print With -escape symbol SELECT * FROM t1; -.check <<END -╭──────────────┬───────────┬──────────────╮ -│ a │ b │ c │ -╞══════════════╪═══════════╪══════════════╡ -│ one │ twotwotwo │ thirty-three │ -│ ␛[91mRED␛[0m │ fourfour │ fifty-five │ -│ six │ seven │ eighty-eight │ -╰──────────────┴───────────┴──────────────╯ -END diff --git a/test/walckptnoop.test b/test/walckptnoop.test index 89055316f..7ff8e90b8 100644 --- a/test/walckptnoop.test +++ b/test/walckptnoop.test @@ -102,11 +102,7 @@ do_catchsql_test 1.8 { PRAGMA wal_checkpoint = noop; } {0 {0 5 0}} -do_test 1.9 { - sqlite3_wal_checkpoint_v2 db noop -} {0 5 0} - -do_execsql_test 1.10 { +do_execsql_test 1.9 { PRAGMA journal_mode = delete; PRAGMA wal_checkpoint = noop; } {delete 0 -1 -1} diff --git a/test/walrestart.test b/test/walrestart.test index cf27a4098..4274b2e33 100644 --- a/test/walrestart.test +++ b/test/walrestart.test @@ -78,12 +78,10 @@ do_execsql_test -db db2 1.3 { proc faultsim {n} { return 0 } do_execsql_test 1.4 { PRAGMA wal_checkpoint; -} {/0 5. 5./} +} {0 58 58} do_catchsql_test 1.5 { PRAGMA integrity_check } {0 ok} -sqlite3_test_control_fault_install - finish_test diff --git a/test/where2.test b/test/where2.test index 6045cb117..83740bffd 100644 --- a/test/where2.test +++ b/test/where2.test @@ -794,41 +794,4 @@ do_execsql_test where2-15.1 { ORDER BY a,EXISTS(SELECT 1 FROM t1 LEFT JOIN (SELECT x AS y FROM t2) AS s2 ON t1.b=s2.y),x; } {12 34 NULL | 56 78 78 | 90 12 12 |} -# Demonstrate that CROSS JOIN is a join reordering barrier. -# -reset_db -do_execsql_test where2-16.1 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT); - CREATE TABLE t2(c INTEGER PRIMARY KEY, d INT); - CREATE TABLE t3(e INTEGER PRIMARY KEY, f INT); - CREATE TABLE t4(g INTEGER PRIMARY KEY, h INT); -} - -# Here the query planner wants to move t4 forward so that it is in front of -# t1. Ensure that does not happen. -do_execsql_test where2-16.2 { - EXPLAIN QUERY PLAN - SELECT * - FROM t1, t2 CROSS JOIN t3, t4 - WHERE t4.g=1 - AND t1.a=t4.h - AND t2.c=t1.b - AND t3.e=t2.d; -} {~/.* t4 .* t[12] .*/} - -# In this case the planner wants to move t1 to come after t4. Ensure that -# does not happen. -do_execsql_test where2-16.2 { - EXPLAIN QUERY PLAN - SELECT * - FROM t1, t2 CROSS JOIN t3, t4 - WHERE t2.c=1 - AND t3.e=t2.d - AND t4.g=t3.f - AND t1.a=t4.h; -} {~/.* t[34] .* t1 .*/} - - - - finish_test diff --git a/test/whereK.test b/test/whereK.test index 995c08371..060d470ff 100644 --- a/test/whereK.test +++ b/test/whereK.test @@ -69,17 +69,4 @@ do_execsql_test 1.5eqp { ORDER BY +a; } {/SEARCH t1 USING INDEX t1bc/} -# https://sqlite.org/forum/forumpost/2026-01-16T11:35:28Z -do_execsql_test 2.1 { - DROP TABLE t1; - CREATE TABLE t0(x COLLATE NOCASE); - CREATE INDEX t0x ON t0(x); - CREATE TABLE t1(y); - INSERT INTO t0 VALUES('a'); - INSERT INTO t1 VALUES('AB'); - SELECT count(*) FROM t0, t1 WHERE (y BETWEEN 1 AND x) OR (x>=y AND x); - SELECT count(*) FROM t0, t1 WHERE (x>=y AND x) OR (y BETWEEN 1 AND x); -} {1 1} - - finish_test diff --git a/test/win32longpath.test b/test/win32longpath.test index 43905f26e..2545c55a1 100644 --- a/test/win32longpath.test +++ b/test/win32longpath.test @@ -115,7 +115,11 @@ do_test 1.6 { db3 close +# winrt platforms do not handle paths with unix-style '/' directory separators. +# set lUri [list 1a 1b 1c 1d 1e 1f] +ifcapable winrt { set lUri [list 1a 1c 1e] } + foreach tn $lUri { sqlite3 db3 $uri($tn) -vfs win32-longpath -uri 1 -translatefilename 0 diff --git a/test/with1.test b/test/with1.test index c87082583..5ddf9dce0 100644 --- a/test/with1.test +++ b/test/with1.test @@ -1237,23 +1237,4 @@ do_execsql_test 27.1 { SELECT k, cte_map, main_map, '|' FROM log ORDER BY k; } {1 cte1 main1 | 2 cte2 main2 |} -# forum post https://sqlite.org/forum/forumpost/2026-03-04T05:06:26Z -# -db null NULL -do_execsql_test 28.1 { - DROP TABLE t1; - CREATE TABLE t1(x INTEGER PRIMARY KEY); - INSERT INTO t1 VALUES(1),(4),(999); - SELECT ( - WITH RECURSIVE t2(y) AS ( - SELECT 4 - UNION - SELECT NULL - UNION - SELECT y+1 FROM t2 WHERE y=4 ORDER BY 1 - ) - SELECT 1 FROM t2 WHERE y=x - ) FROM t1; -} {NULL 1 NULL} - finish_test diff --git a/test/zipfile2.test b/test/zipfile2.test index c1498872a..8ee90d310 100644 --- a/test/zipfile2.test +++ b/test/zipfile2.test @@ -302,19 +302,4 @@ do_execsql_test 7.1 { SELECT length(name) FROM t1; } {60000} - -# https://sqlite.org/forum/forumpost/721a05d2c5 -# -if {[catch { load_static_extension db fileio }]==0} { - forcedelete test.zip - set fd [open test.zip wb] - fconfigure $fd -translation binary - puts -nonewline $fd [db one {SELECT X'504b0506000000000100010030000000160000000000504b01021400140000000000000000000000000000000000000000000100010000000000000000000000000000006100'}] - close $fd - - do_catchsql_test 8.0 { - SELECT name,sz FROM zipfile(readfile('test.zip')); - } {1 {failed to read LFH at offset 0}} -} - finish_test diff --git a/tool/build-all-msvc.bat b/tool/build-all-msvc.bat index ae0d0e5b6..83d660deb 100755 --- a/tool/build-all-msvc.bat +++ b/tool/build-all-msvc.bat @@ -105,6 +105,7 @@ REM When set, these values are expanded and passed to the NMAKE command line, REM after its other arguments. These may be used to specify additional NMAKE REM options, for example: REM +REM SET NMAKE_ARGS=FOR_WINRT=1 REM SET NMAKE_ARGS_DEBUG=MEMDEBUG=1 REM SET NMAKE_ARGS_RETAIL=WIN32HEAP=1 REM @@ -860,4 +861,4 @@ GOTO no_errors GOTO end_of_file :end_of_file -%__ECHO% EXIT /B %ERRORLEVEL% +%__ECHO% EXIT /B %ERRORLEVEL% diff --git a/tool/dbtotxt.c b/tool/dbtotxt.c index ed347840a..fbd6e3d51 100644 --- a/tool/dbtotxt.c +++ b/tool/dbtotxt.c @@ -1,12 +1,6 @@ /* -** 2018-12-13 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. +** Copyright 2008 D. Richard Hipp and Hipp, Wyrick & Company, Inc. +** All Rights Reserved ** ****************************************************************************** ** diff --git a/tool/lemon.c b/tool/lemon.c index 9071e7719..324dda0c5 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -494,7 +494,6 @@ struct lemon { char *filename; /* Name of the input file */ char *outname; /* Name of the current output file */ char *tokenprefix; /* A prefix added to token names in the .h file */ - char *stackSizeLimit; /* Function to return the stack size limit */ char *reallocFunc; /* Function to use to allocate stack space */ char *freeFunc; /* Function to use to free stack space */ int nconflict; /* Number of parsing conflicts */ @@ -2639,9 +2638,6 @@ static void parseonetoken(struct pstate *psp) }else if( strcmp(x,"default_type")==0 ){ psp->declargslot = &(psp->gp->vartype); psp->insertLineMacro = 0; - }else if( strcmp(x,"stack_size_limit")==0 ){ - psp->declargslot = &(psp->gp->stackSizeLimit); - psp->insertLineMacro = 0; }else if( strcmp(x,"realloc")==0 ){ psp->declargslot = &(psp->gp->reallocFunc); psp->insertLineMacro = 0; @@ -3719,7 +3715,7 @@ PRIVATE int compute_action(struct lemon *lemp, struct action *ap) return act; } -#define LINESIZE 10000 +#define LINESIZE 1000 /* The next cluster of routines are for reading the template file ** and writing the results to the generated parser */ /* The first function transfers data from "in" to "out" until @@ -3754,9 +3750,12 @@ PRIVATE void tplt_xfer(char *name, FILE *in, FILE *out, int *lineno) /* Skip forward past the header of the template file to the first "%%" */ -PRIVATE void tplt_skip_header(FILE *in){ +PRIVATE void tplt_skip_header(FILE *in, int *lineno) +{ char line[LINESIZE]; - while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){} + while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){ + (*lineno)++; + } } /* The next function finds the template file and opens it, returning @@ -3826,14 +3825,12 @@ PRIVATE void tplt_linedir(FILE *out, int lineno, char *filename) filename++; } fprintf(out,"\"\n"); - fflush(out); } /* Print a string to the file and keep the linenumber up to date */ PRIVATE void tplt_print(FILE *out, struct lemon *lemp, char *str, int *lineno) { if( str==0 ) return; - fflush(out); while( *str ){ putc(*str,out); if( *str=='\n' ) (*lineno)++; @@ -3846,7 +3843,6 @@ PRIVATE void tplt_print(FILE *out, struct lemon *lemp, char *str, int *lineno) if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); } - fflush(out); return; } @@ -4411,13 +4407,6 @@ static void writeRuleText(FILE *out, struct rule *rp){ } } -/* -** Return true if the string is not NULL and not empty. -*/ -static int notnull(const char *z){ - return z && z[0]; -} - /* Generate C source code for the parser */ void ReportTable( @@ -4425,7 +4414,7 @@ void ReportTable( int mhflag, /* Output in makeheaders format if true */ int sqlFlag /* Generate the *.sql file too */ ){ - FILE *out, *in; + FILE *out, *in, *sql; int lineno; struct state *stp; struct action *ap; @@ -4450,10 +4439,18 @@ void ReportTable( in = tplt_open(lemp); if( in==0 ) return; - if( sqlFlag ){ - FILE *sql = file_open(lemp, ".sql", "wb"); + out = file_open(lemp,".c","wb"); + if( out==0 ){ + fclose(in); + return; + } + if( sqlFlag==0 ){ + sql = 0; + }else{ + sql = file_open(lemp, ".sql", "wb"); if( sql==0 ){ fclose(in); + fclose(out); return; } fprintf(sql, @@ -4518,12 +4515,6 @@ void ReportTable( } } fprintf(sql, "COMMIT;\n"); - fclose(sql); - } - out = file_open(lemp,".c","wb"); - if( out==0 ){ - fclose(in); - return; } lineno = 1; @@ -4552,7 +4543,7 @@ void ReportTable( } } if( lemp->include[0]=='/' ){ - tplt_skip_header(in); + tplt_skip_header(in,&lineno); }else{ tplt_xfer(lemp->name,in,out,&lineno); } @@ -4572,7 +4563,7 @@ void ReportTable( if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; }else{ - fprintf(out,"#ifndef %s%s\n", prefix, lemp->symbols[1]->name); lineno++; + fprintf(out,"#ifndef %s%s\n", prefix, lemp->symbols[1]->name); } for(i=1; i<lemp->nterminal; i++){ fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); @@ -4621,33 +4612,25 @@ void ReportTable( fprintf(out,"#define %sARG_FETCH\n",name); lineno++; fprintf(out,"#define %sARG_STORE\n",name); lineno++; } - fprintf(out, "#undef YYREALLOC\n"); lineno++; if( lemp->reallocFunc ){ fprintf(out,"#define YYREALLOC %s\n", lemp->reallocFunc); lineno++; }else{ fprintf(out,"#define YYREALLOC realloc\n"); lineno++; } - fprintf(out, "#undef YYFREE\n"); lineno++; if( lemp->freeFunc ){ fprintf(out,"#define YYFREE %s\n", lemp->freeFunc); lineno++; }else{ fprintf(out,"#define YYFREE free\n"); lineno++; } - fprintf(out, "#undef YYDYNSTACK\n"); lineno++; if( lemp->reallocFunc && lemp->freeFunc ){ fprintf(out,"#define YYDYNSTACK 1\n"); lineno++; }else{ fprintf(out,"#define YYDYNSTACK 0\n"); lineno++; } - fprintf(out, "#undef YYSIZELIMIT\n"); lineno++; - if( notnull(lemp->ctx) ){ + if( lemp->ctx && lemp->ctx[0] ){ i = lemonStrlen(lemp->ctx); while( i>=1 && ISSPACE(lemp->ctx[i-1]) ) i--; while( i>=1 && (ISALNUM(lemp->ctx[i-1]) || lemp->ctx[i-1]=='_') ) i--; - if( notnull(lemp->stackSizeLimit) ){ - fprintf(out,"#define YYSIZELIMIT %s\n", lemp->stackSizeLimit); lineno++; - } - fprintf(out,"#define %sCTX(P) ((P)->%s)\n",name,&lemp->ctx[i]); lineno++; fprintf(out,"#define %sCTX_SDECL %s;\n",name,lemp->ctx); lineno++; fprintf(out,"#define %sCTX_PDECL ,%s\n",name,lemp->ctx); lineno++; fprintf(out,"#define %sCTX_PARAM ,%s\n",name,&lemp->ctx[i]); lineno++; @@ -4656,7 +4639,6 @@ void ReportTable( fprintf(out,"#define %sCTX_STORE yypParser->%s=%s;\n", name,&lemp->ctx[i],&lemp->ctx[i]); lineno++; }else{ - fprintf(out,"#define %sCTX(P) 0\n",name); lineno++; fprintf(out,"#define %sCTX_SDECL\n",name); lineno++; fprintf(out,"#define %sCTX_PDECL\n",name); lineno++; fprintf(out,"#define %sCTX_PARAM\n",name); lineno++; @@ -4666,13 +4648,10 @@ void ReportTable( if( mhflag ){ fprintf(out,"#endif\n"); lineno++; } - fprintf(out, "#undef YYERRORSYMBOL\n"); lineno++; - fprintf(out, "#undef YYERRSYMDT\n"); lineno++; if( lemp->errsym && lemp->errsym->useCnt ){ fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++; fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++; } - fprintf(out,"#undef YYFALLBACK\n"); lineno++; if( lemp->has_fallback ){ fprintf(out,"#define YYFALLBACK 1\n"); lineno++; } @@ -5024,6 +5003,7 @@ void ReportTable( sp2->destLineno = -1; /* Avoid emitting this destructor again */ } } + emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); fprintf(out," break;\n"); lineno++; } @@ -5123,6 +5103,7 @@ void ReportTable( acttab_free(pActtab); fclose(in); fclose(out); + if( sql ) fclose(sql); return; } diff --git a/tool/lempar.c b/tool/lempar.c index 7b654e650..74314efea 100644 --- a/tool/lempar.c +++ b/tool/lempar.c @@ -299,24 +299,15 @@ static int yyGrowStack(yyParser *p){ int newSize; int idx; yyStackEntry *pNew; -#ifdef YYSIZELIMIT - int nLimit = YYSIZELIMIT(ParseCTX(p)); -#endif newSize = oldSize*2 + 100; -#ifdef YYSIZELIMIT - if( newSize>nLimit ){ - newSize = nLimit; - if( newSize<=oldSize ) return 1; - } -#endif idx = (int)(p->yytos - p->yystack); if( p->yystack==p->yystk0 ){ - pNew = YYREALLOC(0, newSize*sizeof(pNew[0]), ParseCTX(p)); + pNew = YYREALLOC(0, newSize*sizeof(pNew[0])); if( pNew==0 ) return 1; memcpy(pNew, p->yystack, oldSize*sizeof(pNew[0])); }else{ - pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0]), ParseCTX(p)); + pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0])); if( pNew==0 ) return 1; } p->yystack = pNew; @@ -468,9 +459,7 @@ void ParseFinalize(void *p){ } #if YYGROWABLESTACK - if( pParser->yystack!=pParser->yystk0 ){ - YYFREE(pParser->yystack, ParseCTX(pParser)); - } + if( pParser->yystack!=pParser->yystk0 ) YYFREE(pParser->yystack); #endif } diff --git a/tool/mkautoconfamal.sh b/tool/mkautoconfamal.sh index 002a3b8ee..3835799a6 100644 --- a/tool/mkautoconfamal.sh +++ b/tool/mkautoconfamal.sh @@ -59,8 +59,7 @@ cp $TOP/src/sqlite3.rc $TMPSPACE cp $TOP/tool/Replace.cs $TMPSPACE cp $TOP/VERSION $TMPSPACE cp $TOP/main.mk $TMPSPACE -cp $TOP/make.bat $TMPSPACE -tree $TMPSPACE + cd $TMPSPACE #if true; then diff --git a/tool/mkcombo.tcl b/tool/mkcombo.tcl deleted file mode 100644 index 71368ec41..000000000 --- a/tool/mkcombo.tcl +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/tclsh -# -# Use this script to combine multiple source code files into a single -# file. Example: -# -# tclsh mkcombo.tcl file1.c file2.c file3.c -o file123.c -# - -set help {Usage: tclsh mkcombo.tcl [OPTIONS] [FILELIST] - where OPTIONS is zero or more of the following with these effects: - --linemacros=? => Emit #line directives into output or not. (? = 1 or 0) - --o FILE => write to alternative output file named FILE - --help => See this. -} - -set linemacros 0 -set fname {} -set src [list] - - -for {set i 0} {$i<[llength $argv]} {incr i} { - set x [lindex $argv $i] - if {[regexp {^-?-linemacros(?:=([01]))?$} $x ma ulm]} { - if {$ulm == ""} {set ulm 1} - set linemacros $ulm - } elseif {[regexp {^-o$} $x]} { - incr i - if {$i==[llength $argv]} { - error "No argument following $x" - } - set fname [lindex $argv $i] - } elseif {[regexp {^-?-((help)|\?)$} $x]} { - puts $help - exit 0 - } elseif {[regexp {^-?-} $x]} { - error "unknown command-line option: $x" - } else { - lappend src $x - } -} - -# Open the output file and write a header comment at the beginning -# of the file. -# -if {![info exists fname]} { - set fname sqlite3.c - if {$enable_recover} { set fname sqlite3r.c } -} -set out [open $fname wb] - -# Return a string consisting of N "*" characters. -# -proc star N { - set r {} - for {set i 0} {$i<$N} {incr i} {append r *} - return $r -} - -# Force the output to use unix line endings, even on Windows. -fconfigure $out -translation binary -puts $out "/[star 78]" -puts $out {** The following is an amalgamation of these source code files:} -puts $out {**} -foreach s $src { - regsub {^.*/(src|ext)/} $s {\1/} s2 - puts $out "** $s2" -} -puts $out {**} -puts $out "[star 78]/" - -# Insert a comment into the code -# -proc section_comment {text} { - global out s78 - set n [string length $text] - set nstar [expr {60 - $n}] - puts $out "/************** $text [star $nstar]/" -} - -# Read the source file named $filename and write it into the -# sqlite3.c output file. The only transformation is the trimming -# of EOL whitespace. -# -proc copy_file_verbatim {filename} { - global out - set in [open $filename rb] - set tail [file tail $filename] - section_comment "Begin file $tail" - while {![eof $in]} { - set line [string trimright [gets $in]] - puts $out $line - } - section_comment "End of $tail" -} -set taillist "" -foreach file $src { - copy_file_verbatim $file - append taillist ", [file tail $file]" -} - -set taillist "End of the amalgamation of [string range $taillist 2 end]" -set n [string length $taillist] -set ns [expr {(75-$n)/2}] -if {$ns<3} {set ns 3} -puts $out "/[star $ns] $taillist [star $ns]/" -close $out diff --git a/tool/mkkeywordhash.c b/tool/mkkeywordhash.c index d6d54a5a4..188c0a29a 100644 --- a/tool/mkkeywordhash.c +++ b/tool/mkkeywordhash.c @@ -668,8 +668,8 @@ int main(int argc, char **argv){ printf("/* Check to see if z[0..n-1] is a keyword. If it is, write the\n"); printf("** parser symbol code for that keyword into *pType. Always\n"); printf("** return the integer n (the length of the token). */\n"); - printf("static i64 keywordCode(const char *z, i64 n, int *pType){\n"); - printf(" i64 i, j;\n"); + printf("static int keywordCode(const char *z, int n, int *pType){\n"); + printf(" int i, j;\n"); printf(" const char *zKW;\n"); printf(" assert( n>=2 );\n"); printf(" i = ((charMap(z[0])*%d) %c", HASH_C0, HASH_CC); diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl index 45452621b..2f7a6ea25 100644 --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@ -13,90 +13,28 @@ set topdir [file dir [file dir [file normal $argv0]]] set out stdout fconfigure stdout -translation binary if {[lindex $argv 0]!=""} { - set output_file [lindex $argv 0] - file delete -force $output_file - set out [open $output_file wb] -} else { - set output_file {} + set out [open [lindex $argv 0] wb] } - -############################## FIRST PASS ################################ -# Read through the shell.c.in source file to gather information. Do not -# yet generate any code -# -set in [open $topdir/src/shell.c.in] -fconfigure $in -translation binary -set allSource(src/shell.c.in) 1 -set inUsage 0 -set dotcmd {} -while {1} { - set lx [gets $in] - if {[eof $in]} break; - if {[regexp {^INCLUDE } $lx]} { - set cfile [lindex $lx 1] - if {[string match ../* $cfile]} { - set xfile [string range $cfile 3 end] - } else { - set xfile "src/$cfile" - } - set allSource($xfile) 1 - } elseif {[regexp {^\*\* USAGE:\s+([^\s]+)} $lx all dotcmd]} { - set inUsage 1 - set details [string trim [string range $lx 2 end]] - } elseif {$inUsage} { - if {![regexp {^\*\*} $lx] || [regexp { DOT-COMMAND: } $lx]} { - set inUsage 0 - set Usage($dotcmd) [string trim $details] - } else { - append details \n - append details [string range [string trimright $lx] 3 end] - } - } -} - -# Generate dot-command usage text based on the data accumulated in -# the Usage() array. -# -proc generate_usage {out} { - global Usage - puts $out "/**************************************************************" - puts $out "** \"Usage\" help text automatically generated from comments */" - puts $out "static const struct \173" - puts $out " const char *zCmd; /* Name of the dot-command */" - puts $out " const char *zUsage; /* Documentation */" - puts $out "\175 aUsage\[\] = \173" - foreach dotcmd [array names Usage] { - puts $out " \173 \"$dotcmd\"," - foreach line [split $Usage($dotcmd) \n] { - set x [string map [list \\ \\\\ \" \\\"] $line] - puts $out "\"$x\\n\"" - } - puts $out " \175," - } - puts $out "\175;" -} -# generate_usage stderr - -###### SECOND PASS ####### -# Make a second pass through shell.c.in to generate the the final -# output, based on data gathered during the first pass. -# - -puts $out {/* -** This is the amalgamated source code to the "sqlite3" or "sqlite3.exe" -** command-line shell (CLI) for SQLite. This file is automatically -** generated by the tool/mkshellc.tcl script from the following sources: -**} -foreach fn [lsort [array names allSource]] { - puts $out "** $fn" -} -puts $out {** +puts $out {/* DO NOT EDIT! +** This file is automatically generated by the script in the canonical +** SQLite source tree at tool/mkshellc.tcl. That script combines source +** code from various constituent source files of SQLite into this single +** "shell.c" file used to implement the SQLite command-line shell. +** +** Most of the code found below comes from the "src/shell.c.in" file in +** the canonical SQLite source tree. That main file contains "INCLUDE" +** lines that specify other files in the canonical source tree that are +** inserted to getnerate this complete program source file. +** +** The code from multiple files is combined into this single "shell.c" +** source file to help make the command-line program easier to compile. +** ** To modify this program, get a copy of the canonical SQLite source tree, -** edit the src/shell.c.in file and/or some of the other files that are -** listed above, then rerun the command "make shell.c". +** edit the src/shell.c.in" and/or some of the other files that are included +** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script. */} -seek $in 0 start -puts $out "/************************* Begin src/shell.c.in ******************/" +set in [open $topdir/src/shell.c.in] +fconfigure $in -translation binary proc omit_redundant_typedefs {line} { global typedef_seen if {[regexp {^typedef .* ([a-zA-Z0-9_]+);} $line all typename]} { @@ -115,14 +53,9 @@ while {1} { incr iLine if {[regexp {^INCLUDE } $lx]} { set cfile [lindex $lx 1] - if {[string match ../* $cfile]} { - set xfile [string range $cfile 3 end] - } else { - set xfile "src/$cfile" - } - puts $out "/************************* Begin $xfile ******************/" -# puts $out "#line 1 \"$xfile\"" - set in2 [open $topdir/$xfile] + puts $out "/************************* Begin $cfile ******************/" +# puts $out "#line 1 \"$cfile\"" + set in2 [open $topdir/src/$cfile] fconfigure $in2 -translation binary while {![eof $in2]} { set lx [omit_redundant_typedefs [gets $in2]] @@ -136,14 +69,11 @@ while {1} { puts $out $lx } close $in2 - puts $out "/************************* End $xfile ********************/" + puts $out "/************************* End $cfile ********************/" # puts $out "#line [expr $iLine+1] \"shell.c.in\"" - } elseif {[regexp {^INSERT-USAGE-TEXT-HERE} $lx]} { - generate_usage $out - } else { - puts $out $lx + continue } + puts $out $lx } -puts $out "/************************* End src/shell.c.in ******************/" close $in close $out diff --git a/tool/omittest.tcl b/tool/omittest.tcl index 03c9220cd..0452a4c6f 100644 --- a/tool/omittest.tcl +++ b/tool/omittest.tcl @@ -123,6 +123,7 @@ set CompileOptionsToTest { SQLITE_ENABLE_MEMSYS SQLITE_ENABLE_MODULE_COMMENTS SQLITE_ENABLE_MULTIPLEX + SQLITE_ENABLE_MULTITHREADED_CHECKS SQLITE_ENABLE_NORMALIZE SQLITE_ENABLE_NULL_TRIM SQLITE_ENABLE_OFFSET_SQL_FUNC @@ -151,7 +152,6 @@ set CompileOptionsToTest { SQLITE_ENABLE_VFSTRACE SQLITE_ENABLE_WHERETRACE SQLITE_ENABLE_ZIPVFS - SQLITE_THREAD_MISUSE_WARNINGS } # Parse command-line options. diff --git a/tool/showdb.c b/tool/showdb.c index 84813b839..f0bd9737c 100644 --- a/tool/showdb.c +++ b/tool/showdb.c @@ -8,7 +8,6 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> -#include <stdint.h> #if !defined(_MSC_VER) #include <unistd.h> @@ -29,21 +28,13 @@ typedef sqlite3_uint64 u64; /* unsigned 64-bit */ static struct GlobalData { i64 pagesize; /* Size of a database page */ - i64 usablesize; /* pagesize-nRes */ int dbfd; /* File descriptor for reading the DB */ u32 mxPage; /* Last page number */ - u32 nRes; /* Amount of reserve space */ int perLine; /* HEX elements to print per line */ int bRaw; /* True to access db file via OS APIs */ - int bCSV; /* CSV output for "pgidx" */ - int bTmstmp; /* Interpret tmstmpvfs tags on "pgidx" */ sqlite3_file *pFd; /* File descriptor for non-raw mode */ sqlite3 *pDb; /* Database handle that owns pFd */ - char **zPageUse; /* Use for each page */ - struct TmstmpTag { - unsigned char a[16]; /* tmstmpvfs tag for each page */ - } *aPageTag; -} g = {4096, 4096, -1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0}; +} g = {1024, -1, 0, 16, 0, 0, 0}; /* ** Convert the var-int format into i64. Return the number of bytes @@ -151,12 +142,11 @@ static void fileClose(){ static unsigned char *fileRead(sqlite3_int64 ofst, int nByte){ unsigned char *aData; int got; - int rc; aData = sqlite3_malloc64(32+(i64)nByte); if( aData==0 ) out_of_memory(); memset(aData, 0, nByte+32); if( g.bRaw==0 ){ - rc = g.pFd->pMethods->xRead(g.pFd, (void*)aData, nByte, ofst); + int rc = g.pFd->pMethods->xRead(g.pFd, (void*)aData, nByte, ofst); if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){ fprintf(stderr, "error in xRead() - %d\n", rc); exit(1); @@ -164,21 +154,7 @@ static unsigned char *fileRead(sqlite3_int64 ofst, int nByte){ }else{ lseek(g.dbfd, (long)ofst, SEEK_SET); got = read(g.dbfd, aData, nByte); - if( got==nByte ){ - rc = SQLITE_OK; - }else if( got>0 && got<nByte ){ - memset(aData+got, 0, nByte-got); - rc = SQLITE_IOERR_SHORT_READ; - }else{ - memset(aData,0,nByte); - rc = SQLITE_IOERR; - } - } - if( g.aPageTag && nByte==(int)g.pagesize ){ - unsigned int pgno = (unsigned int)(ofst/g.pagesize) + 1; - if( pgno>0 && pgno<=g.mxPage ){ - memcpy(g.aPageTag[pgno].a, &aData[nByte-16], 16); - } + if( got>0 && got<nByte ) memset(aData+got, 0, nByte-got); } return aData; } @@ -406,14 +382,14 @@ static i64 localPayload(i64 nPayload, char cType){ i64 nLocal; if( cType==13 ){ /* Table leaf */ - maxLocal = g.usablesize-35; - minLocal = (g.usablesize-12)*32/255-23; + maxLocal = g.pagesize-35; + minLocal = (g.pagesize-12)*32/255-23; }else{ - maxLocal = (g.usablesize-12)*64/255-23; - minLocal = (g.usablesize-12)*32/255-23; + maxLocal = (g.pagesize-12)*64/255-23; + minLocal = (g.pagesize-12)*32/255-23; } if( nPayload>maxLocal ){ - surplus = minLocal + (nPayload-minLocal)%(g.usablesize-4); + surplus = minLocal + (nPayload-minLocal)%(g.pagesize-4); if( surplus<=maxLocal ){ nLocal = surplus; }else{ @@ -775,7 +751,7 @@ static void decode_trunk_page( print_decode_line(a, 4, 4, "Number of entries on this page"); if( detail ){ n = decodeInt32(&a[4]); - for(i=0; i<n && i<g.usablesize/4; i++){ + for(i=0; i<n && i<g.pagesize/4; i++){ u32 x = decodeInt32(&a[8+4*i]); char zIdx[13]; sprintf(zIdx, "[%d]", i); @@ -793,6 +769,11 @@ static void decode_trunk_page( } } +/* +** A short text comment on the use of each page. +*/ +static char **zPageUse; + /* ** Add a comment on the use of a page. */ @@ -809,13 +790,13 @@ static void page_usage_msg(u32 pgno, const char *zFormat, ...){ sqlite3_free(zMsg); return; } - if( g.zPageUse[pgno]!=0 ){ + if( zPageUse[pgno]!=0 ){ printf("ERROR: page %d used multiple times:\n", pgno); - printf("ERROR: previous: %s\n", g.zPageUse[pgno]); + printf("ERROR: previous: %s\n", zPageUse[pgno]); printf("ERROR: current: %s\n", zMsg); - sqlite3_free(g.zPageUse[pgno]); + sqlite3_free(zPageUse[pgno]); } - g.zPageUse[pgno] = zMsg; + zPageUse[pgno] = zMsg; } /* @@ -856,7 +837,7 @@ static void page_usage_cell( while( ovfl && (cnt++)<g.mxPage ){ page_usage_msg(ovfl, "overflow %d from cell %d of page %u", cnt, cellno, pgno); - a = fileRead((ovfl-1)*(sqlite3_int64)g.pagesize, g.pagesize); + a = fileRead((ovfl-1)*(sqlite3_int64)g.pagesize, 4); ovfl = decodeInt32(a); sqlite3_free(a); } @@ -935,12 +916,12 @@ static void page_usage_btree( u32 ofst; cellidx = cellstart + i*2; - if( cellidx+1 >= g.usablesize ){ + if( cellidx+1 >= g.pagesize ){ printf("ERROR: page %d too many cells (%d)\n", pgno, nCell); break; } ofst = a[cellidx]*256 + a[cellidx+1]; - if( ofst<cellidx+2 || ofst+4>=g.usablesize ){ + if( ofst<cellidx+2 || ofst+4>=g.pagesize ){ printf("ERROR: page %d cell %d out of bounds\n", pgno, i); continue; } @@ -978,9 +959,9 @@ static void page_usage_freelist(u32 pgno){ a = fileRead((pgno-1)*g.pagesize, g.pagesize); iNext = decodeInt32(a); n = decodeInt32(a+4); - if( n>(g.usablesize - 8)/4 ){ + if( n>(g.pagesize - 8)/4 ){ printf("ERROR: page %d too many freelist entries (%d)\n", pgno, n); - n = (g.usablesize - 8)/4; + n = (g.pagesize - 8)/4; } for(i=0; i<n; i++){ int child = decodeInt32(a + (i*4+8)); @@ -1009,63 +990,6 @@ static void page_usage_ptrmap(u8 *a){ } } -/* -** The six bytes at a[] are a big-endian unsigned integer which is the -** number of milliseconds since 1970. Decode that value into an ISO 8601 -** date/time string stored in static space and return a pointer to that -** string. -*/ -static const char *decodeTimestamp(const unsigned char *a){ - uint64_t ms; /* Milliseconds since 1970 */ - uint64_t days; /* Days since 1970-01-01 */ - uint64_t sod; /* Start of date specified by ms */ - uint64_t z; /* Days since 0000-03-01 */ - uint64_t era; /* 400-year era */ - int i; /* Loop counter */ - 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 */ - static char zOut[50]; /* Return results here */ - - for(ms=0, i=0; i<=5; i++) ms = (ms<<8) + a[i]; - if( ms==0 ){ - return " "; - }else if( ms>4102444800000LL ){ /* 2100-01-01 */ - /* YYYY-MM-DD HH:MM:SS.SSS */ - return " (bad date) "; - } - days = ms/86400000; - sod = (ms%86400000)/1000; - f = (int)(ms%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); - snprintf(zOut, sizeof(zOut), - "%04d-%02d-%02d %02d:%02d:%02d.%03d", - Y, M, D, h, m, s, f); - return zOut; -} - /* ** Try to figure out how every page in the database file is being used. */ @@ -1086,22 +1010,14 @@ static void page_usage_report(const char *zPrg, const char *zDbName){ /* Open the database file */ db = openDatabase(zPrg, zDbName); - /* Set up global variables g.zPageUse[] and g.mxPage to record page + /* Set up global variables zPageUse[] and g.mxPage to record page ** usages */ - g.zPageUse = sqlite3_malloc64( sizeof(g.zPageUse[0])*(g.mxPage+1) ); - if( g.zPageUse==0 ) out_of_memory(); - memset(g.zPageUse, 0, sizeof(g.zPageUse[0])*(g.mxPage+1)); + zPageUse = sqlite3_malloc64( sizeof(zPageUse[0])*(g.mxPage+1) ); + if( zPageUse==0 ) out_of_memory(); + memset(zPageUse, 0, sizeof(zPageUse[0])*(g.mxPage+1)); /* Discover the usage of each page */ a = fileRead(0, 100); - if( g.bTmstmp && a[20]==16 ){ - g.aPageTag = sqlite3_malloc64( sizeof(struct TmstmpTag)*(g.mxPage+1) ); - if( g.aPageTag==0 ) out_of_memory(); - memset(g.aPageTag, 0, sizeof(struct TmstmpTag)*(g.mxPage+1) ); - }else{ - g.bTmstmp = 0; - g.aPageTag = 0; - } page_usage_freelist(decodeInt32(a+32)); page_usage_ptrmap(a); sqlite3_free(a); @@ -1126,68 +1042,15 @@ static void page_usage_report(const char *zPrg, const char *zDbName){ sqlite3_close(db); /* Print the report and free memory used */ - if( g.bCSV ){ - if( g.bTmstmp ){ - printf("pgno,tm,frame,flg,salt,parent,child,ovfl,txt\r\n"); - }else{ - printf("pgno,parent,child,ovfl,txt\r\n"); - } - } for(i=1; i<=g.mxPage; i++){ - if( g.zPageUse[i]==0 ){ - g.zPageUse[i] = sqlite3_mprintf("???"); - if( g.zPageUse[i]==0 ) continue; - } - if( g.bCSV ){ - const char *z = g.zPageUse[i]; - const char *s; - printf("%u,", i); - if( g.bTmstmp ){ - const unsigned char *a = g.aPageTag[i].a; - sqlite3_uint64 tm = 0; - unsigned int x; - int k; - for(k=2; k<=7; k++) tm = (tm<<8)+a[k]; - printf("%llu.%03u,", tm/1000, (unsigned int)(tm%1000)); - for(x=0, k=8; k<=11; k++) x = (x<<8)+a[k]; - printf("%u,", x); - printf("%u,", a[12]); - for(x=0, k=13; k<=15; k++) x = (x<<8)+a[k]; - printf("%u,", x); - } - if( (s = strstr(z, " of page "))!=0 ){ - printf("%d,", atoi(s+9)); - }else if( (s = strstr(z, " of trunk page "))!=0 ){ - printf("%d,", atoi(s+15)); - }else{ - printf("0,"); - } - if( (s = strstr(z, "], child "))!=0 ){ - printf("%d,", atoi(s+9)); - }else if( (s = strstr(z, " from cell "))!=0 ){ - printf("%d,", atoi(s+12)); - }else{ - printf("-1,"); - } - if( strncmp(z,"overflow ", 9)==0 ){ - printf("%d,", atoi(z+9)); - }else{ - printf("-1,"); - } - printf("\"%s\"\r\n", z); - }else if( g.bTmstmp ){ - printf("%5u: %s %s\n", i, - decodeTimestamp(&g.aPageTag[i].a[2]), - g.zPageUse[i]); - }else{ - printf("%5u: %s\n", i, g.zPageUse[i]); - } + if( zPageUse[i]==0 ) page_usage_btree(i, -1, 0, 0); + printf("%5u: %s\n", i, zPageUse[i] ? zPageUse[i] : "???"); } for(i=1; i<=g.mxPage; i++){ - sqlite3_free(g.zPageUse[i]); + sqlite3_free(zPageUse[i]); } - sqlite3_free(g.zPageUse); - g.zPageUse = 0; + sqlite3_free(zPageUse); + zPageUse = 0; } /* @@ -1220,7 +1083,7 @@ static void ptrmap_coverage_report(const char *zDbName){ for(pgno=2; pgno<=g.mxPage; pgno += perPage+1){ printf("%5llu: PTRMAP page covering %llu..%llu\n", pgno, pgno+1, pgno+perPage); - a = fileRead((pgno-1)*g.pagesize, g.pagesize); + a = fileRead((pgno-1)*g.pagesize, usable); for(i=0; i+5<=usable; i+=5){ const char *zType; u32 iFrom = decodeInt32(&a[i+1]); @@ -1252,7 +1115,7 @@ static void ptrmap_coverage_report(const char *zDbName){ ** Check the range validity for a page number. Print an error and ** exit if the page is out of range. */ -static void checkPageValidity(unsigned int iPage){ +static void checkPageValidity(int iPage){ if( iPage<1 || iPage>g.mxPage ){ fprintf(stderr, "Invalid page number %d: valid range is 1..%d\n", iPage, g.mxPage); @@ -1267,9 +1130,7 @@ static void usage(const char *argv0){ fprintf(stderr, "Usage %s ?--uri? FILENAME ?args...?\n\n", argv0); fprintf(stderr, "switches:\n" - " --csv CSV output for \"pgidx\"\n" " --raw Read db file directly, bypassing SQLite VFS\n" - " --tmstmp Interpret tmstmpvfs tags\n" "args:\n" " dbheader Show database header\n" " pgidx Index of how each page is used\n" @@ -1293,28 +1154,14 @@ int main(int argc, char **argv){ char **azArg = argv; int nArg = argc; - /* Check for the switches. */ - while( nArg>1 && azArg[1][0]=='-' ){ - const char *z = azArg[1]; - if( z[1]=='-' && z[2]!=0 ) z++; - if( sqlite3_stricmp("-raw", z)==0 ){ + /* Check for the "--uri" or "-uri" switch. */ + if( nArg>1 ){ + if( sqlite3_stricmp("-raw", azArg[1])==0 + || sqlite3_stricmp("--raw", azArg[1])==0 + ){ g.bRaw = 1; azArg++; nArg--; - }else - if( strcmp("-csv", z)==0 ){ - g.bCSV = 1; - azArg++; - nArg--; - }else - if( strcmp("-tmstmp", z)==0 ){ - g.bTmstmp = 1; - azArg++; - nArg--; - }else - { - usage(zPrg); - exit(1); } } @@ -1326,19 +1173,15 @@ int main(int argc, char **argv){ fileOpen(zPrg, azArg[1]); szFile = fileGetsize(); - zPgSz = fileRead(0, 24); - g.pagesize = zPgSz[16]*256 + zPgSz[17]*65536; - if( g.pagesize==0 ) g.pagesize = 4096; - g.nRes = zPgSz[20]; - g.usablesize = g.pagesize - g.nRes; + zPgSz = fileRead(16, 2); + g.pagesize = zPgSz[0]*256 + zPgSz[1]*65536; + if( g.pagesize==0 ) g.pagesize = 1024; sqlite3_free(zPgSz); + + printf("Pagesize: %d\n", (int)g.pagesize); g.mxPage = (u32)((szFile+g.pagesize-1)/g.pagesize); - if( !g.bCSV ){ - printf("Pagesize: %d\n", (int)g.pagesize); - if( g.nRes ) printf("Useable-size: %d\n", (int)g.usablesize); - printf("Available pages: 1..%u\n", g.mxPage); - } + printf("Available pages: 1..%u\n", g.mxPage); if( nArg==2 ){ u32 i; for(i=1; i<=g.mxPage; i++) print_page(i); diff --git a/tool/showtmlog.c b/tool/showtmlog.c deleted file mode 100644 index 4c35b777b..000000000 --- a/tool/showtmlog.c +++ /dev/null @@ -1,254 +0,0 @@ -/* -** A utility program to decode tmstmpvfs log files. -*/ -#include <stdio.h> -#include <assert.h> -#include <string.h> -#include <stdint.h> -#include <stdlib.h> - -/* -** The six bytes at a[] are a big-endian unsigned integer which is the -** number of milliseconds since 1970. Decode that value into an ISO 8601 -** date/time string stored in static space and return a pointer to that -** string. -*/ -static const char *decodeTimestamp(const unsigned char *a){ - uint64_t ms; /* Milliseconds since 1970 */ - uint64_t days; /* Days since 1970-01-01 */ - uint64_t sod; /* Start of date specified by ms */ - uint64_t z; /* Days since 0000-03-01 */ - uint64_t era; /* 400-year era */ - int i; /* Loop counter */ - 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 */ - static char zOut[50]; /* Return results here */ - - for(ms=0, i=0; i<=5; i++) ms = (ms<<8) + a[i]; - if( ms==0 ){ - return " "; - }else if( ms>4102444800000LL ){ /* 2100-01-01 */ - /* YYYY-MM-DD HH:MM:SS.SSS */ - return " (bad date) "; - } - days = ms/86400000; - sod = (ms%86400000)/1000; - f = (int)(ms%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); - snprintf(zOut, sizeof(zOut), - "%04d-%02d-%02d %02d:%02d:%02d.%03d", - Y, M, D, h, m, s, f); - return zOut; -} - -/* -** Render a single 16-byte tmstmpvfs log record as a line to a CSV file. -** -** Columns: tmstmp,fileno,op,pid,pgno,frame,salt,txn -*/ -static void renderCSV(int iFile, unsigned char *a){ - unsigned int a2, a3; - int j; - uint64_t ms; - - for(ms=0, j=2; j<=7; j++) ms = (ms<<8) + a[j]; - printf("%u.%03u,%d,", (unsigned int)(ms/1000), (unsigned)(ms%1000), iFile); - for(a2=0, j=8; j<=11; j++) a2 = (a2<<8)+a[j]; - for(a3=0, j=12; j<=15; j++) a3 = (a3<<8)+a[j]; - switch( a[0] ){ - case 0x01: { - printf("\"open-db\",%u,,,,\r\n",a2); - break; - } - case 0x02: { - printf("\"open-wal\",%u,,,,\r\n", a2); - break; - } - case 0x03: { - printf("\"wal-page\",,%u,%u,,%d\r\n", a2, a3, a[1]); - break; - } - case 0x04: { - printf("\"db-page\",,%u,,,\r\n", a2); - break; - } - case 0x05: { - printf("\"ckpt-start\",,,,,\r\n"); - break; - } - case 0x06: { - printf("\"ckpt-page\",,%u,%u,,\r\n", a2, a3); - break; - } - case 0x07: { - printf("\"ckpt-end\",,,,,\r\n"); - break; - } - case 0x08: { - printf("\"wal-reset\",,,,%u,\r\n", a3); - break; - } - case 0x0e: { - printf("\"close-wal\",,,,,\r\n"); - break; - } - case 0x0f: { - printf("\"close-db\",,,,,\r\n"); - break; - } - default: { - printf("\"invalid-record\",,,,,\r\n"); - break; - } - } -} - -/* -** Render a single 16-byte tmstmpvfs log record as human-readable text -** on stdout. -*/ -static void renderText(unsigned char *a){ - unsigned int a2, a3; - int j; - - printf("%s ", decodeTimestamp(a+2)); - for(a2=0, j=8; j<=11; j++) a2 = (a2<<8)+a[j]; - for(a3=0, j=12; j<=15; j++) a3 = (a3<<8)+a[j]; - switch( a[0] ){ - case 0x01: { - printf("open-db pid %u\n", a2); - break; - } - case 0x02: { - printf("open-wal pid %u\n", a2); - break; - } - case 0x03: { - printf("wal-page pgno %-8u frame %-8u%s\n", a2, a3, - a[1]==1 ? " txn" : ""); - break; - } - case 0x04: { - printf("db-page pgno %-8u\n", a2); - break; - } - case 0x05: { - printf("ckpt-start\n"); - break; - } - case 0x06: { - printf("ckpt-page pgno %-8u frame %-8u\n", a2, a3); - break; - } - case 0x07: { - printf("ckpt-end\n"); - break; - } - case 0x08: { - printf("wal-reset salt1 0x%08x\n", a3); - break; - } - case 0x0e: { - printf("close-wal\n"); - break; - } - case 0x0f: { - printf("close-db\n"); - break; - } - default: { - printf("invalid-record\n"); - break; - } - } -} - -static void usage(const char *argv0){ - printf("Usage: %s [--csv] LOGFILE ...\n", argv0); - printf("Decode one or more tmstmpvfs log files and display the results\n" - "on stdout. Render as CSV if the --csv option is used.\n"); -} - -int main(int argc, char **argv){ - int i; - FILE *in; - unsigned char a[16]; - int bCSV = 0; - const char *z; - int nFile = 0; - int iFile; - for(i=1; i<argc; i++){ - z = argv[i]; - if( z[0]=='-' ){ - if( z[1]=='-' ) z++; - if( strcmp(z,"-csv")==0 ){ - bCSV = 1; - }else - if( strcmp(z,"-help")==0 || strcmp(z,"-?")==0 ){ - usage(argv[0]); - return 0; - }else - { - printf("unknown command-line option: \"%s\"\n", - argv[i]); - usage(argv[0]); - return 1; - } - }else{ - nFile++; - } - } - if( nFile==0 ){ - usage(argv[0]); - return 1; - } - iFile = 0; - if( bCSV ){ - printf("tmstmp,fileno,op,pid,pgno,frame,salt,txn\r\n"); - } - for(i=1; i<argc; i++){ - z = argv[i]; - if( z[0]=='-' ) continue; - in = fopen(z, "rb"); - if( in==0 ){ - printf("%s: can't open\n", z); - continue; - } - iFile++; - if( nFile>1 && !bCSV ){ - printf("*** %s ***\n", z); - } - while( 16==fread(a, 1, 16, in) ){ - if( bCSV ){ - renderCSV(iFile, a); - }else{ - renderText(a); - } - } - fclose(in); - } - return 0; -} diff --git a/tool/sqldiff.c b/tool/sqldiff.c index d27a62e14..44bf488f8 100644 --- a/tool/sqldiff.c +++ b/tool/sqldiff.c @@ -28,9 +28,6 @@ #include "sqlite3.h" #include "sqlite3_stdio.h" -typedef sqlite3_int64 i64; -typedef sqlite3_uint64 u64; - /* ** All global variables are gathered into the "g" singleton. */ @@ -205,12 +202,12 @@ static char **columnNames( int *pbRowid /* OUT: True if PK is an implicit rowid */ ){ char **az = 0; /* List of column names to be returned */ - i64 naz = 0; /* Number of entries in az[] */ + int naz = 0; /* Number of entries in az[] */ sqlite3_stmt *pStmt; /* SQL statement being run */ char *zPkIdxName = 0; /* Name of the PRIMARY KEY index */ int truePk = 0; /* PRAGMA table_info identifies the PK to use */ - i64 nPK = 0; /* Number of PRIMARY KEY columns */ - i64 i, j; /* Loop counters */ + int nPK = 0; /* Number of PRIMARY KEY columns */ + int i, j; /* Loop counters */ if( g.bSchemaPK==0 ){ /* Normal case: Figure out what the true primary key is for the table. @@ -274,7 +271,7 @@ static char **columnNames( } *pnPKey = nPK; naz = nPK; - az = sqlite3_malloc64( sizeof(char*)*(nPK+1) ); + az = sqlite3_malloc( sizeof(char*)*(nPK+1) ); if( az==0 ) runtimeError("out of memory"); memset(az, 0, sizeof(char*)*(nPK+1)); if( g.bSchemaCompare ){ @@ -291,7 +288,7 @@ static char **columnNames( || !(strcmp(sid,"rootpage")==0 ||strcmp(sid,"name")==0 ||strcmp(sid,"type")==0)){ - az = sqlite3_realloc64(az, sizeof(char*)*(naz+2) ); + az = sqlite3_realloc(az, sizeof(char*)*(naz+2) ); if( az==0 ) runtimeError("out of memory"); az[naz++] = sid; } @@ -957,7 +954,7 @@ static int rbuDeltaCreate( unsigned int i, base; char *zOrigDelta = zDelta; hash h; - i64 nHash; /* Number of hash table entries */ + int nHash; /* Number of hash table entries */ int *landmark; /* Primary hash table */ int *collide; /* Collision chain */ int lastRead = -1; /* Last byte of zSrc read by a COPY command */ @@ -985,7 +982,7 @@ static int rbuDeltaCreate( ** source file. */ nHash = lenSrc/NHASH; - collide = sqlite3_malloc64( nHash*2*sizeof(int) ); + collide = sqlite3_malloc( nHash*2*sizeof(int) ); landmark = &collide[nHash]; memset(landmark, -1, nHash*sizeof(int)); memset(collide, -1, nHash*sizeof(int)); @@ -1289,9 +1286,9 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ } }else{ char *zOtaControl; - i64 nOtaControl = sqlite3_column_bytes(pStmt, nCol); + int nOtaControl = sqlite3_column_bytes(pStmt, nCol); - zOtaControl = (char*)sqlite3_malloc64(nOtaControl+1); + zOtaControl = (char*)sqlite3_malloc(nOtaControl+1); memcpy(zOtaControl, sqlite3_column_text(pStmt, nCol), nOtaControl+1); for(i=0; i<nCol; i++){ @@ -1303,11 +1300,11 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ const char *aSrc = sqlite3_column_blob(pStmt, nCol+1+i); int nSrc = sqlite3_column_bytes(pStmt, nCol+1+i); const char *aFinal = sqlite3_column_blob(pStmt, i); - i64 nFinal = sqlite3_column_bytes(pStmt, i); + int nFinal = sqlite3_column_bytes(pStmt, i); char *aDelta; int nDelta; - aDelta = sqlite3_malloc64(nFinal + 60); + aDelta = sqlite3_malloc(nFinal + 60); nDelta = rbuDeltaCreate(aSrc, nSrc, aFinal, nFinal, aDelta); if( nDelta<nFinal ){ int j; @@ -1552,10 +1549,10 @@ static void changeset_one_table(const char *zTab, FILE *out){ sqlite3_stmt *pStmt; /* SQL statment */ char *zId = safeId(zTab); /* Escaped name of the table */ char **azCol = 0; /* List of escaped column names */ - i64 nCol = 0; /* Number of columns */ + int nCol = 0; /* Number of columns */ int *aiFlg = 0; /* 0 if column is not part of PK */ int *aiPk = 0; /* Column numbers for each PK column */ - i64 nPk = 0; /* Number of PRIMARY KEY columns */ + int nPk = 0; /* Number of PRIMARY KEY columns */ sqlite3_str *pSql; /* SQL for the diff query */ int i, k; /* Loop counters */ const char *zSep; /* List separator */ @@ -1567,16 +1564,16 @@ static void changeset_one_table(const char *zTab, FILE *out){ pStmt = db_prepare("PRAGMA main.table_info=%Q", zTab); while( SQLITE_ROW==sqlite3_step(pStmt) ){ nCol++; - azCol = sqlite3_realloc64(azCol, sizeof(char*)*nCol); + azCol = sqlite3_realloc(azCol, sizeof(char*)*nCol); if( azCol==0 ) runtimeError("out of memory"); - aiFlg = sqlite3_realloc64(aiFlg, sizeof(int)*nCol); + aiFlg = sqlite3_realloc(aiFlg, sizeof(int)*nCol); if( aiFlg==0 ) runtimeError("out of memory"); azCol[nCol-1] = safeId((const char*)sqlite3_column_text(pStmt,1)); aiFlg[nCol-1] = i = sqlite3_column_int(pStmt,5); if( i>0 ){ if( i>nPk ){ nPk = i; - aiPk = sqlite3_realloc64(aiPk, sizeof(int)*nPk); + aiPk = sqlite3_realloc(aiPk, sizeof(int)*nPk); if( aiPk==0 ) runtimeError("out of memory"); } aiPk[i-1] = nCol-1; @@ -1899,11 +1896,6 @@ static void showHelp(void){ ); } -/* work-around the Microsoft "WorstFit" bug */ -#ifdef _WIN32 -#define main utf8_main -#endif - int main(int argc, char **argv){ const char *zDb1 = 0; const char *zDb2 = 0; @@ -1916,7 +1908,7 @@ int main(int argc, char **argv){ FILE *out = stdout; void (*xDiff)(const char*,FILE*) = diff_one_table; #ifndef SQLITE_OMIT_LOAD_EXTENSION - i64 nExt = 0; + int nExt = 0; char **azExt = 0; #endif int useTransaction = 0; diff --git a/tool/sqlite3_rsync.c b/tool/sqlite3_rsync.c index b10224b2f..ad9f132bb 100644 --- a/tool/sqlite3_rsync.c +++ b/tool/sqlite3_rsync.c @@ -32,7 +32,6 @@ static const char zUsage[] = "\n" " --exe PATH Name of the sqlite3_rsync program on the remote side\n" " --help Show this help screen\n" - " -p|--port PORT Run SSH over TCP port PORT instead of the default 22\n" " --protocol N Use sync protocol version N.\n" " --ssh PATH Name of the SSH program used to reach the remote side\n" " -v Verbose. Multiple v's for increasing output\n" @@ -325,29 +324,15 @@ static int popen2( ** Close the connection to a child process previously created using ** popen2(). */ -static int pclose2(FILE *pIn, FILE *pOut, int childPid){ +static void pclose2(FILE *pIn, FILE *pOut, int childPid){ #ifdef _WIN32 /* Not implemented, yet */ fclose(pIn); fclose(pOut); - return 0; #else - int wp, rc = 0; fclose(pIn); fclose(pOut); - do{ - wp = waitpid(0, &rc, WNOHANG); - if( wp>0 ){ - if( WIFEXITED(rc) ){ - rc = WEXITSTATUS(rc); - }else if( WIFSIGNALED(rc) ){ - rc = WTERMSIG(rc); - }else{ - rc = 0/*???*/; - } - } - } while( wp>0 ); - return rc; + while( waitpid(0, 0, WNOHANG)>0 ) {} #endif } /***************************************************************************** @@ -2069,7 +2054,6 @@ int main(int argc, char const * const *argv){ FILE *pOut = 0; int childPid = 0; const char *zSsh = "ssh"; - int iPort = 0; const char *zExe = "sqlite3_rsync"; char *zCmd = 0; sqlite3_int64 tmStart; @@ -2110,15 +2094,6 @@ int main(int argc, char const * const *argv){ zSsh = cli_opt_val; continue; } - if( strcmp(z, "-port")==0 || strcmp(z, "-p")==0 ){ - const char *zPort = cli_opt_val; - iPort = atoi(zPort); - if( iPort<1 || iPort>65535 ){ - fprintf(stderr, "invalid TCP port number: \"%s\"\n", zPort); - return 1; - } - continue; - } if( strcmp(z, "-exe")==0 ){ zExe = cli_opt_val; continue; @@ -2260,7 +2235,6 @@ int main(int argc, char const * const *argv){ for(iRetry=0; 1 /*exit-via-break*/; iRetry++){ sqlite3_str *pStr = sqlite3_str_new(0); append_escaped_arg(pStr, zSsh, 1); - if( iPort>0 ) sqlite3_str_appendf(pStr, " -p %d", iPort); sqlite3_str_appendf(pStr, " -e none"); append_escaped_arg(pStr, ctx.zOrigin, 0); if( iRetry ) add_path_argument(pStr); @@ -2309,7 +2283,6 @@ int main(int argc, char const * const *argv){ for(iRetry=0; 1 /*exit-by-break*/; iRetry++){ sqlite3_str *pStr = sqlite3_str_new(0); append_escaped_arg(pStr, zSsh, 1); - if( iPort>0 ) sqlite3_str_appendf(pStr, " -p %d", iPort); sqlite3_str_appendf(pStr, " -e none"); append_escaped_arg(pStr, ctx.zReplica, 0); if( iRetry==1 ) add_path_argument(pStr); @@ -2376,7 +2349,7 @@ int main(int argc, char const * const *argv){ } originSide(&ctx); } - ctx.nErr += !!pclose2(ctx.pIn, ctx.pOut, childPid); + pclose2(ctx.pIn, ctx.pOut, childPid); if( ctx.pLog ) fclose(ctx.pLog); tmEnd = currentTime(); tmElapse = tmEnd - tmStart; /* Elapse time in milliseconds */ diff --git a/tool/sqltclsh.c.in b/tool/sqltclsh.c.in index 3e4d38b2d..da354ee93 100644 --- a/tool/sqltclsh.c.in +++ b/tool/sqltclsh.c.in @@ -33,7 +33,7 @@ INCLUDE $ROOT/ext/misc/appendvfs.c INCLUDE $ROOT/ext/misc/zipfile.c INCLUDE $ROOT/ext/misc/sqlar.c #endif -INCLUDE tclsqlite-ex.c +INCLUDE $ROOT/src/tclsqlite.c const char *sqlite3_tclapp_init_proc(Tcl_Interp *interp){ (void)interp; diff --git a/tool/winmain.c b/tool/winmain.c deleted file mode 100644 index 72ae00d5c..000000000 --- a/tool/winmain.c +++ /dev/null @@ -1,79 +0,0 @@ -/* -** 2025-12-26 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** -** This file implements a substitute process entry point for windows -** that correctly interprets command-line arguments as UTF-8. This is -** a work-around for the (unfixed) Windows bug known as "WorstFit" and -** described at: -** -** * https://news.ycombinator.com/item?id=42647101 -** -** USAGE: -** -** If you have a command-line program coded to C-language standard in which -** the entry point is: -** -** int main(int argc, char **argv){...} -** -** That does not work on Windows if there are non-ASCII characters on the -** command line. Programs are expected to use an alternative entry point: -** -** int wmain(int wargc, wchar_t **wargv){...} -** -** This file implements _wmain() but then converts all of the wargv elements -** to UTF-8 and passes them off to utf8_main(). -** -** So, a command-line tool that implements the standard entry point can be -** modified by adding the following lines just prior to main(): -** -** #ifdef _WIN32 -** #define main utf8_main -** #endif -** -** Then, include this file in the list of files that implement the program, -** and the program will work correctly, even on Windows. -*/ -#include <windows.h> -#include <stdio.h> - -extern int utf8_main(int,char**); -int wmain(int argc, wchar_t **wargv){ - int rc, i; - char **argv = malloc( sizeof(char*) * (argc+1) ); - char **orig = argv; - if( argv==0 ){ - fprintf(stderr, "malloc failed\n"); - exit(1); - } - for(i=0; i<argc; i++){ - int nByte = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, 0, 0, 0, 0); - if( nByte==0 ){ - argv[i] = 0; - }else{ - argv[i] = malloc( nByte ); - if( argv[i]==0 ){ - fprintf(stderr, "malloc failed\n"); - exit(1); - } - nByte = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i],nByte,0,0); - if( nByte==0 ){ - free(argv[i]); - argv[i] = 0; - } - } - } - argv[argc] = 0; - rc = utf8_main(argc, argv); - for(i=0; i<argc; i++) free(orig[i]); - free(argv); - return rc; -} From 778ab890cfc30c3631212dcceb0295498abdcd3e Mon Sep 17 00:00:00 2001 From: Stephen Lombardo <sjlombardo@zetetic.net> Date: Fri, 13 Mar 2026 16:50:55 -0400 Subject: [PATCH 15/15] Update changelog to reflect upstream SQLite 3.51.3 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51fcfd558..cab05a7d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Notable changes to this project are documented in this file. ## [4.14.0] - (? 2026 - [4.14.0 changes]) -- Updates baseline to SQLite 3.52.0 +- Updates baseline to SQLite 3.51.3 - Restores and improves upon LibTomCrypto provder - Minor test improvements