Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -3.42.0 +3.42.1 Index: configure ================================================================== --- configure +++ configure @@ -1,8 +1,8 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for sqlite 3.42.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.42.1. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # @@ -724,12 +724,12 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.42.0' -PACKAGE_STRING='sqlite 3.42.0' +PACKAGE_VERSION='3.42.1' +PACKAGE_STRING='sqlite 3.42.1' PACKAGE_BUGREPORT='' PACKAGE_URL='' # Factoring default headers for most tests. ac_includes_default="\ @@ -1468,11 +1468,11 @@ # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures sqlite 3.42.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.42.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. @@ -1533,11 +1533,11 @@ _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.42.0:";; + short | recursive ) echo "Configuration of sqlite 3.42.1:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options @@ -1663,11 +1663,11 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.42.0 +sqlite configure 3.42.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. @@ -2082,11 +2082,11 @@ } # ac_fn_c_check_header_mongrel cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by sqlite $as_me 3.42.0, which was +It was created by sqlite $as_me 3.42.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ _ACEOF @@ -12455,11 +12455,11 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by sqlite $as_me 3.42.0, which was +This file was extended by sqlite $as_me 3.42.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS @@ -12521,11 +12521,11 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.42.0 +sqlite config.status 3.42.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation Index: ext/fts5/fts5_index.c ================================================================== --- ext/fts5/fts5_index.c +++ ext/fts5/fts5_index.c @@ -2689,11 +2689,10 @@ if( res==0 ){ assert_nc( i2>i1 ); assert_nc( i2!=0 ); pRes->bTermEq = 1; if( p1->iRowid==p2->iRowid ){ - p1->bDel = p2->bDel; return i2; } res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : +1; } assert( res!=0 ); @@ -4709,11 +4708,10 @@ u8 *aIdx = 0; int bLastInDoclist = 0; int iIdx = 0; int iStart = 0; int iKeyOff = 0; - int iPrevKeyOff = 0; int iDelKeyOff = 0; /* Offset of deleted key, if any */ nIdx = nPg-iPgIdx; aIdx = sqlite3Fts5MallocZero(&p->rc, nIdx+16); if( p->rc ) return; @@ -4773,32 +4771,37 @@ iNextOff = pSeg->iLeafOffset + pSeg->nPos; } } iOff = iStart; - if( iNextOff>=iPgIdx ){ - int pgno = pSeg->iLeafPgno+1; - fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); - iNextOff = iPgIdx; - }else{ - /* Set bLastInDoclist to true if the entry being removed is the last - ** in its doclist. */ - for(iIdx=0, iKeyOff=0; iIdxbDel==0 ){ + if( iNextOff>=iPgIdx ){ + int pgno = pSeg->iLeafPgno+1; + fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); + iNextOff = iPgIdx; + }else{ + /* Set bLastInDoclist to true if the entry being removed is the last + ** in its doclist. */ + for(iIdx=0, iKeyOff=0; iIdxbDel ){ + iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta); + aPg[iOff++] = 0x01; + }else if( bLastInDoclist==0 ){ if( iNextOff!=iPgIdx ){ iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta); iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta); } }else if( @@ -4838,11 +4841,13 @@ }else{ if( iKey!=1 ){ iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix); } iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix); - if( nPrefix2>nPrefix ){ + if( nPrefix2>pSeg->term.n ){ + p->rc = FTS5_CORRUPT; + }else if( nPrefix2>nPrefix ){ memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix); iOff += (nPrefix2-nPrefix); } memmove(&aPg[iOff], &aPg[iNextOff], nSuffix2); iOff += nSuffix2; @@ -4891,30 +4896,28 @@ fts5DataRelease(pTerm); } } if( p->rc==SQLITE_OK ){ - const int nMove = nPg - iNextOff; - int nShift = 0; + const int nMove = nPg - iNextOff; /* Number of bytes to move */ + int nShift = iNextOff - iOff; /* Distance to move them */ + int iPrevKeyOut = 0; + int iKeyIn = 0; memmove(&aPg[iOff], &aPg[iNextOff], nMove); - iPgIdx -= (iNextOff - iOff); + iPgIdx -= nShift; nPg = iPgIdx; fts5PutU16(&aPg[2], iPgIdx); - nShift = iNextOff - iOff; - for(iIdx=0, iKeyOff=0, iPrevKeyOff=0; iIdxiOff ){ - iKeyOff -= nShift; - nShift = 0; - } - nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOff - iPrevKeyOff); - iPrevKeyOff = iKeyOff; + iKeyIn += iVal; + if( iKeyIn!=iDelKeyOff ){ + int iKeyOut = (iKeyIn - (iKeyIn>iOff ? nShift : 0)); + nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut); + iPrevKeyOut = iKeyOut; } } if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){ fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno); @@ -5089,14 +5092,20 @@ } if( (pBuf->n + pPgidx->n)>=pgsz ){ fts5WriteFlushLeaf(p, &writer); } }else{ - int bDummy; - int nPos; - int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); - nCopy += nPos; + int bDel = 0; + int nPos = 0; + int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDel); + if( bDel && bSecureDelete ){ + fts5BufferAppendVarint(&p->rc, pBuf, nPos*2); + iOff += nCopy; + nCopy = nPos; + }else{ + nCopy += nPos; + } if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ /* The entire poslist will fit on the current leaf. So copy ** it in one go. */ fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); }else{ Index: ext/fts5/test/fts5corrupt7.test ================================================================== --- ext/fts5/test/fts5corrupt7.test +++ ext/fts5/test/fts5corrupt7.test @@ -93,7 +93,36 @@ set {} {} } {} db close } + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + BEGIN; + INSERT INTO t1 VALUES('abc'); + INSERT INTO t1 VALUES('b d d d'); + COMMIT; + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); +} + +execsql_pp { + SELECT id, quote(block) FROM t1_data +} + +do_execsql_test 2.1 { + SELECT quote(block) FROM t1_data WHERE id > 10; +} {X'0000001A04306162630102020101620202020101640206030303040806'} + +do_execsql_test 2.2 { + UPDATE t1_data SET + block=X'0000001A04306162630102025501620202020101640206030303040806' + WHERE id>10 +} + +do_catchsql_test 2.3 { + DELETE FROM t1 WHERE rowid = 1 +} {1 {database disk image is malformed}} finish_test Index: ext/fts5/test/fts5secure.test ================================================================== --- ext/fts5/test/fts5secure.test +++ ext/fts5/test/fts5secure.test @@ -270,9 +270,22 @@ INSERT INTO t1(t1) VALUES('integrity-check'); } do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2 do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2 + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 6.0 { + CREATE VIRTUAL TABLE fts USING fts5(content); + INSERT INTO fts(fts, rank) VALUES ('secure-delete', 1); + INSERT INTO fts(rowid, content) VALUES + (3407, 'profile profile profile profile profile profile profile profile pull pulling pulling really'); + DELETE FROM fts WHERE rowid IS 3407; + INSERT INTO fts(fts) VALUES ('integrity-check'); +} + finish_test Index: ext/fts5/test/fts5secure6.test ================================================================== --- ext/fts5/test/fts5secure6.test +++ ext/fts5/test/fts5secure6.test @@ -16,11 +16,11 @@ db progress 1 progress_handler set ::PHC 0 proc progress_handler {args} { incr ::PHC - if {($::PHC % 100000)==0} breakpoint + # if {($::PHC % 100000)==0} breakpoint return 0 } proc setup {} { db eval { @@ -48,8 +48,106 @@ do_test 1.3 { expr $phc(1)*5 < $phc(2) } {1} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd) +} + +do_execsql_test 2.1 { + BEGIN; + INSERT INTO t1(rowid, x) VALUES(-100000, 'abc def ghi'); + INSERT INTO t1(rowid, x) VALUES(-99999, 'abc def ghi'); + INSERT INTO t1(rowid, x) VALUES(9223372036854775800, 'abc def ghi'); + COMMIT; +} + +do_execsql_test 2.2 { + SELECT rowid FROM t1('def') +} {-100000 -99999 9223372036854775800} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd) +} + +do_execsql_test 2.1 { + BEGIN; + INSERT INTO t1(rowid, x) + VALUES(51869, 'when whenever where weress what turn'), + (51871, 'to were'); + COMMIT; +} + +do_execsql_test 3.2 { + DELETE FROM t1 WHERE rowid=51871; + INSERT INTO t1(t1) VALUES('integrity-check'); +} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd) +} + +do_execsql_test 2.1 { + BEGIN; + INSERT INTO t1(rowid, x) VALUES(-100000, 'abc def ghi'); + INSERT INTO t1(rowid, x) VALUES(-99999, 'abc def ghi'); + INSERT INTO t1(rowid, x) VALUES(9223372036854775800, 'abc def ghi'); + COMMIT; +} + +do_execsql_test 2.2 { + SELECT rowid FROM t1('def') +} {-100000 -99999 9223372036854775800} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd) +} + +do_execsql_test 3.1 { + BEGIN; + INSERT INTO t1(rowid, x) + VALUES(51869, 'when whenever where weress what turn'), + (51871, 'to were'); + COMMIT; +} + +do_execsql_test 3.2 { + DELETE FROM t1 WHERE rowid=51871; + INSERT INTO t1(t1) VALUES('integrity-check'); +} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(rowid, x) VALUES(10, 'one two'); +} +do_execsql_test 4.1 { + UPDATE t1 SET x = 'one three' WHERE rowid=10; + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); +} +do_execsql_test 4.2 { + DELETE FROM t1 WHERE rowid=10; +} +do_execsql_test 4.3 { + INSERT INTO t1(t1) VALUES('integrity-check'); +} finish_test Index: ext/recover/recovercorrupt2.test ================================================================== --- ext/recover/recovercorrupt2.test +++ ext/recover/recovercorrupt2.test @@ -283,7 +283,189 @@ set R [sqlite3_recover_init db main test.db2] catch { $R run } list [catch { $R finish } msg] $msg } {0 {}} +reset_db +breakpoint +do_test 7.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 4108 pagesize 4096 filename x1.db +| page 1 offset 0 +| 0: 02 01 00 00 00 00 14 15 40 00 00 00 00 00 00 00 ........@....... +| 16: 33 3a 6d 65 6d 6f 72 79 3a 02 02 02 02 02 02 02 3:memory:....... +| 32: 02 02 02 02 02 02 12 02 02 02 63 6f 6c 6f 72 20 ..........color +| 48: 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 02 02 02 spac............ +| 64: 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f 0a 70 02 i.so6.......?.p. +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 01 00 00 ................ +| 96: 06 02 02 02 02 5f 02 02 02 2c 02 02 02 02 02 02 ....._...,...... +| 112: 02 02 02 02 02 02 02 02 02 12 02 02 02 63 6f 6c .............col +| 128: 6f 72 20 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 or spac......... +| 144: 02 02 02 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f ...i.so6.......? +| 160: 0a 70 02 02 02 02 02 02 02 02 02 02 02 02 02 02 .p.............. +| 176: 01 00 00 06 02 02 02 02 5f 02 02 02 2c 02 02 00 ........_...,... +| 192: 00 01 00 01 00 00 00 01 00 02 fe 00 00 03 00 01 ................ +| 208: 00 00 00 01 c5 04 00 00 00 01 00 01 00 00 00 01 ................ +| 224: 00 fa 02 00 00 00 03 00 01 00 00 00 81 00 04 00 ................ +| 240: 00 00 01 00 01 00 00 00 01 00 02 00 fe 00 03 00 ................ +| 256: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 272: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................ +| 288: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 304: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 320: 01 00 02 00 00 00 03 00 01 00 00 00 40 00 84 00 ............@... +| 336: 84 00 84 00 01 00 00 00 09 00 06 00 f5 00 01 00 ................ +| 352: 08 01 03 00 03 00 62 00 62 00 23 00 01 00 62 00 ......b.b.#...b. +| 368: 04 00 1e 00 62 00 62 00 62 00 01 00 00 00 0a 00 ....b.b.b....... +| 384: 01 00 03 00 01 00 03 00 04 00 02 00 01 00 01 00 ................ +| 400: 08 00 01 00 31 c6 00 03 00 0c 00 12 00 18 00 02 ....1........... +| 416: 00 05 00 08 00 02 00 06 00 08 00 02 00 07 00 08 ................ +| 432: 00 02 00 01 00 01 00 08 00 01 00 0c 00 03 00 16 ................ +| 448: 00 1c 00 22 00 01 00 03 00 05 00 06 00 07 00 02 ................ +| 464: 00 05 00 09 00 02 00 06 00 09 00 02 00 07 00 09 ................ +| 480: 00 00 00 00 01 00 05 00 00 00 01 00 01 00 00 00 ................ +| 496: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................ +| 512: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 528: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 544: 01 00 02 00 00 f6 03 00 00 02 00 00 01 00 04 00 ................ +| 560: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 576: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 592: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................ +| 608: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 624: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 640: 01 3d 02 00 00 00 03 00 06 00 00 00 01 00 01 00 .=.............. +| 656: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 672: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 688: 01 00 02 00 00 00 55 52 4c 52 65 71 75 65 73 74 ......URLRequest +| 704: 43 6f 6e 00 00 00 01 01 0e d4 00 04 00 00 00 01 Con............. +| 720: 0e f8 00 04 00 00 00 01 0f 1c 00 04 00 00 00 01 ................ +| 736: 0f 00 00 01 00 00 00 01 0f 86 00 01 00 00 00 01 ................ +| 752: 0f 84 00 01 00 00 00 01 00 00 01 0f c0 00 01 00 ................ +| 768: 00 00 01 0f e8 00 d6 0f 00 01 6f 00 02 0f d6 00 ..........o..... +| 784: 02 34 03 03 03 00 01 00 00 00 01 00 05 00 00 00 .4.............. +| 800: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................ +| 816: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................ +| 832: 02 00 00 00 03 00 01 00 10 00 01 00 04 00 00 00 ................ +| 848: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 ................ +| 864: 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 00 ...@............ +| 880: 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 00 ................ +| 896: 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f 00 .wOF2s@#p...p... +| 912: 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 00 ............C... +| 928: 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 00 ................ +| 1024: 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f 00 ................ +| 1040: 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 0f ..........p..... +| 1056: d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 00 ................ +| 1072: 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 04 .....?#p........ +| 1088: 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 01 .....@.......... +| 1104: 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 0f ................ +| 1120: c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f 00 ..............o. +| 1136: 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 00 ......1.+.*..... +| 1152: 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 0f ........+...2... +| 1168: c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 c0 ..... .......$.. +| 1184: 00 00 00 03 00 00 01 24 00 2a 06 e4 00 00 00 03 .......$.*...... +| 1200: 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 26 ...%.8.........& +| 1216: 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 7a .4.F.......'...z +| 1232: 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 03 .......(.*...... +| 1248: 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 2a ...).4.........* +| 1264: 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 28 g4.........+...( +| 1280: 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 00 ................ +| 1296: 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 ................ +| 1312: 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 00 ................ +| 1328: 01 00 04 00 00 00 01 00 01 00 00 00 01 00 02 00 ................ +| 1344: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................ +| 1360: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 21 ...............! +| 1376: 04 00 01 00 00 00 00 00 01 00 00 00 01 00 02 00 ................ +| 1392: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................ +| 1408: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 00 ................ +| 1424: 01 00 05 00 00 00 01 00 01 00 00 01 00 02 02 02 ................ +| 1440: 12 02 02 02 63 6f 6c 6f 72 20 73 70 61 63 00 f3 ....color spac.. +| 1456: a0 81 a1 00 00 a0 02 02 02 02 69 95 73 6f 36 00 ..........i.so6. +| 1472: ff 0d 00 97 8c 90 3f 0a 70 02 02 02 02 02 02 02 ......?.p....... +| 1488: 02 02 02 02 02 02 02 01 00 00 06 02 02 02 02 5f ..............._ +| 1504: 02 02 02 2c 02 02 00 00 01 00 01 00 00 00 01 00 ...,............ +| 1520: 02 fe 00 00 03 00 01 00 00 00 01 c5 04 00 00 00 ................ +| 1536: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................ +| 1552: 00 00 81 00 04 00 00 00 01 00 01 00 00 00 01 00 ................ +| 1568: 02 00 fe 00 03 00 01 00 00 00 01 00 04 00 00 00 ................ +| 1584: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................ +| 1600: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................ +| 1616: 02 00 00 00 03 00 01 00 00 00 01 00 04 00 00 00 ................ +| 1632: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................ +| 1648: 00 00 40 00 84 00 84 00 84 00 01 00 00 00 09 00 ..@............. +| 1664: 06 00 f5 00 01 00 08 01 03 15 15 15 15 15 15 15 ................ +| 1680: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1696: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1712: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1728: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1744: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1760: 15 15 15 15 15 15 15 15 15 15 15 00 03 00 62 00 ..............b. +| 1776: 62 00 23 00 01 00 62 00 04 00 1e 00 62 00 62 00 b.#...b.....b.b. +| 1792: 62 00 01 00 00 00 0a 00 01 00 03 00 01 00 03 00 b............... +| 1808: 04 00 02 00 01 00 01 00 08 00 01 00 31 c6 00 03 ............1... +| 1824: 00 0c 00 12 00 18 00 02 00 05 00 08 00 02 00 06 ................ +| 1840: 00 08 00 02 00 07 00 08 00 02 00 01 00 01 00 08 ................ +| 1856: 00 01 00 0c 00 03 00 16 00 1c 00 22 00 01 00 03 ................ +| 1872: 00 05 00 06 00 07 00 02 00 05 00 09 00 02 00 06 ................ +| 1888: 00 09 00 02 00 07 00 09 00 00 00 00 01 00 05 00 ................ +| 1904: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 1920: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 1936: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................ +| 1952: 00 00 01 0f d6 00 02 34 03 03 03 00 01 00 00 00 .......4........ +| 1968: 01 00 05 00 00 00 01 00 01 00 00 00 01 00 02 00 ................ +| 1984: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................ +| 2000: 01 00 00 00 01 00 02 fc 42 dc 19 5c 74 23 18 cd ........B...t#.. +| 2016: b3 a5 a8 7a 90 40 1d 66 12 5d e5 4f 85 00 68 f4 ...z.@.f.].O..h. +| 2032: 05 98 86 25 24 dd bc c2 f6 f6 4e a3 e2 61 d2 c6 ...%$.....N..a.. +| 2048: aa c1 56 50 d4 80 82 35 f1 e2 59 41 50 a6 da 51 ..VP...5..YAP..Q +| 2064: d4 62 9c 19 94 58 aa 31 30 8a 22 c2 5f 33 2b c9 .b...X.10..._3+. +| 2080: b6 e6 b4 11 4e 51 82 c4 d8 b6 d8 b4 06 04 fb 68 ....NQ.........h +| 2096: f4 d2 6f e7 cb 8a a8 82 d5 74 00 00 00 00 00 00 ..o......t...... +| 2368: 00 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 ................ +| 2432: 00 00 00 00 00 03 00 01 00 10 00 01 00 04 00 00 ................ +| 2448: 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 ................ +| 2464: 02 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 ....@........... +| 2480: 00 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 ................ +| 2496: 00 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f ..wOF2s@#p...p.. +| 2512: 00 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 .............C.. +| 2528: 00 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 ................ +| 2624: 00 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f ................ +| 2640: 00 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 ...........p.... +| 2656: 0f d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 ................ +| 2672: 00 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 ......?#p....... +| 2688: 04 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 ......@......... +| 2704: 01 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 ................ +| 2720: 0f c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f ...............o +| 2736: 00 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 .......1.+.*.... +| 2752: 00 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 .........+...2.. +| 2768: 0f c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 ...... .......$. +| 2784: c0 00 00 5a 03 00 00 01 24 00 2a 06 e4 00 00 00 ...Z....$.*..... +| 2800: 03 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 ....%.8......... +| 2816: 26 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 &.4.F.......'... +| 2832: 7a 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 z.......(.*..... +| 2848: 03 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 ....).4......... +| 2864: 2a 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 *g4.........+... +| 2880: 28 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 (............... +| 2896: 00 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 ................ +| 2912: 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 ................ +| 2928: 00 01 00 04 00 00 00 01 00 01 00 00 00 00 00 00 ................ +| 2992: 00 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .H.............. +| 3504: 00 00 00 00 00 00 00 00 00 00 00 97 00 00 00 00 ................ +| 3904: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 09 ................ +| 3920: 03 fe 00 00 01 36 00 3c 0a 38 00 00 00 03 00 00 .....6.<.8...... +| 3936: 01 37 00 20 0a 74 00 00 00 fb ff ff 00 38 00 2a .7. .t.......8.* +| 3952: 0a 94 00 00 00 03 00 00 01 39 4f 54 54 4f 00 0e .........9OTTO.. +| 3968: 00 80 00 03 00 60 43 46 46 20 e3 ae 89 2a 00 00 .....`CFF ...*.. +| 3984: 02 b0 00 00 02 76 42 50 4f 53 00 15 00 0a 00 00 .....vBPOS...... +| 4000: 05 28 00 00 00 0c 54 53 55 42 c9 70 c3 06 00 00 .(....TSUB.p.... +| 4016: 05 34 1f 00 40 00 48 00 00 00 00 00 00 00 00 00 .4..@.H......... +| 4064: 00 00 00 00 00 08 00 01 00 01 00 01 00 01 00 06 ................ +| 4080: 00 02 00 08 00 01 00 01 00 01 00 01 00 00 00 00 ................ +| end x1.db +}]} {} +do_test 7.1 { + set R [sqlite3_recover_init db main test.db2] + catch { $R run } + list [catch { $R finish } msg] $msg +} {1 {file is not a database}} + finish_test Index: ext/recover/sqlite3recover.c ================================================================== --- ext/recover/sqlite3recover.c +++ ext/recover/sqlite3recover.c @@ -2101,11 +2101,11 @@ int iNext = 0; int nByte = 0; if( iFree>(n-4) ) return 0; iNext = recoverGetU16(&a[iFree]); nByte = recoverGetU16(&a[iFree+2]); - if( iFree+nByte>n ) return 0; + if( iFree+nByte>n || nByte<4 ) return 0; if( iNext && iNext{ const stmt = db.prepare(sql); try { - return stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined; + const rc = stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined; + stmt.reset(/*for INSERT...RETURNING locking case*/); + return rc; }finally{ stmt.finalize(); } }; @@ -496,10 +503,13 @@ db.pointer) as the db handle, or sqlite3_errstr() if db is falsy. Note that if it's passed a non-error code like SQLITE_ROW or SQLITE_DONE, it will still throw but the error string might be "Not an error." The various non-0 non-error codes need to be checked for in client code where they are expected. + + The thrown exception's `resultCode` property will be the value of + the second argument to this function. If it does not throw, it returns its first argument. */ DB.checkRc = (db,resultCode)=>checkSqlite3Rc(db,resultCode); @@ -544,11 +554,14 @@ try{this.onclose.before(this)} catch(e){/*ignore*/} } const pDb = this.pointer; Object.keys(__stmtMap.get(this)).forEach((k,s)=>{ - if(s && s.pointer) s.finalize(); + if(s && s.pointer){ + try{s.finalize()} + catch(e){/*ignore*/} + } }); __ptrMap.delete(this); __stmtMap.delete(this); capi.sqlite3_close_v2(pDb); if(this.onclose && (this.onclose.after instanceof Function)){ @@ -699,22 +712,22 @@ result rows). If no statement has result columns, this value is unchanged. Achtung: an SQL result may have multiple columns with identical names. - `callback` = a function which gets called for each row of the - result set, but only if that statement has any result - _rows_. The callback's "this" is the options object, noting - that this function synthesizes one if the caller does not pass - one to exec(). The second argument passed to the callback is - always the current Stmt object, as it's needed if the caller - wants to fetch the column names or some such (noting that they - could also be fetched via `this.columnNames`, if the client - provides the `columnNames` option). If the callback returns a - literal `false` (as opposed to any other falsy value, e.g. an - implicit `undefined` return), any ongoing statement-`step()` - iteration stops without an error. The return value of the - callback is otherwise ignored. + result set, but only if that statement has any result rows. The + callback's "this" is the options object, noting that this + function synthesizes one if the caller does not pass one to + exec(). The second argument passed to the callback is always + the current Stmt object, as it's needed if the caller wants to + fetch the column names or some such (noting that they could + also be fetched via `this.columnNames`, if the client provides + the `columnNames` option). If the callback returns a literal + `false` (as opposed to any other falsy value, e.g. an implicit + `undefined` return), any ongoing statement-`step()` iteration + stops without an error. The return value of the callback is + otherwise ignored. ACHTUNG: The callback MUST NOT modify the Stmt object. Calling any of the Stmt.get() variants, Stmt.getColumnName(), or similar, is legal, but calling step() or finalize() is not. Member methods which are illegal in this context will @@ -731,11 +744,11 @@ A) A string describing what type of argument should be passed as the first argument to the callback: A.1) `'array'` (the default) causes the results of `stmt.get([])` to be passed to the `callback` and/or appended - to `resultRows` + to `resultRows`. A.2) `'object'` causes the results of `stmt.get(Object.create(null))` to be passed to the `callback` and/or appended to `resultRows`. Achtung: an SQL result may have multiple columns with identical names. In @@ -742,12 +755,12 @@ that case, the right-most column will be the one set in this object! A.3) `'stmt'` causes the current Stmt to be passed to the callback, but this mode will trigger an exception if - `resultRows` is an array because appending the statement to - the array would be downright unhelpful. + `resultRows` is an array because appending the transient + statement to the array would be downright unhelpful. B) An integer, indicating a zero-based column in the result row. Only that one single value will be passed on. C) A string with a minimum length of 2 and leading character of @@ -773,11 +786,11 @@ - `returnValue`: is a string specifying what this function should return: A) The default value is (usually) `"this"`, meaning that the - DB object itself should be returned. The exceptions is if + DB object itself should be returned. The exception is if the caller passes neither of `callback` nor `returnValue` but does pass an explicit `rowMode` then the default `returnValue` is `"resultRows"`, described below. B) `"resultRows"` means to return the value of the @@ -855,42 +868,57 @@ if(bind && stmt.parameterCount){ stmt.bind(bind); bind = null; } if(evalFirstResult && stmt.columnCount){ - /* Only forward SELECT results for the FIRST query + /* Only forward SELECT-style results for the FIRST query in the SQL which potentially has them. */ + let gotColNames = Array.isArray( + opt.columnNames + /* As reported in + https://sqlite.org/forum/forumpost/7774b773937cbe0a + we need to delay fetching of the column names until + after the first step() (if we step() at all) because + a schema change between the prepare() and step(), via + another connection, may invalidate the column count + and names. */) ? 0 : 1; evalFirstResult = false; - if(Array.isArray(opt.columnNames)){ - stmt.getColumnNames(opt.columnNames); - } if(arg.cbArg || resultRows){ - for(; stmt.step(); stmt._isLocked = false){ - stmt._isLocked = true; + for(; stmt.step(); stmt._lockedByExec = false){ + if(0===gotColNames++) stmt.getColumnNames(opt.columnNames); + stmt._lockedByExec = true; const row = arg.cbArg(stmt); if(resultRows) resultRows.push(row); if(callback && false === callback.call(opt, row, stmt)){ break; } } - stmt._isLocked = false; + stmt._lockedByExec = false; + } + if(0===gotColNames){ + /* opt.columnNames was provided but we visited no result rows */ + stmt.getColumnNames(opt.columnNames); } }else{ stmt.step(); } - stmt.finalize(); + stmt.reset( + /* In order to trigger an exception in the + INSERT...RETURNING locking scenario: + https://sqlite.org/forum/forumpost/36f7a2e7494897df + */).finalize(); stmt = null; - } + }/*prepare() loop*/ }/*catch(e){ sqlite3.config.warn("DB.exec() is propagating exception",opt,e); throw e; }*/finally{ + wasm.scopedAllocPop(stack); if(stmt){ - delete stmt._isLocked; + delete stmt._lockedByExec; stmt.finalize(); } - wasm.scopedAllocPop(stack); } return arg.returnVal(); }/*exec()*/, /** @@ -1105,10 +1133,11 @@ selectValues: function(sql,bind,asType){ const stmt = this.prepare(sql), rc = []; try { stmt.bind(bind); while(stmt.step()) rc.push(stmt.get(0,asType)); + stmt.reset(/*for INSERT...RETURNING locking case*/); }finally{ stmt.finalize(); } return rc; }, @@ -1239,11 +1268,11 @@ /** A convenience form of DB.checkRc(this,resultCode). If it does not throw, it returns this object. */ checkRc: function(resultCode){ - return DB.checkRc(this, resultCode); + return checkSqlite3Rc(this, resultCode); } }/*DB.prototype*/; /** Throws if the given Stmt has been finalized, else stmt is @@ -1300,19 +1329,19 @@ else if(n<1 || n>stmt.parameterCount) toss3("Bind index",key,"is out of range."); return n; }; /** - If stmt._isLocked is truthy, this throws an exception + If stmt._lockedByExec is truthy, this throws an exception complaining that the 2nd argument (an operation name, e.g. "bind()") is not legal while the statement is "locked". Locking happens before an exec()-like callback is passed a statement, to ensure that the callback does not mutate or finalize the statement. If it does not throw, it returns stmt. */ - const affirmUnlocked = function(stmt,currentOpName){ - if(stmt._isLocked){ + const affirmNotLockedByExec = function(stmt,currentOpName){ + if(stmt._lockedByExec){ toss3("Operation is illegal when statement is locked:",currentOpName); } return stmt; }; @@ -1321,18 +1350,15 @@ given index (numeric or named) using the given bindType (see the BindTypes enum) and value. Throws on error. Returns stmt on success. */ const bindOne = function f(stmt,ndx,bindType,val){ - affirmUnlocked(affirmStmtOpen(stmt), 'bind()'); + affirmNotLockedByExec(affirmStmtOpen(stmt), 'bind()'); if(!f._){ f._tooBigInt = (v)=>toss3( "BigInt value is too big to store without precision loss:", v ); - /* Reminder: when not in BigInt mode, it's impossible for - JS to represent a number out of the range we can bind, - so we have no range checking. */ f._ = { string: function(stmt, ndx, val, asBlob){ const [pStr, n] = wasm.allocCString(val, true); const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text; return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_WASM_DEALLOC); @@ -1402,50 +1428,71 @@ return stmt; }; Stmt.prototype = { /** - "Finalizes" this statement. This is a no-op if the - statement has already been finalizes. Returns - undefined. Most methods in this class will throw if called - after this is. + "Finalizes" this statement. This is a no-op if the statement + has already been finalized. Returns the result of + sqlite3_finalize() (0 on success, non-0 on error), or the + undefined value if the statement has already been + finalized. Regardless of success or failure, most methods in + this class will throw if called after this is. + + 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. */ finalize: function(){ if(this.pointer){ - affirmUnlocked(this,'finalize()'); + affirmNotLockedByExec(this,'finalize()'); + const rc = capi.sqlite3_finalize(this.pointer); delete __stmtMap.get(this.db)[this.pointer]; - capi.sqlite3_finalize(this.pointer); __ptrMap.delete(this); delete this._mayGet; - delete this.columnCount; delete this.parameterCount; + delete this._lockedByExec; delete this.db; - delete this._isLocked; + return rc; } }, - /** Clears all bound values. Returns this object. - Throws if this statement has been finalized. */ + /** + Clears all bound values. Returns this object. Throws if this + statement has been finalized or if modification of the + statement is currently illegal (e.g. in the per-row callback of + a DB.exec() call). + */ clearBindings: function(){ - affirmUnlocked(affirmStmtOpen(this), 'clearBindings()') + affirmNotLockedByExec(affirmStmtOpen(this), 'clearBindings()') capi.sqlite3_clear_bindings(this.pointer); this._mayGet = false; return this; }, /** - Resets this statement so that it may be step()ed again - from the beginning. Returns this object. Throws if this - statement has been finalized. + Resets this statement so that it may be step()ed again from the + beginning. Returns this object. Throws if this statement has + been finalized, if it may not legally be reset because it is + currently being used from a DB.exec() callback, or if the + underlying call to sqlite3_reset() returns non-0. If passed a truthy argument then this.clearBindings() is also called, otherwise any existing bindings, along with any memory allocated for them, are retained. + + In verions 3.42.0 and earlier, this function did not throw if + sqlite3_reset() returns non-0, but it was discovered that + throwing (or significant extra client-side code) is necessary + in order to avoid certain silent failure scenarios, as + discussed at: + + https://sqlite.org/forum/forumpost/36f7a2e7494897df */ reset: function(alsoClearBinds){ - affirmUnlocked(this,'reset()'); + affirmNotLockedByExec(this,'reset()'); if(alsoClearBinds) this.clearBindings(); - capi.sqlite3_reset(affirmStmtOpen(this).pointer); + const rc = capi.sqlite3_reset(affirmStmtOpen(this).pointer); this._mayGet = false; + checkSqlite3Rc(this.db, rc); return this; }, /** Binds one or more values to its bindable parameters. It accepts 1 or 2 arguments: @@ -1590,11 +1637,11 @@ row of data is available, a truthy value is returned. If no row of data is available, a falsy value is returned. Throws on error. */ step: function(){ - affirmUnlocked(this, 'step()'); + affirmNotLockedByExec(this, 'step()'); const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer); switch(rc){ case capi.SQLITE_DONE: return this._mayGet = false; case capi.SQLITE_ROW: return this._mayGet = true; default: @@ -1625,15 +1672,13 @@ stepReset: function(){ this.step(); return this.reset(); }, /** - Functions like step() except that it finalizes this statement - immediately after stepping unless the step cannot be performed - because the statement is locked. Throws on error, but any error - other than the statement-is-locked case will also trigger - finalization of this statement. + Functions like step() except that it calls finalize() on this + statement immediately after stepping, even if the step() call + throws. On success, it returns true if the step indicated that a row of data was available, else it returns false. This is intended to simplify use cases such as: @@ -1641,13 +1686,18 @@ ``` aDb.prepare("insert into foo(a) values(?)").bind(123).stepFinalize(); ``` */ stepFinalize: function(){ - const rc = this.step(); - this.finalize(); - return rc; + try{ + const rc = this.step(); + this.reset(/*for INSERT...RETURNING locking case*/); + return rc; + }finally{ + try{this.finalize()} + catch(e){/*ignored*/} + } }, /** Fetches the value from the given 0-based column index of the current data row, throwing if index is out of range. @@ -1684,17 +1734,19 @@ if(!affirmStmtOpen(this)._mayGet){ toss3("Stmt.step() has not (recently) returned true."); } if(Array.isArray(ndx)){ let i = 0; - while(itoss3("The pointer property is read-only.") } Object.defineProperty(Stmt.prototype, 'pointer', prop); Object.defineProperty(DB.prototype, 'pointer', prop); } + /** + Stmt.columnCount is an interceptor for sqlite3_column_count(). + + This requires an unfortunate performance hit compared to caching + columnCount when the Stmt is created/prepared (as was done in + SQLite <=3.42.0), but is necessary in order to handle certain + corner cases, as described in + https://sqlite.org/forum/forumpost/7774b773937cbe0a. + */ + Object.defineProperty(Stmt.prototype, 'columnCount', { + enumerable: false, + get: function(){return capi.sqlite3_column_count(this.pointer)}, + set: ()=>toss3("The columnCount property is read-only.") + }); /** The OO API's public namespace. */ sqlite3.oo1 = { DB, Stmt Index: ext/wasm/common/SqliteTestUtil.js ================================================================== --- ext/wasm/common/SqliteTestUtil.js +++ ext/wasm/common/SqliteTestUtil.js @@ -154,11 +154,10 @@ } return args; } }; - /** This is a module object for use with the emscripten-installed sqlite3InitModule() factory function. */ self.sqlite3TestModule = { Index: ext/wasm/common/whwasmutil.js ================================================================== --- ext/wasm/common/whwasmutil.js +++ ext/wasm/common/whwasmutil.js @@ -172,11 +172,11 @@ https://fossil.wanderinghorse.net/r/jaccwabbyt/file/common/whwasmutil.js */ globalThis.WhWasmUtilInstaller = function(target){ 'use strict'; if(undefined===target.bigIntEnabled){ - target.bigIntEnabled = !!self['BigInt64Array']; + target.bigIntEnabled = !!globalThis['BigInt64Array']; } /** Throws a new Error, the message of which is the concatenation of all args with a space between each. */ const toss = (...args)=>{throw new Error(args.join(' '))}; @@ -353,12 +353,12 @@ case 64: if(c.HEAP64) return unsigned ? c.HEAP64U : c.HEAP64; break; default: if(target.bigIntEnabled){ - if(n===self['BigUint64Array']) return c.HEAP64U; - else if(n===self['BigInt64Array']) return c.HEAP64; + if(n===globalThis['BigUint64Array']) return c.HEAP64U; + else if(n===globalThis['BigInt64Array']) return c.HEAP64; break; } } toss("Invalid heapForSize() size: expecting 8, 16, 32,", "or (if BigInt is enabled) 64."); Index: ext/wasm/jaccwabyt/jaccwabyt.js ================================================================== --- ext/wasm/jaccwabyt/jaccwabyt.js +++ ext/wasm/jaccwabyt/jaccwabyt.js @@ -58,13 +58,13 @@ dealloc = config.dealloc, log = config.log || console.log.bind(console), memberPrefix = (config.memberPrefix || ""), memberSuffix = (config.memberSuffix || ""), bigIntEnabled = (undefined===config.bigIntEnabled - ? !!self['BigInt64Array'] : !!config.bigIntEnabled), - BigInt = self['BigInt'], - BigInt64Array = self['BigInt64Array'], + ? !!globalThis['BigInt64Array'] : !!config.bigIntEnabled), + BigInt = globalThis['BigInt'], + BigInt64Array = globalThis['BigInt64Array'], /* Undocumented (on purpose) config options: */ ptrSizeof = config.ptrSizeof || 4, ptrIR = config.ptrIR || 'i32' ; Index: ext/wasm/tester1-worker.html ================================================================== --- ext/wasm/tester1-worker.html +++ ext/wasm/tester1-worker.html @@ -43,11 +43,11 @@ const urlParams = new URL(self.location.href).searchParams; const workerArgs = []; if(urlParams.has('esm')){ logHtml('warning',"Attempting to run an ES6 Worker Module, "+ "which is not supported by all browsers! "+ - "e.g. Firefox (as of 2022-12) cannot do this."); + "e.g. Firefox (as of 2023-05) cannot do this."); workerArgs.push("tester1.mjs",{type:"module"}); document.querySelectorAll('title,#color-target').forEach((e)=>{ e.innerText = "sqlite3 tester #1: ES6 Worker Module"; }); }else{ Index: ext/wasm/tester1.c-pp.js ================================================================== --- ext/wasm/tester1.c-pp.js +++ ext/wasm/tester1.c-pp.js @@ -1164,11 +1164,12 @@ //Sanity check DB.checkRc()... let ex; try{db.checkRc(rc)} catch(e){ex = e} T.assert(ex instanceof sqlite3.SQLite3Error) - .assert(0===ex.message.indexOf("sqlite3 result code")) + .assert(capi.SQLITE_MISUSE===ex.resultCode) + .assert(0===ex.message.indexOf("SQLITE_MISUSE: sqlite3 result code")) .assert(ex.message.indexOf("Invalid SQL")>0); T.assert(db === db.checkRc(0)) .assert(db === sqlite3.oo1.DB.checkRc(db,0)) .assert(null === sqlite3.oo1.DB.checkRc(null,0)); @@ -1217,10 +1218,11 @@ let st = this.db.prepare( new TextEncoder('utf-8').encode("select 3 as a") ); //debug("statement =",st); this.progressHandlerCount = 0; + let rc; try { T.assert(wasm.isPtr(st.pointer)) .mustThrowMatching(()=>st.pointer=1, /read-only/) .assert(1===this.db.openStatementCount()) .assert( @@ -1227,10 +1229,12 @@ capi.sqlite3_stmt_status( st, capi.SQLITE_STMTSTATUS_RUN, 0 ) === 0) .assert(!st._mayGet) .assert('a' === st.getColumnName(0)) + .mustThrowMatching(()=>st.columnCount=2, + /columnCount property is read-only/) .assert(1===st.columnCount) .assert(0===st.parameterCount) .mustThrow(()=>st.bind(1,null)) .assert(true===st.step()) .assert(3 === st.get(0)) @@ -1262,14 +1266,15 @@ assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")). assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")). assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); }finally{ - st.finalize(); + rc = st.finalize(); } T.assert(!st.pointer) - .assert(0===this.db.openStatementCount()); + .assert(0===this.db.openStatementCount()) + .assert(0===rc); T.mustThrowMatching(()=>new sqlite3.oo1.Stmt("hi"), function(err){ return (err instanceof sqlite3.SQLite3Error) && capi.SQLITE_MISUSE === err.resultCode && 0 < err.message.indexOf("Do not call the Stmt constructor directly.") @@ -1330,25 +1335,33 @@ this.progressHandlerCount = 0; let rc = db.exec({ sql:['CREATE TABLE t(a,b);', // ^^^ using TEMP TABLE breaks the db export test "INSERT INTO t(a,b) VALUES(1,2),(3,4),", - "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for - off-by-one bug in string-to-WASM conversion*/], + "(?,?)"/*intentionally missing semicolon to test for + off-by-one bug in string-to-WASM conversion*/], saveSql: list, bind: [5,6] }); //debug("Exec'd SQL:", list); T.assert(rc === db) .assert(2 === list.length) .assert('string'===typeof list[1]) - .assert(4===db.changes()) + .assert(3===db.changes()) .assert(this.progressHandlerCount > 0, "Expecting progress callback.") if(wasm.bigIntEnabled){ - T.assert(4n===db.changes(false,true)); + T.assert(3n===db.changes(false,true)); } + rc = db.exec({ + sql: "INSERT INTO t(a,b) values('blob',X'6869') RETURNING 13", + rowMode: 0 + }); + T.assert(Array.isArray(rc)) + .assert(1===rc.length) + .assert(13 === rc[0]) + .assert(1===db.changes()); let vals = db.selectValues('select a from t order by a limit 2'); T.assert( 2 === vals.length ) .assert( 1===vals[0] && 3===vals[1] ); vals = db.selectValues('select a from t order by a limit $L', @@ -1371,23 +1384,31 @@ callback: function(row,stmt){ ++counter; T.assert( 3 === this._myState /* Recall that "this" is the options object. */ + ).assert( + this.columnNames===colNames ).assert( this.columnNames[0]==='a' && this.columnNames[1]==='b' - /* options.columnNames is filled out before the first - Stmt.step(). */ ).assert( (row.a%2 && row.a<6) || 'blob'===row.a ); } }); T.assert(2 === colNames.length) .assert('a' === colNames[0]) .assert(4 === counter) .assert(4 === list.length); + colNames = []; + db.exec({ + /* Ensure that columnNames is populated for empty result sets. */ + sql: "SELECT a a, b B FROM t WHERE 0", + columnNames: colNames + }); + T.assert(2===colNames.length) + .assert('a'===colNames[0] && 'B'===colNames[1]); list.length = 0; db.exec("SELECT a a, b b FROM t",{ rowMode: 'array', callback: function(row,stmt){ ++counter; @@ -1436,15 +1457,18 @@ mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1))); } let st = db.prepare("update t set b=:b where a='blob'"); try { + T.assert(0===st.columnCount); const ndx = st.getParamIndex(':b'); T.assert(1===ndx); - st.bindAsBlob(ndx, "ima blob").reset(true); + st.bindAsBlob(ndx, "ima blob") + /*step() skipped intentionally*/.reset(true); } finally { - st.finalize(); + T.assert(0===st.finalize()) + .assert(undefined===st.finalize()); } try { db.prepare("/*empty SQL*/"); toss("Must not be reached."); @@ -1602,11 +1626,23 @@ .assert(1===rc[0].a) .assert(2===rc[0].b) .assert(3===rc[1].a) .assert(4===rc[1].b); }) - + //////////////////////////////////////////////////////////////////////// + .t('selectArray/Object/Values() via INSERT/UPDATE...RETURNING', function(sqlite3){ + let rc = this.db.selectObject("INSERT INTO t(a,b) VALUES(83,84) RETURNING a as AA"); + T.assert(83===rc.AA); + rc = this.db.selectArray("UPDATE T set a=85 WHERE a=83 RETURNING b as BB"); + T.assert(Array.isArray(rc)).assert(84===rc[0]); + //log("select * from t:",this.db.selectObjects("select * from t order by a")); + rc = this.db.selectValues("UPDATE T set a=a*1 RETURNING a"); + T.assert(Array.isArray(rc)) + .assert(5 === rc.length) + .assert('number'===typeof rc[0]) + .assert(rc[0]|0 === rc[0] /* is small integer */); + }) //////////////////////////////////////////////////////////////////////// .t({ name: 'sqlite3_js_db_export()', predicate: ()=>true, test: function(sqlite3){ Index: src/analyze.c ================================================================== --- src/analyze.c +++ src/analyze.c @@ -1847,18 +1847,19 @@ pSample = &pIdx->aSample[pIdx->nSample]; decodeIntArray((char*)sqlite3_column_text(pStmt,1),nCol,pSample->anEq,0,0); decodeIntArray((char*)sqlite3_column_text(pStmt,2),nCol,pSample->anLt,0,0); decodeIntArray((char*)sqlite3_column_text(pStmt,3),nCol,pSample->anDLt,0,0); - /* Take a copy of the sample. Add two 0x00 bytes the end of the buffer. + /* Take a copy of the sample. Add 8 extra 0x00 bytes the end of the buffer. ** This is in case the sample record is corrupted. In that case, the ** sqlite3VdbeRecordCompare() may read up to two varints past the ** end of the allocated buffer before it realizes it is dealing with - ** a corrupt record. Adding the two 0x00 bytes prevents this from causing + ** a corrupt record. Or it might try to read a large integer from the + ** buffer. In any case, eight 0x00 bytes prevents this from causing ** a buffer overread. */ pSample->n = sqlite3_column_bytes(pStmt, 4); - pSample->p = sqlite3DbMallocZero(db, pSample->n + 2); + pSample->p = sqlite3DbMallocZero(db, pSample->n + 8); if( pSample->p==0 ){ sqlite3_finalize(pStmt); return SQLITE_NOMEM_BKPT; } if( pSample->n ){ Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -1632,11 +1632,11 @@ cbrk = usableSize; iCellLast = usableSize - 4; iCellStart = get2byte(&data[hdr+5]); if( nCell>0 ){ temp = sqlite3PagerTempSpace(pPage->pBt->pPager); - memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart); + memcpy(temp, data, usableSize); src = temp; for(i=0; iSQLITE_MAX_PAGE_SIZE || pageSize<=256 ){ goto page1_init_failed; } - pBt->btsFlags |= BTS_PAGESIZE_FIXED; assert( (pageSize & 7)==0 ); /* EVIDENCE-OF: R-59310-51205 The "reserved space" size in the 1-byte ** integer at offset 20 is the number of bytes of space at the end of ** each page to reserve for extensions. ** @@ -3356,10 +3355,11 @@ ** again with the correct page-size. */ releasePageOne(pPage1); pBt->usableSize = usableSize; pBt->pageSize = pageSize; + pBt->btsFlags |= BTS_PAGESIZE_FIXED; freeTempSpace(pBt); rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, pageSize-usableSize); return rc; } @@ -3375,10 +3375,11 @@ ** be less than 480. In other words, if the page size is 512, then the ** reserved space size cannot exceed 32. */ if( usableSize<480 ){ goto page1_init_failed; } + pBt->btsFlags |= BTS_PAGESIZE_FIXED; pBt->pageSize = pageSize; pBt->usableSize = usableSize; #ifndef SQLITE_OMIT_AUTOVACUUM pBt->autoVacuum = (get4byte(&page1[36 + 4*4])?1:0); pBt->incrVacuum = (get4byte(&page1[36 + 7*4])?1:0); @@ -10660,11 +10661,11 @@ pBt = pCheck->pBt; usableSize = pBt->usableSize; if( iPage==0 ) return 0; if( checkRef(pCheck, iPage) ) return 0; pCheck->zPfx = "Tree %u page %u: "; - pCheck->v0 = pCheck->v1 = iPage; + pCheck->v1 = iPage; if( (rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0 ){ checkAppendMsg(pCheck, "unable to get the page. error code=%d", rc); goto end_of_check; } @@ -10997,10 +10998,11 @@ #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum && aRoot[i]>1 && !bPartial ){ checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); } #endif + sCheck.v0 = aRoot[i]; checkTreePage(&sCheck, aRoot[i], ¬Used, LARGEST_INT64); } pBt->db->flags = savedDbFlags; /* Make sure every page in the file is referenced Index: src/delete.c ================================================================== --- src/delete.c +++ src/delete.c @@ -33,10 +33,11 @@ Table *pTab; assert( pItem && pSrc->nSrc>=1 ); pTab = sqlite3LocateTableItem(pParse, 0, pItem); sqlite3DeleteTable(pParse->db, pItem->pTab); pItem->pTab = pTab; + pItem->fg.notCte = 1; if( pTab ){ pTab->nTabRef++; if( pItem->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pItem) ){ pTab = 0; } Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -2189,11 +2189,11 @@ /* ** The argument must be a TK_TRUEFALSE Expr node. Return 1 if it is TRUE ** and 0 if it is FALSE. */ int sqlite3ExprTruthValue(const Expr *pExpr){ - pExpr = sqlite3ExprSkipCollate((Expr*)pExpr); + pExpr = sqlite3ExprSkipCollateAndLikely((Expr*)pExpr); assert( pExpr->op==TK_TRUEFALSE ); assert( !ExprHasProperty(pExpr, EP_IntValue) ); assert( sqlite3StrICmp(pExpr->u.zToken,"true")==0 || sqlite3StrICmp(pExpr->u.zToken,"false")==0 ); return pExpr->u.zToken[4]==0; @@ -6411,10 +6411,11 @@ int k; assert( pAggInfo->iFirstReg==0 ); pCol = pAggInfo->aCol; for(k=0; knColumn; k++, pCol++){ + if( pCol->pCExpr==pExpr ) return; if( pCol->iTable==pExpr->iTable && pCol->iColumn==pExpr->iColumn && pExpr->op!=TK_IF_NULL_ROW ){ goto fix_up_expr; Index: src/select.c ================================================================== --- src/select.c +++ src/select.c @@ -6532,11 +6532,11 @@ assert( pExpr->iAgg>=0 ); pCol = &pAggInfo->aCol[pExpr->iAgg]; pExpr->op = TK_AGG_COLUMN; pExpr->iTable = pCol->iTable; pExpr->iColumn = pCol->iColumn; - ExprClearProperty(pExpr, EP_Skip|EP_Collate); + ExprClearProperty(pExpr, EP_Skip|EP_Collate|EP_Unlikely); return WRC_Prune; } /* ** Convert every pAggInfo->aFunc[].pExpr such that any node within Index: src/shell.c.in ================================================================== --- src/shell.c.in +++ src/shell.c.in @@ -10528,11 +10528,12 @@ zRevText = sqlite3_mprintf( /* lower-case query is first run, producing upper-case query. */ "with tabcols as materialized(\n" "select tname, cname\n" "from (" - " select ss.tname as tname, ti.name as cname\n" + " select printf('\"%%w\"',ss.tname) as tname," + " printf('\"%%w\"',ti.name) as cname\n" " from (%z) ss\n inner join pragma_table_info(tname) ti))\n" "select 'SELECT total(bad_text_count) AS bad_text_count\n" "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n" " from (select 'SELECT COUNT(*) AS bad_text_count\n" "FROM '||tname||' WHERE '\n" @@ -12175,11 +12176,11 @@ ); vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1); #endif #ifdef SQLITE_ENABLE_MULTIPLEX }else if( cli_strcmp(z,"-multiplex")==0 ){ - extern int sqlite3_multiple_initialize(const char*,int); + extern int sqlite3_multiplex_initialize(const char*,int); sqlite3_multiplex_initialize(0, 1); #endif }else if( cli_strcmp(z,"-mmap")==0 ){ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); verify_uninitialized(); @@ -12218,11 +12219,11 @@ sqlite3MemTraceActivate(stderr); }else if( cli_strcmp(z,"-bail")==0 ){ bail_on_error = 1; }else if( cli_strcmp(z,"-nonce")==0 ){ free(data.zNonce); - data.zNonce = strdup(argv[++i]); + data.zNonce = strdup(cmdline_option_value(argc, argv, ++i)); }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ ShellSetFlag(&data,SHFLG_TestingMode); }else if( cli_strcmp(z,"-safe")==0 ){ /* no-op - catch this on the second pass */ } Index: src/treeview.c ================================================================== --- src/treeview.c +++ src/treeview.c @@ -641,11 +641,12 @@ const char *azOp[] = { "IS-FALSE", "IS-TRUE", "IS-NOT-FALSE", "IS-NOT-TRUE" }; assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT ); assert( pExpr->pRight ); - assert( sqlite3ExprSkipCollate(pExpr->pRight)->op==TK_TRUEFALSE ); + assert( sqlite3ExprSkipCollateAndLikely(pExpr->pRight)->op + == TK_TRUEFALSE ); x = (pExpr->op2==TK_ISNOT)*2 + sqlite3ExprTruthValue(pExpr->pRight); zUniOp = azOp[x]; break; } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -222,11 +222,11 @@ pSrc = sqlite3SrcListDup(db, pTabList, 0); pWhere2 = sqlite3ExprDup(db, pWhere, 0); assert( pTabList->nSrc>1 ); if( pSrc ){ - pSrc->a[0].fg.notCte = 1; + assert( pSrc->a[0].fg.notCte ); pSrc->a[0].iCursor = -1; pSrc->a[0].pTab->nTabRef--; pSrc->a[0].pTab = 0; } if( pPk ){ Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -4229,10 +4229,19 @@ ** sqlite3VdbeSerialTypeLen() in the common case. */ if( d1+(u64)serial_type1+2>(u64)nKey1 && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)>(u64)nKey1 ){ + if( serial_type1>=1 + && serial_type1<=7 + && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)<=(u64)nKey1+8 + && CORRUPT_DB + ){ + return 1; /* corrupt record not detected by + ** sqlite3VdbeRecordCompareWithSkip(). Return true + ** to avoid firing the assert() */ + } break; } /* Extract the values to be compared. */ Index: src/vdbemem.c ================================================================== --- src/vdbemem.c +++ src/vdbemem.c @@ -1429,10 +1429,11 @@ if( pRec==0 ) return 0; p->ppRec[0] = pRec; } pRec->nField = p->iVal+1; + sqlite3VdbeMemSetNull(&pRec->aMem[p->iVal]); return &pRec->aMem[p->iVal]; } #else UNUSED_PARAMETER(p); #endif /* defined(SQLITE_ENABLE_STAT4) */ Index: test/analyze.test ================================================================== --- test/analyze.test +++ test/analyze.test @@ -374,7 +374,26 @@ CREATE TABLE t1(b); INSERT INTO t1(b) SELECT a FROM sqliteDemo; ANALYZE; SELECT tbl FROM sqlite_stat1 WHERE idx IS NULL ORDER BY tbl; } {SQLiteDemo2 sqliteDemo t1} + +# The following caused a small buffer overread in STAT4 processing prior +# to check-in [b99135288b157044]. +# +ifcapable stat4 { + reset_db + database_may_be_corrupt + do_execsql_test analyze-7.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER); + INSERT INTO t1 VALUES(1, 7223372036854775); + INSERT INTO t1 VALUES(2, 7223372036854776); + INSERT INTO t1 VALUES(3, 7223372036854777); + CREATE INDEX i1 ON t1(b); + ANALYZE; + UPDATE sqlite_stat4 SET sample = substr(sample, 0, 4); + ANALYZE sqlite_schema; + SELECT * FROM t1 WHERE b>7223372036854775 + } {2 7223372036854776 3 7223372036854777} +} finish_test Index: test/corrupt2.test ================================================================== --- test/corrupt2.test +++ test/corrupt2.test @@ -246,11 +246,11 @@ set result [db2 eval {pragma integrity_check}] break } set result } {{*** in database main *** -Tree 11 page 2 cell 0: 2nd reference to page 10 +Tree 2 page 2 cell 0: 2nd reference to page 10 Page 4: never used}} db2 close proc corruption_test {args} { Index: test/shell2.test ================================================================== --- test/shell2.test +++ test/shell2.test @@ -260,7 +260,16 @@ .separator "\377" "\n" .import dummy.csv t SELECT count(*) FROM t;}]] } {0 1} +# Bug from forum post 7cbe081746dd3803 +# Keywords as column names were producing an error message. +do_test shell2-1.4.12 { + set res [catchcmd :memory: [string trim { + CREATE TABLE "group"("order" text); + INSERT INTO "group" VALUES ('ABC'); +.sha3sum}]] +} {0 ca08bc02b7e95c7df431a3a4b1cc0f8d8743914793473f55b5558e03} + finish_test Index: test/sqldiff1.test ================================================================== --- test/sqldiff1.test +++ test/sqldiff1.test @@ -61,6 +61,28 @@ ||'two'); CREATE TABLE t3(a,b,c); INSERT INTO t3(rowid,a,b,c) VALUES(1,111,222,333); DROP TABLE t4;} +db close +forcedelete test.db test2.db +sqlite3 db test.db + +do_test sqldiff-2.0 { + db eval { + CREATE TABLE t1(a INTEGER PRIMARY KEY); + } + db close + sqlite3 db test2.db + db eval { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + } + db close + set line "exec $PROG test.db test2.db" + unset -nocomplain ::MSG + catch {eval $line} ::MSG +} {0} +do_test sqldiff-2.1 { + set ::MSG +} {ALTER TABLE t1 ADD COLUMN b;} + finish_test Index: test/upfrom4.test ================================================================== --- test/upfrom4.test +++ test/upfrom4.test @@ -124,7 +124,33 @@ SELECT * FROM t1 ORDER BY a; } { 1 100 def 4.5 1000 - - - } + +# Forum post https://sqlite.org/forum/forumpost/36ff78b2a3 +# +ifcapable update_delete_limit { + reset_db + do_execsql_test 500 { + CREATE TABLE t1(abc INT, def INT); + INSERT INTO t1 VALUES(0,0); + INSERT INTO t1 VALUES(0,0); + INSERT INTO t1 VALUES(0,0); + CREATE TABLE dual(dummy TEXT); + INSERT INTO dual(dummy) VALUES('X'); + } {} + + do_execsql_test 510 { + UPDATE t1 + SET (abc, def)=(SELECT x, 123) + FROM dual LEFT JOIN (SELECT 789 AS 'x' FROM dual) AS d2 + LIMIT 2 + } + + do_execsql_test 520 { + SELECT * FROM t1 + } {789 123 789 123 0 0} +} + finish_test Index: test/wherelimit2.test ================================================================== --- test/wherelimit2.test +++ test/wherelimit2.test @@ -297,7 +297,37 @@ set ::log {} execsql { UPDATE "v w" SET "a b" = "a b" || 'x' ORDER BY "a b" LIMIT 5; } set ::log } {ax a bx b cx c dx d ex a} +#----------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t2(x); + INSERT INTO t2(x) VALUES(1),(2),(3),(5),(8),(13); +} {} + +do_execsql_test 6.1 { + WITH t2 AS MATERIALIZED (VALUES(5)) + DELETE FROM t2 ORDER BY rank()OVER() LIMIT 2; +} + +do_execsql_test 6.2 { + SELECT * FROM t2; +} {3 5 8 13} + +#------------------------------------------------------------------------- + +do_execsql_test 7.0 { + CREATE TABLE t1(a INT); INSERT INTO t1(a) VALUES(0); +} {} + +do_execsql_test 7.1 { + WITH t1(b) AS (SELECT * FROM (SELECT * FROM (VALUES(2)))) + UPDATE t1 SET a=3 LIMIT 1; +} + +do_execsql_test 7.2 { + SELECT * FROM t1; +} {3} finish_test Index: test/window1.test ================================================================== --- test/window1.test +++ test/window1.test @@ -2360,7 +2360,20 @@ SELECT (SELECT y+sum(0) OVER ()) FROM t3 LEFT JOIN t4 ON x=y GROUP BY x; } {100 {} 400} do_execsql_test 76.5 { SELECT (SELECT max(y)+sum(0) OVER ()) FROM t3 LEFT JOIN t4 ON x=y GROUP BY x; } {100 {} 400} + +# 2023-05-23 https://sqlite.org/forum/forumpost/fbfe330a20 +# +reset_db +do_execsql_test 77.1 { + CREATE TABLE t1(x INT); + CREATE INDEX t1x ON t1(likely(x)); + INSERT INTO t1 VALUES(1),(2),(4),(8); +} +do_execsql_test 77.2 { + SELECT max(~likely(x)) FILTER (WHERE true) FROM t1 INDEXED BY t1x GROUP BY x; +} {-2 -3 -5 -9} + finish_test Index: tool/sqldiff.c ================================================================== --- tool/sqldiff.c +++ tool/sqldiff.c @@ -603,22 +603,22 @@ zSep = "SELECT "; for(i=0; i