aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in4
-rw-r--r--Makefile.msc5
-rw-r--r--ext/fts5/fts5_index.c61
-rw-r--r--ext/fts5/test/fts5corrupt3.test218
-rw-r--r--ext/fts5/test/fts5corrupt8.test53
-rw-r--r--ext/fts5/test/fts5integrity.test6
-rw-r--r--ext/fts5/test/fts5leftjoin.test49
-rw-r--r--ext/misc/vtablog.c55
-rw-r--r--ext/rtree/rtree.c6
-rw-r--r--ext/rtree/rtreeH.test19
-rw-r--r--ext/session/sessionI.test88
-rw-r--r--ext/session/sqlite3session.c177
-rw-r--r--ext/session/sqlite3session.h67
-rw-r--r--ext/session/test_session.c225
-rw-r--r--ext/wasm/GNUmakefile6
-rw-r--r--ext/wasm/api/sqlite3-api-oo1.c-pp.js329
-rw-r--r--ext/wasm/api/sqlite3-api-prologue.js17
-rw-r--r--ext/wasm/fiddle.make3
-rw-r--r--ext/wasm/tester1.c-pp.js106
-rw-r--r--main.mk4
-rw-r--r--manifest120
-rw-r--r--manifest.uuid2
-rw-r--r--sqlite3.pc.in2
-rw-r--r--src/btree.c28
-rw-r--r--src/btree.h1
-rw-r--r--src/build.c15
-rw-r--r--src/expr.c241
-rw-r--r--src/parse.y19
-rw-r--r--src/resolve.c5
-rw-r--r--src/select.c147
-rw-r--r--src/shell.c.in5
-rw-r--r--src/sqliteInt.h7
-rw-r--r--src/tokenize.c2
-rw-r--r--src/vdbe.c35
-rw-r--r--src/vdbe.h2
-rw-r--r--src/vdbeInt.h2
-rw-r--r--src/vdbeaux.c2
-rw-r--r--src/vdbeblob.c33
-rw-r--r--src/where.c46
-rw-r--r--src/whereInt.h1
-rw-r--r--src/wherecode.c55
-rw-r--r--src/whereexpr.c24
-rw-r--r--test/altertab2.test2
-rw-r--r--test/altertab3.test12
-rw-r--r--test/bestindexC.test74
-rw-r--r--test/between.test17
-rw-r--r--test/eqp.test3
-rw-r--r--test/existsexpr.test426
-rw-r--r--test/existsexpr2.test96
-rw-r--r--test/existsfault.test49
-rw-r--r--test/incrblob4.test98
-rw-r--r--test/json101.test8
-rw-r--r--test/notnull2.test2
-rw-r--r--test/parser1.test22
-rw-r--r--test/rowvalue.test37
-rw-r--r--test/speedtest1.c14
-rwxr-xr-xtest/testrunner.tcl112
-rw-r--r--test/testrunner_estwork.tcl488
-rw-r--r--tool/mktoolzip.tcl64
59 files changed, 3327 insertions, 489 deletions
diff --git a/Makefile.in b/Makefile.in
index 995bbde44..d462d811e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -264,7 +264,9 @@ AS_AUTO_DEF = $(TOP)/auto.def
# invoked with to produce this makefile.
#
AS_AUTORECONFIG = @SQLITE_AUTORECONFIG@
-
+.PHONY: reconfigure
+reconfigure:
+ $(AS_AUTORECONFIG)
USE_AMALGAMATION ?= @USE_AMALGAMATION@
LINK_TOOLS_DYNAMICALLY ?= @LINK_TOOLS_DYNAMICALLY@
AMALGAMATION_GEN_FLAGS ?= --linemacros=@AMALGAMATION_LINE_MACROS@
diff --git a/Makefile.msc b/Makefile.msc
index 1533cf3de..e67638287 100644
--- a/Makefile.msc
+++ b/Makefile.msc
@@ -2596,9 +2596,12 @@ extensiontest: testfixture.exe testloadext.dll
@set PATH=$(LIBTCLPATH);$(PATH)
.\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS)
-tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe sqlite3_rsync.exe $(TOP)\tool\mktoolzip.tcl
+tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe sqlite3_rsync.exe sqlite3.dll $(TOP)\tool\mktoolzip.tcl
.\testfixture.exe $(TOP)\tool\mktoolzip.tcl
+snapshot-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe sqlite3_rsync.exe sqlite3.dll $(TOP)\tool\mktoolzip.tcl
+ .\testfixture.exe $(TOP)\tool\mktoolzip.tcl --snapshot
+
coretestprogs: testfixture.exe sqlite3.exe
testprogs: $(TESTPROGS) srcck1.exe fuzzcheck.exe sessionfuzz.exe
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c
index a91254371..182936cda 100644
--- a/ext/fts5/fts5_index.c
+++ b/ext/fts5/fts5_index.c
@@ -1986,9 +1986,9 @@ static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){
** leave an error in the Fts5Index object.
*/
static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){
- const int nTomb = pIter->pSeg->nPgTombstone;
+ const i64 nTomb = (i64)pIter->pSeg->nPgTombstone;
if( nTomb>0 ){
- int nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1);
+ i64 nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1);
Fts5TombstoneArray *pNew;
pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte);
if( pNew ){
@@ -2481,18 +2481,20 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){
fts5DataRelease(pIter->pLeaf);
pIter->pLeaf = pLast;
pIter->iLeafPgno = pgnoLast;
- iOff = fts5LeafFirstRowidOff(pLast);
- if( iOff>pLast->szLeaf ){
- FTS5_CORRUPT_ITER(p, pIter);
- return;
- }
- iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid);
- pIter->iLeafOffset = iOff;
+ if( p->rc==SQLITE_OK ){
+ iOff = fts5LeafFirstRowidOff(pLast);
+ if( iOff>pLast->szLeaf ){
+ FTS5_CORRUPT_ITER(p, pIter);
+ return;
+ }
+ iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid);
+ pIter->iLeafOffset = iOff;
- if( fts5LeafIsTermless(pLast) ){
- pIter->iEndofDoclist = pLast->nn+1;
- }else{
- pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast);
+ if( fts5LeafIsTermless(pLast) ){
+ pIter->iEndofDoclist = pLast->nn+1;
+ }else{
+ pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast);
+ }
}
}
@@ -5845,7 +5847,7 @@ static Fts5Structure *fts5IndexOptimizeStruct(
}
nByte += (((i64)pStruct->nLevel)+1) * sizeof(Fts5StructureLevel);
- assert( nByte==SZ_FTS5STRUCTURE(pStruct->nLevel+2) );
+ assert( nByte==(i64)SZ_FTS5STRUCTURE(pStruct->nLevel+2) );
pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
if( pNew ){
@@ -8284,19 +8286,27 @@ static int fts5TestUtf8(const char *z, int n){
/*
** This function is also purely an internal test. It does not contribute to
** FTS functionality, or even the integrity-check, in any way.
+**
+** This function sets output variable (*pbFail) to true if the test fails. Or
+** leaves it unchanged if the test succeeds.
*/
static void fts5TestTerm(
Fts5Index *p,
Fts5Buffer *pPrev, /* Previous term */
const char *z, int n, /* Possibly new term to test */
u64 expected,
- u64 *pCksum
+ u64 *pCksum,
+ int *pbFail
){
int rc = p->rc;
if( pPrev->n==0 ){
fts5BufferSet(&rc, pPrev, n, (const u8*)z);
}else
- if( rc==SQLITE_OK && (pPrev->n!=n || memcmp(pPrev->p, z, n)) ){
+ if( *pbFail==0
+ && rc==SQLITE_OK
+ && (pPrev->n!=n || memcmp(pPrev->p, z, n))
+ && (p->pHash==0 || p->pHash->nEntry==0)
+ ){
u64 cksum3 = *pCksum;
const char *zTerm = (const char*)&pPrev->p[1]; /* term sans prefix-byte */
int nTerm = pPrev->n-1; /* Size of zTerm in bytes */
@@ -8346,7 +8356,7 @@ static void fts5TestTerm(
fts5BufferSet(&rc, pPrev, n, (const u8*)z);
if( rc==SQLITE_OK && cksum3!=expected ){
- rc = FTS5_CORRUPT;
+ *pbFail = 1;
}
*pCksum = cksum3;
}
@@ -8355,7 +8365,7 @@ static void fts5TestTerm(
#else
# define fts5TestDlidxReverse(x,y,z)
-# define fts5TestTerm(u,v,w,x,y,z)
+# define fts5TestTerm(t,u,v,w,x,y,z)
#endif
/*
@@ -8613,6 +8623,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){
/* Used by extra internal tests only run if NDEBUG is not defined */
u64 cksum3 = 0; /* Checksum based on contents of indexes */
Fts5Buffer term = {0,0,0}; /* Buffer used to hold most recent term */
+ int bTestFail = 0;
#endif
const int flags = FTS5INDEX_QUERY_NOOUTPUT;
@@ -8655,7 +8666,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){
char *z = (char*)fts5MultiIterTerm(pIter, &n);
/* If this is a new term, query for it. Update cksum3 with the results. */
- fts5TestTerm(p, &term, z, n, cksum2, &cksum3);
+ fts5TestTerm(p, &term, z, n, cksum2, &cksum3, &bTestFail);
if( p->rc ) break;
if( eDetail==FTS5_DETAIL_NONE ){
@@ -8673,7 +8684,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){
}
}
}
- fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3);
+ fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3, &bTestFail);
fts5MultiIterFree(pIter);
if( p->rc==SQLITE_OK && bUseCksum && cksum!=cksum2 ){
@@ -8682,11 +8693,17 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){
"fts5: checksum mismatch for table \"%s\"", p->pConfig->zName
);
}
-
- fts5StructureRelease(pStruct);
#ifdef SQLITE_DEBUG
+ /* In SQLITE_DEBUG builds, expensive extra checks were run as part of
+ ** the integrity-check above. If no other errors were detected, but one
+ ** of these tests failed, set the result to SQLITE_CORRUPT_VTAB here. */
+ if( p->rc==SQLITE_OK && bTestFail ){
+ p->rc = FTS5_CORRUPT;
+ }
fts5BufferFree(&term);
#endif
+
+ fts5StructureRelease(pStruct);
fts5BufferFree(&poslist);
return fts5IndexReturn(p);
}
diff --git a/ext/fts5/test/fts5corrupt3.test b/ext/fts5/test/fts5corrupt3.test
index ea7030a75..eab4c3c91 100644
--- a/ext/fts5/test/fts5corrupt3.test
+++ b/ext/fts5/test/fts5corrupt3.test
@@ -6644,7 +6644,7 @@ do_test 48.0 {
do_catchsql_test 48.1 {
INSERT INTO t1(t1) VALUES('integrity-check');
-} {1 {database disk image is malformed}}
+} {1 {fts5: corruption on page 1, segment 1, table "t1"}}
#--------------------------------------------------------------------------
reset_db
@@ -10106,7 +10106,7 @@ do_test 68.0 {
do_catchsql_test 68.1 {
PRAGMA reverse_unordered_selects=ON;
INSERT INTO t1(t1) SELECT x FROM t2;
-} {1 {database disk image is malformed}}
+} {1 {fts5: corruption on page 1, segment 1, table "t1"}}
#-------------------------------------------------------------------------
reset_db
@@ -15909,8 +15909,220 @@ do_catchsql_test 82.4 {
SAVEPOINT b;
} {1 {database disk image is malformed}}
+#-------------------------------------------------------------------------
+reset_db
+do_test 83.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+.open --hexdb
+| size 24576 pagesize 4096 filename crash-c4a4c5492615bd.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 06 .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 00 ................
+| 96: 00 00 00 00 0d 00 00 00 06 0e 0f 00 0f aa 0f 53 ...............S
+| 112: 0e e8 0e 8b 0e 33 0e 0f 00 01 00 00 00 00 00 00 .....3..........
+| 3584: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 22 ................
+| 3600: 06 06 17 11 11 01 31 74 61 62 6c 65 62 62 62 62 ......1tablebbbb
+| 3616: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 62 62 .CREATE TABLE bb
+| 3632: 28 61 29 56 05 06 17 1f 1f 01 7d 74 61 62 6c 65 (a)V.......table
+| 3648: 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 t1_configt1_conf
+| 3664: 69 67 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 ig.CREATE TABLE
+| 3680: 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 't1_config'(k PR
+| 3696: 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 20 57 49 IMARY KEY, v) WI
+| 3712: 54 48 4f 55 54 20 52 4f 57 49 44 5b 04 07 17 21 THOUT ROWID[...!
+| 3728: 21 01 81 01 74 61 62 6c 65 74 31 5f 64 6f 63 73 !...tablet1_docs
+| 3744: 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 04 43 52 izet1_docsize.CR
+| 3760: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 EATE TABLE 't1_d
+| 3776: 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 54 45 47 ocsize'(id INTEG
+| 3792: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY,
+| 3808: 73 7a 20 42 4c 4f 42 29 69 03 07 17 19 19 01 81 sz BLOB)i.......
+| 3824: 2d 74 61 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 -tablet1_idxt1_i
+| 3840: 64 78 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 dx.CREATE TABLE
+| 3856: 27 74 31 5f 69 64 78 27 28 73 65 67 69 64 2c 20 't1_idx'(segid,
+| 3872: 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 4d term, pgno, PRIM
+| 3888: 41 52 59 20 4b 45 59 28 73 65 67 69 64 2c 20 74 ARY KEY(segid, t
+| 3904: 65 72 6d 29 29 20 57 49 54 48 4f 55 54 20 52 4f erm)) WITHOUT RO
+| 3920: 57 49 44 55 02 07 17 1b 1b 01 81 01 74 61 62 6c WIDU........tabl
+| 3936: 65 74 31 5f 64 61 74 61 74 31 5f 64 61 74 61 02 et1_datat1_data.
+| 3952: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1
+| 3968: 5f 64 61 74 61 27 28 69 64 20 49 4e 54 45 47 45 _data'(id INTEGE
+| 3984: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 R PRIMARY KEY, b
+| 4000: 6c 6f 63 6b 20 42 4c 4f 42 29 54 01 07 17 11 11 lock BLOB)T.....
+| 4016: 08 81 15 74 61 62 6c 65 74 31 74 31 43 52 45 41 ...tablet1t1CREA
+| 4032: 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 4c 45 TE VIRTUAL TABLE
+| 4048: 20 74 31 20 55 53 49 4e 47 20 66 74 73 35 28 61 t1 USING fts5(a
+| 4064: 2c 62 2c 70 72 65 66 69 78 3d 22 31 2c 32 2c 33 ,b,prefix=.1,2,3
+| 4080: 2c 34 22 2c 20 63 6f 6e 74 65 6e 74 3d 22 22 29 ,4., content=..)
+| page 2 offset 4096
+| 0: 0d 0b 6a 00 37 09 4c 02 0f e7 09 4c 0f c6 0f a4 ..j.7.L....L....
+| 16: 0f 88 0f 6d 0f 4b 0f 2c 0f 0e 0e ec 0e cd 0e ad ...m.K.,........
+| 32: 0e 8e 0e 6c 0e 4b 0e 29 0e 08 0d e6 0d c4 0d b5 ...l.K.)........
+| 48: 0d 97 0d 76 0d 54 0d 31 0d 15 0c f3 0c d3 0c b5 ...v.T.1........
+| 64: 0c 95 0c 73 0c 54 0c 32 0c 10 0b ee 0b cc 0b b0 ...s.T.2........
+| 80: 0b 8d 0b 7e 0b 48 0b 2e 0b 00 4a ef fa cc 0a ad ...~.H....J.....
+| 96: 0a 8c 0a 6d 0a 4d 0a 2b 0a 0c 09 00 00 00 00 00 ...m.M.+........
+| 2368: 00 00 00 00 00 00 00 00 00 00 00 00 15 0a 03 00 ................
+| 2384: 30 00 00 00 01 01 03 35 00 03 01 01 12 02 01 12 0......5........
+| 2400: 03 01 11 1c 8c 80 80 80 80 10 03 00 3e 00 00 00 ............>...
+| 2416: 17 01 05 05 34 74 61 62 6c 03 02 03 01 04 77 68 ....4tabl.....wh
+| 2432: 65 72 03 02 06 09 1b 8c 80 80 80 80 0f 03 00 3c er.............<
+| 2448: 00 00 00 16 05 34 66 74 73 34 03 02 02 01 04 6e .....4fts4.....n
+| 2464: 75 6d 62 03 07 01 04 09 1b 8c 80 80 80 80 0e 03 umb.............
+| 2480: 00 3c 00 00 00 16 04 33 74 68 65 03 06 01 01 04 .<.....3the.....
+| 2496: 01 03 77 68 65 03 02 04 04 0a 1b 8c 80 80 80 80 ..whe...........
+| 2512: 0d 03 00 3c 00 00 00 16 04 33 6e 75 6d 03 06 01 ...<.....3num...
+| 2528: 01 05 01 03 74 61 62 03 02 03 04 0a 19 8c 80 80 ....tab.........
+| 2544: 80 80 0c 03 00 38 00 00 00 14 03 32 77 68 03 02 .....8.....2wh..
+| 2560: 04 00 04 33 66 74 73 03 02 02 04 07 18 8c 80 80 ...3fts.........
+| 2576: 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 03 02 .....6.....2ta..
+| 2592: 03 02 01 68 03 06 01 01 04 04 07 1b 8c 80 80 80 ...h............
+| 2608: 80 0a 03 00 3c 00 00 00 16 03 32 6e 75 03 06 01 ....<.....2nu...
+| 2624: 01 05 01 02 6f 66 03 06 01 01 06 04 09 19 8c 80 ....of..........
+| 2640: 80 80 80 09 03 00 38 00 00 00 14 03 32 66 74 03 ......8.....2ft.
+| 2656: 02 02 01 02 69 73 02 06 01 01 03 04 07 18 8c 80 ....is..........
+| 2672: 80 80 22 08 03 00 36 00 00 00 13 02 31 74 03 08 ......6.....1t..
+| 2688: 03 01 01 04 01 01 77 03 02 04 04 09 1a 8c 80 80 ......w.........
+| 2704: 80 80 07 03 00 3a 00 00 00 15 02 31 6e 03 08 01 .....:.....1n...
+| 2720: 01 02 05 01 01 6f 03 06 01 01 06 04 09 18 8c 80 .....o..........
+| 2736: 80 80 80 06 03 00 36 00 00 00 13 04 02 31 66 03 ......6......1f.
+| 2752: 02 02 01 01 69 03 06 01 01 03 05 06 1c 8c 80 80 ....i...........
+| 2768: 80 80 05 03 00 3e 00 00 00 17 04 30 74 68 65 03 .....>.....0the.
+| 2784: 06 01 01 04 01 05 77 68 65 72 65 03 02 04 0a 15 ......where.....
+| 2800: 8c 80 80 80 80 04 03 00 30 00 00 00 11 01 01 06 ........0.......
+| 2816: 06 30 74 61 62 6c 65 03 01 f3 07 1c 8c 80 80 80 .0table.........
+| 2832: 80 03 03 00 3e 00 00 00 17 07 30 6e 75 6d 62 65 ....>.....0numbe
+| 2848: 72 03 06 01 01 05 01 02 6f 66 03 06 04 0d 13 8c r.......of......
+| 2864: 80 80 80 80 02 03 00 2c 00 00 00 0f 01 01 03 02 .......,........
+| 2880: 30 6e 03 06 01 01 02 07 1b 8c 80 80 80 80 01 03 0n..............
+| 2896: 00 3c 00 00 00 16 08 30 66 74 73 34 61 75 78 03 .<.....0fts4aux.
+| 2912: 02 02 01 02 69 73 03 06 04 0c 00 00 00 14 2a 00 ....is........*.
+| 2928: 00 00 01 01 02 24 00 02 01 01 12 02 01 12 08 88 .....$..........
+| 2944: 80 80 80 80 12 03 00 16 00 00 00 05 02 1c 88 80 ................
+| 2960: 80 80 80 11 03 00 3e 00 00 00 17 05 34 72 6f 77 ......>.....4row
+| 2976: 73 02 06 01 01 05 01 04 74 68 65 72 02 02 04 0b s.......ther....
+| 2992: 15 88 80 80 80 80 10 03 00 30 00 00 00 11 02 01 .........0......
+| 3008: 01 07 05 34 62 65 74 77 02 02 04 08 1b 88 80 80 ...4betw........
+| 3024: 80 80 0f 03 00 3c 00 00 00 16 04 04 33 72 6f 77 .....<......3row
+| 3040: 02 06 01 01 05 01 03 74 68 65 02 08 05 0a 1b 88 .......the......
+| 3056: 80 80 80 80 0e 03 00 3c 00 00 00 16 01 01 02 04 .......<........
+| 3072: 33 61 72 65 02 02 b3 01 03 62 65 74 02 02 07 08 3are.....bet....
+| 3088: 1b 88 80 80 80 80 0d 03 00 3c 00 00 00 16 03 32 .........<.....2
+| 3104: 74 68 02 08 02 01 01 07 00 04 33 61 6e 64 02 06 th........3and..
+| 3120: 04 0a 1b 88 80 80 80 80 0c 03 00 3c 00 00 00 16 ...........<....
+| 3136: 03 32 69 6e 02 06 01 01 06 01 02 72 6f 02 06 01 .2in.......ro...
+| 3152: 01 05 04 09 18 88 80 80 80 80 0b 03 00 36 00 0f .............6..
+| 3168: f0 13 02 03 32 61 72 02 02 03 01 02 62 65 02 02 ....2ar.....be..
+| 3184: 03 05 07 1b 88 80 80 80 80 0a 03 00 3c dd 00 00 ............<...
+| 3200: 18 c2 31 74 02 08 02 01 01 07 00 03 32 61 6e 02 ..1t........2an.
+| 3216: 06 01 01 04 09 19 88 80 80 80 80 09 03 00 38 00 ..............8.
+| 3232: 00 00 14 02 31 6e 02 06 01 01 03 01 01 72 02 06 ....1n.......r..
+| 3248: 01 01 05 04 08 17 88 80 80 80 80 08 03 00 34 00 ..............4.
+| 3264: 00 00 12 02 31 62 02 02 04 01 01 69 02 06 01 01 ....1b.....i....
+| 3280: 06 04 06 19 88 80 90 80 80 07 03 00 38 00 00 00 ............8...
+| 3296: 14 04 02 31 32 02 02 05 01 01 61 02 08 03 01 01 ...12.....a.....
+| 3312: 02 05 06 1b 88 80 80 80 80 06 03 00 3c 00 00 00 ............<...
+| 3328: 16 06 30 74 68 65 72 65 02 02 02 00 02 31 31 02 ..0there.....11.
+| 3344: 06 01 01 04 0a 15 88 80 80 80 80 05 03 00 30 00 ..............0.
+| 3360: 00 00 11 01 01 05 04 30 74 68 65 02 06 01 01 07 .......0the.....
+| 3376: 07 1c 88 80 80 80 80 04 03 00 3e 00 00 00 17 01 ..........>.....
+| 3392: 01 06 02 30 6e 02 06 01 01 03 01 04 72 6f 77 73 ...0n.......rows
+| 3408: 02 06 07 08 1b 88 80 80 80 80 03 03 00 3c 00 00 .............<..
+| 3424: 00 16 08 30 62 65 74 77 65 65 6e 02 02 04 01 02 ...0between.....
+| 3440: 69 6e 02 06 04 0c 1a 88 80 80 80 80 02 03 00 3a in.............:
+| 3456: 00 00 00 15 04 30 61 6e 64 02 06 01 01 02 02 02 .....0and.......
+| 3472: 72 65 02 02 03 04 0a 17 88 80 80 80 80 01 03 00 re..............
+| 3488: 34 00 00 0c 52 02 30 31 02 06 01 01 04 01 01 32 4...R.01.......2
+| 3504: 02 02 05 04 08 08 84 80 80 80 80 12 03 00 16 00 ................
+| 3520: 00 00 05 04 1b 84 80 80 80 80 11 03 00 3c 00 00 .............<..
+| 3536: 00 16 05 34 74 61 62 6c 01 06 00 f1 05 02 03 65 ...4tabl.......e
+| 3552: 72 6d 01 02 04 0b 1b 84 80 80 80 80 10 03 00 3c rm.............<
+| 3568: 00 00 00 16 05 34 65 61 63 68 01 02 03 01 04 70 .....4each.....p
+| 3584: 72 65 73 01 02 05 04 09 1a 84 80 80 80 80 0f 03 res.............
+| 3600: 00 3a 00 00 00 15 04 33 74 65 72 01 02 04 02 02 .:.....3ter.....
+| 3616: 68 65 01 06 01 01 03 04 08 1b 84 80 80 80 80 0e he..............
+| 3632: 03 00 3c 00 00 00 16 04 33 70 72 65 01 02 05 01 ..<.....3pre....
+| 3648: 03 74 61 62 01 06 01 01 05 04 08 1a 84 80 80 80 .tab............
+| 3664: 80 0d 03 00 3a 00 00 00 15 04 33 66 6f 72 01 02 ....:.....3for..
+| 3680: 02 02 02 74 73 01 06 01 01 04 04 08 1b 84 80 80 ...ts...........
+| 3696: 80 80 0c 03 00 3c 00 00 00 16 03 32 74 68 01 06 .....<.....2th..
+| 3712: 01 01 03 00 04 33 65 61 63 01 02 03 04 09 18 74 .....3eac......t
+| 3728: 80 80 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 .......6.....2ta
+| 3744: 01 06 01 01 05 02 01 65 01 02 04 04 09 19 84 80 .......e........
+| 3760: 80 80 80 0a 03 00 38 00 00 00 14 03 32 69 6e 01 ......8.....2in.
+| 3776: 06 01 01 02 11 02 70 62 01 02 05 04 09 18 84 80 ......pb........
+| 3792: 80 80 80 09 03 00 36 00 00 00 13 03 32 66 6f 01 ......6.....2fo.
+| 3808: 02 02 02 01 74 01 06 01 01 04 04 07 1b 84 80 80 ....t...........
+| 3824: 80 80 08 03 00 3c 0d c0 00 16 12 31 74 01 0a 04 .....<.....1t...
+| 3840: 01 01 03 04 00 03 32 65 61 01 02 03 04 0a 17 84 ......2ea.......
+| 3856: 80 80 80 80 07 03 00 34 00 00 00 12 02 31 69 01 .......4.....1i.
+| 3872: 06 01 01 02 01 01 70 01 02 05 04 08 18 84 80 80 ......p.........
+| 3888: 80 80 06 03 00 36 00 00 00 13 02 31 65 01 02 03 .....6.....1e...
+| 3904: 01 01 66 01 08 02 5b 01 04 04 06 1b 84 80 80 80 ..f...[.........
+| 3920: 80 05 03 00 3c 00 00 00 16 05 30 74 65 72 6d 01 ....<.....0term.
+| 3936: 02 04 02 02 68 65 01 06 01 01 03 04 09 14 84 80 ....he..........
+| 3952: 80 80 80 04 03 00 2e 00 00 00 10 06 30 74 61 62 ............0tab
+| 3968: 6c 65 01 06 01 01 05 04 15 84 80 80 80 80 03 03 le..............
+| 3984: 00 30 00 00 00 11 01 f8 30 70 72 65 73 65 6e 74 .0......0present
+| 4000: 01 02 05 05 1b 84 80 80 80 80 02 03 00 3c 00 00 .............<..
+| 4016: 00 16 04 30 66 74 73 01 06 01 01 04 01 02 69 6e ...0fts.......in
+| 4032: 01 06 01 01 04 0a 1a 84 80 80 80 80 01 03 00 3a ...............:
+| 4048: 00 00 00 15 05 30 65 61 63 68 01 02 03 01 03 66 .....0each.....f
+| 4064: 6f 72 01 02 02 04 09 06 01 03 00 12 03 0b 0f 00 or..............
+| 4080: 00 08 8c 80 80 80 80 11 03 00 16 00 00 00 05 04 ................
+| page 3 offset 8192
+| 0: 0a 00 00 00 32 0e 4f 00 0f fa 0f f1 0f e9 0f e1 ....2.O.........
+| 16: 0f d8 0f d1 0f c9 0f c1 0f b9 0f b1 0f a9 0f a0 ................
+| 32: 0f 98 0f 90 0f 87 0f 80 0f 78 0f 71 0f 68 0f 5f .........x.q.h._
+| 48: 0f 56 0f 4d 0f 41 0f 38 0f 2f 0f 26 0f 1d 0f 13 .V.M.A.8./.&....
+| 64: 0f 0a 0f 01 0e f7 0e ee 0e e6 0e dd 0e d6 0e cd ................
+| 80: 0e c3 0e ba 0e b0 0e a8 0e 9f 0e 96 0e 00 00 00 ................
+| 3648: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 ................
+| 3664: 04 01 10 01 03 34 74 20 07 04 02 4e 01 03 34 1e .....4t ...N..4.
+| 3680: 09 04 01 12 01 03 33 74 68 1c 08 04 01 10 01 03 ......3th.......
+| 3696: 33 6e 1a 08 04 01 10 01 03 32 77 18 08 04 01 10 3n.......2w.....
+| 3712: 01 03 32 74 16 08 04 01 10 01 03 32 6e 14 07 04 ..2t.......2n...
+| 3728: 01 0e 01 03 32 12 08 04 01 10 01 03 31 74 10 08 ....2.......1t..
+| 3744: 04 01 10 01 03 31 6e 0e 07 04 01 0e 01 03 31 0c .....1n.......1.
+| 3760: 09 04 01 12 01 03 30 74 68 0a 08 04 01 10 01 03 ......0th.......
+| 3776: 30 74 08 19 04 01 12 01 03 30 6e 75 06 08 04 01 0t.......0nu....
+| 3792: 10 01 03 30 6e 04 06 04 01 0c 01 03 02 08 04 01 ...0n...........
+| 3808: 10 01 02 34 72 22 07 04 01 0e 01 02 34 20 08 04 ...4r.......4 ..
+| 3824: 01 10 01 02 33 72 1e 09 04 01 12 01 02 33 61 72 ....3r.......3ar
+| 3840: 1c 08 04 01 10 01 02 32 74 1a 08 04 01 10 01 02 .......2t.......
+| 3856: 32 69 18 09 04 01 12 01 02 32 60 82 16 08 04 01 2i.......2`.....
+| 3872: 10 01 02 31 74 14 08 04 01 10 01 02 31 6e 12 08 ...1t.......1n..
+| 3888: 04 01 10 01 02 31 62 10 08 04 01 10 01 02 31 32 .....1b.......12
+| 3904: 0e 0b 04 01 16 01 02 30 74 68 65 72 0c 08 04 01 .......0ther....
+| 3920: 10 01 02 30 74 0a 08 04 01 10 01 02 30 6e 08 08 ...0t.......0n..
+| 3936: 04 01 10 01 02 30 62 06 08 04 01 10 01 02 30 61 .....0b.......0a
+| 3952: 04 06 04 01 0c 01 02 02 07 04 09 10 01 34 74 22 .............4t.
+| 3968: 06 04 09 0e 01 34 20 08 04 09 12 01 33 74 65 1e .....4 .....3te.
+| 3984: 07 04 09 10 01 33 70 1c 07 04 09 10 01 33 66 1a .....3p......3f.
+| 4000: 08 04 09 12 01 32 74 68 18 07 04 09 10 01 32 74 .....2th......2t
+| 4016: 16 07 04 09 10 01 32 69 14 07 04 09 10 01 32 66 ......2i......2f
+| 4032: 12 07 04 09 10 01 31 74 10 07 04 09 10 01 31 69 ......1t......1i
+| 4048: 0e 06 04 09 0e 01 31 0c 08 04 09 12 01 30 74 65 ......1......0te
+| 4064: 0a 07 04 09 10 01 30 74 08 07 04 09 10 01 30 70 ......0t......0p
+| 4080: 06 08 04 09 12 01 30 66 74 04 05 04 09 0c 01 02 ......0ft.......
+| page 4 offset 12288
+| 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 4064: 00 00 00 00 00 00 00 00 00 00 00 05 03 03 00 10 ................
+| 4080: 03 05 05 02 03 00 10 04 06 05 01 03 00 10 04 04 ................
+| page 5 offset 16384
+| 0: 0a 00 00 00 02 0f eb 00 0f eb 0f f4 00 00 00 00 ................
+| 4064: 00 00 00 00 00 00 00 00 00 00 00 08 03 15 01 70 ...............p
+| 4080: 67 83 7a 18 0b 03 1b 01 76 65 72 73 69 6f 6e 04 g.z.....version.
+| page 6 offset 20480
+| 0: 0d 00 00 00 03 0f f2 00 0f fc 0f 00 00 00 00 00 ................
+| 4080: 00 00 03 03 02 01 03 03 02 02 01 02 02 01 02 09 ................
+| end crash-c4a4c5492615bd.db
+}]} {}
+
+do_catchsql_test 83.1 {
+ SELECT * FROM t1('R*R*R*R*R*R*R*R*') WHERE (a,b)<=(current_date,0 BETWEEN 'a'<>11 AND '') ORDER BY rowid DESC;
+} {/.*fts5: corruption found/}
sqlite3_fts5_may_be_corrupt 0
finish_test
-
diff --git a/ext/fts5/test/fts5corrupt8.test b/ext/fts5/test/fts5corrupt8.test
index a43bbaa03..471a1b0e3 100644
--- a/ext/fts5/test/fts5corrupt8.test
+++ b/ext/fts5/test/fts5corrupt8.test
@@ -90,5 +90,58 @@ do_execsql_test 3.7 {
SELECT * FROM sqlite_schema
}
+#-------------------------------------------------------------------------
+reset_db
+
+proc hex_to_blob {hex} {
+ binary encode hex $hex
+}
+db func hex_to_blob hex_to_blob
+
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(x, content='', contentless_delete=1);
+ BEGIN;
+ INSERT INTO x1(rowid, x) VALUES(1, 'a b c d e f g h');
+ INSERT INTO x1(rowid, x) VALUES(2, 'a b c d e f g h');
+ COMMIT;
+ DELETE FROM x1 WHERE rowid=1;
+}
+
+do_execsql_test 4.1 {
+ SELECT hex(block) FROM x1_data WHERE id=10
+} {
+ 00000000FF00000101010200010101010101010102
+}
+
+do_execsql_test 4.2.1 {
+ UPDATE x1_data SET block=
+ X'00000000FF00000101010200010101010101819C9B95A8000102'
+ WHERE id=10;
+}
+
+do_catchsql_test 4.2.2 {
+ SELECT * FROM x1('c d e');
+} {1 {out of memory}}
+
+do_execsql_test 4.3.1 {
+ UPDATE x1_data SET block=
+ X'00000000FF000001010102000101010101019282AFF9A0000102'
+ WHERE id=10;
+}
+
+do_catchsql_test 4.3.2 {
+ SELECT * FROM x1('c d e');
+} {1 {out of memory}}
+
+do_execsql_test 4.4.1 {
+ UPDATE x1_data SET block=
+ X'00000000FF000001010102000101010101018181808080130102'
+ WHERE id=10;
+}
+
+do_catchsql_test 4.3.2 {
+ SELECT * FROM x1('c d e');
+} {1 {out of memory}}
+
finish_test
diff --git a/ext/fts5/test/fts5integrity.test b/ext/fts5/test/fts5integrity.test
index 5c4002180..4bf120c44 100644
--- a/ext/fts5/test/fts5integrity.test
+++ b/ext/fts5/test/fts5integrity.test
@@ -37,6 +37,12 @@ do_execsql_test 2.1 {
INSERT INTO yy(yy) VALUES('integrity-check');
}
+db close
+sqlite3 db test.db
+do_execsql_test 2.1 {
+ INSERT INTO yy(yy) VALUES('integrity-check');
+}
+
#--------------------------------------------------------------------
#
do_execsql_test 3.0 {
diff --git a/ext/fts5/test/fts5leftjoin.test b/ext/fts5/test/fts5leftjoin.test
index 4ef6a8961..69a172bd4 100644
--- a/ext/fts5/test/fts5leftjoin.test
+++ b/ext/fts5/test/fts5leftjoin.test
@@ -40,4 +40,53 @@ do_execsql_test 1.2 {
SELECT * FROM t1 LEFT JOIN vt ON (vt MATCH 'abc')
} {1 abc 2 abc}
+
+do_execsql_test 1.3 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES(14);
+}
+
+do_execsql_test 1.4 {
+ SELECT * FROM vt LEFT JOIN t1 ON vt.rowid = 1;
+} {
+ abc 14
+ xyz {}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t0 USING fts5(a,b);
+ INSERT INTO t0(a,b)VALUES(1,0);
+ CREATE TABLE t1(x);
+}
+
+do_execsql_test 2.1 {
+ SELECT * FROM t0 LEFT JOIN t1;
+} {1 0 {}}
+
+breakpoint
+do_catchsql_test 2.2 {
+ SELECT * FROM t0 LEFT JOIN t1 ON t0.b MATCH '1';
+} {1 {no query solution}}
+
+do_execsql_test 2.3 {
+ SELECT * FROM t0 LEFT JOIN t1 ON +b MATCH '1';
+} {1 0 {}}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t0 USING fts5(c0, c1);
+ INSERT INTO t0(c0,c1) VALUES (1,0);
+}
+
+do_catchsql_test 3.1 {
+ SELECT * FROM t0
+ LEFT JOIN ( SELECT 0 AS col_0 )
+ ON ((((t0.c1 MATCH '1')AND(CASE WHEN t0.c0 THEN CAST(t0.c1 AS INTEGER) ELSE 1 END))));
+} {1 {no query solution}}
+
+
finish_test
diff --git a/ext/misc/vtablog.c b/ext/misc/vtablog.c
index e8f084e1b..44acc32e6 100644
--- a/ext/misc/vtablog.c
+++ b/ext/misc/vtablog.c
@@ -14,6 +14,13 @@
** on stdout when its key interfaces are called. This is intended for
** interactive analysis and debugging of virtual table interfaces.
**
+** To build this extension as a separately loaded shared library or
+** DLL, use compiler command-lines similar to the following:
+**
+** (linux) gcc -fPIC -shared vtablog.c -o vtablog.so
+** (mac) clang -fPIC -dynamiclib vtablog.c -o vtablog.dylib
+** (windows) cl vtablog.c -link -dll -out:vtablog.dll
+**
** Usage example:
**
** .load ./vtablog
@@ -436,6 +443,39 @@ static int vtablogFilter(
}
/*
+** Return an sqlite3_index_info operator name in static space.
+** The name is possibly overwritten on subsequent calls.
+*/
+static char *vtablogOpName(unsigned char op){
+ static char zUnknown[30];
+ char *zOut;
+ switch( op ){
+ case SQLITE_INDEX_CONSTRAINT_EQ: zOut = "EQ"; break;
+ case SQLITE_INDEX_CONSTRAINT_GT: zOut = "GT"; break;
+ case SQLITE_INDEX_CONSTRAINT_LE: zOut = "LE"; break;
+ case SQLITE_INDEX_CONSTRAINT_LT: zOut = "LT"; break;
+ case SQLITE_INDEX_CONSTRAINT_GE: zOut = "GE"; break;
+ case SQLITE_INDEX_CONSTRAINT_MATCH: zOut = "MATCH"; break;
+ case SQLITE_INDEX_CONSTRAINT_LIKE: zOut = "LIKE"; break;
+ case SQLITE_INDEX_CONSTRAINT_GLOB: zOut = "GLOB"; break;
+ case SQLITE_INDEX_CONSTRAINT_REGEXP: zOut = "REGEXP"; break;
+ case SQLITE_INDEX_CONSTRAINT_NE: zOut = "NE"; break;
+ case SQLITE_INDEX_CONSTRAINT_ISNOT: zOut = "ISNOT"; break;
+ case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: zOut = "ISNOTNULL"; break;
+ case SQLITE_INDEX_CONSTRAINT_ISNULL: zOut = "ISNULL"; break;
+ case SQLITE_INDEX_CONSTRAINT_IS: zOut = "IS"; break;
+ case SQLITE_INDEX_CONSTRAINT_LIMIT: zOut = "LIMIT"; break;
+ case SQLITE_INDEX_CONSTRAINT_OFFSET: zOut = "OFFSET"; break;
+ case SQLITE_INDEX_CONSTRAINT_FUNCTION: zOut = "FUNCTION"; break;
+ default:
+ sqlite3_snprintf(sizeof(zUnknown),zUnknown,"%d",op);
+ zOut = zUnknown;
+ break;
+ }
+ return zOut;
+}
+
+/*
** SQLite will invoke this method one or more times while planning a query
** that uses the vtablog virtual table. This routine needs to create
** a query plan for each invocation and compute an estimated cost for that
@@ -451,14 +491,23 @@ static int vtablogBestIndex(
printf(" colUsed: 0x%016llx\n", p->colUsed);
printf(" nConstraint: %d\n", p->nConstraint);
for(i=0; i<p->nConstraint; i++){
+ sqlite3_value *pVal = 0;
+ int rc = sqlite3_vtab_rhs_value(p, i, &pVal);
printf(
- " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n",
+ " constraint[%d]: col=%d termid=%d op=%s usabled=%d coll=%s rhs=",
i,
p->aConstraint[i].iColumn,
p->aConstraint[i].iTermOffset,
- p->aConstraint[i].op,
+ vtablogOpName(p->aConstraint[i].op),
p->aConstraint[i].usable,
- sqlite3_vtab_collation(p,i));
+ sqlite3_vtab_collation(p,i)
+ );
+ if( rc==SQLITE_OK ){
+ vtablogQuote(pVal);
+ printf("\n");
+ }else{
+ printf("N/A\n");
+ }
}
printf(" nOrderBy: %d\n", p->nOrderBy);
for(i=0; i<p->nOrderBy; i++){
diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c
index f7d3bda01..fb35bc10e 100644
--- a/ext/rtree/rtree.c
+++ b/ext/rtree/rtree.c
@@ -1135,6 +1135,12 @@ static void resetCursor(RtreeCursor *pCsr){
pCsr->base.pVtab = (sqlite3_vtab*)pRtree;
pCsr->pReadAux = pStmt;
+ /* The following will only fail if the previous sqlite3_step() call failed,
+ ** in which case the error has already been caught. This statement never
+ ** encounters an error within an sqlite3_column_xxx() function, as it
+ ** calls sqlite3_column_value(), which does not use malloc(). So it is safe
+ ** to ignore the error code here. */
+ sqlite3_reset(pStmt);
}
/*
diff --git a/ext/rtree/rtreeH.test b/ext/rtree/rtreeH.test
index e26107f07..79bf9808d 100644
--- a/ext/rtree/rtreeH.test
+++ b/ext/rtree/rtreeH.test
@@ -99,5 +99,24 @@ do_execsql_test rtreeH-300 {
ORDER BY id;
} {box-48,48 box-49,48 box-48,49 xbox-49,49}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test rtreeH-300 {
+ CREATE TABLE t0(c0);
+ INSERT INTO t0(c0) VALUES (NULL);
+ INSERT INTO t0(c0) VALUES (1);
+ CREATE VIRTUAL TABLE t1 USING rtree(c0, c1, c2, +c3 BLOB );
+ INSERT INTO t1(c2, c3, c0) VALUES (1, 2, 1);
+}
+
+do_execsql_test rtreeH-310 {
+ SELECT 1 FROM t1 WHERE t1.c3;
+} {1}
+
+do_execsql_test rtreeH-320 {
+ SELECT * FROM t0 WHERE NOT EXISTS (
+ SELECT 1 FROM t1 WHERE t1.c3 OR t0.c0 ISNULL
+ );
+} {}
finish_test
diff --git a/ext/session/sessionI.test b/ext/session/sessionI.test
new file mode 100644
index 000000000..2012f24dc
--- /dev/null
+++ b/ext/session/sessionI.test
@@ -0,0 +1,88 @@
+# 2015 July 14
+#
+# 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.
+#
+#***********************************************************************
+#
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionI
+
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_test 1.0 {
+ do_common_sql {
+ CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
+ }
+} {}
+
+set C [changeset_from_sql {
+ INSERT INTO t1 VALUES(1, 'one');
+ INSERT INTO t1 VALUES(2, 'two');
+ INSERT INTO t1 VALUES(3, 'three');
+ INSERT INTO t1 VALUES(4, 'four');
+ INSERT INTO t1 VALUES(5, 'five');
+ INSERT INTO t1 VALUES(6, 'six');
+}]
+
+do_execsql_test 1.1 {
+ SELECT * FROM t1
+} {
+ 1 one 2 two 3 three 4 four 5 five 6 six
+}
+
+proc xFilter {data} {
+ foreach {op tname flag pk old new} $data {}
+ if {$op=="INSERT"} {
+ set ipk [lindex $new 1]
+ return [expr $ipk % 2]
+ }
+ return 1
+}
+proc xConflict {args} {
+}
+
+sqlite3changeset_apply_v3 db2 $C xConflict xFilter
+
+do_execsql_test -db db2 1.2 {
+ SELECT * FROM t1
+} {
+ 1 one 3 three 5 five
+}
+
+do_execsql_test -db db2 1.3 {
+ DELETE FROM t1
+}
+sqlite3changeset_apply_v3 db2 $C xConflict
+
+do_execsql_test -db db2 1.4 {
+ SELECT * FROM t1
+} {
+ 1 one 2 two 3 three 4 four 5 five 6 six
+}
+
+proc xFilter2 {data} {
+ return 0
+}
+do_execsql_test -db db2 1.5 {
+ DELETE FROM t1
+}
+sqlite3changeset_apply_v3 db2 $C xConflict xFilter2
+do_execsql_test -db db2 1.6 {
+ SELECT * FROM t1
+} { }
+
+finish_test
diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c
index 175cacbe8..df40fdc1c 100644
--- a/ext/session/sqlite3session.c
+++ b/ext/session/sqlite3session.c
@@ -5182,6 +5182,10 @@ static int sessionChangesetApply(
void *pCtx, /* Copy of sixth arg to _apply() */
const char *zTab /* Table name */
),
+ int(*xFilterIter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ sqlite3_changeset_iter *p
+ ),
int(*xConflict)(
void *pCtx, /* Copy of fifth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
@@ -5322,6 +5326,9 @@ static int sessionChangesetApply(
** next change. A log message has already been issued. */
if( schemaMismatch ) continue;
+ /* If this is a call to apply_v3(), invoke xFilterIter here. */
+ if( xFilterIter && 0==xFilterIter(pCtx, pIter) ) continue;
+
rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx);
}
@@ -5390,17 +5397,64 @@ static int sessionChangesetApply(
}
/*
-** Apply the changeset passed via pChangeset/nChangeset to the main
-** database attached to handle "db".
+** This function is called by all six sqlite3changeset_apply() variants:
+**
+** + sqlite3changeset_apply()
+** + sqlite3changeset_apply_v2()
+** + sqlite3changeset_apply_v3()
+** + sqlite3changeset_apply_strm()
+** + sqlite3changeset_apply_strm_v2()
+** + sqlite3changeset_apply_strm_v3()
+**
+** Arguments passed to this function are as follows:
+**
+** db:
+** Database handle to apply changeset to main database of.
+**
+** nChangeset/pChangeset:
+** These are both passed zero for the streaming variants. For the normal
+** apply() functions, these are passed the size of and the buffer containing
+** the changeset, respectively.
+**
+** xInput/pIn:
+** These are both passed zero for the normal variants. For the streaming
+** apply() functions, these are passed the input callback and context
+** pointer, respectively.
+**
+** xFilter:
+** The filter function as passed to apply() or apply_v2() (to filter by
+** table name), if any. This is always NULL for apply_v3() calls.
+**
+** xFilterIter:
+** The filter function as passed to apply_v3(), if any.
+**
+** xConflict:
+** The conflict handler callback (must not be NULL).
+**
+** pCtx:
+** The context pointer passed to the xFilter and xConflict handler callbacks.
+**
+** ppRebase, pnRebase:
+** Zero for apply(). The rebase changeset output pointers, if any, for
+** apply_v2() and apply_v3().
+**
+** flags:
+** Zero for apply(). The flags parameter for apply_v2() and apply_v3().
*/
-int sqlite3changeset_apply_v2(
+static int sessionChangesetApplyV23(
sqlite3 *db, /* Apply change to "main" db of this handle */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset, /* Changeset blob */
+ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
+ void *pIn, /* First arg for xInput */
int(*xFilter)(
void *pCtx, /* Copy of sixth arg to _apply() */
const char *zTab /* Table name */
),
+ int(*xFilterIter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ sqlite3_changeset_iter *p /* Handle describing current change */
+ ),
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
@@ -5411,19 +5465,75 @@ int sqlite3changeset_apply_v2(
int flags
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
- int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1);
-
+ int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+ int rc = sessionChangesetStart(
+ &pIter, xInput, pIn, nChangeset, pChangeset, bInverse, 1
+ );
if( rc==SQLITE_OK ){
- rc = sessionChangesetApply(
- db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
+ rc = sessionChangesetApply(db, pIter,
+ xFilter, xFilterIter, xConflict, pCtx, ppRebase, pnRebase, flags
);
}
-
return rc;
}
/*
+** Apply the changeset passed via pChangeset/nChangeset to the main
+** database attached to handle "db".
+*/
+int sqlite3changeset_apply_v2(
+ sqlite3 *db, /* Apply change to "main" db of this handle */
+ int nChangeset, /* Size of changeset in bytes */
+ void *pChangeset, /* Changeset blob */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase,
+ int flags
+){
+ return sessionChangesetApplyV23(db,
+ nChangeset, pChangeset, 0, 0,
+ xFilter, 0, xConflict, pCtx,
+ ppRebase, pnRebase, flags
+ );
+}
+
+/*
+** Apply the changeset passed via pChangeset/nChangeset to the main
+** database attached to handle "db".
+*/
+int sqlite3changeset_apply_v3(
+ sqlite3 *db, /* Apply change to "main" db of this handle */
+ int nChangeset, /* Size of changeset in bytes */
+ void *pChangeset, /* Changeset blob */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ sqlite3_changeset_iter *p /* Handle describing current change */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase,
+ int flags
+){
+ return sessionChangesetApplyV23(db,
+ nChangeset, pChangeset, 0, 0,
+ 0, xFilter, xConflict, pCtx,
+ ppRebase, pnRebase, flags
+ );
+}
+
+/*
** Apply the changeset passed via pChangeset/nChangeset to the main database
** attached to handle "db". Invoke the supplied conflict handler callback
** to resolve any conflicts encountered while applying the change.
@@ -5443,8 +5553,10 @@ int sqlite3changeset_apply(
),
void *pCtx /* First argument passed to xConflict */
){
- return sqlite3changeset_apply_v2(
- db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0, 0
+ return sessionChangesetApplyV23(db,
+ nChangeset, pChangeset, 0, 0,
+ xFilter, 0, xConflict, pCtx,
+ 0, 0, 0
);
}
@@ -5453,6 +5565,29 @@ int sqlite3changeset_apply(
** attached to handle "db". Invoke the supplied conflict handler callback
** to resolve any conflicts encountered while applying the change.
*/
+int sqlite3changeset_apply_v3_strm(
+ sqlite3 *db, /* Apply change to "main" db of this handle */
+ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
+ void *pIn, /* First arg for xInput */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ sqlite3_changeset_iter *p
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase,
+ int flags
+){
+ return sessionChangesetApplyV23(db,
+ 0, 0, xInput, pIn,
+ 0, xFilter, xConflict, pCtx,
+ ppRebase, pnRebase, flags
+ );
+}
int sqlite3changeset_apply_v2_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
@@ -5470,15 +5605,11 @@ int sqlite3changeset_apply_v2_strm(
void **ppRebase, int *pnRebase,
int flags
){
- sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
- int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1);
- if( rc==SQLITE_OK ){
- rc = sessionChangesetApply(
- db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
- );
- }
- return rc;
+ return sessionChangesetApplyV23(db,
+ 0, 0, xInput, pIn,
+ xFilter, 0, xConflict, pCtx,
+ ppRebase, pnRebase, flags
+ );
}
int sqlite3changeset_apply_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
@@ -5495,8 +5626,10 @@ int sqlite3changeset_apply_strm(
),
void *pCtx /* First argument passed to xConflict */
){
- return sqlite3changeset_apply_v2_strm(
- db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0, 0
+ return sessionChangesetApplyV23(db,
+ 0, 0, xInput, pIn,
+ xFilter, 0, xConflict, pCtx,
+ 0, 0, 0
);
}
diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h
index 4dff5ce87..a3b6987b9 100644
--- a/ext/session/sqlite3session.h
+++ b/ext/session/sqlite3session.h
@@ -1115,13 +1115,22 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** the changeset passed via the second and third arguments.
**
** The fourth argument (xFilter) passed to these functions is the "filter
-** callback". If it is not NULL, then for each table affected by at least one
-** change in the changeset, the filter callback is invoked with
-** the table name as the second argument, and a copy of the context pointer
-** passed as the sixth argument as the first. If the "filter callback"
-** returns zero, then no attempt is made to apply any changes to the table.
-** Otherwise, if the return value is non-zero or the xFilter argument to
-** is NULL, all changes related to the table are attempted.
+** callback". This may be passed NULL, in which case all changes in the
+** changeset are applied to the database. For sqlite3changeset_apply() and
+** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once
+** for each table affected by at least one change in the changeset. In this
+** case the table name is passed as the second argument, and a copy of
+** the context pointer passed as the sixth argument to apply() or apply_v2()
+** as the first. If the "filter callback" returns zero, then no attempt is
+** made to apply any changes to the table. Otherwise, if the return value is
+** non-zero, all changes related to the table are attempted.
+**
+** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once
+** per change. The second argument in this case is an sqlite3_changeset_iter
+** that may be queried using the usual APIs for the details of the current
+** change. If the "filter callback" returns zero in this case, then no attempt
+** is made to apply the current change. If it returns non-zero, the change
+** is applied.
**
** For each table that is not excluded by the filter callback, this function
** tests that the target database contains a compatible table. A table is
@@ -1142,11 +1151,11 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** one such warning is issued for each table in the changeset.
**
** For each change for which there is a compatible table, an attempt is made
-** to modify the table contents according to the UPDATE, INSERT or DELETE
-** change. If a change cannot be applied cleanly, the conflict handler
-** function passed as the fifth argument to sqlite3changeset_apply() may be
-** invoked. A description of exactly when the conflict handler is invoked for
-** each type of change is below.
+** to modify the table contents according to each UPDATE, INSERT or DELETE
+** change that is not excluded by a filter callback. If a change cannot be
+** applied cleanly, the conflict handler function passed as the fifth argument
+** to sqlite3changeset_apply() may be invoked. A description of exactly when
+** the conflict handler is invoked for each type of change is below.
**
** Unlike the xFilter argument, xConflict may not be passed NULL. The results
** of passing anything other than a valid function pointer as the xConflict
@@ -1297,6 +1306,23 @@ int sqlite3changeset_apply_v2(
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
int flags /* SESSION_CHANGESETAPPLY_* flags */
);
+int sqlite3changeset_apply_v3(
+ sqlite3 *db, /* Apply change to "main" db of this handle */
+ int nChangeset, /* Size of changeset in bytes */
+ void *pChangeset, /* Changeset blob */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ sqlite3_changeset_iter *p /* Handle describing change */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase, /* OUT: Rebase data */
+ int flags /* SESSION_CHANGESETAPPLY_* flags */
+);
/*
** CAPI3REF: Flags for sqlite3changeset_apply_v2
@@ -1716,6 +1742,23 @@ int sqlite3changeset_apply_v2_strm(
void **ppRebase, int *pnRebase,
int flags
);
+int sqlite3changeset_apply_v3_strm(
+ sqlite3 *db, /* Apply change to "main" db of this handle */
+ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
+ void *pIn, /* First arg for xInput */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ sqlite3_changeset_iter *p
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase,
+ int flags
+);
int sqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
diff --git a/ext/session/test_session.c b/ext/session/test_session.c
index f28604abc..6ad5b3774 100644
--- a/ext/session/test_session.c
+++ b/ext/session/test_session.c
@@ -520,6 +520,65 @@ static int test_obj_eq_string(Tcl_Obj *p, const char *z){
return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
}
+static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){
+ Tcl_Obj *pVar = 0;
+ int nCol; /* Number of columns in table */
+ int nCol2; /* Number of columns in table */
+ int op; /* SQLITE_INSERT, UPDATE or DELETE */
+ const char *zTab; /* Name of table change applies to */
+ Tcl_Obj *pOld; /* Vector of old.* values */
+ Tcl_Obj *pNew; /* Vector of new.* values */
+ int bIndirect;
+
+ char *zPK;
+ unsigned char *abPK;
+ int i;
+
+ sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
+ pVar = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
+ op==SQLITE_INSERT ? "INSERT" :
+ op==SQLITE_UPDATE ? "UPDATE" :
+ "DELETE", -1
+ ));
+
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
+
+ zPK = ckalloc(nCol+1);
+ memset(zPK, 0, nCol+1);
+ sqlite3changeset_pk(pIter, &abPK, &nCol2);
+ assert( nCol==nCol2 );
+ for(i=0; i<nCol; i++){
+ zPK[i] = (abPK[i] ? 'X' : '.');
+ }
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
+ ckfree(zPK);
+
+ pOld = Tcl_NewObj();
+ if( op!=SQLITE_INSERT ){
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_old(pIter, i, &pVal);
+ test_append_value(pOld, pVal);
+ }
+ }
+ pNew = Tcl_NewObj();
+ if( op!=SQLITE_DELETE ){
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_new(pIter, i, &pVal);
+ test_append_value(pNew, pVal);
+ }
+ }
+ Tcl_ListObjAppendElement(0, pVar, pOld);
+ Tcl_ListObjAppendElement(0, pVar, pNew);
+
+ return pVar;
+}
+
+
static int test_filter_handler(
void *pCtx, /* Pointer to TestConflictHandler structure */
const char *zTab /* Table name */
@@ -543,6 +602,29 @@ static int test_filter_handler(
return res;
}
+static int test_filter_v3_handler(
+ void *pCtx, /* Pointer to TestConflictHandler structure */
+ sqlite3_changeset_iter *pIter
+){
+ TestConflictHandler *p = (TestConflictHandler *)pCtx;
+ int res = 1;
+ Tcl_Obj *pEval = 0;
+ Tcl_Interp *interp = p->interp;
+
+ pEval = Tcl_DuplicateObj(p->pFilterScript);
+ Tcl_IncrRefCount(pEval);
+ Tcl_ListObjAppendElement(0, pEval, testIterData(pIter));
+
+ if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL)
+ || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
+ ){
+ Tcl_BackgroundError(interp);
+ }
+
+ Tcl_DecrRefCount(pEval);
+ return res;
+}
+
static int test_conflict_handler(
void *pCtx, /* Pointer to TestConflictHandler structure */
int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
@@ -776,7 +858,7 @@ static int testStreamInput(
static int SQLITE_TCLAPI testSqlite3changesetApply(
- int bV2,
+ int iVersion,
void * clientData,
Tcl_Interp *interp,
int objc,
@@ -793,11 +875,13 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
int nRebase = 0;
int flags = 0; /* Flags for apply_v2() */
+ assert( iVersion==1 || iVersion==2 || iVersion==3 );
+
memset(&sStr, 0, sizeof(sStr));
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
/* Check for the -nosavepoint, -invert or -ignorenoop switches */
- if( bV2 ){
+ if( iVersion==2 || iVersion==3 ){
while( objc>1 ){
const char *z1 = Tcl_GetString(objv[1]);
int n = (int)strlen(z1);
@@ -822,7 +906,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
if( objc!=4 && objc!=5 ){
const char *zMsg;
- if( bV2 ){
+ if( iVersion==2 || iVersion==3 ){
zMsg = "?-nosavepoint? ?-inverse? ?-ignorenoop? "
"DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
}else{
@@ -842,30 +926,49 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
ctx.interp = interp;
if( sStr.nStream==0 ){
- if( bV2==0 ){
- rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset,
- (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
- );
- }else{
- rc = sqlite3changeset_apply_v2(db, (int)nChangeset, pChangeset,
- (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
- &pRebase, &nRebase, flags
- );
+ switch( iVersion ){
+ case 1:
+ rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset,
+ (objc==5)?test_filter_handler:0, test_conflict_handler, (void*)&ctx
+ );
+ break;
+ case 2:
+ rc = sqlite3changeset_apply_v2(db, (int)nChangeset, pChangeset,
+ (objc==5)?test_filter_handler:0, test_conflict_handler, (void*)&ctx,
+ &pRebase, &nRebase, flags
+ );
+ break;
+ case 3:
+ rc = sqlite3changeset_apply_v3(db, (int)nChangeset, pChangeset,
+ (objc==5)?test_filter_v3_handler:0, test_conflict_handler,
+ (void*)&ctx, &pRebase, &nRebase, flags
+ );
+ break;
}
}else{
sStr.aData = (unsigned char*)pChangeset;
sStr.nData = (int)nChangeset;
- if( bV2==0 ){
- rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
- (objc==5) ? test_filter_handler : 0,
- test_conflict_handler, (void *)&ctx
- );
- }else{
- rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
- (objc==5) ? test_filter_handler : 0,
- test_conflict_handler, (void *)&ctx,
- &pRebase, &nRebase, flags
- );
+ switch( iVersion ){
+ case 1:
+ rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
+ (objc==5) ? test_filter_handler : 0,
+ test_conflict_handler, (void *)&ctx
+ );
+ break;
+ case 2:
+ rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
+ (objc==5) ? test_filter_handler : 0,
+ test_conflict_handler, (void *)&ctx,
+ &pRebase, &nRebase, flags
+ );
+ break;
+ case 3:
+ rc = sqlite3changeset_apply_v3_strm(db, testStreamInput, (void*)&sStr,
+ (objc==5) ? test_filter_v3_handler : 0,
+ test_conflict_handler, (void *)&ctx,
+ &pRebase, &nRebase, flags
+ );
+ break;
}
}
@@ -873,7 +976,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
return test_session_error(interp, rc, 0);
}else{
Tcl_ResetResult(interp);
- if( bV2 && pRebase ){
+ if( (iVersion==2 || iVersion==3) && pRebase ){
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase));
}
}
@@ -890,7 +993,7 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply(
int objc,
Tcl_Obj *CONST objv[]
){
- return testSqlite3changesetApply(0, clientData, interp, objc, objv);
+ return testSqlite3changesetApply(1, clientData, interp, objc, objv);
}
/*
** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
@@ -901,7 +1004,18 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2(
int objc,
Tcl_Obj *CONST objv[]
){
- return testSqlite3changesetApply(1, clientData, interp, objc, objv);
+ return testSqlite3changesetApply(2, clientData, interp, objc, objv);
+}
+/*
+** sqlite3changeset_apply_v3 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
+*/
+static int SQLITE_TCLAPI test_sqlite3changeset_apply_v3(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ return testSqlite3changesetApply(3, clientData, interp, objc, objv);
}
/*
@@ -1034,64 +1148,6 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat(
return rc;
}
-static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){
- Tcl_Obj *pVar = 0;
- int nCol; /* Number of columns in table */
- int nCol2; /* Number of columns in table */
- int op; /* SQLITE_INSERT, UPDATE or DELETE */
- const char *zTab; /* Name of table change applies to */
- Tcl_Obj *pOld; /* Vector of old.* values */
- Tcl_Obj *pNew; /* Vector of new.* values */
- int bIndirect;
-
- char *zPK;
- unsigned char *abPK;
- int i;
-
- sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
- pVar = Tcl_NewObj();
-
- Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
- op==SQLITE_INSERT ? "INSERT" :
- op==SQLITE_UPDATE ? "UPDATE" :
- "DELETE", -1
- ));
-
- Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
- Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
-
- zPK = ckalloc(nCol+1);
- memset(zPK, 0, nCol+1);
- sqlite3changeset_pk(pIter, &abPK, &nCol2);
- assert( nCol==nCol2 );
- for(i=0; i<nCol; i++){
- zPK[i] = (abPK[i] ? 'X' : '.');
- }
- Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
- ckfree(zPK);
-
- pOld = Tcl_NewObj();
- if( op!=SQLITE_INSERT ){
- for(i=0; i<nCol; i++){
- sqlite3_value *pVal;
- sqlite3changeset_old(pIter, i, &pVal);
- test_append_value(pOld, pVal);
- }
- }
- pNew = Tcl_NewObj();
- if( op!=SQLITE_DELETE ){
- for(i=0; i<nCol; i++){
- sqlite3_value *pVal;
- sqlite3changeset_new(pIter, i, &pVal);
- test_append_value(pNew, pVal);
- }
- }
- Tcl_ListObjAppendElement(0, pVar, pOld);
- Tcl_ListObjAppendElement(0, pVar, pNew);
-
- return pVar;
-}
-
/*
** sqlite3session_foreach VARNAME CHANGESET SCRIPT
*/
@@ -1751,6 +1807,7 @@ int TestSession_Init(Tcl_Interp *interp){
{ "sqlite3changeset_concat", test_sqlite3changeset_concat },
{ "sqlite3changeset_apply", test_sqlite3changeset_apply },
{ "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 },
+ { "sqlite3changeset_apply_v3", test_sqlite3changeset_apply_v3 },
{ "sqlite3changeset_apply_replace_all",
test_sqlite3changeset_apply_replace_all },
{ "sql_exec_changeset", test_sql_exec_changeset },
diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile
index bf1a49111..51a6bf965 100644
--- a/ext/wasm/GNUmakefile
+++ b/ext/wasm/GNUmakefile
@@ -429,7 +429,7 @@ define SQLITE.CALL.C-PP.FILTER
$(2): $(1) $$(MAKEFILE_LIST) $$(bin.c-pp)
@mkdir -p $$(dir $$@)
$$(bin.c-pp) -f $(1) -o $$@ $(3) $(SQLITE.CALL.C-PP.FILTER.global)
-#CLEAN_FILES += $(2)
+CLEAN_FILES += $(2)
endef
# /end SQLITE.CALL.C-PP.FILTER
########################################################################
@@ -617,9 +617,9 @@ emcc.jsflags += -sDYNAMIC_EXECUTION=0
emcc.jsflags += -sNO_POLYFILL
emcc.jsflags += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.api)
emcc.exportedRuntimeMethods := \
- -sEXPORTED_RUNTIME_METHODS=wasmMemory,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAP64,HEAPU64
+ -sEXPORTED_RUNTIME_METHODS=wasmMemory
# wasmMemory ==> required by our code for use with -sIMPORTED_MEMORY
-# Emscripten 4.0.7 (2025-04-15) stops exporting HEAP* by default
+# Emscripten 4.0.7 (2025-04-15) stops exporting HEAP* by default.
emcc.jsflags += $(emcc.exportedRuntimeMethods)
emcc.jsflags += -sUSE_CLOSURE_COMPILER=0
emcc.jsflags += -sIMPORTED_MEMORY
diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js
index 06f916002..62c44fa9d 100644
--- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js
+++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js
@@ -38,6 +38,21 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/
const __ptrMap = new WeakMap();
/**
+ A Set of oo1.DB or oo1.Stmt objects which are proxies for
+ (sqlite3*) resp. (sqlite3_stmt*) pointers which themselves are
+ owned elsewhere. Objects in this Set do not own their underlying
+ handle and that handle must be guaranteed (by the client) to
+ outlive the proxy. DB.close()/Stmt.finalize() methods will remove
+ the object from this Set _instead_ of closing/finalizing the
+ pointer. These proxies are primarily intended as a way to briefly
+ wrap an (sqlite3[_stmt]*) object as an oo1.DB/Stmt without taking
+ over ownership, to take advantage of simplifies usage compared to
+ the C API while not imposing any change of ownership.
+
+ See DB.wrapHandle() and Stmt.wrapHandle().
+ */
+ const __doesNotOwnHandle = new Set();
+ /**
Map of DB instances to objects, each object being a map of Stmt
wasm pointers to Stmt objects.
*/
@@ -234,73 +249,89 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
};
}
const opt = ctor.normalizeArgs(...args);
- let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
- if(('string'!==typeof fn && 'number'!==typeof fn)
- || 'string'!==typeof flagsStr
- || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){
- sqlite3.config.error("Invalid DB ctor args",opt,arguments);
- toss3("Invalid arguments for DB constructor.");
- }
- let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn;
- const vfsCheck = ctor._name2vfs[fnJs];
- if(vfsCheck){
- vfsName = vfsCheck.vfs;
- fn = fnJs = vfsCheck.filename(fnJs);
- }
- let pDb, oflags = 0;
- if( flagsStr.indexOf('c')>=0 ){
- oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
- }
- if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
- if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
- oflags |= capi.SQLITE_OPEN_EXRESCODE;
- const stack = wasm.pstack.pointer;
- try {
- const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
- let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
- pDb = wasm.peekPtr(pPtr);
- checkSqlite3Rc(pDb, rc);
- capi.sqlite3_extended_result_codes(pDb, 1);
- if(flagsStr.indexOf('t')>=0){
- capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
- __dbTraceToConsole, pDb);
+ //sqlite3.config.debug("DB ctor",opt);
+ let pDb;
+ if( (pDb = opt['sqlite3*']) ){
+ /* This property ^^^^^ is very specifically NOT DOCUMENTED and
+ NOT part of the public API. This is a back door for functions
+ like DB.wrapDbHandle(). */
+ //sqlite3.config.debug("creating proxy db from",opt);
+ if( !opt['sqlite3*:takeOwnership'] ){
+ /* This is object does not own its handle. */
+ __doesNotOwnHandle.add(this);
}
- }catch( e ){
- if( pDb ) capi.sqlite3_close_v2(pDb);
- throw e;
- }finally{
- wasm.pstack.restore(stack);
+ this.filename = capi.sqlite3_db_filename(pDb,'main');
+ }else{
+ let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
+ if(('string'!==typeof fn && 'number'!==typeof fn)
+ || 'string'!==typeof flagsStr
+ || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){
+ sqlite3.config.error("Invalid DB ctor args",opt,arguments);
+ toss3("Invalid arguments for DB constructor.");
+ }
+ let fnJs = ('number'===typeof 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;
+ }
+ if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
+ if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
+ oflags |= capi.SQLITE_OPEN_EXRESCODE;
+ const stack = wasm.pstack.pointer;
+ try {
+ const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
+ let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
+ pDb = wasm.peekPtr(pPtr);
+ checkSqlite3Rc(pDb, rc);
+ capi.sqlite3_extended_result_codes(pDb, 1);
+ if(flagsStr.indexOf('t')>=0){
+ capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
+ __dbTraceToConsole, pDb);
+ }
+ }catch( e ){
+ if( pDb ) capi.sqlite3_close_v2(pDb);
+ throw e;
+ }finally{
+ wasm.pstack.restore(stack);
+ }
+ this.filename = fnJs;
}
- this.filename = fnJs;
__ptrMap.set(this, pDb);
__stmtMap.set(this, Object.create(null));
- try{
+ if( !opt['sqlite3*'] ){
+ try{
//#if enable-see
- dbCtorApplySEEKey(this,opt);
+ dbCtorApplySEEKey(this,opt);
//#endif
- // Check for per-VFS post-open SQL/callback...
- const pVfs = capi.sqlite3_js_db_vfs(pDb)
- || toss3("Internal error: cannot get VFS for new db handle.");
- const postInitSql = __vfsPostOpenCallback[pVfs];
- if(postInitSql){
- /**
- Reminder: if this db is encrypted and the client did _not_ pass
- in the key, any init code will fail, causing the ctor to throw.
- We don't actually know whether the db is encrypted, so we cannot
- sensibly apply any heuristics which skip the init code only for
- encrypted databases for which no key has yet been supplied.
- */
- if(postInitSql instanceof Function){
- postInitSql(this, sqlite3);
- }else{
- checkSqlite3Rc(
- pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
- );
+ // Check for per-VFS post-open SQL/callback...
+ const pVfs = capi.sqlite3_js_db_vfs(pDb)
+ || toss3("Internal error: cannot get VFS for new db handle.");
+ const postInitSql = __vfsPostOpenCallback[pVfs];
+ if(postInitSql){
+ /**
+ Reminder: if this db is encrypted and the client did _not_ pass
+ in the key, any init code will fail, causing the ctor to throw.
+ We don't actually know whether the db is encrypted, so we cannot
+ sensibly apply any heuristics which skip the init code only for
+ encrypted databases for which no key has yet been supplied.
+ */
+ if(postInitSql instanceof Function){
+ postInitSql(this, sqlite3);
+ }else{
+ checkSqlite3Rc(
+ pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
+ );
+ }
}
+ }catch(e){
+ this.close();
+ throw e;
}
- }catch(e){
- this.close();
- throw e;
}
};
@@ -403,7 +434,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- `vfs`: the VFS fname
//#if enable-see
-
SEE-capable builds optionally support ONE of the following
additional options:
@@ -429,7 +459,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
is supplied and the database is encrypted, execution of the
post-initialization SQL will fail, causing the constructor to
throw.
-
//#endif enable-see
The `filename` and `vfs` arguments may be either JS strings or
@@ -457,8 +486,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/**
Internal-use enum for mapping JS types to DB-bindable types.
These do not (and need not) line up with the SQLITE_type
- values. All values in this enum must be truthy and distinct
- but they need not be numbers.
+ values. All values in this enum must be truthy and (mostly)
+ distinct but they need not be numbers.
*/
const BindTypes = {
null: 1,
@@ -467,7 +496,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
boolean: 4,
blob: 5
};
- BindTypes['undefined'] == BindTypes.null;
if(wasm.bigIntEnabled){
BindTypes.bigint = BindTypes.number;
}
@@ -486,26 +514,30 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- `db`: the DB object which created the statement.
- `columnCount`: the number of result columns in the query, or 0
- for queries which cannot return results. This property is a proxy
- for sqlite3_column_count() and its use in loops should be avoided
- because of the call overhead associated with that. The
- `columnCount` is not cached when the Stmt is created because a
- schema change made via a separate db connection between this
- statement's preparation and when it is stepped may invalidate it.
+ for queries which cannot return results. This property is a
+ read-only proxy for sqlite3_column_count() and its use in loops
+ should be avoided because of the call overhead associated with
+ that. The `columnCount` is not cached when the Stmt is created
+ because a schema change made between this statement's preparation
+ and when it is stepped may invalidate it.
- - `parameterCount`: the number of bindable parameters in the query.
+ - `parameterCount`: the number of bindable parameters in the
+ query. Like `columnCount`, this property is ready-only and is a
+ proxy for a C API call.
As a general rule, most methods of this class will throw if
called on an instance which has been finalized. For brevity's
sake, the method docs do not all repeat this warning.
*/
- const Stmt = function(){
+ const Stmt = function(/*oo1db, stmtPtr, BindTypes [,takeOwnership=true] */){
if(BindTypes!==arguments[2]){
toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare().");
}
this.db = arguments[0];
__ptrMap.set(this, arguments[1]);
- this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer);
+ if( arguments.length>3 && !arguments[3] ){
+ __doesNotOwnHandle.add(this);
+ }
};
/** Throws if the given DB has been closed, else it is returned. */
@@ -698,10 +730,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
},
/**
Finalizes all open statements and closes this database
- connection. This is a no-op if the db has already been
- closed. After calling close(), `this.pointer` will resolve to
- `undefined`, so that can be used to check whether the db
- instance is still opened.
+ connection (with one exception noted below). This is a no-op if
+ the db has already been closed. After calling close(),
+ `this.pointer` will resolve to `undefined`, and that can be
+ used to check whether the db instance is still opened.
If this.onclose.before is a function then it is called before
any close-related cleanup.
@@ -721,14 +753,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
all, will never trigger close(), so onclose handlers are not a
reliable way to implement close-time cleanup or maintenance of
a db.
+
+ If this instance was created using DB.wrapHandle() and does not
+ own this.pointer then it does not close the db handle but it
+ does perform all other work, such as calling onclose callbacks
+ and disassociating this object from this.pointer.
*/
close: function(){
- if(this.pointer){
+ const pDb = this.pointer;
+ if(pDb){
if(this.onclose && (this.onclose.before instanceof Function)){
try{this.onclose.before(this)}
catch(e){/*ignore*/}
}
- const pDb = this.pointer;
Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
if(s && s.pointer){
try{s.finalize()}
@@ -737,7 +774,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
});
__ptrMap.delete(this);
__stmtMap.delete(this);
- capi.sqlite3_close_v2(pDb);
+ if( !__doesNotOwnHandle.delete(this) ){
+ capi.sqlite3_close_v2(pDb);
+ }
if(this.onclose && (this.onclose.after instanceof Function)){
try{this.onclose.after(this)}
catch(e){/*ignore*/}
@@ -1450,9 +1489,63 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/
checkRc: function(resultCode){
return checkSqlite3Rc(this, resultCode);
- }
+ },
}/*DB.prototype*/;
+ /**
+ Returns a new oo1.DB instance which wraps the given (sqlite3*)
+ WASM pointer, optionally with or without taking over ownership of
+ that pointer.
+
+ The first argument must be either a non-NULL (sqlite3*) WASM
+ pointer.
+
+ The second argument, defaulting to false, specifies ownership of
+ the first argument. If it is truthy, the returned object will
+ pass that pointer to sqlite3_close() when its close() method is
+ called, otherwise it will not.
+
+ Throws if pDb is not a non-0 WASM pointer.
+
+ The caller MUST GUARANTEE that the passed-in handle will outlive
+ the returned object, i.e. that it will not be closed. If it is closed,
+ this object will hold a stale pointer and results are undefined.
+
+ Aside from its lifetime, the proxy is to be treated as any other
+ DB instance, including the requirement of calling close() on
+ it. close() will free up internal resources owned by the proxy
+ and disassociate the proxy from that handle but will not
+ actually close the proxied db handle unless this function is
+ passed a thruthy second argument.
+
+ To stress:
+
+ - DO NOT call sqlite3_close() (or similar) on the being-proxied
+ pointer while a proxy is active.
+
+ - ALWAYS eventually call close() on the returned object. If the
+ proxy does not own the underlying handle then its MUST be
+ closed BEFORE the being-proxied handle is closed.
+
+ Design notes:
+
+ - wrapHandle() "could" accept a DB object instance as its first
+ argument and proxy thatDb.pointer but there is currently no use
+ case where doing so would be useful, so it does not allow
+ that. That restriction may be lifted in a future version.
+ */
+ DB.wrapHandle = function(pDb, takeOwnership=false){
+ if( !pDb || !wasm.isPtr(pDb) ){
+ throw new sqlite3.SQLite3Error(capi.SQLITE_MISUSE,
+ "Argument must be a WASM sqlite3 pointer");
+ }
+ return new DB({
+ /* This ctor call style is very specifically internal-use-only.
+ It is not documented and may change at any time. */
+ "sqlite3*": pDb,
+ "sqlite3*:takeOwnership": !!takeOwnership
+ });
+ };
/** Throws if the given Stmt has been finalized, else stmt is
returned. */
@@ -1474,8 +1567,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case BindTypes.string:
return t;
case BindTypes.bigint:
- if(wasm.bigIntEnabled) return t;
- /* else fall through */
+ return wasm.bigIntEnabled ? t : undefined;
default:
return util.isBindableTypedArray(v) ? BindTypes.blob : undefined;
}
@@ -1641,12 +1733,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
This method always throws if called when it is illegal to do
so. Namely, when triggered via a per-row callback handler of a
DB.exec() call.
+
+ If Stmt does not own its underlying (sqlite3_stmt*) (see
+ Stmt.wrapHandle()) then this function will not pass it to
+ sqlite3_finalize().
*/
finalize: function(){
- if(this.pointer){
+ const ptr = this.pointer;
+ if(ptr){
affirmNotLockedByExec(this,'finalize()');
- const rc = capi.sqlite3_finalize(this.pointer);
- delete __stmtMap.get(this.db)[this.pointer];
+ const rc = (__doesNotOwnHandle.delete(this)
+ ? 0
+ : capi.sqlite3_finalize(ptr));
+ delete __stmtMap.get(this.db)[ptr];
__ptrMap.delete(this);
__execLock.delete(this);
__stmtMayGet.delete(this);
@@ -2134,6 +2233,64 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
set: ()=>toss3("The columnCount property is read-only.")
});
+ Object.defineProperty(Stmt.prototype, 'parameterCount', {
+ enumerable: false,
+ get: function(){return capi.sqlite3_bind_parameter_count(this.pointer)},
+ set: ()=>toss3("The parameterCount property is read-only.")
+ });
+
+ /**
+ The Stmt counterpart of oo1.DB.wrapHandle(), this creates a Stmt
+ instance which wraps a WASM (sqlite3_stmt*) in the oo1 API,
+ optionally with or without taking over ownership of that pointer.
+
+ The first argument must be an oo1.DB instance[^1].
+
+ The second argument must be a valid WASM (sqlite3_stmt*), as
+ produced by sqlite3_prepare_v2() and sqlite3_prepare_v3().
+
+ The third argument, defaulting to false, specifies whether the
+ returned Stmt object takes over ownership of the underlying
+ (sqlite3_stmt*). If true, the returned object's finalize() method
+ will finalize that handle, else it will not. If it is false,
+ ownership of pStmt is unchanged and pStmt MUST outlive the
+ returned object or results are undefined.
+
+ This function throws if the arguments are invalid. On success it
+ returns a new Stmt object which wraps the given statement
+ pointer.
+
+ Like all Stmt objects, the finalize() method must eventually be
+ called on the returned object to free up internal resources,
+ regardless of whether this function's third argument is true or
+ not.
+
+ [^1]: The first argument cannot be a (sqlite3*) because the
+ resulting Stmt object requires a parent DB object. It is not yet
+ determined whether it would be of general benefit to refactor the
+ DB/Stmt pair internals to communicate in terms of the underlying
+ (sqlite3*) rather than a DB object. If so, we could laxen the
+ first argument's requirement and allow an (sqlite3*). Because
+ DB.wrapHandle() enables multiple DB objects to proxy the same
+ (sqlite3*), we cannot unambiguously translate the first arugment
+ from (sqlite3*) to DB instances for us with this function's first
+ argument.
+ */
+ Stmt.wrapHandle = function(oo1db, pStmt, takeOwnership=false){
+ let ctor = Stmt;
+ if( !(oo1db instanceof DB) || !oo1db.pointer ){
+ throw new sqlite3.SQLite3Error(sqlite3.SQLITE_MISUSE,
+ "First argument must be an opened "+
+ "sqlite3.oo1.DB instance");
+ }
+ if( !pStmt || !wasm.isPtr(pStmt) ){
+ throw new sqlite3.SQLite3Error(sqlite3.SQLITE_MISUSE,
+ "Second argument must be a WASM "+
+ "sqlite3_stmt pointer");
+ }
+ return new Stmt(oo1db, pStmt, BindTypes, !!takeOwnership);
+ }
+
/** The OO API's public namespace. */
sqlite3.oo1 = {
DB,
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js
index 7e128a3fa..e3807a314 100644
--- a/ext/wasm/api/sqlite3-api-prologue.js
+++ b/ext/wasm/api/sqlite3-api-prologue.js
@@ -134,22 +134,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
const config = Object.assign(Object.create(null),{
exports: undefined,
memory: undefined,
- bigIntEnabled: (()=>{
- if('undefined'!==typeof Module){
- /* Emscripten module will contain HEAPU64 when built with
- -sWASM_BIGINT=1, else it will not.
-
- As of emsdk 3.1.55, when building in strict mode, HEAPxyz
- are only available if _explicitly_ included in the exports,
- else they are not. We do not (as of 2024-03-04) use -sSTRICT
- for the canonical builds.
- */
- if( !!Module.HEAPU64 ) return true;
- /* Else fall through and hope for the best. Nobody _really_
- builds this without BigInt support, do they? */
- }
- return !!globalThis.BigInt64Array;
- })(),
+ bigIntEnabled: !!globalThis.BigInt64Array,
debug: console.debug.bind(console),
warn: console.warn.bind(console),
error: console.error.bind(console),
diff --git a/ext/wasm/fiddle.make b/ext/wasm/fiddle.make
index 8110384a6..5b1eb5e77 100644
--- a/ext/wasm/fiddle.make
+++ b/ext/wasm/fiddle.make
@@ -40,9 +40,8 @@ fiddle.emcc-flags = \
-sWASM_BIGINT=$(emcc.WASM_BIGINT) \
-sEXPORT_NAME=$(sqlite3.js.init-func) \
-Wno-limited-postlink-optimizations \
- $(emcc.exportedRuntimeMethods) \
+ $(emcc.exportedRuntimeMethods),FS \
-sEXPORTED_FUNCTIONS=@$(abspath $(EXPORTED_FUNCTIONS.fiddle)) \
- -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory \
$(SQLITE_OPT.full-featured) \
$(SQLITE_OPT.common) \
$(SHELL_OPT) \
diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js
index 5b94c7c05..dd70024ab 100644
--- a/ext/wasm/tester1.c-pp.js
+++ b/ext/wasm/tester1.c-pp.js
@@ -41,7 +41,7 @@
ES6 worker module build:
- ./c-pp -f tester1.c-pp.js -o tester1-esm.js -Dtarget=es6-module
+ ./c-pp -f tester1.c-pp.js -o tester1-esm.mjs -Dtarget=es6-module
*/
//#if target=es6-module
import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs';
@@ -221,7 +221,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
else if(filter instanceof Function) pass = filter(err);
else if('string' === typeof filter) pass = (err.message === filter);
if(!pass){
- throw new Error(msg || ("Filter rejected this exception: "+err.message));
+ throw new Error(msg || ("Filter rejected this exception: <<"+err.message+">>"));
}
return this;
},
@@ -1209,6 +1209,104 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
}
}
})
+
+ ////////////////////////////////////////////////////////////////////
+ .t({
+ name: "oo1.DB/Stmt.wrapDbHandle()",
+ test: function(sqlite3){
+ /* Maintenance reminder: this function is early in the list to
+ demonstrate that the wrappers for this.db created by this
+ function do not interfere with downstream tests, e.g. by
+ closing this.db.pointer. */
+ //sqlite3.config.debug("Proxying",this.db);
+ const misuseMsg = "SQLITE_MISUSE: Argument must be a WASM sqlite3 pointer";
+ T.mustThrowMatching(()=>sqlite3.oo1.DB.wrapHandle(this.db), misuseMsg)
+ .mustThrowMatching(()=>sqlite3.oo1.DB.wrapHandle(0), misuseMsg);
+ let dw = sqlite3.oo1.DB.wrapHandle(this.db.pointer);
+ //sqlite3.config.debug('dw',dw);
+ T.assert( dw, '!!dw' )
+ .assert( dw instanceof sqlite3.oo1.DB, 'dw is-a oo1.DB' )
+ .assert( dw.pointer, 'dw.pointer' )
+ .assert( dw.pointer === this.db.pointer, 'dw.pointer===db.pointer' )
+ .assert( dw.filename === this.db.filename, 'dw.filename===db.filename' );
+
+ T.assert( dw === dw.exec("select 1") );
+ let q;
+ try {
+ q = dw.prepare("select 1");
+ T.assert( q.step() )
+ .assert( !q.step() );
+ }finally{
+ if( q ) q.finalize();
+ }
+ dw.close();
+ T.assert( !dw.pointer )
+ .assert( this.db === this.db.exec("select 1") );
+ dw = undefined;
+
+ let pDb = 0, pStmt = 0;
+ const stack = wasm.pstack.pointer;
+ try {
+ const ppOut = wasm.pstack.allocPtr();
+ T.assert( 0===wasm.peekPtr(ppOut) );
+ let rc = capi.sqlite3_open_v2( ":memory:", ppOut,
+ capi.SQLITE_OPEN_CREATE
+ | capi.SQLITE_OPEN_READWRITE,
+ 0);
+ T.assert( 0===rc, 'open_v2()' );
+ pDb = wasm.peekPtr(ppOut);
+ wasm.pokePtr(ppOut, 0);
+ T.assert( pDb>0, 'pDb>0' );
+ const pTmp = pDb;
+ dw = sqlite3.oo1.DB.wrapHandle(pDb, true);
+ pDb = 0;
+ //sqlite3.config.debug("dw",dw);
+ T.assert( pTmp===dw.pointer, 'pDb===dw.pointer' );
+ T.assert( dw.filename === "", "dw.filename == "+dw.filename );
+ let q = dw.prepare("select 1");
+ try {
+ T.assert( q.step(), "step()" );
+ T.assert( !q.step(), "!step()" );
+ }finally{
+ q.finalize();
+ q = undefined;
+ }
+ T.assert( dw===dw.exec("select 1") );
+ dw.affirmOpen();
+ const select1 = "select 1";
+ rc = capi.sqlite3_prepare_v2( dw, select1, -1, ppOut, 0 );
+ T.assert( 0===rc, 'prepare_v2() rc='+rc );
+ pStmt = wasm.peekPtr(ppOut);
+ T.assert( pStmt && wasm.isPtr(pStmt), 'pStmt is valid?' );
+ try {
+ //log( "capi.sqlite3_sql() =",capi.sqlite3_sql(pStmt));
+ T.assert( select1 === capi.sqlite3_sql(pStmt), 'SQL mismatch' );
+ q = sqlite3.oo1.Stmt.wrapHandle(dw, pStmt, false);
+ //log("q@"+pStmt+" does not own handle");
+ T.assert( q.step(), "step()" )
+ .assert( !q.step(), "!step()" );
+ q.finalize();
+ q = undefined;
+ T.assert( select1 === capi.sqlite3_sql(pStmt), 'SQL mismatch'
+ /* This will fail if we've mismanaged pStmt's lifetime */);
+ q = sqlite3.oo1.Stmt.wrapHandle(dw, pStmt, true);
+ pStmt = 0;
+ q.reset();
+ T.assert( q.step(), "step()" )
+ .assert( !q.step(), "!step()" );
+ }finally{
+ if( pStmt ) capi.sqlite3_finalize(pStmt)
+ if( q ) q.finalize();
+ }
+
+ }finally{
+ wasm.pstack.restore(stack);
+ if( pDb ){ capi.sqlite3_close_v2(pDb); }
+ else if( dw ){ dw.close(); }
+ }
+ }
+ })/*oo1.DB/Stmt.wrapHandle()*/
+
////////////////////////////////////////////////////////////////////
.t('sqlite3_db_config() and sqlite3_db_status()', function(sqlite3){
let rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, 0, 0);
@@ -1268,6 +1366,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
/columnCount property is read-only/)
.assert(1===st.columnCount)
.assert(0===st.parameterCount)
+ .assert(0===capi.sqlite3_bind_parameter_count(st))
.mustThrow(()=>st.bind(1,null))
.assert(true===st.step())
.assert(3 === st.get(0))
@@ -1490,6 +1589,8 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
let st = db.prepare("update t set b=:b where a='blob'");
try {
T.assert(0===st.columnCount)
+ .assert(1===st.parameterCount)
+ .assert(1===capi.sqlite3_bind_parameter_count(st))
.assert( false===st.isReadOnly() );
const ndx = st.getParamIndex(':b');
T.assert(1===ndx);
@@ -3329,6 +3430,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
db.exec("create table t(a)");
const stmt = db.prepare("insert into t(a) values($a)");
T.assert( 1===capi.sqlite3_bind_parameter_count(stmt) )
+ .assert( 1===stmt.parameterCount )
.assert( 1===capi.sqlite3_bind_parameter_index(stmt, "$a") )
.assert( 0===capi.sqlite3_bind_parameter_index(stmt, ":a") )
.assert( 1===stmt.getParamIndex("$a") )
diff --git a/main.mk b/main.mk
index 34c1cc3cf..40d059ad4 100644
--- a/main.mk
+++ b/main.mk
@@ -2113,6 +2113,10 @@ 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)
./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)
+ ./testfixture$(T.exe) $(TOP)/tool/mktoolzip.tcl --snapshot
clean-tool-zip:
rm -f sqlite-tools-*.zip
clean: clean-tool-zip
diff --git a/manifest b/manifest
index 353f38b6e..6dd5cd61a 100644
--- a/manifest
+++ b/manifest
@@ -1,12 +1,12 @@
-C Squelch\sa\slegitimate\sbut\sharmless\s'discards\sconst'\swarning.
-D 2025-07-16T09:20:02.408
+C Teach\sthe\ssqlite3\sCLI\sshell\sto\slook\sin\sXDG_STATE_HOME\sfor\sthe\sCLI\shistory\sfile\sbefore\sfalling\sback\sto\sits\shistorical\slocation\sin\sthe\suser's\shome\sdirectory.
+D 2025-07-16T09:27:55.137
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md e108e1e69ae8e8a59e93c455654b8ac9356a11720d3345df2a4743e9590fb20d
-F Makefile.in d8bc4aee9fb645c9f2ff0e3a30585d17a0df076bb6a33f0f20bab4999abb45a0
+F Makefile.in a6c14b6906f5322920dd5d1ff9a84808337911068f2a6e777ec7088a87cbc3a8
F Makefile.linux-generic bd3e3cacd369821a6241d4ea1967395c962dfe3057e38cb0a435cee0e8b789d0
-F Makefile.msc ec2011bbdfc917d6a1c7c173dabb29c14ead0dd8e2e0b67278a00ae4ba576a77
+F Makefile.msc e17608035fb75d3e5b322f846c6c3cbe80a5f34abf5df9e400c40dd55436dc14
F README.md e28077cfbef795e99c9c75ed95aa7257a1166709b562076441a8506ac421b7c1
F VERSION 16eddb43056a79c1977427ab7a05f3457c373fa159dcdced8754eb89ce7e06b8
F art/icon-243x273.gif 9750b734f82fdb3dc43127753d5e6fbf3b62c9f4e136c2fbf573b2f57ea87af5
@@ -113,7 +113,7 @@ F ext/fts5/fts5_buffer.c f1e6d0324d7c55329d340673befc26681a372a4d36086caa8d1ec7d
F ext/fts5/fts5_config.c e7d8dd062b44a66cd77e5a0f74f23a2354cd1f3f8575afb967b2773c3384f7f8
F ext/fts5/fts5_expr.c be9e5f7f11d87e7bd3680832c93c13050fe351994b5052b0215c2ef40312c23a
F ext/fts5/fts5_hash.c a6266cedd801ab7964fa9e74ebcdda6d30ec6a96107fa24148ec6b7b5b80f6e0
-F ext/fts5/fts5_index.c 1906bb292b65121aa7d88cabb2a486bea58de75c600271e5011fb86ccc2e427a
+F ext/fts5/fts5_index.c c1ca58fbc4b323d2153ab42ed787d78d5beb64f5710d0dcb83d94c6ddd184492
F ext/fts5/fts5_main.c e558225168845dc708abeb2ad10415696e5a3249bcba1810ba3c7ef80764962e
F ext/fts5/fts5_storage.c 19bc7c4cbe1e6a2dd9849ef7d84b5ca1fcbf194cefc3e386b901e00e08bf05c2
F ext/fts5/fts5_tcl.c 7fb5a3d3404099075aaa2457307cb459bbc257c0de3dbd52b1e80a5b503e0329
@@ -162,12 +162,12 @@ F ext/fts5/test/fts5contentless4.test ec34dc69ef474ca9997dae6d91e072906e0e9a5a4b
F ext/fts5/test/fts5contentless5.test 38cd0392c730dc7090c550321ce3c24ba4c392bc97308b51a4180e9959dca7b5
F ext/fts5/test/fts5corrupt.test 237fce1c3261bb3a5bec333b0f0dbf5b105ec32627ef14cccbda3cfe13833193
F ext/fts5/test/fts5corrupt2.test 4a03a158c2cb617c9f76d26b35c1ef2534124bc0bbddcea38dfd5b170ebea27b
-F ext/fts5/test/fts5corrupt3.test e4852b2472dd25556431fd48fe88e4d0c8cf3e18e010c5ea308345e1cdf97bd5
+F ext/fts5/test/fts5corrupt3.test 43d6a836892d79ab738ab89b3b6f4ae46c07ee966193e4b357bbb14e7f81d5da
F ext/fts5/test/fts5corrupt4.test dc08d19f5b8943e95a7778a7d8da592042504faf18dd93f68f7d7a0d7d7dd733
F ext/fts5/test/fts5corrupt5.test 73985d4fe6d8f0d5d5c7bcf79ae7c6522c376cd6ad710a0ff2f26e0c2e222abe
F ext/fts5/test/fts5corrupt6.test 2d72db743db7b5d9c9a6d0cfef24d799ed1aa5e8192b66c40e871a37ed9eed06
F ext/fts5/test/fts5corrupt7.test 814aab492d7a09abb5bfdd81cc66fc206d7f3868f9a3bae91876e02efc466fb3
-F ext/fts5/test/fts5corrupt8.test 7618b102b9b3a5a3494271f4975ab5837e2fb3f61f5adfcdeeb31772c859e6df
+F ext/fts5/test/fts5corrupt8.test 0b10750caf8aa23fa1c379ca4caf6130d41454505e4d5315590f4061eedcbe44
F ext/fts5/test/fts5delete.test 2a5008f8b1174ef41d1974e606928c20e4f9da77d9f8347aed818994d89cced4
F ext/fts5/test/fts5detail.test 54015e9c43ec4ba542cfb93268abdf280e0300f350efd08ee411284b03595cc4
F ext/fts5/test/fts5determin.test 1b77879b2ae818b5b71c859e534ee334dac088b7cf3ff3bf76a2c82b1c788d11
@@ -197,11 +197,11 @@ 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 646796671205dae46af5bb12a49b5696483cfe8e12d71d21454940b13ace95ab
+F ext/fts5/test/fts5integrity.test c423ce16fd1ccadcac7fc22f794226b2bb00f5a187c0ab1d9f8502521b1bae05
F ext/fts5/test/fts5integrity2.test 4c3636615c0201232c44a8105d5cb14fd5499fd0ee3014d7ffd7e83aac76ece8
F ext/fts5/test/fts5interrupt.test 20d04204d3e341b104c0c24a41596b6393a3a81eba1044c168db0e106f9ac92c
F ext/fts5/test/fts5lastrowid.test f36298a1fb9f988bde060a274a7ce638faa9c38a31400f8d2d27ea9373e0c4a1
-F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc2782680740513c4d1fc114b43d4ad
+F ext/fts5/test/fts5leftjoin.test 1c14b51f4d1344a89e488160882f05a2246dd7e70c5cf077c8fb473e03c66338
F ext/fts5/test/fts5limits.test 8ab67cf5d311c124b6ceb0062d0297767176df4572d955fce79fa43004dff01c
F ext/fts5/test/fts5locale.test 83ba7ee12628b540d3098f39c39c1de0c0440eddff8f7512c8c698d0c4a3ae3c
F ext/fts5/test/fts5matchinfo.test bc9e74157773db7f00aec1e85587f1145956ebdf1672c136f0f04323b2752aa0
@@ -456,7 +456,7 @@ 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 a197addbbd1e267a5476274b74953e1b6f050e28516f0a5fe7d6382753165ee6
+F ext/misc/vtablog.c 9f7e02e9e8de585f3bfb48405db36c2eb4b680a23a67d7a4b738dd20f6ad7aec
F ext/misc/vtshim.c e5bce24ab8c532f4fdc600148718fe1802cb6ed57417f1c1032d8961f72b0e8f
F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f65b4fc0668
F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c
@@ -539,7 +539,7 @@ F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c3350
F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c
F ext/rtree/README 734aa36238bcd2dee91db5dba107d5fcbdb02396612811377a8ad50f1272b1c1
F ext/rtree/geopoly.c f0573d5109fdc658a180db0db6eec86ab2a1cf5ce58ec66cbf3356167ea757eb
-F ext/rtree/rtree.c a1f04a2013bc8f982307760615d1cee591355ac4723c74c3761c128d5c6954ab
+F ext/rtree/rtree.c 86967c5a501f895b9705366b8cd9c37f15a9ebdff770ceb719c7deeeb2c22b72
F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412
F ext/rtree/rtree1.test e0608db762b2aadca0ecb6f97396cf66244490adc3ba88f2a292b27be3e1da3e
F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d
@@ -557,7 +557,7 @@ F ext/rtree/rtreeD.test fe46aa7f012e137bd58294409b16c0d43976c3bb92c8f710481e577c
F ext/rtree/rtreeE.test e65d3fc625da1800b412fc8785817327d43ccfec5f5973912d8c9e471928caa9
F ext/rtree/rtreeF.test 81ffa7ef51c4e4618d497a57328c265bf576990c7070633b623b23cd450ed331
F ext/rtree/rtreeG.test 1b9ca6e3effb48f4161edaa463ddeaa8fca4b2526d084f9cbf5dbe4e0184939c
-F ext/rtree/rtreeH.test 0885151ee8429242625600ae47142cca935332c70a06737f35af53a7bd7aaf90
+F ext/rtree/rtreeH.test c304651ee87dbb60296366d2c802f3043c3d00506c79a5c85bd20f2c9274498b
F ext/rtree/rtreeI.test 608e77f7fde9be5a12eae316baef640fffaafcfa90a3d67443e78123e19c4ca4
F ext/rtree/rtreeJ.test 93227ccd4d6c328f5ac46a902b8880041509dd2d68f6ce71560f0d8ab5bb507a
F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195
@@ -594,6 +594,7 @@ F ext/session/sessionE.test b2010949c9d7415306f64e3c2072ddabc4b8250c98478d3c0c4d
F ext/session/sessionF.test d37ed800881e742c208df443537bf29aa49fd56eac520d0f0c6df3e6320f3401
F ext/session/sessionG.test 3efe388282d641b65485b5462e67851002cd91a282dc95b685d085eb8efdad0a
F ext/session/sessionH.test 71bbff6b1abb2c4ac62b84dee53273c37e0b21e5fde3aed80929403e091ef859
+F ext/session/sessionI.test 11e7b6729fc942982a5104a40132f70a2e964d64d60dc5809b8206465af74822
F ext/session/session_common.tcl a31f537a929a695a852d241c9434f2847cadf329856401921139fbb03a5a7697
F ext/session/session_gen.test 942a0002df10da53c45b40b581cc3ed25e7ff42bda1e7ba497273dc2887aa8e6
F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3
@@ -617,11 +618,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 6b0877fe1ab832aa4b85eaca72606dfd1630a1363a1be7af10ee1042a5ec719e
-F ext/session/sqlite3session.h 9bb1a6687b467764b35178dc29bbd2c57ab8cd3acdc8a62f088c34ad17e4fe2b
-F ext/session/test_session.c 2ddff73ea368d827028c32851b291416e1008845832feb27b751d15e57e13cc3
+F ext/session/sqlite3session.c 19e14bcca2fbc63a8022ffd708ea6e6986c4003a1e9bbca9b2989fd230362e15
+F ext/session/sqlite3session.h b81e8536ce4b83babafd700f4ff67017804b6c1d71df963b30d3972958e7f4a7
+F ext/session/test_session.c 8766b5973a6323934cb51248f621c3dc87ad2a98f023c3cc280d79e7d78d36fb
F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
-F ext/wasm/GNUmakefile a05bb1766f97c88cb42fbfe9e349c799c691f8ae0dc959e9d9469f0bcee89350
+F ext/wasm/GNUmakefile d62af1b0914eb2e03fa6e4e75e93acadc8f4faeb2d56335da25d61b9ea144c53
F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a
F ext/wasm/README.md b89605f65661cf35bf034ff6d43e448cc169b8017fc105d498e33b81218b482c
F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff
@@ -641,8 +642,8 @@ F ext/wasm/api/post-js-header.js 53740d824e5d9027eb1e6fd59e216abbd2136740ce260ea
F ext/wasm/api/pre-js.c-pp.js a614a2c82b12c4d96d8e3ba77330329efc53c4d56a8a7e60ade900f341866cfb
F ext/wasm/api/sqlite3-api-cleanup.js 3ac1786e461ada63033143be8c3b00b26b939540661f3e839515bb92f2e35359
F ext/wasm/api/sqlite3-api-glue.c-pp.js 0b76510f3650053bac67ca8947cb6ab9d050ad2218118a2e7796dd37be832ffa
-F ext/wasm/api/sqlite3-api-oo1.c-pp.js c68d6da0088c2527156fca9163a721abe08e7bd077b15404fd8d292f4612adc1
-F ext/wasm/api/sqlite3-api-prologue.js 8708570165f5b4bce9a78ccd91bc9ddf8735970ac1c4d659e36c9a7d9a644bb4
+F ext/wasm/api/sqlite3-api-oo1.c-pp.js f59e59f0d94ba5835c6b7fc9b800a4aa5084e1224721a07e3cd6cc7fef1789c2
+F ext/wasm/api/sqlite3-api-prologue.js 4f1c2a9dc9caf631907766e9872c27d11b255ccae779e8af01c7f8b932817214
F ext/wasm/api/sqlite3-api-worker1.c-pp.js f646a65257973b8c4481f8a6a216370b85644f23e64b126e7ae113570587c0ab
F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
F ext/wasm/api/sqlite3-opfs-async-proxy.js 9654b565b346dc609b75d15337f20acfa7af7d9d558da1afeb9b6d8eaa404966
@@ -674,7 +675,7 @@ F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2
F ext/wasm/demo-worker1.js 08720227e98fa5b44761cf6e219269cee3e9dd0421d8d91459535da776950314
F ext/wasm/dist.make 92ef4ffe33022a50f92d602acabad10bd8dd91759f3eb7df27fc6d7d37072b96
F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c95839afd8b05862f
-F ext/wasm/fiddle.make c6d7a3d6cc03bb5f21acb295c1233820d0dbf5c6a89b28dc2e093edcc001c45a
+F ext/wasm/fiddle.make 2df87f12bcbae2c966c2cef34ce71bb1584c440c69e14ca6d32f443d8d550dc5
F ext/wasm/fiddle/fiddle-worker.js 850e66fce39b89d59e161d1abac43a181a4caa89ddeea162765d660277cd84ce
F ext/wasm/fiddle/fiddle.js 2a2f27b4be2674f501fff61c4a09e44dcf2295731a26b5c28e439f3a573bd269
F ext/wasm/fiddle/index.html 7fcfb221165183bef0e05d5af9ceb79b527e799b1708ab05de0ec0eaebd5b7bf
@@ -698,7 +699,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c
F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c
F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2
-F ext/wasm/tester1.c-pp.js 766a2ba51a2619d41a49be7c6a1ad014c1d23fc97b67496e4f103038203eb17d
+F ext/wasm/tester1.c-pp.js 0abba4bd54f6b22adaadf836c04d3163399f7a8a490fd60f20daac5f9c42b47d
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
@@ -709,7 +710,7 @@ F ext/wasm/tests/opfs/sahpool/sahpool-pausing.js f264925cfc82155de38cecb3d204c36
F ext/wasm/tests/opfs/sahpool/sahpool-worker.js bd25a43fc2ab2d1bafd8f2854ad3943ef673f7c3be03e95ecf1612ff6e8e2a61
F ext/wasm/wasmfs.make 68999f5bd8c489239592d59a420f8c627c99169bbd6fa16a404751f757b9f702
F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
-F main.mk 3ced12d068b22957f4e82a0b94a2c1d83b37dcf39c087263691267f7bfb39a84
+F main.mk a5d698cf8d38e4eb42a89f08a9521906b24f4acac61bbafa56d72cd9b39617b6
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421
@@ -718,7 +719,7 @@ F mptest/mptest.c aa41ace6dbc5050d76b02548d3521e6bbccae4f0
F mptest/multiwrite01.test dab5c5f8f9534971efce679152c5146da265222d
F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
F sqlite3.1 9e426a150af072be26b0661bcd54567692d979e99bc7daf55b22b952ff8e41a6
-F sqlite3.pc.in 0977c03a4da7c4204bd60e784a0efb8d51a190448aba78a4e973fe7192bdaf03
+F sqlite3.pc.in e6dee284fba59ef500092fdc1843df3be8433323a3733c91da96690a50a5b398
F src/alter.c fc7bbbeb9e89c7124bf5772ce474b333b7bdc18d6e080763211a40fde69fb1da
F src/analyze.c 03bcfc083fc0cccaa9ded93604e1d4244ea245c17285d463ef6a60425fcb247d
F src/attach.c 9af61b63b10ee702b1594ecd24fb8cea0839cfdb6addee52fba26fa879f5db9d
@@ -726,17 +727,17 @@ F src/auth.c 54ab9c6c5803b47c0d45b76ce27eff22a03b4b1f767c5945a3a4eb13aa4c78dc
F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523
F src/bitvec.c e242d4496774dfc88fa278177dd23b607dce369ccafb3f61b41638eea2c9b399
F src/btmutex.c 30dada73a819a1ef5b7583786370dce1842e12e1ad941e4d05ac29695528daea
-F src/btree.c 81fb44041929a605e293185bd4091cb24c468a108afe8ba1d93a81a8823de47f
-F src/btree.h 18e5e7b2124c23426a283523e5f31a4bff029131b795bb82391f9d2f3136fc50
+F src/btree.c cb5b8ceb9baa02a63a2f83dec09c4153e1cfbdf9c2adef5c62c26d2160eeb067
+F src/btree.h e823c46d87f63d904d735a24b76146d19f51f04445ea561f71cc3382fd1307f0
F src/btreeInt.h 9c0f9ea5c9b5f4dcaea18111d43efe95f2ac276cd86d770dce10fd99ccc93886
-F src/build.c 67c1db4c5e89a8519fe9b6dafc287f6bc3627696b5b8536dc5e06db570d8c05f
+F src/build.c cc4f287348790bbb7219f7e8dee13b1c345c3377fcdd98eca866e7457ecd07e7
F src/callback.c acae8c8dddda41ee85cfdf19b926eefe830f371069f8aadca3aa39adf5b1c859
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
F src/date.c 9db4d604e699a73e10b8e85a44db074a1f04c0591a77e2abfd77703f50dce1e9
F src/dbpage.c b3e218f8ed74fcbb7fa805df8ca669a3718d397617b3d8a8aac3307dc315c4d6
F src/dbstat.c 73362c0df0f40ad5523a6f5501224959d0976757b511299bf892313e79d14f5c
F src/delete.c 03a77ba20e54f0f42ebd8eddf15411ed6bdb06a2c472ac4b6b336521bf7cea42
-F src/expr.c dbb55f616dc6e5ad3507bb62ef609a48aaf1f909e40d6878d57d7626213933c5
+F src/expr.c 12d8a79f6a0eb9bfae2d468a6d5bd7b9a0ff00988b3f5326ec9bbaad61bbf385
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
F src/fkey.c 928ed2517e8732113d2b9821aa37af639688d752f4ea9ac6e0e393d713eeb76f
F src/func.c de47a8295503aa130baae5e6d9868ecf4f7c4dbffa65d83ad1f70bdbac0ee2d6
@@ -775,7 +776,7 @@ F src/os_win.c b8d3cfdf2f40e2f9715b7d8df64f3c0c7ee18743a2dd0c4fc70c1d57fa1aadc7
F src/os_win.h 4c247cdb6d407c75186c94a1e84d5a22cbae4adcec93fcae8d2bc1f956fd1f19
F src/pager.c 23c0f17deb892da6b32fef1f465507df7ab5cd01d774288cb43695658a649259
F src/pager.h 6137149346e6c8a3ddc1eeb40aee46381e9bc8b0fcc6dda8a1efde993c2275b8
-F src/parse.y e426d7323311554c75b0aebc426d0fe3c88d9777ffefed236f343ad9e661dc4c
+F src/parse.y 619c3e92a54686c5e47923688c4b9bf7ec534a4690db5677acc28b299c403250
F src/pcache.c 588cc3c5ccaaadde689ed35ce5c5c891a1f7b1f4d1f56f6cf0143b74d8ee6484
F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5
F src/pcache1.c 131ca0daf4e66b4608d2945ae76d6ed90de3f60539afbd5ef9ec65667a5f2fcd
@@ -783,14 +784,14 @@ F src/pragma.c 30b535d0a66348df844ee36f890617b4cf45e9a22dcbc47ec3ca92909c50aaf1
F src/prepare.c 1832be043fce7d489959aae6f994c452d023914714c4d5457beaed51c0f3d126
F src/printf.c 71b6d3a0093bf23f473e25480ca0024e8962681506c75f4ffd3d343a3f0ab113
F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
-F src/resolve.c d40fe18d7c2fd0339f5846ffcf7d6809866e380acdf14c76fb2af87e9fe13f64
+F src/resolve.c d3ee7ed308d46f4ee6d3bb6316d8d6f87158f93a7fd616732138cc953cf364f0
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
-F src/select.c 882d739e0d5e6c7a8b46a3cca3ada37fe1a56301f1360d6b141312c666bbe482
-F src/shell.c.in 550ca8a09f8ac55203e69aae34e429b79d92cb44edf7419d8f3dd88eefab5d39
+F src/select.c fc003cad96a105765261f7b6c5f4596e505894262bb5593cb29e10b682800d12
+F src/shell.c.in 2be7d0b2ba7221bd991f96d0c72728c06cead09bec3965e230ad703c9daf0c8a
F src/sqlite.h.in 5c54f2461a1ea529bab8499148a2b238e2d4bb571d59e8ea5322d0c190abb693
F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
F src/sqlite3ext.h 0bfd049bb2088cc44c2ad54f2079d1c6e43091a4e1ce8868779b75f6c1484f1e
-F src/sqliteInt.h 005542f8760edf9b62f014abccb876cf64533b64475a40a89402054d62535288
+F src/sqliteInt.h 984e80f6cfdb1cd65ab465d14d82b6b56af4f7dc308c4bc1da85074a5b4ca755
F src/sqliteLimit.h 6d817c28a8f19af95e6f4921933b7fbbca48a962bce0eb0ec81e8bb3ef38e68b
F src/status.c 0e72e4f6be6ccfde2488eb63210297e75f569f3ce9920f6c3d77590ec6ce5ffd
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -844,7 +845,7 @@ 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 3e37ac2b6cbb9b0abe33827b0153c27595269afd7152b48019808974481aca2c
+F src/tokenize.c 8400646d2830afc2f2dc465a75e3a92e4bedeea623f19dbd79c0c12d0dd6dda2
F src/treeview.c d85ce76e6d1498d781957c07cb234da6d77ce0ed2d196480d516f54dabc62279
F src/trigger.c 3ffb8ed6b64dbcc0ccae6e82435d01be3bf547e13b814e2d46f7df9bef84748e
F src/update.c 3e5e7ff66fa19ebe4d1b113d480639a24cc1175adbefabbd1a948a07f28e37cf
@@ -852,12 +853,12 @@ F src/upsert.c 215328c3f91623c520ec8672c44323553f12caeb4f01b1090ebdca99fdf7b4f1
F src/utf.c 7267c3fb9e2467020507601af3354c2446c61f444387e094c779dccd5ca62165
F src/util.c 36fb1150062957280777655976f3f9a75db236cb8207a0770ceae8d5ec17fcd3
F src/vacuum.c 1bacdd0a81d2b5dc1c508fbf0d938c89fa78dd8d5b46ec92686d44030d4f4789
-F src/vdbe.c 7e29623ca387880b8893e69135a0ff240c3dcaf0710f7a46a5f95b062cf93883
-F src/vdbe.h 93761ed7c6b8bc19524912fd9b9b587d41bf4f1d0ade650a00dadc10518d8958
-F src/vdbeInt.h 0bc581a9763be385e3af715e8c0a503ba8422c2b7074922faf4bb0d6ae31b15e
+F src/vdbe.c e505b8b879a330e8dafbe3ed9582eae2fc671b44a64748d1b58c07e4e0f527da
+F src/vdbe.h ea1f1b52f0efe422f80d88da3c57e4eadc72856e29a22f1ff08e502ec6ba5f08
+F src/vdbeInt.h 626465ff6d673f73a2476dc230b7cd07bdaf4acea9d4ecceaa12a5174bb2c8d7
F src/vdbeapi.c f9a4881a9674fec3fa13da35044a1484d3c4b95f9ec891cc8ffb02ef2b7a41df
-F src/vdbeaux.c fd2c6b19a8892c31a2adc719f156f313560f9cc490cdbd04ff08fdae5d7aedb7
-F src/vdbeblob.c b1b4032cac46b41e44b957c4d00aee9851f862dfd85ecb68116ba49884b03dfd
+F src/vdbeaux.c ed06d2892e830b7ad158f81b2b610c918fbb36a0b717b48d299daca80e8264c6
+F src/vdbeblob.c b3f0640db9642fbdc88bd6ebcc83d6009514cafc98f062f675f2c8d505d82692
F src/vdbemem.c e67d9c6484d868c879d20c70d00bf4a9058082f1d4058607ca15d50eb3aebc21
F src/vdbesort.c cb6f472e83ca12c46aa7de0ac0a9d11458b357986f2617a1c90dfb19a542ecbe
F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823
@@ -867,10 +868,10 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
F src/wal.c 20be6f0a25a80b7897cf2a5369bfd37ef198e6f0b6cdef16d83eee856056b159
F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452
F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014
-F src/where.c a99fa3061a0155d2cb0e2c91df76dbf834750272a8d79ec5e2dce3ed4e6abad6
-F src/whereInt.h 02b646ea41a8342815b3628f8064c32618ea2e0f20b83216ea08cad11f0ac5aa
-F src/wherecode.c 9710e62379c000189476404f923d4d1b192d0def222fdd287b820cc085a0d555
-F src/whereexpr.c 566ca4382e07a4ba1fd86c97ae0781cdf84004c7d9c59466bf5db75733548807
+F src/where.c 6a9266dd1a559d48d8c7ca670a3e80143c7913153f7d1ceb0a4eca1087318951
+F src/whereInt.h 8d94cb116c9e06205c3d5ac87af065fc044f8cf08bfdccd94b6ea1c1308e65da
+F src/wherecode.c 2a2d2993fd98c46f525f71b3bfd330fde73d8613aa0ff3e20402dd1fc63470af
+F src/whereexpr.c 78c28a8da187816d5d82049f2e343fb39f4a8e30b5bf1bda9b96cecde40ca8bd
F src/window.c d01227141f622f24fbe36ca105fbe6ef023f9fd98f1ccd65da95f88886565db5
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
F test/affinity2.test 4d7a34d328e58ca2a2d78fd76c27614a41ca7ddf4312ded9c68c04f430b3b47d
@@ -898,8 +899,8 @@ F test/altermalloc2.test 17fb3724c4b004c469c27dc4ef181608aa644555fbd3f3236767584
F test/altermalloc3.test 8040e486368403f2fdd6fc3998258b499bd4cc2f3ddbb5f8f874cd436f076e81
F test/alterqf.test 8ec03d776de9c391daa0078ea8f838903bdcfb11dfae4ba3576b48436834ccba
F test/altertab.test 8a2712f9076da5012a002d0b5cc0a421398a5bf61c25bab41b77c427586a7a27
-F test/altertab2.test 4bad0fa9b1ad6e62d07bc2ddb0807fb98ba80ee06d6593db2e514ec1821cae3a
-F test/altertab3.test b331ae34e69594e19605e3297805202d6156fcc8f75379dfd972a2e51cae8721
+F test/altertab2.test 0889ba0700cc1cdb7bc7d25975aa61fece34f621de963d0886e2395716b38576
+F test/altertab3.test 471b8898d10bbc6488db9c23dc76811f405de6707d2d342b1b8b6fd1f13cd3c8
F test/altertrig.test aacc980b657354fe2d3d4d3a004f07d04ccc1a93e5ef82d68a79088c274ddc6b
F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f
F test/analyze.test 2fb21d7d64748636384e6cb8998dbf83968caf644c07fcb4f76c18f2e7ede94b
@@ -962,9 +963,9 @@ F test/bestindex8.test b63a4f171a2c83d481bb14c431a8b72e85d27b2ffdaa0435a95d58ca9
F test/bestindex9.test 1a4b93db117fd8abe74ae9be982f86aa72f01e60cd4ac541e6ede39673a451a0
F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572eef44c7f
F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce
-F test/bestindexC.test 2df6ada16d8f00d9bb6a9664d9c323560aeed0e0ebc7a32b99d85d70037fd250
+F test/bestindexC.test 95b4a527b1a5d07951d731604a6d4cf7e5a806b39cea0e7819d4c9667e11c3fc
F test/bestindexD.test 6a8f6f84990bcf17dfa59652a1f935beddb7afd96f8302830fbc86b0a13df3c3
-F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263
+F test/between.test e7587149796101cbe8d5f8abae8d2a7b87f04d8226610aa1091615005dcf4d54
F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59
F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc
F test/bigmmap.test 6021e205487347c6d7e5a541aa472a4b8efc4e9f4a3799a823b61a8e6616105d
@@ -1131,7 +1132,7 @@ F test/enc.test b5503a87b31cea8a5084c6e447383f9ca08933bd2f29d97b6b6201081b2343eb
F test/enc2.test 872afe58db772e7dfa1ad8e0759f8cc820e9efc8172d460fae83023101c2e435
F test/enc3.test 55ef64416d72975c66167310a51dc9fc544ba3ae4858b8d5ab22f4cb6500b087
F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020
-F test/eqp.test 82f221e8cd588434d7f3bba9a0f4c78cbe7a541615a41632e12f50608bfb4a99
+F test/eqp.test 746db9fe11629a0d00328e1721cc2a2e4726d574b677ab14de35fd914f54cc82
F test/eqp2.test 6e8996148de88f0e7670491e92e712a2920a369b4406f21a27c3c9b6a46b68dd
F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9
F test/errofst1.test 6da78363739ba8991f498396ab331b5d64e7ab5c4172c12b5884683ef523ac53
@@ -1140,6 +1141,9 @@ F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650
F test/exclusive2.test cd70b1d9c6fffd336f9795b711dcc5d9ceba133ad3f7001da3fda63615bdc91e
F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7
F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac
+F test/existsexpr.test 40ddd9500109579dd949cd15bbb4e3a88f79f905d1f31905b9493651f60aacf6
+F test/existsexpr2.test dc23e76389eff3d29f6488ff733012a3560cd67ec8cfaecbecd52cced5d5af11
+F test/existsfault.test ff41c11f3052c1bbd4f8dd557802310026253d67d7c4e3a180c16d2f0862973e
F test/expr.test 4ada8eb822c45ef27a36851a258004d43c1e95e7c82585a1217e732084e4482c
F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8
F test/exprfault.test da33606d799718e2f8e34efd0e5858884a1ad87f608774c552a7f5517cc27181
@@ -1315,7 +1319,7 @@ F test/in7.test d9efdee00b074a60c6343993b2eda78bc369ab080dad864513c73f8aca89d566
F test/incrblob.test c9b96afc292aeff43d6687bcb09b0280aa599822
F test/incrblob2.test a494c9e848560039a23974b9119cfc2cf3ad3bd15cc2694ee6367ae537ef8f1f
F test/incrblob3.test 67621a04b3084113bf38ce03797d70eca012d9d8f948193b8f655df577b0da6f
-F test/incrblob4.test 21a52a6843a56cdcce968c6a86b72a7066d0e6ba
+F test/incrblob4.test a8d6b5ff04055fcec747a50b78485ebf4fcd80074e0b3cedbe952bde346da54a
F test/incrblob_err.test 89372a28f1d98254f03fed705f9efcd34ef61a674df16d2dbb4726944a2de5e9
F test/incrblobfault.test de274b1e329169c2c3438f9528994807ea8201ebf38ae9f157d34bf3ec0cc549
F test/incrcorrupt.test 6c567fbf870aa9e91866fe52ce6f200cd548939a
@@ -1387,7 +1391,7 @@ F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd286
F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307
F test/json/json-speed-check.sh 7d5898808ce7542762318306ae6075a30f5e7ee115c4a409f487e123afe91d88 x
F test/json/jsonb-q1.txt 1e180fe6491efab307e318b22879e3a736ac9a96539bbde7911a13ee5b33abc7
-F test/json101.test 8237a484c256965eab1678fd950a32ac56325bb7d0dadbd095a46b0ddd95d62b
+F test/json101.test cf53254f0f0c1399a01b21fc58fee0e63a12a556be91b9ee9faccdb8b82c083c
F test/json102.test 9b2e5ada10845ff84853b3feaae2ce51ce7145ae458f74c6a6cecc6ef6ee3ae1
F test/json103.test 355746a6b66aa438f214b4fae454b13068fad2444b5f693e0d538ad1c059b264
F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1
@@ -1494,7 +1498,7 @@ F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf
F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161
F test/notify3.test 796c7b7157f55c93b4e672b724e9c923a6fc6aa72ac419379a623e2350472e22
F test/notnull.test a37b663d5bb728d66fc182016613fb8e4a0a4bbf3d75b8876a7527f7d4ed3f18
-F test/notnull2.test 2ac7b4e04917148c7a1a9ed36df20150175ce942f07f5714375b29acbaca7106
+F test/notnull2.test 5b7dd6e82c409b2d011ad6acf19ae4bf0816a9c69ccf600b529d7405d7c49874
F test/notnullfault.test fc4bb7845582a2b3db376001ef49118393b1b11abe0d24adb03db057ee2b73d5
F test/null.test b7ff206a1c60fe01aa2abd33ef9ea83c93727d993ca8a613de86e925c9f2bc6f
F test/nulls1.test 7a5e4346ee4285034100b4cd20e6784f16a9d6c927e44ecdf10034086bbee9c9
@@ -1529,7 +1533,7 @@ F test/pagerfault2.test caf4c7facb914fd3b03a17b31ae2b180c8d6ca1f
F test/pagerfault3.test 1003fcda009bf48a8e22a516e193b6ef0dd1bbd8
F test/pageropt.test 84e4cc5cbca285357f7906e99b21be4f2bf5abc0
F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305
-F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035ce4b3
+F test/parser1.test 131f4733472252d53d8ed681115257866f55740ab697fa05900d766049348f27
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
F test/pendingrace.test e99efc5ab3584da3dfc8cd6a0ec4e5a42214820574f5ea24ee93f1d84655f463
@@ -1576,7 +1580,7 @@ F test/round1.test 29c3c9039936ed024d672f003c4d35ee11c14c0acb75c5f7d6188ff16190c
F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc
F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
F test/rowid.test d27191b5ce794c05bf61081e8b2c546a1844c1641321dcaf7fb785234256cc8e
-F test/rowvalue.test 9c873b2f6e7ce72b24ef133f93515c07a6a7dac4846a344ebc2af7b8bfdf5147
+F test/rowvalue.test 8a3f0fea3a3cbbfc7cb9885b76185a774cd8d891e0c133e289567c755d39eb9f
F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b
F test/rowvalue3.test 103e9a224ca0548dd0d67e439f39c5dd16de4200221a333927372408c025324c
F test/rowvalue4.test bac9326d1e886656650f67c0ec484eb5f452244a8209c6af508e9a862ace08ed
@@ -1686,7 +1690,7 @@ F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa
F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c
F test/speedtest.md ee958457ae1b729d9715ae33c0320600000bf1d9ddea1a88dcf79f56729d6fad
F test/speedtest.tcl 405411356fbc54af15987b7ffeec330a49138f71584220fb8fe1948b2f7ac907 x
-F test/speedtest1.c 64b8804b053a796eab22f8b23fb181000f05d7b3e2aa44f022117ea543bc5a2a
+F test/speedtest1.c a9b002a7bfed99ba3166c2a9b8ae45a95b4c2d37f891e1637c022f9e1d15e3f9
F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e
F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3
F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33
@@ -1733,8 +1737,9 @@ F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d163
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
F test/tester.tcl 463ae33b8bf75ac77451df19bd65e7c415c2e9891227c7c9e657d0a2d8e1074a
-F test/testrunner.tcl 614c4a28f7f730acd7bec53e17d76602fb480e0d538b6ec548169e03a093f92d x
+F test/testrunner.tcl b42f7736968cafc9e69bb5d0b87fc81b375fb4c3f44e19b472cccd91d41a416a x
F test/testrunner_data.tcl 02dd645b647d907c959fbf232b7ff7d869c2ae430d5117443fc1e16a0d32243a
+F test/testrunner_estwork.tcl 7927a84327259a32854926f68a75292e33a61e7e052fdbfcb01f18696c99c724
F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
@@ -2168,7 +2173,7 @@ F tool/mksqlite3c.tcl f11b63445c4840509248bd4aa151a81aea25d5415fef71943c8d436eba
F tool/mksqlite3h.tcl 989948c6a26e188e673d7c2f2f093ea3acd816ad6ac65bab596280075c8f3a45
F tool/mksqlite3internalh.tcl 46ef6ed6ccd3c36e23051109dd25085d8edef3887635cea25afa81c4adf4d4db
F tool/mksrczip.tcl 81efd9974dbb36005383f2cd655520057a2ae5aa85ac2441a80c7c28f803ac52
-F tool/mktoolzip.tcl 34b4e92be544f820e2cc26f143f7d5aec511e826ec394cc82969a5dcf7c7a27c
+F tool/mktoolzip.tcl c9f388b2b0751982aef29a0c1d80f7959dbe38065ebb4421c39844e92622b086
F tool/mkvsix.tcl 67b40996a50f985a573278eea32fc5a5eb6110bdf14d33f1d8086e48c69e540a
F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c6115285845
F tool/omittest-msvc.tcl d6b8f501ac1d7798c4126065030f89812379012cad98a1735d6d7221492abc08
@@ -2208,8 +2213,9 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P ebb346c5aa427b9e84a035278ee245d54810d9954efef12b898f2d7bc0ff630c
-R debb7a97a27a2dab2543b88f76ae8cc2
+P 9f184f8dfa5ef6d57e10376adc30e0060ceda07d283c23dfdfe3dbdd6608f839 73539fe0932494234b8f2293b0dbc1f0aac60a7d00fdaf4a59c2da654ce26f5b
+R 301553834aa3054a0cc5a0c4c21b9798
+T +closed 73539fe0932494234b8f2293b0dbc1f0aac60a7d00fdaf4a59c2da654ce26f5b Closed\sby\sintegrate-merge.
U stephan
-Z affb48d71da77c1ae112258e0a7a4bb5
+Z f8c26aad77c21d4fab9a1de2f2326205
# Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.uuid b/manifest.uuid
index d86bd127b..abbd3fd28 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-73539fe0932494234b8f2293b0dbc1f0aac60a7d00fdaf4a59c2da654ce26f5b
+1e663374c3fcc20ab0b3250aa1ff9d5e5ac391c89808ad589aa30c8882d4b61e
diff --git a/sqlite3.pc.in b/sqlite3.pc.in
index a9f941b1e..723dd5156 100644
--- a/sqlite3.pc.in
+++ b/sqlite3.pc.in
@@ -9,5 +9,5 @@ Name: SQLite
Description: SQL database engine
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lsqlite3
-Libs.private: @LDFLAGS_MATH@ @LDFLAGS_ZLIB@ @LDFLAGS_ICU@
+Libs.private: @LDFLAGS_MATH@ @LDFLAGS_ZLIB@ @LDFLAGS_DLOPEN@ @LDFLAGS_PTHREAD@ @LDFLAGS_ICU@
Cflags: -I${includedir}
diff --git a/src/btree.c b/src/btree.c
index 1806a914a..a931b0d12 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -1929,10 +1929,10 @@ static int freeSpace(MemPage *pPage, int iStart, int iSize){
assert( pPage->pBt!=0 );
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
assert( CORRUPT_DB || iStart>=pPage->hdrOffset+6+pPage->childPtrSize );
- assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize );
+ assert( CORRUPT_DB || iEnd <= (int)pPage->pBt->usableSize );
assert( sqlite3_mutex_held(pPage->pBt->mutex) );
assert( iSize>=4 ); /* Minimum cell size is 4 */
- assert( CORRUPT_DB || iStart<=pPage->pBt->usableSize-4 );
+ assert( CORRUPT_DB || iStart<=(int)pPage->pBt->usableSize-4 );
/* The list of freeblocks must be in ascending order. Find the
** spot on the list where iStart should be inserted.
@@ -5667,6 +5667,30 @@ int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){
return rc;
}
+/* Set *pRes to 1 (true) if the BTree pointed to by cursor pCur contains zero
+** rows of content. Set *pRes to 0 (false) if the table contains content.
+** Return SQLITE_OK on success or some error code (ex: SQLITE_NOMEM) if
+** something goes wrong.
+*/
+int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes){
+ int rc;
+
+ assert( cursorOwnsBtShared(pCur) );
+ assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ if( pCur->eState==CURSOR_VALID ){
+ *pRes = 0;
+ return SQLITE_OK;
+ }
+ rc = moveToRoot(pCur);
+ if( rc==SQLITE_EMPTY ){
+ *pRes = 1;
+ rc = SQLITE_OK;
+ }else{
+ *pRes = 0;
+ }
+ return rc;
+}
+
#ifdef SQLITE_DEBUG
/* The cursors is CURSOR_VALID and has BTCF_AtLast set. Verify that
** this flags are true for a consistent database.
diff --git a/src/btree.h b/src/btree.h
index 241261dc6..96f4c4c60 100644
--- a/src/btree.h
+++ b/src/btree.h
@@ -317,6 +317,7 @@ struct BtreePayload {
int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload,
int flags, int seekResult);
int sqlite3BtreeFirst(BtCursor*, int *pRes);
+int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes);
int sqlite3BtreeLast(BtCursor*, int *pRes);
int sqlite3BtreeNext(BtCursor*, int flags);
int sqlite3BtreeEof(BtCursor*);
diff --git a/src/build.c b/src/build.c
index 5bd3aac3c..5495cef18 100644
--- a/src/build.c
+++ b/src/build.c
@@ -4219,7 +4219,6 @@ void sqlite3CreateIndex(
assert( j<=0x7fff );
if( j<0 ){
j = pTab->iPKey;
- pIndex->bIdxRowid = 1;
}else{
if( pTab->aCol[j].notNull==0 ){
pIndex->uniqNotNull = 0;
@@ -5138,16 +5137,22 @@ void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){
** are deleted by this function.
*/
SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){
- assert( p1 && p1->nSrc==1 );
+ assert( p1 );
+ assert( p2 || pParse->nErr );
+ assert( p2==0 || p2->nSrc>=1 );
+ testcase( p1->nSrc==0 );
if( p2 ){
- SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, 1);
+ int nOld = p1->nSrc;
+ SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, nOld);
if( pNew==0 ){
sqlite3SrcListDelete(pParse->db, p2);
}else{
p1 = pNew;
- memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem));
+ memcpy(&p1->a[nOld], p2->a, p2->nSrc*sizeof(SrcItem));
+ assert( nOld==1 || (p2->a[0].fg.jointype & JT_LTORJ)==0 );
+ assert( p1->nSrc>=1 );
+ p1->a[0].fg.jointype |= (JT_LTORJ & p2->a[0].fg.jointype);
sqlite3DbFree(pParse->db, p2);
- p1->a[0].fg.jointype |= (JT_LTORJ & p1->a[1].fg.jointype);
}
}
return p1;
diff --git a/src/expr.c b/src/expr.c
index 6dcf8c3ac..3f040309a 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -1144,7 +1144,7 @@ Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){
return pLeft;
}else{
u32 f = pLeft->flags | pRight->flags;
- if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse))==EP_IsFalse
+ if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse|EP_HasFunc))==EP_IsFalse
&& !IN_RENAME_OBJECT
){
sqlite3ExprDeferredDelete(pParse, pLeft);
@@ -2374,6 +2374,86 @@ Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){
}
/*
+** Return true if it might be advantageous to compute the right operand
+** of expression pExpr first, before the left operand.
+**
+** Normally the left operand is computed before the right operand. But if
+** the left operand contains a subquery and the right does not, then it
+** might be more efficient to compute the right operand first.
+*/
+static int exprEvalRhsFirst(Expr *pExpr){
+ if( ExprHasProperty(pExpr->pLeft, EP_Subquery)
+ && !ExprHasProperty(pExpr->pRight, EP_Subquery)
+ ){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
+/*
+** Compute the two operands of a binary operator.
+**
+** If either operand contains a subquery, then the code strives to
+** compute the operand containing the subquery second. If the other
+** operand evalutes to NULL, then a jump is made. The address of the
+** IsNull operand that does this jump is returned. The caller can use
+** this to optimize the computation so as to avoid doing the potentially
+** expensive subquery.
+**
+** If no optimization opportunities exist, return 0.
+*/
+static int exprComputeOperands(
+ Parse *pParse, /* Parsing context */
+ Expr *pExpr, /* The comparison expression */
+ int *pR1, /* OUT: Register holding the left operand */
+ int *pR2, /* OUT: Register holding the right operand */
+ int *pFree1, /* OUT: Temp register to free if not zero */
+ int *pFree2 /* OUT: Another temp register to free if not zero */
+){
+ int addrIsNull;
+ int r1, r2;
+ Vdbe *v = pParse->pVdbe;
+
+ assert( v!=0 );
+ /*
+ ** If the left operand contains a (possibly expensive) subquery and the
+ ** right operand does not and the right operation might be NULL,
+ ** then compute the right operand first and do an IsNull jump if the
+ ** right operand evalutes to NULL.
+ */
+ if( exprEvalRhsFirst(pExpr) && sqlite3ExprCanBeNull(pExpr->pRight) ){
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pFree2);
+ addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r2);
+ VdbeComment((v, "skip left operand"));
+ VdbeCoverage(v);
+ }else{
+ r2 = 0; /* Silence a false-positive uninit-var warning in MSVC */
+ addrIsNull = 0;
+ }
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pFree1);
+ if( addrIsNull==0 ){
+ /*
+ ** If the right operand contains a subquery and the left operand does not
+ ** and the left operand might be NULL, then check the left operand do
+ ** an IsNull check on the left operand before computing the right
+ ** operand.
+ */
+ if( ExprHasProperty(pExpr->pRight, EP_Subquery)
+ && sqlite3ExprCanBeNull(pExpr->pLeft)
+ ){
+ addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r1);
+ VdbeComment((v, "skip right operand"));
+ VdbeCoverage(v);
+ }
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pFree2);
+ }
+ *pR1 = r1;
+ *pR2 = r2;
+ return addrIsNull;
+}
+
+/*
** pExpr is a TK_FUNCTION node. Try to determine whether or not the
** function is a constant function. A function is constant if all of
** the following are true:
@@ -3817,17 +3897,23 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
VdbeComment((v, "Init EXISTS result"));
}
if( pSel->pLimit ){
- /* The subquery already has a limit. If the pre-existing limit is X
- ** then make the new limit X<>0 so that the new limit is either 1 or 0 */
- sqlite3 *db = pParse->db;
- pLimit = sqlite3Expr(db, TK_INTEGER, "0");
- if( pLimit ){
- pLimit->affExpr = SQLITE_AFF_NUMERIC;
- pLimit = sqlite3PExpr(pParse, TK_NE,
- sqlite3ExprDup(db, pSel->pLimit->pLeft, 0), pLimit);
- }
- sqlite3ExprDeferredDelete(pParse, pSel->pLimit->pLeft);
- pSel->pLimit->pLeft = pLimit;
+ /* The subquery already has a limit. If the pre-existing limit X is
+ ** not already integer value 1 or 0, then make the new limit X<>0 so that
+ ** the new limit is either 1 or 0 */
+ Expr *pLeft = pSel->pLimit->pLeft;
+ if( ExprHasProperty(pLeft, EP_IntValue)==0
+ || (pLeft->u.iValue!=1 && pLeft->u.iValue!=0)
+ ){
+ sqlite3 *db = pParse->db;
+ pLimit = sqlite3Expr(db, TK_INTEGER, "0");
+ if( pLimit ){
+ pLimit->affExpr = SQLITE_AFF_NUMERIC;
+ pLimit = sqlite3PExpr(pParse, TK_NE,
+ sqlite3ExprDup(db, pLeft, 0), pLimit);
+ }
+ sqlite3ExprDeferredDelete(pParse, pLeft);
+ pSel->pLimit->pLeft = pLimit;
+ }
}else{
/* If there is no pre-existing limit add a limit of 1 */
pLimit = sqlite3Expr(pParse->db, TK_INTEGER, "1");
@@ -4961,11 +5047,17 @@ expr_code_doover:
case TK_NE:
case TK_EQ: {
Expr *pLeft = pExpr->pLeft;
+ int addrIsNull = 0;
if( sqlite3ExprIsVector(pLeft) ){
codeVectorCompare(pParse, pExpr, target, op, p5);
}else{
- r1 = sqlite3ExprCodeTemp(pParse, pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ if( ExprHasProperty(pExpr, EP_Subquery) && p5!=SQLITE_NULLEQ ){
+ addrIsNull = exprComputeOperands(pParse, pExpr,
+ &r1, &r2, &regFree1, &regFree2);
+ }else{
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ }
sqlite3VdbeAddOp2(v, OP_Integer, 1, inReg);
codeCompare(pParse, pLeft, pExpr->pRight, op, r1, r2,
sqlite3VdbeCurrentAddr(v)+2, p5,
@@ -4980,9 +5072,15 @@ expr_code_doover:
sqlite3VdbeAddOp2(v, OP_Integer, 0, inReg);
}else{
sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, inReg, r2);
+ if( addrIsNull ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2);
+ sqlite3VdbeJumpHere(v, addrIsNull);
+ sqlite3VdbeAddOp2(v, OP_Null, 0, inReg);
+ }
}
testcase( regFree1==0 );
testcase( regFree2==0 );
+
}
break;
}
@@ -4998,6 +5096,7 @@ expr_code_doover:
case TK_LSHIFT:
case TK_RSHIFT:
case TK_CONCAT: {
+ int addrIsNull;
assert( TK_AND==OP_And ); testcase( op==TK_AND );
assert( TK_OR==OP_Or ); testcase( op==TK_OR );
assert( TK_PLUS==OP_Add ); testcase( op==TK_PLUS );
@@ -5009,11 +5108,23 @@ expr_code_doover:
assert( TK_LSHIFT==OP_ShiftLeft ); testcase( op==TK_LSHIFT );
assert( TK_RSHIFT==OP_ShiftRight ); testcase( op==TK_RSHIFT );
assert( TK_CONCAT==OP_Concat ); testcase( op==TK_CONCAT );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ if( ExprHasProperty(pExpr, EP_Subquery) ){
+ addrIsNull = exprComputeOperands(pParse, pExpr,
+ &r1, &r2, &regFree1, &regFree2);
+ }else{
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ addrIsNull = 0;
+ }
sqlite3VdbeAddOp3(v, op, r2, r1, target);
testcase( regFree1==0 );
testcase( regFree2==0 );
+ if( addrIsNull ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2);
+ sqlite3VdbeJumpHere(v, addrIsNull);
+ sqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ VdbeComment((v, "short-circut value"));
+ }
break;
}
case TK_UMINUS: {
@@ -5862,17 +5973,27 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr);
if( pAlt!=pExpr ){
sqlite3ExprIfTrue(pParse, pAlt, dest, jumpIfNull);
- }else if( op==TK_AND ){
- int d2 = sqlite3VdbeMakeLabel(pParse);
- testcase( jumpIfNull==0 );
- sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2,
- jumpIfNull^SQLITE_JUMPIFNULL);
- sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
- sqlite3VdbeResolveLabel(v, d2);
}else{
- testcase( jumpIfNull==0 );
- sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
- sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ Expr *pFirst, *pSecond;
+ if( exprEvalRhsFirst(pExpr) ){
+ pFirst = pExpr->pRight;
+ pSecond = pExpr->pLeft;
+ }else{
+ pFirst = pExpr->pLeft;
+ pSecond = pExpr->pRight;
+ }
+ if( op==TK_AND ){
+ int d2 = sqlite3VdbeMakeLabel(pParse);
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfFalse(pParse, pFirst, d2,
+ jumpIfNull^SQLITE_JUMPIFNULL);
+ sqlite3ExprIfTrue(pParse, pSecond, dest, jumpIfNull);
+ sqlite3VdbeResolveLabel(v, d2);
+ }else{
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfTrue(pParse, pFirst, dest, jumpIfNull);
+ sqlite3ExprIfTrue(pParse, pSecond, dest, jumpIfNull);
+ }
}
break;
}
@@ -5911,10 +6032,16 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
case TK_GE:
case TK_NE:
case TK_EQ: {
+ int addrIsNull;
if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr;
- testcase( jumpIfNull==0 );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ if( ExprHasProperty(pExpr, EP_Subquery) && jumpIfNull!=SQLITE_NULLEQ ){
+ addrIsNull = exprComputeOperands(pParse, pExpr,
+ &r1, &r2, &regFree1, &regFree2);
+ }else{
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ addrIsNull = 0;
+ }
codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op,
r1, r2, dest, jumpIfNull, ExprHasProperty(pExpr,EP_Commuted));
assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
@@ -5929,6 +6056,13 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
VdbeCoverageIf(v, op==OP_Ne && jumpIfNull!=SQLITE_NULLEQ);
testcase( regFree1==0 );
testcase( regFree2==0 );
+ if( addrIsNull ){
+ if( jumpIfNull ){
+ sqlite3VdbeChangeP2(v, addrIsNull, dest);
+ }else{
+ sqlite3VdbeJumpHere(v, addrIsNull);
+ }
+ }
break;
}
case TK_ISNULL:
@@ -6036,17 +6170,27 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr);
if( pAlt!=pExpr ){
sqlite3ExprIfFalse(pParse, pAlt, dest, jumpIfNull);
- }else if( pExpr->op==TK_AND ){
- testcase( jumpIfNull==0 );
- sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
- sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
}else{
- int d2 = sqlite3VdbeMakeLabel(pParse);
- testcase( jumpIfNull==0 );
- sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2,
- jumpIfNull^SQLITE_JUMPIFNULL);
- sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
- sqlite3VdbeResolveLabel(v, d2);
+ Expr *pFirst, *pSecond;
+ if( exprEvalRhsFirst(pExpr) ){
+ pFirst = pExpr->pRight;
+ pSecond = pExpr->pLeft;
+ }else{
+ pFirst = pExpr->pLeft;
+ pSecond = pExpr->pRight;
+ }
+ if( pExpr->op==TK_AND ){
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfFalse(pParse, pFirst, dest, jumpIfNull);
+ sqlite3ExprIfFalse(pParse, pSecond, dest, jumpIfNull);
+ }else{
+ int d2 = sqlite3VdbeMakeLabel(pParse);
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfTrue(pParse, pFirst, d2,
+ jumpIfNull^SQLITE_JUMPIFNULL);
+ sqlite3ExprIfFalse(pParse, pSecond, dest, jumpIfNull);
+ sqlite3VdbeResolveLabel(v, d2);
+ }
}
break;
}
@@ -6088,10 +6232,16 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
case TK_GE:
case TK_NE:
case TK_EQ: {
+ int addrIsNull;
if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr;
- testcase( jumpIfNull==0 );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ if( ExprHasProperty(pExpr, EP_Subquery) && jumpIfNull!=SQLITE_NULLEQ ){
+ addrIsNull = exprComputeOperands(pParse, pExpr,
+ &r1, &r2, &regFree1, &regFree2);
+ }else{
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ addrIsNull = 0;
+ }
codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op,
r1, r2, dest, jumpIfNull,ExprHasProperty(pExpr,EP_Commuted));
assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
@@ -6106,6 +6256,13 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
VdbeCoverageIf(v, op==OP_Ne && jumpIfNull==SQLITE_NULLEQ);
testcase( regFree1==0 );
testcase( regFree2==0 );
+ if( addrIsNull ){
+ if( jumpIfNull ){
+ sqlite3VdbeChangeP2(v, addrIsNull, dest);
+ }else{
+ sqlite3VdbeJumpHere(v, addrIsNull);
+ }
+ }
break;
}
case TK_ISNULL:
diff --git a/src/parse.y b/src/parse.y
index a5691cad4..617eb7303 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -1429,12 +1429,21 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] {
** expr1 IN ()
** expr1 NOT IN ()
**
- ** simplify to constants 0 (false) and 1 (true), respectively,
- ** regardless of the value of expr1.
+ ** simplify to constants 0 (false) and 1 (true), respectively.
+ **
+ ** Except, do not apply this optimization if expr1 contains a function
+ ** because that function might be an aggregate (we don't know yet whether
+ ** it is or not) and if it is an aggregate, that could change the meaning
+ ** of the whole query.
*/
- sqlite3ExprUnmapAndDelete(pParse, A);
- A = sqlite3Expr(pParse->db, TK_STRING, N ? "true" : "false");
- if( A ) sqlite3ExprIdToTrueFalse(A);
+ Expr *pB = sqlite3Expr(pParse->db, TK_STRING, N ? "true" : "false");
+ if( pB ) sqlite3ExprIdToTrueFalse(pB);
+ if( !ExprHasProperty(A, EP_HasFunc) ){
+ sqlite3ExprUnmapAndDelete(pParse, A);
+ A = pB;
+ }else{
+ A = sqlite3PExpr(pParse, N ? TK_OR : TK_AND, pB, A);
+ }
}else{
Expr *pRHS = Y->a[0].pExpr;
if( Y->nExpr==1 && sqlite3ExprIsConstant(pParse,pRHS) && A->op!=TK_VECTOR ){
diff --git a/src/resolve.c b/src/resolve.c
index 3961a2009..57ccd0c07 100644
--- a/src/resolve.c
+++ b/src/resolve.c
@@ -1358,11 +1358,13 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
return WRC_Prune;
}
#ifndef SQLITE_OMIT_SUBQUERY
+ case TK_EXISTS:
case TK_SELECT:
- case TK_EXISTS: testcase( pExpr->op==TK_EXISTS );
#endif
case TK_IN: {
testcase( pExpr->op==TK_IN );
+ testcase( pExpr->op==TK_EXISTS );
+ testcase( pExpr->op==TK_SELECT );
if( ExprUseXSelect(pExpr) ){
int nRef = pNC->nRef;
testcase( pNC->ncFlags & NC_IsCheck );
@@ -1370,6 +1372,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
testcase( pNC->ncFlags & NC_IdxExpr );
testcase( pNC->ncFlags & NC_GenCol );
assert( pExpr->x.pSelect );
+ if( pExpr->op==TK_EXISTS ) pParse->bHasExists = 1;
if( pNC->ncFlags & NC_SelfRef ){
notValidImpl(pParse, pNC, "subqueries", pExpr, pExpr);
}else{
diff --git a/src/select.c b/src/select.c
index 6c0e7c92d..1b1266313 100644
--- a/src/select.c
+++ b/src/select.c
@@ -384,7 +384,7 @@ static int tableAndColumnIndex(
int iEnd, /* Last member of pSrc->a[] to check */
const char *zCol, /* Name of the column we are looking for */
int *piTab, /* Write index of pSrc->a[] here */
- int *piCol, /* Write index of pSrc->a[*piTab].pTab->aCol[] here */
+ int *piCol, /* Write index of pSrc->a[*piTab].pSTab->aCol[] here */
int bIgnoreHidden /* Ignore hidden columns */
){
int i; /* For looping over tables in pSrc */
@@ -3036,7 +3036,9 @@ static int multiSelect(
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 );
@@ -3075,6 +3077,8 @@ static int multiSelect(
*/
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;
@@ -3095,6 +3099,7 @@ static int multiSelect(
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;
@@ -3125,9 +3130,10 @@ static int multiSelect(
int tab1, tab2;
int iCont, iBreak, iStart;
Expr *pLimit;
- int addr;
+ 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
@@ -3151,15 +3157,29 @@ static int multiSelect(
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;
- p->pPrior = 0;
+
+ /* 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)));
@@ -3172,19 +3192,21 @@ static int multiSelect(
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 );
- iBreak = sqlite3VdbeMakeLabel(pParse);
- iCont = sqlite3VdbeMakeLabel(pParse);
- computeLimitRegisters(pParse, p, iBreak);
- sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v);
+ 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);
@@ -3194,6 +3216,7 @@ static int multiSelect(
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;
}
@@ -3872,6 +3895,7 @@ typedef struct SubstContext {
int iTable; /* Replace references to this table */
int iNewTable; /* New table number */
int isOuterJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */
+ int nSelDepth; /* Depth of sub-query recursion. Top==1 */
ExprList *pEList; /* Replacement expressions */
ExprList *pCList; /* Collation sequences for replacement expr */
} SubstContext;
@@ -3979,6 +4003,9 @@ static Expr *substExpr(
if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){
pExpr->iTable = pSubst->iNewTable;
}
+ if( pExpr->op==TK_AGG_FUNCTION && pExpr->op2>=pSubst->nSelDepth ){
+ pExpr->op2--;
+ }
pExpr->pLeft = substExpr(pSubst, pExpr->pLeft);
pExpr->pRight = substExpr(pSubst, pExpr->pRight);
if( ExprUseXSelect(pExpr) ){
@@ -4016,6 +4043,7 @@ static void substSelect(
SrcItem *pItem;
int i;
if( !p ) return;
+ pSubst->nSelDepth++;
do{
substExprList(pSubst, p->pEList);
substExprList(pSubst, p->pGroupBy);
@@ -4033,6 +4061,7 @@ static void substSelect(
}
}
}while( doPrior && (p = p->pPrior)!=0 );
+ pSubst->nSelDepth--;
}
#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
@@ -4644,7 +4673,7 @@ static int flattenSubquery(
** complete, since there may still exist Expr.pTab entries that
** refer to the subquery even after flattening. Ticket #3346.
**
- ** pSubitem->pTab is always non-NULL by test restrictions and tests above.
+ ** pSubitem->pSTab is always non-NULL by test restrictions and tests above.
*/
if( ALWAYS(pSubitem->pSTab!=0) ){
Table *pTabToDel = pSubitem->pSTab;
@@ -4774,6 +4803,7 @@ static int flattenSubquery(
x.iTable = iParent;
x.iNewTable = iNewParent;
x.isOuterJoin = isOuterJoin;
+ x.nSelDepth = 0;
x.pEList = pSub->pEList;
x.pCList = findLeftmostExprlist(pSub);
substSelect(&x, pParent, 0);
@@ -5359,6 +5389,7 @@ static int pushDownWhereTerms(
x.iTable = pSrc->iCursor;
x.iNewTable = pSrc->iCursor;
x.isOuterJoin = 0;
+ x.nSelDepth = 0;
x.pEList = pSubq->pEList;
x.pCList = findLeftmostExprlist(pSubq);
pNew = substExpr(&x, pNew);
@@ -5756,7 +5787,7 @@ With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){
** CTE expression, through routine checks to see if the reference is
** a recursive reference to the CTE.
**
-** If pFrom matches a CTE according to either of these two above, pFrom->pTab
+** If pFrom matches a CTE according to either of these two above, pFrom->pSTab
** and other fields are populated accordingly.
**
** Return 0 if no match is found.
@@ -7384,6 +7415,83 @@ static int fromClauseTermCanBeCoroutine(
}
/*
+** Argument pWhere is the WHERE clause belonging to SELECT statement p. This
+** function attempts to transform expressions of the form:
+**
+** EXISTS (SELECT ...)
+**
+** into joins. For example, given
+**
+** CREATE TABLE sailors(sid INTEGER PRIMARY KEY, name TEXT);
+** CREATE TABLE reserves(sid INT, day DATE, PRIMARY KEY(sid, day));
+**
+** SELECT name FROM sailors AS S WHERE EXISTS (
+** SELECT * FROM reserves AS R WHERE S.sid = R.sid AND R.day = '2022-10-25'
+** );
+**
+** the SELECT statement may be transformed as follows:
+**
+** SELECT name FROM sailors AS S, reserves AS R
+** WHERE S.sid = R.sid AND R.day = '2022-10-25';
+**
+** **Approximately**. Really, we have to ensure that the FROM-clause term
+** that was formerly inside the EXISTS is only executed once. This is handled
+** by setting the SrcItem.fg.fromExists flag, which then causes code in
+** the where.c file to exit the corresponding loop after the first successful
+** match (if any).
+*/
+static SQLITE_NOINLINE void existsToJoin(
+ Parse *pParse, /* Parsing context */
+ Select *p, /* The SELECT statement being optimized */
+ Expr *pWhere /* part of the WHERE clause currently being examined */
+){
+ if( pParse->nErr==0
+ && pWhere!=0
+ && !ExprHasProperty(pWhere, EP_OuterON|EP_InnerON)
+ && ALWAYS(p->pSrc!=0)
+ && p->pSrc->nSrc<BMS
+ ){
+ if( pWhere->op==TK_AND ){
+ Expr *pRight = pWhere->pRight;
+ existsToJoin(pParse, p, pWhere->pLeft);
+ existsToJoin(pParse, p, pRight);
+ }
+ else if( pWhere->op==TK_EXISTS ){
+ Select *pSub = pWhere->x.pSelect;
+ Expr *pSubWhere = pSub->pWhere;
+ if( pSub->pSrc->nSrc==1
+ && (pSub->selFlags & SF_Aggregate)==0
+ && !pSub->pSrc->a[0].fg.isSubquery
+ ){
+ 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;
+ p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc);
+ if( pSubWhere ){
+ p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSubWhere);
+ pSub->pWhere = 0;
+ }
+ pSub->pSrc = 0;
+ sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pSub);
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x100000 ){
+ TREETRACE(0x100000,pParse,p,
+ ("After EXISTS-to-JOIN optimization:\n"));
+ sqlite3TreeViewSelect(0, p, 0);
+ }
+#endif
+ existsToJoin(pParse, p, pSubWhere);
+ }
+ }
+ }
+}
+
+/*
** Generate byte-code for the SELECT statement given in the p argument.
**
** The results are returned according to the SelectDest structure.
@@ -7751,6 +7859,13 @@ int sqlite3Select(
}
#endif
+ /* If there may be an "EXISTS (SELECT ...)" in the WHERE clause, attempt
+ ** to change it into a join. */
+ if( pParse->bHasExists && OptimizationEnabled(db,SQLITE_ExistsToJoin) ){
+ existsToJoin(pParse, p, p->pWhere);
+ pTabList = p->pSrc;
+ }
+
/* Do the WHERE-clause constant propagation optimization if this is
** a join. No need to spend time on this operation for non-join queries
** as the equivalent optimization will be handled by query planner in
@@ -8537,12 +8652,12 @@ int sqlite3Select(
** for the next GROUP BY batch.
*/
sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow);
- VdbeComment((v, "output one row"));
+ VdbeComment((v, "output one row of %d", p->selId));
sqlite3ExprCodeMove(pParse, iBMem, iAMem, pGroupBy->nExpr);
sqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd); VdbeCoverage(v);
VdbeComment((v, "check abort flag"));
sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
- VdbeComment((v, "reset accumulator"));
+ VdbeComment((v, "reset accumulator %d", p->selId));
/* Update the aggregate accumulators based on the content of
** the current row
@@ -8550,7 +8665,7 @@ int sqlite3Select(
sqlite3VdbeJumpHere(v, addr1);
updateAccumulator(pParse, iUseFlag, pAggInfo, eDist);
sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag);
- VdbeComment((v, "indicate data in accumulator"));
+ VdbeComment((v, "indicate data in accumulator %d", p->selId));
/* End of the loop
*/
@@ -8567,7 +8682,7 @@ int sqlite3Select(
/* Output the final row of result
*/
sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow);
- VdbeComment((v, "output final row"));
+ VdbeComment((v, "output final row of %d", p->selId));
/* Jump over the subroutines
*/
@@ -8588,7 +8703,7 @@ int sqlite3Select(
addrOutputRow = sqlite3VdbeCurrentAddr(v);
sqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2);
VdbeCoverage(v);
- VdbeComment((v, "Groupby result generator entry point"));
+ VdbeComment((v, "Groupby result generator entry point %d", p->selId));
sqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
finalizeAggFunctions(pParse, pAggInfo);
sqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, SQLITE_JUMPIFNULL);
@@ -8596,14 +8711,14 @@ int sqlite3Select(
&sDistinct, pDest,
addrOutputRow+1, addrSetAbort);
sqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
- VdbeComment((v, "end groupby result generator"));
+ VdbeComment((v, "end groupby result generator %d", p->selId));
/* Generate a subroutine that will reset the group-by accumulator
*/
sqlite3VdbeResolveLabel(v, addrReset);
resetAccumulator(pParse, pAggInfo);
sqlite3VdbeAddOp2(v, OP_Integer, 0, iUseFlag);
- VdbeComment((v, "indicate accumulator empty"));
+ VdbeComment((v, "indicate accumulator %d empty", p->selId));
sqlite3VdbeAddOp1(v, OP_Return, regReset);
if( distFlag!=0 && eDist!=WHERE_DISTINCT_NOOP ){
diff --git a/src/shell.c.in b/src/shell.c.in
index 7b28bf684..a311d85c4 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -11710,6 +11710,7 @@ static int do_meta_command(char *zLine, ShellState *p){
{ 0x08000000, 1, "OnePass" },
{ 0x10000000, 1, "OrderBySubq" },
{ 0x20000000, 1, "StarQuery" },
+ { 0x40000000, 1, "ExistsToJoin" },
{ 0xffffffff, 0, "All" },
};
unsigned int curOpt;
@@ -12774,7 +12775,9 @@ static char *find_home_dir(int clearFlag){
** becomes the fallback.
**
** Both zSubdir and zBaseName may contain subdirectory parts. zSubdir
-** will conventionally be ".config" or ".local/state".
+** will conventionally be ".config" or ".local/state", which, not
+** coincidentally, is the typical subdir of the corresponding XDG_...
+** var with the XDG var's $HOME prefix.
**
** The returned string is obtained from sqlite3_malloc() and should be
** sqlite3_free()'d by the caller.
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 36a21d92e..df0e1c3ba 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -1154,6 +1154,7 @@ extern u32 sqlite3TreeTrace;
** 0x00040000 SELECT tree dump after all code has been generated
** 0x00080000 NOT NULL strength reduction
** 0x00100000 Pointers are all shown as zero
+** 0x00200000 EXISTS-to-JOIN optimization
*/
/*
@@ -1926,6 +1927,7 @@ struct sqlite3 {
#define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */
#define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */
#define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */
+#define SQLITE_ExistsToJoin 0x40000000 /* The EXISTS-to-JOIN optimization */
#define SQLITE_AllOpts 0xffffffff /* All optimizations */
/*
@@ -2164,7 +2166,7 @@ struct FuncDestructor {
#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \
{nArg, SQLITE_FUNC_BUILTIN|\
SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- pArg, 0, xFunc, 0, 0, 0, #zName, }
+ pArg, 0, xFunc, 0, 0, 0, #zName, {0} }
#define LIKEFUNC(zName, nArg, arg, flags) \
{nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \
(void *)arg, 0, likeFunc, 0, 0, 0, #zName, {0} }
@@ -2808,7 +2810,6 @@ struct Index {
unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
unsigned bNoQuery:1; /* Do not use this index to optimize queries */
unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
- unsigned bIdxRowid:1; /* One or more of the index keys is the ROWID */
unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
unsigned bHasExpr:1; /* Index contains an expression, either a literal
** expression, or a reference to a VIRTUAL column */
@@ -3370,6 +3371,7 @@ struct SrcItem {
unsigned rowidUsed :1; /* The ROWID of this table is referenced */
unsigned fixedSchema :1; /* Uses u4.pSchema, not u4.zDatabase */
unsigned hadSchema :1; /* Had u4.zDatabase before u4.pSchema */
+ unsigned fromExists :1; /* Comes from WHERE EXISTS(...) */
} fg;
int iCursor; /* The VDBE cursor number used to access this table */
Bitmask colUsed; /* Bit N set if column N used. Details above for N>62 */
@@ -3900,6 +3902,7 @@ struct Parse {
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 */
diff --git a/src/tokenize.c b/src/tokenize.c
index e4d9f5371..6f7bab35b 100644
--- a/src/tokenize.c
+++ b/src/tokenize.c
@@ -199,7 +199,7 @@ static int getToken(const unsigned char **pz){
int t; /* Token type to return */
do {
z += sqlite3GetToken(z, &t);
- }while( t==TK_SPACE );
+ }while( t==TK_SPACE || t==TK_COMMENT );
if( t==TK_ID
|| t==TK_STRING
|| t==TK_JOIN_KW
diff --git a/src/vdbe.c b/src/vdbe.c
index 9e696d0c3..9e456a1cd 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -6398,6 +6398,32 @@ case OP_Rewind: { /* jump0, ncycle */
break;
}
+/* Opcode: IfEmpty P1 P2 * * *
+** Synopsis: if( empty(P1) ) goto P2
+**
+** Check to see if the b-tree table that cursor P1 references is empty
+** and jump to P2 if it is.
+*/
+case OP_IfEmpty: { /* jump */
+ VdbeCursor *pC;
+ BtCursor *pCrsr;
+ int res;
+
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ assert( pOp->p2>=0 && pOp->p2<p->nOp );
+
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ assert( pC->eCurType==CURTYPE_BTREE );
+ pCrsr = pC->uc.pCursor;
+ assert( pCrsr );
+ rc = sqlite3BtreeIsEmpty(pCrsr, &res);
+ if( rc ) goto abort_due_to_error;
+ VdbeBranchTaken(res!=0,2);
+ if( res ) goto jump_to_p2;
+ break;
+}
+
/* Opcode: Next P1 P2 P3 * P5
**
** Advance cursor P1 so that it points to the next key/data pair in its
@@ -8269,7 +8295,14 @@ case OP_VOpen: { /* ncycle */
const sqlite3_module *pModule;
assert( p->bIsReader );
- pCur = 0;
+ pCur = p->apCsr[pOp->p1];
+ if( pCur!=0
+ && ALWAYS( pCur->eCurType==CURTYPE_VTAB )
+ && ALWAYS( pCur->uc.pVCur->pVtab==pOp->p4.pVtab->pVtab )
+ ){
+ /* This opcode is a no-op if the cursor is already open */
+ break;
+ }
pVCur = 0;
pVtab = pOp->p4.pVtab->pVtab;
if( pVtab==0 || NEVER(pVtab->pModule==0) ){
diff --git a/src/vdbe.h b/src/vdbe.h
index a7aedfbb0..9a9cf3dec 100644
--- a/src/vdbe.h
+++ b/src/vdbe.h
@@ -314,7 +314,9 @@ int sqlite3VdbeHasSubProgram(Vdbe*);
void sqlite3MemSetArrayInt64(sqlite3_value *aMem, int iIdx, i64 val);
+#ifndef SQLITE_OMIT_DATETIME_FUNCS
int sqlite3NotPureFunc(sqlite3_context*);
+#endif
#ifdef SQLITE_ENABLE_BYTECODE_VTAB
int sqlite3VdbeBytecodeVtabInit(sqlite3*);
#endif
diff --git a/src/vdbeInt.h b/src/vdbeInt.h
index 0faa32747..07160f590 100644
--- a/src/vdbeInt.h
+++ b/src/vdbeInt.h
@@ -288,7 +288,7 @@ struct sqlite3_value {
** MEM_Int, MEM_Real, and MEM_IntReal.
**
** * MEM_Blob|MEM_Zero A blob in Mem.z of length Mem.n plus
-** MEM.u.i extra 0x00 bytes at the end.
+** Mem.u.nZero extra 0x00 bytes at the end.
**
** * MEM_Int Integer stored in Mem.u.i.
**
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index 8a900aeff..b1406724e 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -5378,6 +5378,7 @@ void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
}
}
+#ifndef SQLITE_OMIT_DATETIME_FUNCS
/*
** Cause a function to throw an error if it was call from OP_PureFunc
** rather than OP_Function.
@@ -5411,6 +5412,7 @@ int sqlite3NotPureFunc(sqlite3_context *pCtx){
}
return 1;
}
+#endif /* SQLITE_OMIT_DATETIME_FUNCS */
#if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG)
/*
diff --git a/src/vdbeblob.c b/src/vdbeblob.c
index 42edcf7de..a15fec6c4 100644
--- a/src/vdbeblob.c
+++ b/src/vdbeblob.c
@@ -385,7 +385,7 @@ static int blobReadWrite(
int iOffset,
int (*xCall)(BtCursor*, u32, u32, void*)
){
- int rc;
+ int rc = SQLITE_OK;
Incrblob *p = (Incrblob *)pBlob;
Vdbe *v;
sqlite3 *db;
@@ -425,17 +425,32 @@ static int blobReadWrite(
** using the incremental-blob API, this works. For the sessions module
** anyhow.
*/
- sqlite3_int64 iKey;
- iKey = sqlite3BtreeIntegerKey(p->pCsr);
- assert( v->apCsr[0]!=0 );
- assert( v->apCsr[0]->eCurType==CURTYPE_BTREE );
- sqlite3VdbePreUpdateHook(
- v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol
- );
+ if( sqlite3BtreeCursorIsValidNN(p->pCsr)==0 ){
+ /* If the cursor is not currently valid, try to reseek it. This
+ ** always either fails or finds the correct row - the cursor will
+ ** have been marked permanently CURSOR_INVALID if the open row has
+ ** been deleted. */
+ int bDiff = 0;
+ rc = sqlite3BtreeCursorRestore(p->pCsr, &bDiff);
+ assert( bDiff==0 || sqlite3BtreeCursorIsValidNN(p->pCsr)==0 );
+ }
+ if( sqlite3BtreeCursorIsValidNN(p->pCsr) ){
+ sqlite3_int64 iKey;
+ iKey = sqlite3BtreeIntegerKey(p->pCsr);
+ assert( v->apCsr[0]!=0 );
+ assert( v->apCsr[0]->eCurType==CURTYPE_BTREE );
+ sqlite3VdbePreUpdateHook(
+ v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol
+ );
+ }
}
+ if( rc==SQLITE_OK ){
+ rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
+ }
+#else
+ rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
#endif
- rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
sqlite3BtreeLeaveCursor(p->pCsr);
if( rc==SQLITE_ABORT ){
sqlite3VdbeFinalize(v);
diff --git a/src/where.c b/src/where.c
index 11e24a8d3..ab1b419a2 100644
--- a/src/where.c
+++ b/src/where.c
@@ -1201,7 +1201,9 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
VdbeCoverage(v);
VdbeComment((v, "next row of %s", pSrc->pSTab->zName));
}else{
- addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v);
+ assert( pLevel->addrHalt );
+ addrTop = sqlite3VdbeAddOp2(v, OP_Rewind,pLevel->iTabCur,pLevel->addrHalt);
+ VdbeCoverage(v);
}
if( pPartial ){
iContinue = sqlite3VdbeMakeLabel(pParse);
@@ -1229,11 +1231,14 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
pSrc->u4.pSubq->regResult, pLevel->iIdxCur);
sqlite3VdbeGoto(v, addrTop);
pSrc->fg.viaCoroutine = 0;
+ sqlite3VdbeJumpHere(v, addrTop);
}else{
sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v);
sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX);
+ if( (pSrc->fg.jointype & JT_LEFT)!=0 ){
+ sqlite3VdbeJumpHere(v, addrTop);
+ }
}
- sqlite3VdbeJumpHere(v, addrTop);
sqlite3ReleaseTempReg(pParse, regRecord);
/* Jump here when skipping the initialization */
@@ -3234,6 +3239,7 @@ static int whereLoopAddBtreeIndex(
if( ExprUseXSelect(pExpr) ){
/* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */
int i;
+ int bRedundant = 0;
nIn = 46; assert( 46==sqlite3LogEst(25) );
/* The expression may actually be of the form (x, y) IN (SELECT...).
@@ -3242,7 +3248,20 @@ static int whereLoopAddBtreeIndex(
** for each such term. The following loop checks that pTerm is the
** first such term in use, and sets nIn back to 0 if it is not. */
for(i=0; i<pNew->nLTerm-1; i++){
- if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ) nIn = 0;
+ if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ){
+ nIn = 0;
+ if( pNew->aLTerm[i]->u.x.iField == pTerm->u.x.iField ){
+ /* Detect when two or more columns of an index match the same
+ ** column of a vector IN operater, and avoid adding the column
+ ** to the WhereLoop more than once. See tag-20250707-01
+ ** in test/rowvalue.test */
+ bRedundant = 1;
+ }
+ }
+ }
+ if( bRedundant ){
+ pNew->nLTerm--;
+ continue;
}
}else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){
/* "x IN (value, value, ...)" */
@@ -3474,7 +3493,7 @@ static int whereLoopAddBtreeIndex(
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
&& pNew->u.btree.nEq<pProbe->nColumn
&& (pNew->u.btree.nEq<pProbe->nKeyCol ||
- (pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY && !pProbe->bIdxRowid))
+ pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY)
){
if( pNew->u.btree.nEq>3 ){
sqlite3ProgressCheck(pParse);
@@ -3513,6 +3532,7 @@ static int whereLoopAddBtreeIndex(
&& pProbe->hasStat1!=0
&& OptimizationEnabled(db, SQLITE_SkipScan)
&& pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */
+ && pSrc->fg.fromExists==0
&& (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK
){
LogEst nIter;
@@ -7070,6 +7090,14 @@ WhereInfo *sqlite3WhereBegin(
pTab = pTabItem->pSTab;
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
pLoop = pLevel->pWLoop;
+ pLevel->addrBrk = sqlite3VdbeMakeLabel(pParse);
+ if( ii==0 || (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){
+ pLevel->addrHalt = pLevel->addrBrk;
+ }else if( pWInfo->a[ii-1].pRJ ){
+ pLevel->addrHalt = pWInfo->a[ii-1].addrBrk;
+ }else{
+ pLevel->addrHalt = pWInfo->a[ii-1].addrHalt;
+ }
if( (pTab->tabFlags & TF_Ephemeral)!=0 || IsView(pTab) ){
/* Do nothing */
}else
@@ -7121,6 +7149,13 @@ WhereInfo *sqlite3WhereBegin(
sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, pTabItem->iCursor, 0, 0,
(const u8*)&pTabItem->colUsed, P4_INT64);
#endif
+ if( ii>=2
+ && (pTabItem[0].fg.jointype & (JT_LTORJ|JT_LEFT))==0
+ && pLevel->addrHalt==pWInfo->a[0].addrHalt
+ ){
+ sqlite3VdbeAddOp2(v, OP_IfEmpty, pTabItem->iCursor, pWInfo->iBreak);
+ VdbeCoverage(v);
+ }
}else{
sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
}
@@ -7377,6 +7412,9 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
sqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2);
}
#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */
+ if( pTabList->a[pLevel->iFrom].fg.fromExists ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2);
+ }
/* The common case: Advance to the next row */
if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont);
sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3);
diff --git a/src/whereInt.h b/src/whereInt.h
index 3a9353e07..09e02c8c7 100644
--- a/src/whereInt.h
+++ b/src/whereInt.h
@@ -75,6 +75,7 @@ struct WhereLevel {
int iTabCur; /* The VDBE cursor used to access the table */
int iIdxCur; /* The VDBE cursor used to access pIdx */
int addrBrk; /* Jump here to break out of the loop */
+ int addrHalt; /* Abort the query due to empty table or similar */
int addrNxt; /* Jump here to start the next IN combination */
int addrSkip; /* Jump here for next iteration of skip-scan */
int addrCont; /* Jump here to continue with the next loop cycle */
diff --git a/src/wherecode.c b/src/wherecode.c
index 9581ac389..43a669d81 100644
--- a/src/wherecode.c
+++ b/src/wherecode.c
@@ -126,7 +126,6 @@ void sqlite3WhereAddExplainText(
#endif
{
VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe, addr);
-
SrcItem *pItem = &pTabList->a[pLevel->iFrom];
sqlite3 *db = pParse->db; /* Database handle */
int isSearch; /* True for a SEARCH. False for SCAN. */
@@ -149,7 +148,10 @@ void sqlite3WhereAddExplainText(
sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH);
str.printfFlags = SQLITE_PRINTF_INTERNAL;
- sqlite3_str_appendf(&str, "%s %S", isSearch ? "SEARCH" : "SCAN", pItem);
+ sqlite3_str_appendf(&str, "%s %S%s",
+ isSearch ? "SEARCH" : "SCAN",
+ pItem,
+ pItem->fg.fromExists ? " EXISTS" : "");
if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){
const char *zFmt = 0;
Index *pIdx;
@@ -598,7 +600,9 @@ static Expr *removeUnindexableInClauseTerms(
int iField;
assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 );
iField = pLoop->aLTerm[i]->u.x.iField - 1;
- if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */
+ if( NEVER(pOrigRhs->a[iField].pExpr==0) ){
+ continue; /* Duplicate PK column */
+ }
pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
pOrigRhs->a[iField].pExpr = 0;
if( pRhs ) pRhs->a[pRhs->nExpr-1].u.x.iOrderByCol = iField+1;
@@ -695,7 +699,7 @@ static SQLITE_NOINLINE void codeINTerm(
return;
}
}
- for(i=iEq;i<pLoop->nLTerm; i++){
+ for(i=iEq; i<pLoop->nLTerm; i++){
assert( pLoop->aLTerm[i]!=0 );
if( pLoop->aLTerm[i]->pExpr==pX ) nEq++;
}
@@ -704,22 +708,13 @@ static SQLITE_NOINLINE void codeINTerm(
if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab);
}else{
- Expr *pExpr = pTerm->pExpr;
- if( pExpr->iTable==0 || !ExprHasProperty(pExpr, EP_Subrtn) ){
- sqlite3 *db = pParse->db;
- pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
- if( !db->mallocFailed ){
- aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq);
- eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap,&iTab);
- pExpr->iTable = iTab;
- }
- sqlite3ExprDelete(db, pX);
- }else{
- int n = sqlite3ExprVectorSize(pX->pLeft);
- aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*MAX(nEq,n));
- eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab);
+ sqlite3 *db = pParse->db;
+ Expr *pXMod = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
+ if( !db->mallocFailed ){
+ aiMap = (int*)sqlite3DbMallocZero(db, sizeof(int)*nEq);
+ eType = sqlite3FindInIndex(pParse, pXMod, IN_INDEX_LOOP, 0, aiMap, &iTab);
}
- pX = pExpr;
+ sqlite3ExprDelete(db, pXMod);
}
if( eType==IN_INDEX_INDEX_DESC ){
@@ -749,7 +744,7 @@ static SQLITE_NOINLINE void codeINTerm(
if( pIn ){
int iMap = 0; /* Index in aiMap[] */
pIn += i;
- for(i=iEq;i<pLoop->nLTerm; i++){
+ for(i=iEq; i<pLoop->nLTerm; i++){
if( pLoop->aLTerm[i]->pExpr==pX ){
int iOut = iTarget + i - iEq;
if( eType==IN_INDEX_ROWID ){
@@ -1400,6 +1395,7 @@ static SQLITE_NOINLINE void filterPullDown(
int addrNxt, /* Jump here to bypass inner loops */
Bitmask notReady /* Loops that are not ready */
){
+ int saved_addrBrk;
while( ++iLevel < pWInfo->nLevel ){
WhereLevel *pLevel = &pWInfo->a[iLevel];
WhereLoop *pLoop = pLevel->pWLoop;
@@ -1408,7 +1404,7 @@ static SQLITE_NOINLINE void filterPullDown(
/* ,--- Because sqlite3ConstructBloomFilter() has will not have set
** vvvvv--' pLevel->regFilter if this were true. */
if( NEVER(pLoop->prereq & notReady) ) continue;
- assert( pLevel->addrBrk==0 );
+ saved_addrBrk = pLevel->addrBrk;
pLevel->addrBrk = addrNxt;
if( pLoop->wsFlags & WHERE_IPK ){
WhereTerm *pTerm = pLoop->aLTerm[0];
@@ -1438,7 +1434,7 @@ static SQLITE_NOINLINE void filterPullDown(
VdbeCoverage(pParse->pVdbe);
}
pLevel->regFilter = 0;
- pLevel->addrBrk = 0;
+ pLevel->addrBrk = saved_addrBrk;
}
}
@@ -1485,7 +1481,6 @@ Bitmask sqlite3WhereCodeOneLoopStart(
sqlite3 *db; /* Database connection */
SrcItem *pTabItem; /* FROM clause term being coded */
int addrBrk; /* Jump here to break out of the loop */
- int addrHalt; /* addrBrk for the outermost loop */
int addrCont; /* Jump here to continue with next cycle */
int iRowidReg = 0; /* Rowid is stored in this register, if not zero */
int iReleaseReg = 0; /* Temp register to free before returning */
@@ -1529,7 +1524,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
** there are no IN operators in the constraints, the "addrNxt" label
** is the same as "addrBrk".
*/
- addrBrk = pLevel->addrBrk = pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse);
+ addrBrk = pLevel->addrNxt = pLevel->addrBrk;
addrCont = pLevel->addrCont = sqlite3VdbeMakeLabel(pParse);
/* If this is the right table of a LEFT OUTER JOIN, allocate and
@@ -1545,14 +1540,6 @@ Bitmask sqlite3WhereCodeOneLoopStart(
VdbeComment((v, "init LEFT JOIN match flag"));
}
- /* Compute a safe address to jump to if we discover that the table for
- ** this loop is empty and can never contribute content. */
- for(j=iLevel; j>0; j--){
- if( pWInfo->a[j].iLeftJoin ) break;
- if( pWInfo->a[j].pRJ ) break;
- }
- addrHalt = pWInfo->a[j].addrBrk;
-
/* Special case of a FROM clause subquery implemented as a co-routine */
if( pTabItem->fg.viaCoroutine ){
int regYield;
@@ -1791,7 +1778,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
VdbeCoverageIf(v, pX->op==TK_GE);
sqlite3ReleaseTempReg(pParse, rTemp);
}else{
- sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrHalt);
+ sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, pLevel->addrHalt);
VdbeCoverageIf(v, bRev==0);
VdbeCoverageIf(v, bRev!=0);
}
@@ -2586,7 +2573,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
codeCursorHint(pTabItem, pWInfo, pLevel, 0);
pLevel->op = aStep[bRev];
pLevel->p1 = iCur;
- pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrHalt);
+ pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev],iCur,pLevel->addrHalt);
VdbeCoverageIf(v, bRev==0);
VdbeCoverageIf(v, bRev!=0);
pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP;
diff --git a/src/whereexpr.c b/src/whereexpr.c
index 53c8508e5..0d17b0d75 100644
--- a/src/whereexpr.c
+++ b/src/whereexpr.c
@@ -948,7 +948,7 @@ static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){
if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* (3) */
assert( pSrc!=0 );
if( pExpr->op==TK_IS
- && pSrc->nSrc
+ && pSrc->nSrc>=2
&& (pSrc->a[0].fg.jointype & JT_LTORJ)!=0
){
return 0; /* (4) */
@@ -1541,7 +1541,7 @@ static void exprAnalyze(
idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
testcase( idxNew==0 );
pNewTerm = &pWC->a[idxNew];
- pNewTerm->prereqRight = prereqExpr;
+ pNewTerm->prereqRight = prereqExpr | extraRight;
pNewTerm->leftCursor = pLeft->iTable;
pNewTerm->u.x.leftColumn = pLeft->iColumn;
pNewTerm->eOperator = WO_AUX;
@@ -1652,7 +1652,7 @@ static void whereAddLimitExpr(
**
** 1. The SELECT statement has a LIMIT clause, and
** 2. The SELECT statement is not an aggregate or DISTINCT query, and
-** 3. The SELECT statement has exactly one object in its from clause, and
+** 3. The SELECT statement has exactly one object in its FROM clause, and
** that object is a virtual table, and
** 4. There are no terms in the WHERE clause that will not be passed
** to the virtual table xBestIndex method.
@@ -1689,8 +1689,22 @@ void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){
** (leftCursor==iCsr) test below. */
continue;
}
- if( pWC->a[ii].leftCursor!=iCsr ) return;
- if( pWC->a[ii].prereqRight!=0 ) return;
+ if( pWC->a[ii].leftCursor==iCsr && pWC->a[ii].prereqRight==0 ) continue;
+
+ /* If this term has a parent with exactly one child, and the parent will
+ ** be passed through to xBestIndex, then this term can be ignored. */
+ if( pWC->a[ii].iParent>=0 ){
+ WhereTerm *pParent = &pWC->a[ pWC->a[ii].iParent ];
+ if( pParent->leftCursor==iCsr
+ && pParent->prereqRight==0
+ && pParent->nChild==1
+ ){
+ continue;
+ }
+ }
+
+ /* This term will not be passed through. Do not add a LIMIT clause. */
+ return;
}
/* Check condition (5). Return early if it is not met. */
diff --git a/test/altertab2.test b/test/altertab2.test
index 56e42f1a6..f2a1d74c4 100644
--- a/test/altertab2.test
+++ b/test/altertab2.test
@@ -358,7 +358,7 @@ do_catchsql_test 8.6 {
CREATE INDEX i0 ON t0(likelihood(1,2) AND 0);
ALTER TABLE t0 RENAME TO t1;
SELECT sql FROM sqlite_master WHERE name='i0';
-} {1 {error in index i0: second argument to likelihood() must be a constant between 0.0 and 1.0}}
+} {1 {second argument to likelihood() must be a constant between 0.0 and 1.0}}
reset_db
diff --git a/test/altertab3.test b/test/altertab3.test
index 5f5c11b0b..92060fb41 100644
--- a/test/altertab3.test
+++ b/test/altertab3.test
@@ -190,14 +190,14 @@ do_execsql_test 8.1 {
}
do_execsql_test 8.2.1 {
CREATE TABLE t2 (c0);
- CREATE INDEX i2 ON t2((LIKELIHOOD(c0, 100) IN ()));
+ CREATE INDEX i2 ON t2((LIKELIHOOD(c0, 1.0) IN ()));
ALTER TABLE t2 RENAME COLUMN c0 TO c1;
}
do_execsql_test 8.2.2 {
SELECT sql FROM sqlite_master WHERE tbl_name = 't2';
} {
{CREATE TABLE t2 (c1)}
- {CREATE INDEX i2 ON t2((LIKELIHOOD(c0, 100) IN ()))}
+ {CREATE INDEX i2 ON t2((LIKELIHOOD(c1, 1.0) IN ()))}
}
do_test 8.2.3 {
sqlite3 db2 test.db
@@ -662,14 +662,6 @@ do_execsql_test 27.2 {
{CREATE TABLE t1(a, b AS ((WITH w1 (xyz) AS ( SELECT t1.b FROM t1 ) SELECT 123) IN ()))}
}
-do_execsql_test 27.3 {
- CREATE TABLE t0(c0 , c1 AS (CASE TRUE NOT IN () WHEN NULL THEN CASE + 0xa ISNULL WHEN NOT + 0x9 THEN t0.c1 ELSE CURRENT_TIME LIKE CAST (t0.c1 REGEXP '-([1-9]\d*.\d*|0\.\d*[1-9]\d*)'ESCAPE (c1) COLLATE BINARY BETWEEN c1 AND c1 NOT IN (WITH t4 (c0) AS (WITH t3 (c0) AS NOT MATERIALIZED (WITH RECURSIVE t2 (c0) AS (WITH RECURSIVE t1 AS (VALUES (x'717171ff71717171' ) ) SELECT DISTINCT t0.c0 FROM t0 NOT INDEXED WHERE t0.c0 =t0.c0 GROUP BY 0x9 ) SELECT DISTINCT t0.c0 FROM t0 NOT INDEXED WHERE t0.c0 =t0.c1 ) SELECT DISTINCT t0.c0 FROM t0 NOT INDEXED WHERE t0.c0 =t0.c0 GROUP BY typeof(0x9 ) ) SELECT DISTINCT t0.c0 FROM t0 NOT INDEXED WHERE t0.c0 =t0.c0 GROUP BY typeof(typeof(0x9 ) ) ) IN t0 BETWEEN typeof(typeof(typeof(hex(*) FILTER (WHERE + x'5ccd1e68' ) ) ) ) AND 1 >0xa AS BLOB (+4.4E4 , -0xe ) ) END <> c1 IN () END ) VIRTUAL , c35 PRIMARY KEY , c60 , c64 NUMERIC (-6.8 , -0xE ) ) WITHOUT ROWID ;
-} {}
-
-do_execsql_test 27.4 {
- ALTER TABLE t0 DROP COLUMN c60;
-} {}
-
#-------------------------------------------------------------------------
reset_db
do_execsql_test 28.1 {
diff --git a/test/bestindexC.test b/test/bestindexC.test
index 48f3a2765..8b96a19e6 100644
--- a/test/bestindexC.test
+++ b/test/bestindexC.test
@@ -349,4 +349,78 @@ do_execsql_test 5.9 {
three six seven
}
+#--------------------------------------------------------------------------
+
+reset_db
+register_tcl_module db
+
+proc quote {str} {
+ return "'[string map {' ''} $str]'"
+}
+
+proc vtab_command {lVal method args} {
+ switch -- $method {
+ xConnect {
+ return "CREATE TABLE t1(a, b, c, d)"
+ }
+
+ xBestIndex {
+ set hdl [lindex $args 0]
+ set clist [$hdl constraints]
+
+ set idx 0
+ set idxnum 0
+
+ foreach c $clist {
+ array set a $c
+ if {$a(usable)==0} continue
+
+ if {$a(op)=="limit"} {
+ set idxnum [$hdl rhs_value $idx 555]
+ }
+
+ incr idx
+ }
+
+ return "cost 1000 rows 1000 idxnum $idxnum"
+
+ }
+
+ xFilter {
+ foreach {idxnum idxstr lArg} $args {}
+ return [list sql "SELECT 0, $idxnum, $idxnum, $idxnum, $idxnum"]
+ }
+ }
+
+ return {}
+}
+
+do_execsql_test 6.0 {
+ CREATE TABLE t1(x, y);
+ INSERT INTO t1 VALUES(2, 2);
+ CREATE VIRTUAL TABLE x1 USING tcl(vtab_command t1);
+}
+
+do_execsql_test 6.1 { SELECT * FROM x1 LIMIT 50 } {50 50 50 50}
+
+do_execsql_test 6.2 { SELECT * FROM x1 WHERE b=c LIMIT 5 } {0 0 0 0}
+
+do_execsql_test 6.3 {
+ SELECT (SELECT a FROM x1 WHERE t1.x=t1.y LIMIT 10) FROM t1
+} {0}
+
+do_execsql_test 6.4 {
+ SELECT (SELECT a FROM x1 WHERE x1.a=1) FROM t1
+} {1}
+
+do_execsql_test 6.5 {
+ SELECT (SELECT a FROM x1 WHERE x1.a=1 LIMIT 1) FROM t1
+} {1}
+
+do_execsql_test 6.6 {
+ SELECT (SELECT a FROM x1 WHERE x1.a=555 LIMIT 2) FROM t1
+} {555}
+
finish_test
+
+
diff --git a/test/between.test b/test/between.test
index 16c3913d1..5e02ef9b2 100644
--- a/test/between.test
+++ b/test/between.test
@@ -140,4 +140,21 @@ foreach {tn expr res} {
do_execsql_test between-2.1.$tn $sql $res
}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test between-3.0 {
+ CREATE TABLE t1(x, y);
+ CREATE INDEX i1 ON t1(x);
+ INSERT INTO t1 VALUES(4, 4);
+ CREATE TABLE t2(a, b);
+}
+
+do_execsql_test between-3.1 {
+ SELECT * FROM t1 LEFT JOIN t2 ON (x BETWEEN 1 AND 3);
+} {4 4 {} {}}
+
+do_execsql_test between-3.2 {
+ SELECT * FROM t1 LEFT JOIN t2 ON (x BETWEEN 5 AND 7);
+} {4 4 {} {}}
+
finish_test
diff --git a/test/eqp.test b/test/eqp.test
index 5d2659be7..147b5ceaf 100644
--- a/test/eqp.test
+++ b/test/eqp.test
@@ -338,8 +338,7 @@ det 3.3.3 {
} {
QUERY PLAN
|--SCAN t1
- `--CORRELATED SCALAR SUBQUERY xxxxxx
- `--SCAN t2
+ `--SCAN t2 EXISTS
}
#-------------------------------------------------------------------------
diff --git a/test/existsexpr.test b/test/existsexpr.test
new file mode 100644
index 000000000..2bf2e8223
--- /dev/null
+++ b/test/existsexpr.test
@@ -0,0 +1,426 @@
+# 2024 May 25
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+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);
+ CREATE INDEX x1b ON x1(b);
+
+ CREATE TABLE x2(x, y);
+ INSERT INTO x2 VALUES(1, 2), (3, 4), (5, 6);
+}
+
+do_execsql_test 1.1 {
+ SELECT 1 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=5)
+} {1}
+
+do_execsql_test 1.2 {
+ SELECT * FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x)
+} {1 2 3 4 5 6}
+
+# With "a=x", the UNIQUE index means the EXIST can be transformed to a join.
+# So no "SUBQUERY". With "b=x", the index is not UNIQUE and so there is a
+# "SUBQUERY".
+do_execsql_test 1.3.1 {
+ EXPLAIN QUERY PLAN
+ SELECT * FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x)
+} {~/SUBQUERY/}
+do_execsql_test 1.3.2 {
+ EXPLAIN QUERY PLAN
+ SELECT * FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE b=x)
+} {~/SUBQUERY/}
+
+do_execsql_test 1.4.1 {
+ EXPLAIN QUERY PLAN
+ SELECT * FROM x2 WHERE x=1 AND EXISTS (SELECT 1 FROM x1 WHERE a=x)
+} {~/SUBQUERY/}
+do_execsql_test 1.4.2 {
+ EXPLAIN QUERY PLAN
+ SELECT * FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x) AND y=2
+} {~/SUBQUERY/}
+
+do_execsql_test 1.5 {
+ SELECT count(*) FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x)
+} {3}
+
+#-------------------------------------------------------------------------
+do_execsql_test 2.0 {
+ CREATE TABLE t1(a, b);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ ) INSERT INTO t1 SELECT i, i FROM s;
+
+ CREATE TABLE t2(c, d);
+ WITH s(i) AS (
+ SELECT 10 UNION ALL SELECT i+10 FROM s WHERE i<1000
+ ) INSERT INTO t2 SELECT i, i FROM s;
+}
+
+do_execsql_test 2.1 {
+ SELECT count(*) FROM t1;
+ SELECT count(*) FROM t2;
+} {1000 100}
+
+do_execsql_test 2.2 {
+ SELECT count(*) FROM t1, t2 WHERE a=c;
+} {100}
+
+do_execsql_test 2.3 {
+ SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a)
+} {100}
+do_eqp_test 2.4 {
+ SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a)
+} {SCAN t1}
+
+do_execsql_test 2.4.0 {
+ CREATE UNIQUE INDEX t2c ON t2(c);
+ CREATE UNIQUE INDEX t1a ON t1(a);
+}
+
+do_eqp_test 2.4.1 {
+ SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a);
+} {SCAN t1*t2 EXISTS}
+do_execsql_test 2.4.2 {
+ ANALYZE;
+}
+do_eqp_test 2.4.3 {
+ SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a);
+} {SCAN t1*t2 EXISTS}
+do_execsql_test 2.4.4 {
+ SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a);
+} {100}
+
+do_execsql_test 2.5.1 {
+ EXPLAIN QUERY PLAN
+ SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.rowid=a);
+} {~/SUBQUERY/}
+
+#-------------------------------------------------------------------------
+proc do_subquery_test {tn bSub sql res} {
+ set r1(0) ~/SUBQUERY/
+ set r1(1) /SUBQUERY/
+ do_execsql_test $tn.1 "explain query plan $sql" $r1($bSub)
+ do_execsql_test $tn.2 $sql $res
+}
+
+do_execsql_test 3.0 {
+ CREATE TABLE y1(a, b, c);
+ CREATE TABLE y2(x, y, z);
+ CREATE UNIQUE INDEX y2zy ON y2(z, y);
+
+ INSERT INTO y1 VALUES(1, 1, 1);
+ INSERT INTO y1 VALUES(2, 2, 2);
+ INSERT INTO y1 VALUES(3, 3, 3);
+ INSERT INTO y1 VALUES(4, 4, 4);
+
+ INSERT INTO y2 VALUES(1, 1, 1);
+ INSERT INTO y2 VALUES(3, 3, 3);
+}
+
+do_subquery_test 3.1 0 {
+ SELECT * FROM y1 WHERE EXISTS (
+ SELECT 1 FROM y2 WHERE z=a AND y=b AND x=z
+ )
+} {
+ 1 1 1 3 3 3
+}
+
+do_subquery_test 3.2 0 {
+ SELECT * FROM y1 WHERE EXISTS (
+ SELECT 1 FROM y2 WHERE z=max(a,b) AND y=min(b,a) AND x=z
+ )
+} {
+ 1 1 1 3 3 3
+}
+
+do_subquery_test 3.3 0 {
+ SELECT * FROM y1 WHERE EXISTS (
+ SELECT 1 FROM y2 WHERE z=max(a,b) AND y=min(b,a) AND c!=3
+ )
+} {
+ 1 1 1
+}
+
+do_subquery_test 3.4 0 {
+ SELECT * FROM y1 WHERE EXISTS (
+ SELECT 1 FROM y2 WHERE z=max(a,b) AND b=3
+ )
+} {
+ 3 3 3
+}
+
+do_subquery_test 3.5 0 {
+ SELECT * FROM y1 WHERE EXISTS (
+ SELECT 1 FROM y2 WHERE z=a-1 AND y=a-1
+ )
+} {
+ 2 2 2
+ 4 4 4
+}
+
+do_subquery_test 3.6 0 {
+ SELECT * FROM y1 WHERE EXISTS (
+ SELECT 1 FROM y2 WHERE z=a-1 AND y+1=a
+ )
+} {
+ 2 2 2
+ 4 4 4
+}
+
+do_subquery_test 3.7 1 {
+ SELECT * FROM y1 WHERE EXISTS (
+ SELECT count(*) FROM y2 WHERE z=a-1 AND y=a-1
+ )
+} {
+ 1 1 1
+ 2 2 2
+ 3 3 3
+ 4 4 4
+}
+
+do_subquery_test 3.8 0 {
+ SELECT * FROM y1 WHERE EXISTS ( SELECT a+1 FROM y2 )
+} {
+ 1 1 1
+ 2 2 2
+ 3 3 3
+ 4 4 4
+}
+
+do_subquery_test 3.9 1 {
+ SELECT * FROM y1 WHERE EXISTS (
+ SELECT 1 FROM y2 one, y2 two WHERE one.z=a-1 AND one.y=a-1
+ )
+} {
+ 2 2 2
+ 4 4 4
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+ CREATE TABLE tx1(a TEXT COLLATE nocase, b TEXT);
+ CREATE UNIQUE INDEX tx1ab ON tx1(a, b);
+
+ INSERT INTO tx1 VALUES('a', 'a');
+ INSERT INTO tx1 VALUES('B', 'b');
+ INSERT INTO tx1 VALUES('c', 'c');
+ INSERT INTO tx1 VALUES('D', 'd');
+ INSERT INTO tx1 VALUES('e', 'e');
+
+ CREATE TABLE tx2(x, y);
+ INSERT INTO tx2 VALUES('A', 'a');
+ INSERT INTO tx2 VALUES('b', 'b');
+ INSERT INTO tx2 VALUES('C', 'c');
+ INSERT INTO tx2 VALUES('D', 'd');
+}
+
+do_subquery_test 4.1 0 {
+ SELECT * FROM tx2 WHERE EXISTS (
+ SELECT 1 FROM tx1 WHERE a=x AND b=y
+ )
+} {
+ A a
+ b b
+ C c
+ D d
+}
+
+do_subquery_test 4.1.1 0 {
+ SELECT * FROM tx2 WHERE EXISTS (
+ SELECT 1 FROM tx1 WHERE (a COLLATE nocase)=x AND b=y
+ )
+} {
+ A a b b C c D d
+}
+do_subquery_test 4.1.2 0 {
+ SELECT * FROM tx2 WHERE EXISTS (
+ SELECT 1 FROM tx1 WHERE a=x AND (b COLLATE binary)=y
+ )
+} {
+ A a b b C c D d
+}
+do_subquery_test 4.1.1 0 {
+ SELECT * FROM tx2 WHERE EXISTS (
+ SELECT 1 FROM tx1 WHERE x=(a COLLATE nocase) AND b=y
+ )
+} {
+ A a b b C c D d
+}
+do_subquery_test 4.1.2 0 {
+ SELECT * FROM tx2 WHERE EXISTS (
+ SELECT 1 FROM tx1 WHERE a=x AND y=(b COLLATE binary)
+ )
+} {
+ A a b b C c D d
+}
+
+do_subquery_test 4.2 0 {
+ SELECT * FROM tx2 WHERE EXISTS (
+ SELECT 1 FROM tx1 WHERE a=x AND b=y COLLATE nocase
+ )
+} {
+ A a
+ b b
+ C c
+ D d
+}
+
+do_execsql_test 4.3 {
+ DROP INDEX tx1ab;
+ CREATE UNIQUE INDEX tx1ab ON tx1(a COLLATE binary, b);
+}
+
+do_subquery_test 4.4 0 {
+ SELECT * FROM tx2 WHERE EXISTS (
+ SELECT 1 FROM tx1 WHERE a=x AND b=y
+ )
+} {
+ A a
+ b b
+ C c
+ D d
+}
+
+do_subquery_test 4.4 0 {
+ SELECT * FROM tx2 WHERE EXISTS (
+ SELECT 1 FROM tx1 WHERE a=x COLLATE binary AND b=y
+ )
+} {
+ D d
+}
+
+do_subquery_test 4.4 1 {
+ SELECT EXISTS ( SELECT x FROM tx1 ) FROM tx2
+} {
+ 1 1 1 1
+}
+
+do_subquery_test 4.4 1 {
+ SELECT (SELECT EXISTS ( SELECT x FROM tx1 ) WHERE 1) FROM tx2
+} {
+ 1 1 1 1
+}
+
+#-------------------------------------------------------------------------
+proc cols {s f} {
+ set lCols [list]
+ for {set i $s} {$i<=$f} {incr i} {
+ lappend lCols [format "c%02d" $i]
+ }
+ join $lCols ", "
+}
+proc vals {n val} {
+ set lVal [list]
+ for {set i 0} {$i<$n} {incr i} {
+ lappend lVal $val
+ }
+ join $lVal ", "
+}
+proc exprs {s f} {
+ set lExpr [list]
+ for {set i $s} {$i<=$f} {incr i} {
+ lappend lExpr [format "c%02d = o" $i]
+ }
+ join $lExpr " AND "
+}
+
+
+do_execsql_test 5.0 "
+ CREATE TABLE a1( [cols 0 99] );
+"
+do_execsql_test 5.1 "
+ -- 63 column index
+ CREATE UNIQUE INDEX a1idx1 ON a1( [cols 0 62] );
+"
+do_execsql_test 5.2 "
+ -- 64 column index
+ CREATE UNIQUE INDEX a1idx2 ON a1( [cols 10 73] );
+"
+do_execsql_test 5.2 "
+ -- 65 column index
+ CREATE UNIQUE INDEX a1idx3 ON a1( [cols 20 84] );
+"
+
+do_test 5.3 {
+ foreach v {1 2 3 4 5 6} {
+ execsql "INSERT INTO a1 VALUES( [vals 100 $v] )"
+ }
+} {}
+
+do_execsql_test 5.4 {
+ CREATE TABLE a2(o);
+ INSERT INTO a2 VALUES(2), (5);
+}
+
+do_subquery_test 5.5 0 "
+ SELECT o FROM a2 WHERE EXISTS (
+ SELECT 1 FROM a1 WHERE [exprs 0 62]
+ )
+" {
+ 2 5
+}
+
+do_subquery_test 5.6 0 "
+ SELECT o FROM a2 WHERE EXISTS (
+ SELECT 1 FROM a1 WHERE [exprs 10 73]
+ )
+" {
+ 2 5
+}
+
+do_subquery_test 5.7 0 "
+ SELECT o FROM a2 WHERE EXISTS (
+ SELECT 1 FROM a1 WHERE [exprs 20 84]
+ )
+" {
+ 2 5
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 6.0 {
+ CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
+ CREATE TABLE t2(a INfEGER PRIMARY KEY, b);
+ CREATE UNIQUE INDEX t2b ON t2(b);
+}
+
+do_catchsql_test 6.1 {
+ SELECT a FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c COLLATE f = a)
+} {1 {no such collation sequence: f}}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 7.0 {
+ CREATE TABLE t1(x);
+ CREATE TABLE t2(y UNIQUE);
+
+ INSERT INTO t1 VALUES(1), (2);
+ INSERT INTO t2 VALUES(1), (3);
+
+ SELECT * FROM t1 one LEFT JOIN t1 two ON (one.x=two.x AND EXISTS (
+ SELECT 1 FROM t2 WHERE y=one.x
+ ));
+} {
+ 1 1
+ 2 {}
+}
+
+
+
+finish_test
diff --git a/test/existsexpr2.test b/test/existsexpr2.test
new file mode 100644
index 000000000..f7644bf80
--- /dev/null
+++ b/test/existsexpr2.test
@@ -0,0 +1,96 @@
+# 2024 June 14
+#
+# 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
+source $testdir/lock_common.tcl
+set testprefix existsexpr2
+
+
+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);
+ CREATE INDEX x1b ON x1(b);
+
+ CREATE TABLE x2(x, y);
+ INSERT INTO x2 VALUES(1, 2), (3, 4), (5, 6);
+}
+
+do_execsql_test 1.1 {
+ SELECT * FROM x1 WHERE EXISTS (SELECT 1 FROM x2 WHERE a!=123)
+} {1 2 3 4 5 6}
+
+do_execsql_test 1.2 {
+ CREATE TABLE x3(u, v);
+ CREATE INDEX x3u ON x3(u);
+ INSERT INTO x3 VALUES
+ (1, 1), (1, 2), (1, 3),
+ (2, 1), (2, 2), (2, 3);
+}
+
+do_execsql_test 1.3 {
+ SELECT * FROM x1 WHERE EXISTS (
+ SELECT 1 FROM x3 WHERE u IN (1, 2, 3, 4) AND v=b
+ );
+} {
+ 1 2
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 2.0 {
+ CREATE TABLE t1(a, b, c);
+ CREATE INDEX t1ab ON t1(a,b);
+
+ INSERT INTO t1 VALUES
+ ('abc', 1, 1),
+ ('abc', 2, 2),
+ ('abc', 2, 3),
+
+ ('def', 1, 1),
+ ('def', 2, 2),
+ ('def', 2, 3);
+
+ CREATE TABLE t2(x, y);
+ INSERT INTO t2 VALUES(1, 1), (2, 2), (3, 3);
+
+ ANALYZE;
+ DELETE FROM sqlite_stat1;
+ INSERT INTO sqlite_stat1 VALUES('t1','t1ab','10000 5000 2');
+ ANALYZE sqlite_master;
+}
+
+
+do_execsql_test 2.1 {
+ SELECT a,b,c FROM t1 WHERE b=2 ORDER BY a
+} {
+ abc 2 2
+ abc 2 3
+ def 2 2
+ def 2 3
+}
+
+do_execsql_test 2.2 {
+ SELECT x, y FROM t2 WHERE EXISTS (
+ SELECT 1 FROM t1 WHERE b=x
+ )
+} {
+ 1 1
+ 2 2
+}
+
+
+
+finish_test
+
+
diff --git a/test/existsfault.test b/test/existsfault.test
new file mode 100644
index 000000000..4b335d84c
--- /dev/null
+++ b/test/existsfault.test
@@ -0,0 +1,49 @@
+# 2024 May 25
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+source $testdir/malloc_common.tcl
+set testprefix existsfault
+
+db close
+sqlite3_shutdown
+sqlite3_config_lookaside 0 0
+sqlite3_initialize
+autoinstall_test_functions
+sqlite3 db test.db
+
+do_execsql_test 1.0 {
+ CREATE TABLE x1(a, b);
+ INSERT INTO x1 VALUES(1, 2), (3, 4), (5, 6);
+ CREATE UNIQUE INDEX x1a ON x1(a);
+ CREATE INDEX x1b ON x1(b);
+
+ CREATE TABLE x2(x, y);
+ INSERT INTO x2 VALUES(1, 2), (3, 4), (5, 6);
+}
+
+do_faultsim_test 1 -faults oom* -prep {
+ sqlite3 db test.db
+ execsql { SELECT * FROM sqlite_schema }
+} -body {
+ execsql {
+ SELECT count(*) FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x) AND y!=11
+ }
+} -test {
+ faultsim_test_result {0 3}
+}
+
+finish_test
+
+
diff --git a/test/incrblob4.test b/test/incrblob4.test
index dbff8eb7d..c9bcee8a3 100644
--- a/test/incrblob4.test
+++ b/test/incrblob4.test
@@ -106,4 +106,102 @@ do_test 4.4 {
} {SQLITE_LOCKED}
close $blob
+#-------------------------------------------------------------------------
+
+ifcapable preupdate {
+
+reset_db
+do_execsql_test 5.1 {
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t2 VALUES(1000, 'abcdefghijklmnopqrstuvwxyz');
+ INSERT INTO t2 VALUES(2000, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
+ INSERT INTO t2 VALUES(3000, 'abcdefghijklmnopqrstuvwxyz');
+}
+
+do_test 5.2.1 {
+ execsql BEGIN
+ set blob [db incrblob t2 b 2000]
+ seek $blob 0
+ puts -nonewline $blob "hello "
+ flush $blob
+ execsql ROLLBACK
+} {}
+
+do_test 5.2.2 {
+ puts -nonewline $blob "world"
+ set rc [catch { flush $blob } msg]
+ list $rc [regsub {input/output} $msg {I/O}]
+} "1 {error flushing \"$blob\": I/O error}"
+catch { close $blob }
+
+set preupdate_count 0
+proc preupdate {args} { incr ::preupdate_count ; return {} }
+db preupdate hook preupdate
+
+set preupdate_count 0
+do_test 5.3.1 {
+ execsql BEGIN
+ set blob [db incrblob t2 b 1000]
+ seek $blob 0
+ puts -nonewline $blob "hello "
+ flush $blob
+ execsql ROLLBACK
+} {}
+
+do_test 5.3.2 {
+ puts -nonewline $blob "world"
+ set rc [catch { flush $blob } msg]
+ list $rc [regsub {input/output} $msg {I/O}]
+} "1 {error flushing \"$blob\": I/O error}"
+catch { close $blob }
+
+do_test 5.3.3 {
+ set ::preupdate_count
+} {1}
+
+set preupdate_count 0
+do_test 5.4.1 {
+ execsql BEGIN
+ set blob [db incrblob t2 b 1000]
+ seek $blob 0
+ puts -nonewline $blob "hello "
+ flush $blob
+ execsql { DELETE FROM t2 WHERE a=3000; }
+} {}
+
+do_test 5.4.2 {
+ puts -nonewline $blob "world"
+ list [catch { flush $blob } msg] $msg
+} "0 {}"
+catch { close $blob }
+catchsql { ROLLBACK }
+
+do_test 5.3.3 {
+ set ::preupdate_count
+} {3}
+
+set preupdate_count 0
+do_test 5.4.3 {
+ execsql BEGIN
+ set blob [db incrblob t2 b 2000]
+ seek $blob 0
+ puts -nonewline $blob "hello "
+ flush $blob
+ execsql { UPDATE t2 SET b='abcdefghijklmnopqrstuvwxyz' WHERE a=2000 }
+} {}
+
+do_test 5.4.4 {
+ puts -nonewline $blob "world"
+ set rc [catch { flush $blob } msg]
+ list $rc [regsub {input/output} $msg {I/O}]
+} "1 {error flushing \"$blob\": I/O error}"
+catch { close $blob }
+catchsql { ROLLBACK }
+
+do_test 5.4.5 {
+ set ::preupdate_count
+} {2}
+
+}
+
finish_test
diff --git a/test/json101.test b/test/json101.test
index e22902f86..7582d14a6 100644
--- a/test/json101.test
+++ b/test/json101.test
@@ -892,15 +892,15 @@ do_execsql_test json101-13.100 {
INSERT INTO t2(id,json) VALUES(4,'{"value":4}');
INSERT INTO t2(id,json) VALUES(5,'{"value":5}');
INSERT INTO t2(id,json) VALUES(6,'{"value":6}');
- SELECT * FROM t1 CROSS JOIN t2
+ SELECT *, 'NL' FROM t1 CROSS JOIN t2
WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z
WHERE Z.value==t2.id);
-} {1 {{"items":[3,5]}} 3 {{"value":3}} 1 {{"items":[3,5]}} 5 {{"value":5}}}
+} {1 {{"items":[3,5]}} 3 {{"value":3}} NL 1 {{"items":[3,5]}} 5 {{"value":5}} NL}
do_execsql_test json101-13.110 {
- SELECT * FROM t2 CROSS JOIN t1
+ SELECT *, 'NL' FROM t2 CROSS JOIN t1
WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z
WHERE Z.value==t2.id);
-} {3 {{"value":3}} 1 {{"items":[3,5]}} 5 {{"value":5}} 1 {{"items":[3,5]}}}
+} {3 {{"value":3}} 1 {{"items":[3,5]}} NL 5 {{"value":5}} 1 {{"items":[3,5]}} NL}
# 2018-05-16
# Incorrect fullkey output from json_each()
diff --git a/test/notnull2.test b/test/notnull2.test
index 09161efbd..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
)
-} +8000 {0}
+} 4000 {0}
#-------------------------------------------------------------------------
reset_db
diff --git a/test/parser1.test b/test/parser1.test
index ad95b4909..b8d3d8b42 100644
--- a/test/parser1.test
+++ b/test/parser1.test
@@ -100,4 +100,26 @@ do_execsql_test parser1-3.1 {
PRAGMA foreign_key_list(t301);
} {0 0 t300 c2 id RESTRICT CASCADE NONE 1 0 t300 c1 id RESTRICT CASCADE NONE}
+# 2025-07-01 https://sqlite.org/forum/forumpost/f4878de3e7dd4764
+# Do not allow parse-time optimizations to omit aggregate functions,
+# because doing so can change the meaning of the query.
+#
+unset -nocomplain zero
+set zero [expr {0+0}]
+do_execsql_test parser1-4.1 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(x);
+ SELECT max(x) AND $zero FROM t1;
+} 0
+do_execsql_test parser1-4.2 {
+ SELECT max(x) AND 0 FROM t1;
+} 0
+do_execsql_test parser1-4.3 {
+ SELECT max(x) IN () FROM t1;
+} 0
+do_execsql_test parser1-4.4 {
+ SELECT max(x) NOT IN () FROM t1;
+} 1
+
+
finish_test
diff --git a/test/rowvalue.test b/test/rowvalue.test
index e2688e903..387780c45 100644
--- a/test/rowvalue.test
+++ b/test/rowvalue.test
@@ -788,6 +788,9 @@ do_execsql_test 33.3 {
# INTEGER PRIMARY KEY, and the columns that UNIQUE constraint are
# used in a rowvalue-IN operator constraint.
#
+# 2025-07-07 Discovered that the original fix was incomplete and
+# new tests added. See tag-20250707-01 in the code.
+#
reset_db
do_execsql_test 34.1 {
CREATE TABLE items (
@@ -804,5 +807,39 @@ do_execsql_test 34.1 {
WHERE (Id, Item) IN (SELECT Id, Item FROM items);
SELECT Id, Item, test FROM items ORDER BY id;
} {1 2 ok 2 2 ok 3 3 ok 4 5 ok}
+db null NULL
+do_execsql_test 34.2 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d);
+ CREATE INDEX idx ON t1(b,a);
+ INSERT INTO t1(a,b) VALUES (1, 22);
+ SELECT * FROM t1 INDEXED BY idx WHERE (b,a) IN (SELECT b,a FROM t1);
+} {1 22 NULL NULL}
+do_execsql_test 34.3 {
+ DROP TABLE t1;
+ CREATE TABLE t1(a, b, c, d);
+ CREATE INDEX idx ON t1(b,a,a);
+ INSERT INTO t1(a,b) VALUES (1, 22);
+ SELECT * FROM t1 INDEXED BY idx WHERE (b,a) IN (SELECT b,a FROM t1);
+} {1 22 NULL NULL}
+do_execsql_test 34.4 {
+ DROP TABLE t1;
+ CREATE TABLE t1(id INTEGER PRIMARY KEY, a INT);
+ CREATE INDEX t1a ON t1(a,id); -- index includes PRIMARY KEY
+ CREATE TABLE t2(id INTEGER PRIMARY KEY);
+ WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<100)
+ INSERT INTO t1(id,a) SELECT n, 777 FROM c;
+ INSERT INTO t2 SELECT id FROM t1;
+ SELECT *
+ FROM t1 JOIN t2 USING(id)
+ WHERE t1.a=777 AND t2.id>999
+ ORDER BY t1.id;
+} {}
+do_execsql_test 34.5 {
+ EXPLAIN QUERY PLAN
+ SELECT *
+ FROM t1 JOIN t2 USING(id)
+ WHERE t1.a=777 AND t2.id>999
+ ORDER BY t1.id;
+} {/SEARCH t1 USING COVERING INDEX t1a .a=. AND id>../}
finish_test
diff --git a/test/speedtest1.c b/test/speedtest1.c
index 10cd30f1c..968f2cb00 100644
--- a/test/speedtest1.c
+++ b/test/speedtest1.c
@@ -159,6 +159,12 @@ static void fatal_error(const char *zMsg, ...){
va_start(ap, zMsg);
vfprintf(stderr, zMsg, ap);
va_end(ap);
+#ifdef SQLITE_SPEEDTEST1_WASM
+ /* Emscripten complains when exit() is called and anything is left
+ in the I/O buffers. */
+ fflush(stdout);
+ fflush(stderr);
+#endif
exit(1);
}
@@ -2994,7 +3000,13 @@ int main(int argc, char **argv){
/* "mix1" is a macro testset: */
static char zMix1Tests[] =
- "main,orm/25,cte/20,json,fp/3,parsenumber/25,rtree/10,star,app";
+ "main,orm/25,cte/20,json,fp/3,parsenumber/25,rtree/10,star"
+#if !defined(SQLITE_SPEEDTEST1_WASM)
+ ",app"
+ /* This test misbehaves in WASM builds: sqlite3_open_v2() is
+ failing to find the db file for reasons not yet understood. */
+#endif
+ ;
#ifdef SQLITE_SPEEDTEST1_WASM
/* Resetting all state is important for the WASM build, which may
diff --git a/test/testrunner.tcl b/test/testrunner.tcl
index 0c6982f42..52bc0f34b 100755
--- a/test/testrunner.tcl
+++ b/test/testrunner.tcl
@@ -7,6 +7,18 @@ set testdir [file normalize [file dirname $argv0]]
set saved $argv
set argv [list]
source [file join $testdir testrunner_data.tcl]
+
+# Estimated amount of work required by displaytype, relative to 'tcl'
+#
+set estwork(tcl) 1
+set estwork(fuzz) 22
+set estwork(bld) 66
+set estwork(make) 102
+
+set estworkfile [file join $testdir testrunner_estwork.tcl]
+if {[file readable $estworkfile]} {
+ source $estworkfile
+}
source [file join $testdir permutations.test]
set argv $saved
cd $dir
@@ -92,6 +104,7 @@ Usage:
$a0 script ?-msvc? CONFIG
$a0 status ?-d SECS? ?--cls?
$a0 halt
+ $a0 estwork
where SWITCHES are:
--buildonly Build test exes but do not run tests
@@ -341,6 +354,7 @@ set TRG(schema) {
endtime INTEGER, -- End time
span INTEGER, -- Total run-time in milliseconds
estwork INTEGER, -- Estimated amount of work
+ estkey TEXT, -- Key used to compute estwork
state TEXT CHECK( state IN ('','ready','running','done','failed','omit','halt') ),
ntest INT, -- Number of test cases run
nerr INT, -- Number of errors reported
@@ -359,13 +373,6 @@ set TRG(schema) {
}
#-------------------------------------------------------------------------
-# Estimated amount of work required by displaytype, relative to 'tcl'
-#
-set estwork(tcl) 1
-set estwork(fuzz) 11
-set estwork(bld) 56
-set estwork(make) 97
-
#--------------------------------------------------------------------------
# Check if this script is being invoked to run a single file. If so,
# run it.
@@ -453,6 +460,59 @@ if {[llength $argv]==1
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
+# Check if this is the "estwork" command:
+#
+# Generate (on standard output) a set of estwork() values based on the lastest
+# test case, that can be used to replace the test/testrunner_estwork.tcl file.
+#
+if {[llength $argv]==1
+ && [string compare -nocase estwork [lindex $argv 0]]==0
+} {
+ sqlite3 mydb $TRG(dbname)
+ set njob [mydb one {SELECT count(*) FROM jobs WHERE state='done'}]
+ if {$njob<1000} {
+ puts "Too few completed jobs to do a work estimate."
+ puts "Have $njob but not need at least 1000."
+ mydb close
+ exit 1
+ }
+ set badjobs [mydb one {SELECT count(*) FROM jobs WHERE state<>'done'}]
+ if {$badjobs} {
+ puts "Database contains $badjobs incomplete jobs."
+ mydb close
+ exit 1
+ }
+ set half [mydb one {SELECT count(*)/2 FROM jobs WHERE displaytype='tcl'}]
+ set scale [mydb one {SELECT span FROM jobs WHERE displaytype='tcl'
+ ORDER BY span LIMIT 1 OFFSET $half}]
+ mydb eval {
+ SELECT estkey, CAST(avg(span)/$scale AS INT) AS cost
+ FROM jobs
+ GROUP BY estkey
+ HAVING cost>=2
+ } {
+ set estwork($estkey) $cost
+ }
+ set avgtcl [mydb one {SELECT avg(span) FROM jobs WHERE displaytype='tcl'}]
+ set estwork(tcl) 1
+ foreach type {bld fuzz make} {
+ set avg [mydb one {SELECT avg(span) FROM jobs WHERE displaytype=$type}]
+ if {$avg!=""} {
+ set estwork($type) [expr {int($avg/$avgtcl)}]
+ }
+ }
+ mydb close
+ puts "# Estimated relative cost of various jobs, based on the \"estkey\" field."
+ puts "# Computed by the \"test/testrunner.tcl estwork\" command."
+ puts "#"
+ foreach key [lsort [array names estwork]] {
+ puts "set [list estwork($key)] $estwork($key)"
+ }
+ exit
+}
+#--------------------------------------------------------------------------
+
+#--------------------------------------------------------------------------
# Check if this is the "help" command:
#
if {[string compare -nocase help [lindex $argv 0]]==0} {
@@ -562,8 +622,8 @@ proc show_status {db cls} {
set srcdir [file dirname [file dirname $TRG(info_script)]]
set line "Running: $S(running) (max: $nJob)"
- if {$S(running)>0 && $fin>10} {
- set tmleft [expr {($tm/$fin)*($totalw-$fin)}]
+ if {$S(running)>0 && [set pct [expr {int(($fin*100.0)/$totalw)}]]>=4} {
+ set tmleft [expr {($tm/double($fin))*($totalw-$fin)}]
if {$tmleft<0.02*$tm} {
set tmleft [expr {$tm*0.02}]
}
@@ -571,6 +631,7 @@ proc show_status {db cls} {
if {[string length $line]+[string length $etc]<80} {
append line $etc
}
+ # append line " $pct%"
}
puts [format %-79.79s $line]
if {$S(running)>0} {
@@ -985,12 +1046,31 @@ proc add_job {args} {
set state ""
if {$A(-depid)==""} { set state ready }
set type $A(-displaytype)
- set ew $estwork($type)
+ set displayname $A(-displayname)
+ switch $type {
+ tcl {
+ set ek [file tail [lindex $displayname end]]
+ }
+ bld {
+ set ek [lindex $displayname end]
+ }
+ fuzz {
+ set ek [lrange $displayname 1 2]
+ }
+ make {
+ set ek [lindex $displayname end]
+ }
+ }
+ if {[info exists estwork($ek)]} {
+ set ew $estwork($ek)
+ } else {
+ set ew $estwork($type)
+ }
trdb eval {
INSERT INTO jobs(
- displaytype, displayname, build, dirname, cmd, depid, priority, estwork,
- state
+ displaytype, displayname, build, dirname, cmd, depid, priority,
+ estwork, estkey, state
) VALUES (
$type,
$A(-displayname),
@@ -1000,6 +1080,7 @@ proc add_job {args} {
$A(-depid),
$A(-priority),
$ew,
+ $ek,
$state
)
}
@@ -1586,12 +1667,13 @@ proc progress_report {} {
}
}
set report "[elapsetime $tmms] [join $text { }]"
- if {$wdone>0} {
- set tmleft [expr {($tmms/$wdone)*($wtotal-$wdone)}]
- set etc " ETC [elapsetime $tmleft]"
+ if {$wdone>0 && [set pct [expr {int(($wdone*100.0)/$wtotal)}]]>=4} {
+ set tmleft [expr {($tmms/double($wdone))*($wtotal-$wdone)}]
+ set etc " ETC [elapsetime $tmleft]"
if {[string length $report]+[string length $etc]<80} {
append report $etc
}
+ # append report " $pct%"
}
puts -nonewline [format %-79.79s $report]\r
flush stdout
diff --git a/test/testrunner_estwork.tcl b/test/testrunner_estwork.tcl
new file mode 100644
index 000000000..c139394a5
--- /dev/null
+++ b/test/testrunner_estwork.tcl
@@ -0,0 +1,488 @@
+# Estimated relative cost of various jobs, based on the "estkey" field.
+# Computed by the "test/testrunner.tcl estwork" command.
+#
+set estwork((fuzzcheck)) 1291
+set estwork((fuzzcheck-asan)) 2427
+set estwork((fuzzcheck-ubsan)) 2749
+set estwork((sessionfuzz)) 1276
+set estwork((sqlite3)) 1281
+set estwork((testfixture)) 1546
+set estwork(aggerror.test) 5
+set estwork(alter.test) 20
+set estwork(alter3.test) 2
+set estwork(alterauth.test) 5
+set estwork(altercol.test) 5
+set estwork(alterdropcol.test) 17
+set estwork(alterlegacy.test) 2
+set estwork(altertab.test) 34
+set estwork(altertab2.test) 2
+set estwork(altertab3.test) 12
+set estwork(amatch1.test) 40
+set estwork(atof1.test) 146
+set estwork(atomic2.test) 2
+set estwork(auth.test) 2
+set estwork(autoindex1.test) 5
+set estwork(autoindex3.test) 5
+set estwork(autovacuum.test) 15
+set estwork(avfs.test) 16
+set estwork(avtrans.test) 285
+set estwork(backup2.test) 15
+set estwork(bestindex4.test) 12
+set estwork(bigrow.test) 5
+set estwork(bitvec.test) 94
+set estwork(bld) 99
+set estwork(blob.test) 2
+set estwork(boundary1.test) 8
+set estwork(boundary2.test) 16
+set estwork(boundary3.test) 14
+set estwork(btree01.test) 20
+set estwork(busy.test) 2
+set estwork(busy2.test) 288
+set estwork(capi3.test) 5
+set estwork(capi3b.test) 6
+set estwork(capi3c.test) 5
+set estwork(capi3d.test) 8
+set estwork(capi3e.test) 20
+set estwork(changes.test) 11
+set estwork(chunksize.test) 5
+set estwork(closure01.test) 63
+set estwork(collate5.test) 2
+set estwork(conflict.test) 8
+set estwork(conflict2.test) 4
+set estwork(conflict3.test) 8
+set estwork(corrupt2.test) 2
+set estwork(corrupt4.test) 91
+set estwork(corrupt7.test) 5
+set estwork(corrupt8.test) 3
+set estwork(corruptB.test) 4
+set estwork(corruptF.test) 6
+set estwork(count.test) 18
+set estwork(crash8.test) 14
+set estwork(createtab.test) 3
+set estwork(csv01.test) 7
+set estwork(cursorhint.test) 4
+set estwork(date.test) 17
+set estwork(date4.test) 64
+set estwork(date5.test) 3
+set estwork(dbdata.test) 77
+set estwork(dbfuzz001.test) 2
+set estwork(dbstatus.test) 4
+set estwork(decimal.test) 2
+set estwork(delete.test) 4
+set estwork(diskfull.test) 34
+set estwork(e_blobbytes.test) 11
+set estwork(e_createtable.test) 14
+set estwork(e_delete.test) 3
+set estwork(e_droptrigger.test) 6
+set estwork(e_dropview.test) 2
+set estwork(e_expr.test) 251
+set estwork(e_select.test) 5
+set estwork(e_select2.test) 5
+set estwork(e_vacuum.test) 6
+set estwork(e_walauto.test) 51
+set estwork(e_walckpt.test) 6
+set estwork(enc.test) 2
+set estwork(enc3.test) 11
+set estwork(enc4.test) 3
+set estwork(eval.test) 2
+set estwork(exists.test) 4
+set estwork(existsexpr.test) 44
+set estwork(expert1.test) 14
+set estwork(expr.test) 5
+set estwork(filectrl.test) 5
+set estwork(filefmt.test) 6
+set estwork(fkey2.test) 9
+set estwork(fpconv1.test) 40
+set estwork(fts3aa.test) 18
+set estwork(fts3ad.test) 10
+set estwork(fts3ag.test) 6
+set estwork(fts3aj.test) 8
+set estwork(fts3am.test) 5
+set estwork(fts3auto.test) 29
+set estwork(fts3b.test) 41
+set estwork(fts3c.test) 22
+set estwork(fts3conf.test) 3
+set estwork(fts3corrupt4.test) 7
+set estwork(fts3corrupt5.test) 8
+set estwork(fts3corrupt6.test) 13
+set estwork(fts3d.test) 2
+set estwork(fts3defer2.test) 2
+set estwork(fts3expr.test) 10
+set estwork(fts3expr2.test) 8
+set estwork(fts3expr3.test) 145
+set estwork(fts3f.test) 9
+set estwork(fts3first.test) 3
+set estwork(fts3matchinfo.test) 4
+set estwork(fts3misc.test) 54
+set estwork(fts3prefix.test) 4
+set estwork(fts3prefix2.test) 8
+set estwork(fts3query.test) 23
+set estwork(fts3varint.test) 2
+set estwork(fts4aa.test) 34
+set estwork(fts4content.test) 5
+set estwork(fts4incr.test) 19
+set estwork(fts4noti.test) 3
+set estwork(fts4opt.test) 129
+set estwork(fts4unicode.test) 45
+set estwork(fts5aa.test) 442
+set estwork(fts5ab.test) 54
+set estwork(fts5ad.test) 33
+set estwork(fts5ae.test) 3
+set estwork(fts5af.test) 13
+set estwork(fts5ag.test) 13
+set estwork(fts5ah.test) 265
+set estwork(fts5al.test) 2
+set estwork(fts5auto.test) 92
+set estwork(fts5connect.test) 5
+set estwork(fts5content.test) 9
+set estwork(fts5contentless.test) 23
+set estwork(fts5contentless2.test) 189
+set estwork(fts5contentless4.test) 234
+set estwork(fts5contentless5.test) 9
+set estwork(fts5delete.test) 62
+set estwork(fts5doclist.test) 11
+set estwork(fts5expr.test) 10
+set estwork(fts5full.test) 42
+set estwork(fts5hash.test) 17
+set estwork(fts5integrity.test) 84
+set estwork(fts5interrupt.test) 16
+set estwork(fts5locale.test) 5
+set estwork(fts5matchinfo.test) 11
+set estwork(fts5merge.test) 29
+set estwork(fts5merge2.test) 9
+set estwork(fts5misc.test) 8
+set estwork(fts5multiclient.test) 2
+set estwork(fts5optimize.test) 13
+set estwork(fts5optimize2.test) 737
+set estwork(fts5optimize3.test) 280
+set estwork(fts5origintext.test) 144
+set estwork(fts5origintext3.test) 8
+set estwork(fts5origintext4.test) 30
+set estwork(fts5origintext5.test) 462
+set estwork(fts5origintext6.test) 55
+set estwork(fts5porter.test) 57
+set estwork(fts5query.test) 8
+set estwork(fts5restart.test) 10
+set estwork(fts5rowid.test) 35
+set estwork(fts5secure.test) 61
+set estwork(fts5secure3.test) 796
+set estwork(fts5secure4.test) 141
+set estwork(fts5secure5.test) 29
+set estwork(fts5secure6.test) 33
+set estwork(fts5secure7.test) 680
+set estwork(fts5simple.test) 7
+set estwork(fts5simple2.test) 2
+set estwork(fts5synonym.test) 13
+set estwork(fts5synonym2.test) 384
+set estwork(fts5tok2.test) 92
+set estwork(fts5tokenizer.test) 2
+set estwork(fts5tokenizer3.test) 8
+set estwork(fts5trigram.test) 2
+set estwork(fts5unicode2.test) 28
+set estwork(fts5unicode3.test) 72
+set estwork(fts5unindexed.test) 7
+set estwork(fts5update.test) 161
+set estwork(fts5update2.test) 11
+set estwork(fts5vocab.test) 11
+set estwork(fts5vocab2.test) 15
+set estwork(func.test) 36
+set estwork(fuzz) 68
+set estwork(fuzz-oss1.test) 11
+set {estwork(fuzzcheck --slice)} 499
+set {estwork(fuzzcheck fuzzdata1.db)} 301
+set {estwork(fuzzcheck fuzzdata2.db)} 275
+set {estwork(fuzzcheck fuzzdata3.db)} 72
+set {estwork(fuzzcheck fuzzdata4.db)} 30
+set {estwork(fuzzcheck fuzzdata5.db)} 299
+set {estwork(fuzzcheck fuzzdata6.db)} 91
+set {estwork(fuzzcheck fuzzdata7.db)} 201
+set {estwork(fuzzcheck fuzzdata8.db)} 331
+set {estwork(fuzzcheck-asan --slice)} 2037
+set {estwork(fuzzcheck-asan fuzzdata1.db)} 3112
+set {estwork(fuzzcheck-asan fuzzdata2.db)} 8753
+set {estwork(fuzzcheck-asan fuzzdata3.db)} 459
+set {estwork(fuzzcheck-asan fuzzdata4.db)} 123
+set {estwork(fuzzcheck-asan fuzzdata5.db)} 1178
+set {estwork(fuzzcheck-asan fuzzdata6.db)} 1356
+set {estwork(fuzzcheck-asan fuzzdata7.db)} 938
+set {estwork(fuzzcheck-asan fuzzdata8.db)} 1451
+set {estwork(fuzzcheck-ubsan --slice)} 1729
+set {estwork(fuzzcheck-ubsan fuzzdata1.db)} 1180
+set {estwork(fuzzcheck-ubsan fuzzdata2.db)} 876
+set {estwork(fuzzcheck-ubsan fuzzdata3.db)} 306
+set {estwork(fuzzcheck-ubsan fuzzdata4.db)} 95
+set {estwork(fuzzcheck-ubsan fuzzdata5.db)} 1356
+set {estwork(fuzzcheck-ubsan fuzzdata6.db)} 333
+set {estwork(fuzzcheck-ubsan fuzzdata7.db)} 883
+set {estwork(fuzzcheck-ubsan fuzzdata8.db)} 1124
+set estwork(fuzzer1.test) 6
+set estwork(gencol1.test) 3
+set estwork(hook.test) 2
+set estwork(in4.test) 4
+set estwork(in7.test) 6
+set estwork(incrblob2.test) 2
+set estwork(incrblob3.test) 5
+set estwork(incrvacuum.test) 10
+set estwork(incrvacuum2.test) 17
+set estwork(incrvacuum3.test) 17
+set estwork(index.test) 3
+set estwork(index2.test) 8
+set estwork(index4.test) 33
+set estwork(index5.test) 99
+set estwork(index6.test) 2
+set estwork(indexexpr1.test) 2
+set estwork(insert3.test) 5
+set estwork(insert4.test) 2
+set estwork(intarray.test) 8
+set estwork(intck1.test) 34
+set estwork(intck2.test) 37
+set estwork(interrupt.test) 28
+set estwork(io.test) 3
+set estwork(join.test) 3
+set estwork(join3.test) 6
+set estwork(join5.test) 20
+set estwork(join7.test) 2
+set estwork(join8.test) 3
+set estwork(join9.test) 2
+set estwork(joinA.test) 11
+set estwork(joinB.test) 7
+set estwork(joinC.test) 6
+set estwork(joinD.test) 168
+set estwork(json103.test) 6
+set estwork(json106.test) 690
+set estwork(keyword1.test) 3
+set estwork(like2.test) 2
+set estwork(like3.test) 2
+set estwork(limit.test) 3
+set estwork(literal.test) 6
+set estwork(lock.test) 39
+set estwork(lock4.test) 2
+set estwork(lock5.test) 4
+set estwork(make) 102
+set estwork(manydb.test) 12
+set estwork(mem5.test) 2
+set estwork(memdb.test) 9
+set estwork(memdb1.test) 2
+set estwork(memjournal2.test) 164
+set estwork(memsubsys1.test) 8
+set estwork(misc1.test) 6
+set estwork(misc2.test) 6
+set estwork(misc5.test) 11
+set estwork(misc8.test) 5
+set estwork(mmap1.test) 8
+set estwork(mmap2.test) 10
+set estwork(mmapwarm.test) 2
+set estwork(multiplex.test) 30
+set estwork(multiplex2.test) 7
+set estwork(nan.test) 2
+set estwork(notify3.test) 5
+set estwork(orderby1.test) 11
+set estwork(orderby2.test) 2
+set estwork(orderby5.test) 11
+set estwork(orderby6.test) 6
+set estwork(orderby8.test) 20
+set estwork(orderbyA.test) 2
+set estwork(oserror.test) 7
+set estwork(ovfl.test) 11
+set estwork(pager1.test) 81
+set estwork(pager2.test) 129
+set estwork(pagesize.test) 3
+set estwork(percentile.test) 86
+set estwork(pragma.test) 4
+set estwork(pragma4.test) 18
+set estwork(printf.test) 21
+set estwork(printf2.test) 5
+set estwork(quota.test) 14
+set estwork(randexpr1.test) 35
+set estwork(rbu10.test) 16
+set estwork(rbu13.test) 5
+set estwork(rbuexlock.test) 5
+set estwork(rbumisc.test) 2
+set estwork(rbutemplimit.test) 2
+set estwork(readonly.test) 95
+set estwork(recover.test) 3
+set estwork(recover1.test) 7
+set estwork(recovercorrupt3.test) 5
+set estwork(recoverold.test) 7
+set estwork(recoverpgsz.test) 6
+set estwork(recoverrowid.test) 3
+set estwork(returning1.test) 6
+set estwork(rollback2.test) 2
+set estwork(round1.test) 261
+set estwork(rowhash.test) 85
+set estwork(rowid.test) 3
+set estwork(rowvalue.test) 2
+set estwork(rowvalue2.test) 38
+set estwork(rowvalue4.test) 2
+set estwork(rowvalueA.test) 2
+set estwork(rowvaluevtab.test) 2
+set estwork(rtree1.test) 4
+set estwork(rtree2.test) 758
+set estwork(rtree6.test) 6
+set estwork(rtree8.test) 15
+set estwork(rtree9.test) 15
+set estwork(rtreeA.test) 7
+set estwork(rtreeB.test) 7
+set estwork(rtreeE.test) 47
+set estwork(rtreeH.test) 24
+set estwork(rtreecheck.test) 2
+set estwork(rtreedoc.test) 8
+set estwork(rtreedoc3.test) 76
+set estwork(savepoint.test) 10
+set estwork(savepoint2.test) 57
+set estwork(schema2.test) 15
+set estwork(schema3.test) 2
+set estwork(schema5.test) 5
+set estwork(select1.test) 2
+set estwork(select2.test) 17
+set estwork(select3.test) 2
+set estwork(selectA.test) 2
+set estwork(selectB.test) 2
+set estwork(selectG.test) 30
+set estwork(session1.test) 5
+set estwork(session2.test) 33
+set estwork(session5.test) 63
+set estwork(session9.test) 3
+set estwork(sessionG.test) 60
+set estwork(sessionH.test) 18
+set estwork(sessionalter.test) 3
+set estwork(sessionat.test) 2
+set estwork(sessionblob.test) 3
+set {estwork(sessionfuzz sessionfuzz-data1.db)} 5
+set estwork(sessioninvert.test) 2
+set estwork(sessionnoop.test) 2
+set estwork(sessionnoop2.test) 8
+set estwork(sessionrebase.test) 8
+set estwork(shared.test) 7
+set estwork(sharedA.test) 48
+set estwork(shell1.test) 36
+set estwork(shell2.test) 11
+set estwork(shell3.test) 3
+set estwork(shell4.test) 2
+set estwork(shell5.test) 16
+set estwork(shell6.test) 3
+set estwork(shell8.test) 104
+set estwork(shell9.test) 3
+set estwork(shellA.test) 2
+set estwork(shmlock.test) 27
+set estwork(sidedelete.test) 10
+set estwork(skipscan1.test) 7
+set estwork(skipscan2.test) 5
+set estwork(sort.test) 38
+set estwork(sort2.test) 540
+set estwork(sort5.test) 16
+set estwork(spellfix.test) 5
+set estwork(spellfix2.test) 5
+set estwork(spellfix4.test) 11
+set estwork(starschema1.test) 2
+set estwork(strict1.test) 5
+set estwork(swarmvtab.test) 110
+set estwork(swarmvtab3.test) 14
+set estwork(syscall.test) 4
+set estwork(table.test) 62
+set estwork(tableapi.test) 8
+set estwork(tcl) 1
+set estwork(tclsqlite.test) 29
+set estwork(temptable2.test) 274
+set estwork(thread3.test) 21
+set estwork(timediff1.test) 5
+set estwork(tkt-2d1a5c67d.test) 6
+set estwork(tkt-38cb5df375.test) 2
+set estwork(tkt-4dd95f6943.test) 2
+set estwork(tkt-5e10420e8d.test) 5
+set estwork(tkt-6bfb98dfc0.test) 5
+set estwork(tkt-80e031a00f.test) 25
+set estwork(tkt-9d68c883.test) 3
+set estwork(tkt-9f2eb3abac.test) 5
+set estwork(tkt-b1d3a2e531.test) 5
+set estwork(tkt-b72787b1.test) 5
+set estwork(tkt-b75a9ca6b0.test) 8
+set estwork(tkt-d11f09d36e.test) 9
+set estwork(tkt-fc62af4523.test) 10
+set estwork(tkt1435.test) 7
+set estwork(tkt1644.test) 5
+set estwork(tkt1667.test) 20
+set estwork(tkt1873.test) 2
+set estwork(tkt2192.test) 3
+set estwork(tkt2285.test) 19
+set estwork(tkt2332.test) 2
+set estwork(tkt2409.test) 24
+set estwork(tkt3334.test) 5
+set estwork(tkt3357.test) 10
+set estwork(tkt3630.test) 2
+set estwork(tkt3832.test) 8
+set estwork(tkt3838.test) 5
+set estwork(tkt3918.test) 3
+set estwork(tkt4018.test) 48
+set estwork(tokenize.test) 9
+set estwork(tpch01.test) 6
+set estwork(trans.test) 258
+set estwork(trigger2.test) 15
+set estwork(trigger5.test) 2
+set estwork(trigger8.test) 30
+set estwork(triggerA.test) 36
+set estwork(triggerB.test) 4
+set estwork(triggerC.test) 67
+set estwork(triggerD.test) 3
+set estwork(types.test) 2
+set estwork(types2.test) 2
+set estwork(unionall2.test) 9
+set estwork(unionvtab.test) 2
+set estwork(update.test) 7
+set estwork(upsert3.test) 6
+set estwork(upsert5.test) 5
+set estwork(vacuum.test) 2
+set estwork(vacuum5.test) 3
+set estwork(vacuum6.test) 174
+set estwork(vacuummem.test) 132
+set estwork(varint.test) 8
+set estwork(view.test) 2
+set estwork(vtab1.test) 13
+set estwork(vtab6.test) 12
+set estwork(vtabC.test) 33
+set estwork(vtabD.test) 56
+set estwork(vtab_alter.test) 6
+set estwork(wal.test) 38
+set estwork(wal2.test) 7
+set estwork(wal3.test) 196
+set estwork(wal4.test) 70
+set estwork(wal5.test) 22
+set estwork(wal64k.test) 20
+set estwork(wal7.test) 3
+set estwork(wal9.test) 24
+set estwork(walbak.test) 2
+set estwork(walcksum.test) 3
+set estwork(walcrash4.test) 59
+set estwork(waloverwrite.test) 3
+set estwork(walpersist.test) 6
+set estwork(walprotocol2.test) 2
+set estwork(walro2.test) 11
+set estwork(walsetlk.test) 1610
+set estwork(walsetlk3.test) 3
+set estwork(walsetlk_recover.test) 193
+set estwork(walshared.test) 5
+set estwork(walvfs.test) 476
+set estwork(where.test) 24
+set estwork(where3.test) 4
+set estwork(where6.test) 2
+set estwork(where7.test) 20
+set estwork(where8.test) 95
+set estwork(where9.test) 16
+set estwork(whereB.test) 5
+set estwork(whereE.test) 6
+set estwork(whereN.test) 2
+set estwork(window1.test) 5
+set estwork(window2.test) 8
+set estwork(window3.test) 89
+set estwork(window4.test) 3
+set estwork(window5.test) 2
+set estwork(window8.test) 33
+set estwork(windowA.test) 5
+set estwork(windowD.test) 6
+set estwork(with1.test) 114
+set estwork(with2.test) 4
+set estwork(with5.test) 2
+set estwork(withM.test) 4
+set estwork(without_rowid1.test) 2
+set estwork(without_rowid3.test) 9
+set estwork(without_rowid4.test) 6
diff --git a/tool/mktoolzip.tcl b/tool/mktoolzip.tcl
index c22318441..041bf28cd 100644
--- a/tool/mktoolzip.tcl
+++ b/tool/mktoolzip.tcl
@@ -12,9 +12,27 @@
# sqlite3_analyzer -- Space analyzer
# sqlite3_rsync -- Remote db sync
#
+# On Windows, add:
+#
+# sqlite3.def
+# sqlite3.dll
+#
+# Add the --snapshot option to generate a snapshot ZIP archive instead of
+# a release ZIP archive.
+#
+set bSnapshot 0
+for {set i 0} {$i<[llength $argv]} {incr i} {
+ set a [lindex $argv $i]
+ if {$a eq "-snapshot" || $a eq "--snapshot"} {
+ set bSnapshot 1
+ continue
+ }
+ puts stderr "unknown argument: $a"
+ exit 1
+}
switch $tcl_platform(os) {
{Windows NT} {
- set OS win32
+ set OS win
set EXE .exe
}
Linux {
@@ -49,13 +67,39 @@ switch $tcl_platform(machine) {
set ARCH unk
}
}
-set in [open [file join [file dirname [file dirname [info script]]] VERSION]]
-set vers [read $in]
-close $in
-scan $vers %d.%d.%d v1 v2 v3
-set v2 [format 3%02d%02d00 $v2 $v3]
+if {$bSnapshot} {
+ set in [open [file join [file dirname [file dirname [info script]]] manifest]]
+ set manifest [read $in]
+ close $in
+ regexp {\nD (.{16})} $manifest all date
+ regsub -all {[-:T]} $date {} v2
+} else {
+ set in [open [file join [file dirname [file dirname [info script]]] VERSION]]
+ set vers [read $in]
+ close $in
+ scan $vers %d.%d.%d v1 v2 v3
+ set v2 [format 3%02d%02d00 $v2 $v3]
+}
+
set name sqlite-tools-$OS-$ARCH-$v2.zip
-set toollist "sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE sqlite3_rsync$EXE"
-puts "zip $name {*}$toollist"
-exec zip $name {*}$toollist
-puts "$name: [file size $name] bytes"
+set filelist "sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE sqlite3_rsync$EXE"
+proc make_zip_archive {name filelist} {
+ file delete -force $name
+ puts "fossil test-filezip $name $filelist"
+ if {[catch {exec fossil test-filezip $name {*}$filelist}]} {
+ puts "^--- Unable. Trying again as:"
+ puts "zip $name $filelist"
+ file delete -force $name
+ exec zip $name {*}$filelist
+ }
+ puts "$name: [file size $name] bytes"
+}
+make_zip_archive $name $filelist
+
+# On Windows, also try to construct a DLL
+#
+if {$OS eq "win" && [file exists sqlite3.dll] && [file exists sqlite3.def]} {
+ set name sqlite-dll-win-$ARCH-$v2.zip
+ set filelist [list sqlite3.def sqlite3.dll]
+ make_zip_archive $name $filelist
+}